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…