Mirrors on flat objects


1. Intro

This technique allows to create nicely looking mirrors on flat objects. Right now it is limited to quads (any polygon with 4 corners that is more-or-less flat), but in the future it should be useful on any flat geometry.

Note that to display a mirror on a curved object (like a sphere, or a teapot) it is better to use cubemap reflections (GeneratedCubeMapTexture).

This feature was implemented thanks to your support on Patreon!

2. Demo models

Example models using this technique can be seen in our demo models. Get that repository, and open these models:

You can open them using castle-model-viewer.

3. Demo movies

4. Defining it in X3D

4.1. Summary

In short:

  • Use RenderedTexture as a texture.
  • As the RenderedTexture.viewpoint use a special new node ViewpointMirror. This makes the mirror texture generated with a suitable camera settings (we reflect the current camera by the mirror plane).
  • Generate the texture coordinates using the TextureCoordinateGenerator node, with TextureCoordinateGenerator.mode equal to "MIRROR-PLANE". This will match the texture obtained using the ViewpointMirror, and will make it look perfectly to the observer.

Here is a simple example in X3D classic encoding:

Shape {
  appearance Appearance {
    texture RenderedTexture {
      dimensions [ 1024 1024 3 ]
      viewpoint ViewpointMirror { }
      repeatS FALSE
      repeatT FALSE
      update "ALWAYS"
  geometry IndexedFaceSet {
    coord Coordinate { point [ 0 0 0, 100 0 0, 100 100 0, 0 100 0, ] }
    coordIndex [ 0 1 2 3 ]
    texCoord TextureCoordinateGenerator { mode "MIRROR-PLANE" }

4.2. Mirror texture: ViewpointMirror inside RenderedTexture

ViewpointMirror {
  SFFloat    [in,out]      distanceFromShape  0.01      
  # In XML encoding, the default containerField of this node is "viewpoint"

ViewpointMirror is a viewpoint that defines a mirrored version of the current viewpoint. The mirror viewpoint is calculated by reflecting the current viewpoint by the current shape's plane.

  • The "current viewpoint" is just the current camera used to render this scene.

    In case the scene is rendered in multiple viewports (using TCastleViewport), it is for now undefined which camera is used for mirror — so it's safest to use this feature only when rendering the scene from a single viewport. Warning: do not confuse the terms "viewpoint" (camera vectors) and "viewport" (2D window through which you observe 3D world on a computer screen), they mean very different things.

  • The "current shape" is the shape using this RenderedTexture.

    Don't use the same RenderedTexture node multiple times (through X3D DEF/USE mechanism) on various shapes.

    We assume that the current shape is more-or-less flat. That is, all the shape's coordinates should lie on the same plane in 3D. We will automatically calculate the plane equation internally.

    The shape must be an IndexedFaceSet with 4 points now. We will extend this in the future to account for any shape.

Together with RenderedTexture node, this allows to easily achieve a mirror effect. You can use the ViewpointMirror node only in RenderedTexture.viewpoint.

The field distanceFromShape specifies a shift from the current shape, to avoid rendering the mirror surface itself into the mirror. In case of shapes that are not actually perfectly flat (e.g. using this to render a mirror for a hemisphere), increasing this makes sense.

Mirror contents are kept in a texture instead of being generated each time on screen. This has advantages and disadvantages:

  • It's never perfect, as it's squeezed into a square/rectangle texture that is then stretched over a quad. You have to set the texture size sufficiently large, to make it look good enough.

  • Since the texture size is configurable, you can easily make this technique faster by sacrificing quality: just decrease RenderedTexture size. You can of course make it configurable for the user (like a lower quality graphics / higher quality graphics toggle).

  • Since the mirror contents are in the texture, you don't need to regenerate them every frame. If the world (reflected in a texture) isn't dynamic (nothing animates) and camera doesn't move (noticeably), you don't have to update the texture. You can set RenderedTexture.update=NEXT_FRAME_ONLY to force regeneration at next frame only, without automatically updating in the later frames.

  • The texture is generated assuming a planar surface, but you can apply it on slightly non-planar surfaces too, like a surface with some vertexes slightly perturbed (e.g. to simulate water waves). You can also play with tweaking texture coordinates to achieve more interesting water look.

4.3. Texture coordinates for the mirror texture: TextureCoordinateGenerator.mode = MIRROR-PLANE

To map the generated mirror texture on a geometry, use a special texture coordinate generation mode "MIRROR-PLANE". It matches the ViewpointMirror behavior.