A question about z-sorting

Here’s a slight elaboration on something I showed at the Openrndr meetup (which was great!), which makes me confused about z-sorting

    data class Trigger(var t: Double = 0.0){
        fun update(){
            t *= 0.9
        }
        fun restart(){
            t = 1.0
        }
    }
    
    fun getTriangle(p: Vector3, L: Double, noise: Boolean = false): List<Vector3>{
    
            val n = p.normalized
            val eX = if (p.x != 0.0 && p.y != 0.0 ) Vector3(-p.y, p.x, 0.0).normalized else Vector3.UNIT_X
            val eY = n.cross(eX)
            val verts = mutableListOf<Vector3>()
            val offset = if(noise) Double.uniform(0.0, 1.0) else 0.0
            for (x in 0 until 3){
                verts.add(p + (eX * cos((x * 1.0/3  + offset) * 2.0 * PI) + eY * sin((x * 1.0/3  + offset) * 2.0 * PI)) * L)
            }
            return verts
    }
    
    fun main() = application {
        configure {
            multisample = WindowMultisample.SampleCount(4)
            fullscreen = Fullscreen.CURRENT_DISPLAY_MODE
        }
        program {
            val trigger = Trigger()
            var time = 0.0
            var prob = 0.0
            val geometry = vertexBuffer(vertexFormat {
                position(3)
                normal(3)
            }, 3 * 500).also {
                it.put {
                    for (i in 0 until it.vertexCount / 3) {
                        val pos =
                            Vector3.fromSpherical(Spherical(Double.uniform(0.0, 180.0), Double.uniform(0.0, 360.0), Double.uniform(40.0, 40.0)))
                        val positions = getTriangle(pos,5.0, false)
                        val normal = pos.normalized
                        positions.map {
                            write(it)
                            write(normal)
                        }
                    }
                }
            }
    
            val rt = renderTarget(width, height) {
                colorBuffer()
            }
    
            val prevFrame = colorBuffer(width, height)
            prevFrame.fill(ColorRGBa.BLACK)
            val actualFrame = colorBuffer(width, height)
            prevFrame.fill(ColorRGBa.BLACK)
    
            extend {
                drawer.isolatedWithTarget(rt) {
                    drawer.clear(ColorRGBa.BLACK)
                    drawer.perspective(60.0, width * 1.0 / height, 0.01, 1000.0)
                    drawer.depthWrite = true
                    drawer.depthTestPass = DepthTestPass.LESS_OR_EQUAL
    
                    drawer.fill = ColorRGBa.WHITE.opacify(1.0)
                    drawer.shadeStyle = shadeStyle {
                        vertexPreamble = """
                            float rand(vec2 co){
                                return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
                            }
                        """.trimIndent()
                        vertexTransform = """
                            x_position = a_position * (1.0 + rand(vec2(floor(gl_VertexID/3) )) * 0.1);
                        """.trimIndent()
                        fragmentTransform = """              
                            float dot_prod = dot(v_worldNormal, p_light);
                            float normalized = (1 + dot_prod) / 2;
                            float light = clamp(normalized, 0.0, 1.0);
                            x_fill.rgb = vec3(light);   
                        """.trimIndent()
                        parameter("time", time)
                        parameter("light", Vector3(0.0, 0.0, 1.0).normalized)
                    }
                    drawer.translate(0.0, 0.0, -150.0)
                   // drawer.rotate(Vector3.UNIT_X, seconds * 15 + 30)
                  //  drawer.rotate(Vector3.UNIT_Y, seconds * 5 + 60)
                    drawer.scale(1.3)
                    drawer.vertexBuffer(geometry, DrawPrimitive.TRIANGLES)
                }
    
                rt.colorBuffer(0).copyTo(actualFrame)
    
                drawer.isolatedWithTarget(rt){
                    drawer.clear(ColorRGBa.BLACK)
                    drawer.fill = ColorRGBa.WHITE
                    drawer.shadeStyle = shadeStyle {
                        fragmentPreamble = """
                            float rand(vec2 co){
                                return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
                            }
                            
                             vec3 laplacian(in vec2 uv, in sampler2D tex, in vec2 texelSize) {
                                      vec3 rg = vec3(0.0);
                                     
                                      rg += texture(tex, uv + vec2(-1.0, -1.0)*texelSize).rgb * 0.05;
                                      rg += texture(tex, uv + vec2(-0.0, -1.0)*texelSize).rgb * 0.2;
                                      rg += texture(tex, uv + vec2(1.0, -1.0)*texelSize).rgb * 0.05;
                                      rg += texture(tex, uv + vec2(-1.0, 0.0)*texelSize).rgb * 0.2;
                                      rg += texture(tex, uv + vec2(0.0, 0.0)*texelSize).rgb * -1;
                                      rg += texture(tex, uv + vec2(1.0, 0.0)*texelSize).rgb * 0.2;
                                      rg += texture(tex, uv + vec2(-1.0, 1.0)*texelSize).rgb * 0.05;
                                      rg += texture(tex, uv + vec2(0.0, 1.0)*texelSize).rgb * 0.2;
                                      rg += texture(tex, uv + vec2(1.0, 1.0)*texelSize).rgb * 0.05;
                                                    
                                      return rg;
                                    }
                        """.trimIndent()
                        fragmentTransform = """
                  
                            vec2 texCoord = c_boundsPosition.xy;
                            texCoord.y = 1.0 - texCoord.y;
                            vec3 col = texture(p_actualFrame, texCoord).rgb;
                            vec2 size = 1.0/textureSize(p_prevFrame, 0);
                            float theta = rand(vec2(mod(p_time , 1.0), mod(p_time, 1.0)) );
                            float d = rand(vec2(mod(p_time * 2.0,1.0))) + 0.1 * length(texCoord - vec2(0.5));
                            vec2 offset = vec2(cos(theta * 2.0 * 3.14 ), sin(theta * 2.0 * 3.14 ))  * 0.09 * d;
                            texCoord -= vec2(0.5);
                            texCoord *= 0.98 - 0.1 * d;
                            texCoord += vec2(0.5);
                            vec3 prevCol = vec3(texture(p_prevFrame, texCoord).r, texture(p_prevFrame, texCoord + offset * p_t).gb);
                            vec3 diffuse = laplacian(texCoord, p_prevFrame, size);
                            x_fill.rgb = col + p_t * prevCol + 0.8 * diffuse;
                        """.trimIndent()
                                parameter("actualFrame", actualFrame)
                                parameter("prevFrame", prevFrame)
                                parameter("t", trigger.t)
                                parameter("time", time)
                    }
    
                    drawer.rectangle(drawer.bounds)
                }
    
    
                drawer.image(rt.colorBuffer(0))
                rt.colorBuffer(0).copyTo(prevFrame)
                trigger.update()
                if (Random.bool(prob)) {
                    trigger.restart()
                    prob += Double.uniform(-0.05, 0.05)
                    prob = max(0.01, prob )
                    prob = min(0.2, prob)
                }
                time += 0.1
            }
        }
    }

