This chapter briefly shows how to render a scene with global illumination using PRT.
Figure 1: Efficient global illumination effects such as interreflections and self-shadowing with Precomputed Radiance Transfer for rigid surfaces.
The idea of PRT is to model incoming and outgoing radiance as an environment map approximated with spherical harmonics. Then, radiance propagation becomes the process of turning incoming radiance into outgoing radiance according to the BRDF. At every step of the propagation, we represent radiance with spherical harmonics. Using this representation, radiance transfer can be reduced to linear transformations. Such a transfer function models the surface-light interaction at a point on the surface. It is a matrix. Radiance transfer becomes a matrix vector multiplication.
Finding a transfer matrix involves simulating how radiance propagates through the scene with ray tracing. We need to simulate locally incident radiance at every point on the surface which requires us to account for global lighting effects.
Because transfer functions only depend on the scene geometry and materials, they can be precomputed independently of view or lighting environments. In practice, transfer matrices are computed for each vertex in the scene and interpolated over triangles during rendering.
After this brief refresher we are now going to render a scene with the faces-scala-prt framework.
First we load an example mesh and a lighting environment. Feel free to load other files from the data
folder.
val light = readEnvironmentMap("data/env01.jpg")
val mesh = readMesh("data/test-scene.ply")
val color = mesh.getVertexColorOrElseWhite
The central construct in the PRT framework is the ParametricPrtTechnique
class and its subclasses which form a easy-to-use abstraction layer over the lower-level PRT components.
val technique = LambertTechnique // try GlossyTechnique
.withDecorator(ProfiledTechnique(_)) // Displays precomputation times
Next we are going to specify the render parameters for our scene. Please note that they are tied to the type of the used ParametricPrtTechnique
val parameters =
technique.getDefaultParameters(mesh.triangleMesh)
.withColoredBrdf(color, brdfSharpness = 3)
.withLightBounces(2) // number of bounces used to compute interreflections
.withPixelShading // Per-vertex lighting
After having defined our PRT rendering technique and its corresponding parameters we can precompute the light transfer matrices for our scene. Depending on your machine, this step might take up to a minute to compute.
val transfer = technique.simulateTransfer(parameters)
With the parameters and the precomputed transfer matrices we can instantiate a PRT renderer.
val renderer = technique.getRenderer(parameters, transfer).withClearColor(RGBA.WhiteTransparent)
That we then use to render the scene inside an interactive window:
val renderPanel = new InteractiveRenderPanel {
def rotatedLight(light: RGBTuple[DenseVector[Double]]): RGBTuple[DenseVector[Double]] = {
val rot = SHRotation(input.mouseDraggedRotationSecondary)
light.mapRGB(sh => rot * sh)
}
override def render(param: RenderParameter): PixelImage[RGBA] =
renderer.renderImage(
param.withEnvironmentMap(rotatedLight(light).rgb),
PrtRenderParameter.default.withoutLightIntensityFix
)
}
val fig = new InteractiveFigure("Demo", interactiveRenderPanel = renderPanel) // starts automatically