Geometry Shader support in Shadestyle

Hello there, long time since my last post here!
I was reading a little bit about geometry shaders here, and after finding out that there’s support in shadestyle for them, I decided to give it a try :slight_smile: .
I tried a simple application, namely transform a LINE primitive type into a pair of triangles forming a rectangle to achieve a “widened” line.
Here’s some little code

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.draw.vertexBuffer
import org.openrndr.draw.vertexFormat
import org.openrndr.math.Vector3


fun main() = application {
    configure {
        width = 1000
        height = 1000
    }

    program {
        val bufferVertices = vertexBuffer(vertexFormat {
            position(3)
        }, 2)

        bufferVertices.put {
            write(Vector3(0.0, -200.0, 0.0))
            write(Vector3(0.0, 200.0, 0.0))
        }

        extend {
            drawer.stroke = ColorRGBa.WHITE
            drawer.fill = ColorRGBa.WHITE
            drawer.shadeStyle = shadeStyle {
                vertexTransform = """
                    x_position += vec3(100.0, 0.0, 0.0) * (2.0 * gl_VertexID - 1.0); 
                """.trimIndent()

                geometryPreamble ="""
                        layout (lines) in;
                        layout (triangle_strip, max_vertices = 4) out;
                        """.trimIndent()

                geometryTransform = """
                      
                               vec3 a = vec3(gl_in[0].gl_Position);
                                vec3 b = vec3(gl_in[1].gl_Position);
                                vec3 n = normalize(b - a);
                                vec3 o = vec2(n.y, -n.x, 0.0);
                                
                                gl_Position = vec4(a + o * 100.0, 0.0); 
                                EmitVertex();
                            
                                gl_Position = vec4(a - o * 100.0, 0.0); 
                                EmitVertex();
                                
                                gl_Position = vec4(b + o * 100.0, 0.0); 
                                EmitVertex();
                                
                                gl_Position =  vec4(b - o * 100.0, 0.0); 
                                EmitVertex();
                                
                                EndPrimitive();                          
                        """.trimIndent()
                fragmentTransform = """
                    x_fill.rgb = vec3(1.0, 0.0, 0.0);
                """.trimIndent()
            }
            drawer.translate(drawer.bounds.center)
            drawer.vertexBuffer(bufferVertices, DrawPrimitive.LINES)
        }
    }
}

If you run the code, you’ll notice that while the vertex and fragment shaders are correctly run, the geometry shader seems not to be called. To check this, just remove one of those precious semi-columns in the geometryTransform string, and you’ll see that the code still runs with no error, making me think the string is indeed not added to the graphic pipeline.
I have the feeling that drawer.vertexBuffer might not be the right place to use a geometry shader, but I couldn’t figure it out from the source code.
Has anyone successfully used geometry shaders in openRNDR?

UPDATE: This low-level approach seems to work as intended outside of shadeStyle…

import org.openrndr.application
import org.openrndr.draw.*
import org.openrndr.internal.Driver
import org.openrndr.math.Vector3

fun main() = application {
    configure {
        width = 1000
        height = 1000
    }

    program {
        val bufferVertices = vertexBuffer(vertexFormat {
            position(3)
        }, 2)

        bufferVertices.put {
            write(Vector3(0.0, -0.5, 0.0))
            write(Vector3(0.0, 0.5, 0.0))
        }

        val vs = """
            #version 330                
            in vec3 a_position;
            uniform float t;
            vec2 rotate(vec2 v, float a) {
                float s = sin(a);
                float c = cos(a);
                mat2 m = mat2(c, s, -s, c);
                return m * v;
            }
            void main() {
                vec2 a = rotate(a_position.xy, t);
                gl_Position = vec4(a, 0.0, 1.0);                
            }
            """

        val fs = """
            #version 330
            out vec4 o_output;
            void main() {
                o_output = vec4(1.0, 0.0, 0.0, 1.0);                                                                                                                
            }                                                                
            """

        val gs = """
            #version 330
            layout (lines) in;
            layout (triangle_strip, max_vertices = 4) out;
            void main(){
                vec3 a = vec3(gl_in[0].gl_Position);
                vec3 b = vec3(gl_in[1].gl_Position);
                vec3 n = normalize(b - a);
                vec3 o = vec3(n.y, -n.x, 0.0);
                                    
                gl_Position = vec4(a + o * 0.02, 1.0); 
                EmitVertex();
                                
                gl_Position = vec4(a - o * 0.02, 1.0); 
                EmitVertex();
                                    
                gl_Position = vec4(b + o * 0.02, 1.0); 
                EmitVertex();
                                    
                gl_Position =  vec4(b - o * 0.02, 1.0);
                EmitVertex();
                                    
                EndPrimitive();        
            }
            """
        val shader = Shader.createFromCode(vsCode = vs, fsCode = fs, gsCode = gs, name = "custom-shader")

        extend {
            shader.begin()
            shader.uniform("t", (seconds * 0.9).toFloat())
            Driver.instance.drawVertexBuffer(shader, listOf(bufferVertices), DrawPrimitive.LINES,0, 4)
            shader.end()
        }
    }
}