Putting aside the visual effect (which I set to 0.0), basically I generate triangles normal to a sphere and then by using the vertex ID I randomly displace them in the vertex shader. They all have the same size, nevertheless even if no random displacement is present I get this

I can see that the smaller black triangle should be in the back, but are rather rendered in front. I am aware of this post , so I set up the depthWrite and depthTestPass as suggested there. I am also drawing in isolated, though with rendering. I was wondering if this has something to do with the fact that I’m sending a vertex buffer containing all the geometry, rather than instancing each triangle with its transform matrix…

I suspect you are right about the “sending a vertex buffer containing all the geometry”. It’s maybe similar to this Drawer.lineSegments() rendering issue · Issue #155 · openrndr/openrndr · GitHub

Maybe the depth buffer is only updated once per drawing operation? So if you draw the triangles one by one the depth buffer is updated for each (drawing the full triangle, part of it, or not at all depending on the depth buffer’s current state), but if you draw them all in one operation the depth is updated after they are all drawn. At least what I suspect.

What if you do drawer.contours() instead? Would it be fast enough?

1 Like

The problem seems to be with .isolatedWithTarget: here it is the (correct) result with just .isolated

This of course begs the question about how to do postprocessing in this approach.

1 Like

Alright, solved: the render target must have a depth buffer, i.e. just declare it as

    val rt = renderTarget(width, height) {
              colorBuffer()
              depthBuffer()
          }
1 Like