I have been recently interested in stencil buffers (here and here), which are quite useful when creating reflections, shadows, portals, etc.
Here’s a little implementation of reflections from a planar mirror of any shape. The main idea is to render the object(s) as reflected by the mirror, and through enabling and disabling the stencil buffer we can make so that the only fragments of the reflected object(s) which don’t get discarded are those that coincide with the reflective surface.
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extensions.Screenshots
import org.openrndr.extra.meshgenerators.boxMesh
import org.openrndr.extra.noise.Random
import org.openrndr.extra.shapes.hobbyCurve
import org.openrndr.math.Polar
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
import org.openrndr.shape.ShapeContour
fun main() = application {
configure {
width = 1000
height = 1000
multisample = WindowMultisample.SampleCount(8)
}
program {
val n = 30
val points = List(n) {
Polar(it * 360 / n.toDouble(), Random.double(1.0, 20.0) + 40.0).cartesian
}
val sh = ShapeContour.fromPoints(points, closed = true).hobbyCurve()
val numVerts = 200
val vertices = sh.equidistantPositions(numVerts)
val shapeBuffer = vertexBuffer(vertexFormat {
position(3)
normal(3)
}, numVerts * 3)
shapeBuffer.put {
repeat(numVerts) {
write(Vector3(0.0, 0.0, 0.0))
write(Vector3(0.0, 0.0, 1.0))
write(Vector3(vertices[it % numVerts].x, vertices[it % numVerts].y, 0.0))
write(Vector3(0.0, 0.0, 1.0))
write(Vector3(vertices[(it + 1) % numVerts].x, vertices[(it + 1) % numVerts].y, 0.0))
write(Vector3(0.0, 0.0, 1.0))
}
}
val cube = boxMesh(25.0, 25.0, 25.0)
extend {
val lightPos = Vector3(0.0, 30.0, -80.0)
val objectColor = Vector3(0.5, 0.1, 0.6)
val tr_mirror = transform {
translate(0.0, 0.0, -150.0)
rotate(Vector3.UNIT_X, -70.0)
}
val tr_cube = transform {
translate(0.0, 25.0, -150.0)
rotate(Vector3.UNIT_XYZ, 70.0 + seconds * 40.0)
}
val tr_reflected = transform {
scale(1.0, -1.0, 1.0)
translate(0.0, 25.0, -150.0)
rotate(Vector3.UNIT_XYZ, 70.0 + seconds * 40.0)
}
drawer.perspective(60.0, width * 1.0 / height, 0.01, 1000.0)
//Draw object
drawer.depthWrite = true
drawer.depthTestPass = DepthTestPass.LESS_OR_EQUAL
drawer.stroke = null
drawer.fill = ColorRGBa.WHITE
drawer.shadeStyle = shadeStyle {
vertexTransform = """
x_modelMatrix = x_modelMatrix * p_tr;
x_modelNormalMatrix = x_modelNormalMatrix * p_tr;
""".trimIndent()
parameter("tr", tr_cube)
fragmentTransform = """
vec3 lightDir = normalize(p_lightPos - v_worldPosition);
float d = max(dot(v_worldNormal, lightDir), 0.0);
x_fill.rgb = d * vec3(0.9, 0.2, 0.9);
""".trimIndent()
parameter("lightPos", lightPos)
parameter("objectColor", objectColor)
}
drawer.vertexBuffer(cube, DrawPrimitive.TRIANGLES)
//Draw mirror
drawer.drawStyle.depthWrite = false
drawer.drawStyle.stencil.stencilFunc(StencilTest.ALWAYS, 1, 0xFF)
drawer.drawStyle.stencil.stencilWriteMask = 0xFF
drawer.drawStyle.stencil.stencilOp(
StencilOperation.KEEP,
StencilOperation.KEEP,
StencilOperation.REPLACE
)
drawer.shadeStyle = shadeStyle {
vertexTransform = """
x_modelMatrix = x_modelMatrix * p_tr;
x_modelNormalMatrix = x_modelNormalMatrix * p_tr;
""".trimIndent()
parameter("tr", tr_mirror)
fragmentTransform = """
vec3 lightDir = normalize(p_lightPos - v_worldPosition);
float d = max(dot(v_worldNormal, lightDir), 0.0);
x_fill.rgb = d * vec3(0.0, 0.8, 0.9);
""".trimIndent()
parameter("lightPos", lightPos)
}
drawer.vertexBuffer(shapeBuffer, DrawPrimitive.TRIANGLES)
//Draw reflected object
drawer.depthWrite = true
drawer.drawStyle.stencil.stencilFunc(StencilTest.EQUAL, 1, 0xFF)
drawer.drawStyle.stencil.stencilWriteMask = 0x00
drawer.shadeStyle = shadeStyle {
vertexTransform = """
x_modelMatrix = x_modelMatrix * p_tr;
x_modelNormalMatrix = x_modelNormalMatrix * p_tr;
""".trimIndent()
parameter("tr", tr_reflected)
fragmentTransform = """
vec3 lightDir = normalize(p_lightPos - v_worldPosition);
float d = max(dot(v_worldNormal, lightDir), 0.0);
x_fill.rgb = d * p_objectColor * 0.5;
""".trimIndent()
parameter("lightPos", lightPos)
parameter("objectColor", objectColor)
}
drawer.vertexBuffer(cube, DrawPrimitive.TRIANGLES)
}
}
}
It looks like this