Thank you for sharing your discoveries :slight_smile:

In the orx demo folder there’s just one example for geometry shaders:

I remember trying it but couldn’t make full sense of it.
I still have a bunch of uncommitted changes / tests to the three gs shader files from that example.

Maybe now that it’s fresh for you, could you take a look at that demo and see if it works? Adding some comments would be super welcome too :slight_smile:

Cheers!

1 Like

Hello!
I have given a look at the code: the geometry shader there is “transparent”, namely it emits the exact same geometry, and doesn’t produce any new information.
Here I have modified the example

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.Shader
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.boxMesh

fun main() {
    application {
        configure {
            width = 1000
            height = 1000
        }
        program {
            val vb = boxMesh(5.0, 5.0, 5.0)
            val vsCode = """
                #version 410 core

                in vec3 a_position;
                in vec3 a_normal;
                in vec2 a_texCoord0;

                out InVertex {
                    vec3 va_position;
                    vec3 va_normal;
                    vec4 v_addedProperty;
                } vertexOut;

                uniform mat4 view;
                uniform mat4 proj;
                uniform mat4 model;

                void main() {
                    vertexOut.v_addedProperty = vec4(1.0, 0.0, 0.0, 1.0);
                    vertexOut.va_position = a_position;
                    vertexOut.va_normal = a_normal;
                    gl_Position = vec4(a_position, 1.0);
                }
            """.trimIndent()
            val gsCode = """
                #version 410 core

                layout (triangles) in;
                layout (line_strip, max_vertices = 3) out;

                in InVertex {
                    vec3 va_position;
                    vec3 va_normal;
                    vec4 v_addedProperty;
                } vertices[];

                out vec3 va_position;
                out vec3 va_normal;
                out vec4 v_addedProperty;
                out vec3 color;

                uniform vec3 offset;
                
                uniform mat4 view;
                uniform mat4 proj;
                uniform mat4 model;

                void main() {
                    int i;
                    for(i = 0;i < gl_in.length();i++) {
                        gl_Position = proj * view * model * gl_in[i].gl_Position;
                        color = vec3(gl_in[i].gl_Position.x * 0.6, gl_in[i].gl_Position.y * 0.6, 1.0);
                        EmitVertex();
                    }
                    EndPrimitive();
                }
            """.trimIndent()
            val fsCode = """
                #version 410 core
                in vec3 color;
                out vec4 o_color;
                void main() {
                    o_color = vec4(color, 1.0);
                }
            """.trimIndent()
            val shader = Shader.createFromCode(
                vsCode = vsCode,
                gsCode = gsCode,
                fsCode = fsCode,
                name = "x"
            )
            extend(Orbital())
            extend {
                drawer.clear(ColorRGBa.PINK)

                shader.begin()
                shader.uniform("offset", mouse.position.xy0)
                shader.uniform("view", drawer.view)
                shader.uniform("proj", drawer.projection)
                shader.uniform("model", drawer.model)
                driver.drawVertexBuffer(shader, listOf(vb), DrawPrimitive.TRIANGLES, 0, vb.vertexCount)
                shader.end()
            }
        }
    }
}

This time what will happen is that a wireframe of the box mesh is produced, and the color attribute is changed by the geometry shader itself. In other words, the geometry shader receives a triangle primitive, and produces a line strip primitive, with varying colors of the vertices.
Notice the the position transformation is now delegated to the geometry shader: this is due to the fact that the color should stick to the vertex itself, and not to its view coordinates, otherwise when rotating the cube the color “would not follow”.
Maybe I could add to the guide something about geometry shaders and their usage…
Neverthless, this still does not address the point of how to use geometry shaders in shadestyle, since in this example the Shader class and Driver class are used instead.

1 Like