This documentation is intended to instruct developers in the authoring of custom lights. Developers should also consult the
RixLight.h header file for complete details.
RixLightFactory interface is a subclass of
RixShadingPlugin, and defines a shading plugin responsible for creating a
RixLight interface characterizes the light emitting from an analytic light source - a light source that can be described programmatically or by a formula.
RixLightFactory is a subclass of
RixShadingPlugin, and therefore shares the same initialization, synchronization, and parameter table logic as other shading plugins. Therefore to start developing your own Light, you can
#include "RixLight.h" and make sure your light factory class implements the required methods inherited from the RixShadingPlugin interface:
CreateInstanceData(). Generally, there is one shading plugin instance of a
RixLightFactory per bound
RiLight (RIB) request. This instance may be active in multiple threads simultaneously.
The RIX_LIGHTFACTORYCREATE() macro defines the CreateRixLightFactory() function, which is called by the renderer to create an instance of the light factory plugin. Generally, the implementation of this method should simply return a new allocated copy of your light factory class. Similarly, the RIX_LIGHTFACTORYDESTROY() macro defines the DestroyRixLightFactory() function called by the renderer to delete an instance of the light factory plugin; a typical implementation of this method is to delete the passed in light factory pointer:
RixLight is the abstract base class from which you can derive your own light implementations. To illustrate the API, we have provided PxrSimpleRectLight.cpp, which implements a simple non-textured single-sided light of rectangular shape. Note that the PxrRectLight that ships with RenderMan offers more features than illustrated here, and uses more sophisticated sampling strategies. It also supports bidrectional sampling and photon emission, which the example does not.
The light's constructor is called by the corresponding
We have two methods used to communicate geometric properties to the ray tracer.
GetBounds() returns a sequence of points describing the bounding shape of the light. The bounds should be expressed in the local space of the light. For our rect light example, there are four points in the range +/- 0.5 in x and y. The rect light lies on the z=0 plane. Intersect, the second method, will compute an intersection between the light and an incoming ray. The intersection is computed in the local space of the light. A consequence of this is that the ray direction will not be normalized if the light's transform contains a scale. It's important, therefore, not to make use of any optimisations in your intersection function that does assume a unit length direction.
Light selection methods
There are three methods that act as helpers for the renderer's light selection scheme. Light selection is a stochastic process whereby, according to integrator settings, one or more lights are assigned to a shade point in a rendering iteration. The lights that are selected have samples generated for them (see below). The purpose of selection is to attempt to choose the lights liable to contribute most to the shade point in question, thereby keeping variance low.
To help with this calculation, the renderer will call
GetIncidentRadianceEstimate() on the light, providing both the position of the shade point (in 'current' space) and a pair of transforms. In our RectLight example, we check to see if the shade point lies to the front of the light. If it does, we multiply its intensity by its area (which may be non-unity in the event of a scale transform) and the cosine of the angle between its normal and the vector between shade point and light center. We then divide by the squared distance to the light center and return the result.
A second overload of
GetIncidentRadianceEstimate() is used to compute estimates for ray segments rather than individual points. This is used exclusively for equiangular sampling of volumes. In our example, we find the nearest point on the incoming line segment to the light and then treat that just as the shade point in the simpler case. Note that this overload has minT and maxT as return values. These can be used to 'clip' the line segment, providing a subset over which the light provides non-zero illumination. For example, since the rect light is single-sided, we could clip the segment against the light's plane. Similarly, if the light was a spot light, we could clip the segment against the cone's frustum.
GetPowerEstimate() should return the light's intensity by its area. This is a crude estimate given independent of any shade point.
GenerateSamples() is the function used to create a sample on the light and put it in the
GenerateSamplesResult structure, defined in RixLight.h.
UVW indicates the position of the sample in the light's parametric space; direction is the normalized vector from the shade point to the light sample position in 'current' space; distance is the distance between the two points; and pdf is the pdf of the chosen point in solid angle measure. In the example case, we have a uniform probability of sampling across the light's surface, so the area pdf is 1/area. This is then converted to solid angle measure by multiplying by the cosine of the angle between light and outgoing direction, and dividing by the squared distance. The light returns both radiance in both
specularColor. These will be interpreted separately by a bxdf's diffuse and specular lobes, and allows for a light to contribute different radiances for each. The light should also return the local-space normal at the sampled point on the light. (The normal is constant in the example rect light.) Note that the input
RixLightContext grants the function access to the sample's time in normalized shutter time (ie 0 at shutter open and 1 at shutter close); a function
GetLightToCurrentTransform() will return a matrix at the appropriate time, and gives access to a random-number pair in a well-stratified sequence. A flag on the
GenerateSamplesResult indicates whether the light is being used in a bidirectional setting. If so, it expected to provide three further return values (not covered by the example).
solidAngleToArea is a conversion factor to convert between the two pdf measures. For a rect light, this would be the cosine of the angle between light normal and the direction vector divided by the squared distance.
pdfEmit is the probability of emitting a photon from the selected sample position on the light, again expressed in a solid angle measure. (For a rect light with a uniform sampling scheme,
pdfEmit would be 1/area.)
pdfEmitDirection is the probability of emitting a photon in the selected direction given the selected sample position. (For a rect light with cosine emission distribution, this would be cos(theta) / PI.)
EvaluateSamples() is called so that the light can compute intensity and angular-measure pdf for an incoming ray direction (typically generated by sampling a Bxdf).
EvaluateSamples() will only be called for a ray if a previous Intersect call returned true for the same ray. Results are returned in the
EvaluateSamplesResult structure, definied in RixLight.h. '
pdfDirect' is the solid-angle-measure pdf for the ray;
specularColor are the light's contribution for diffuse and specular lobes respectively, and 'normal' is the light's surface normal at the point of intersection. The bidirectional result quantities are the same as described above for
GenerateEmission() is the function used to create photons from the light, used in a bidirectional pathtracing context. Note that it requires four random numbers: two for picking a point on the surface (with uniform probability in our example) and two for picking a direction (with a cosine distribution). Note that in this special case, since we don't at this stage in the process of a shade point, the pdfs are not in the solid angle measure. We return
pdfEmitDirection (see above) and the renderer will employ a solid-angle-measure conversion once the emitted photon has struck a surface internally.
EvaluateEmissionForCamera() will be called if a light is marked as camera-visible and is intersected by a camera ray. Its result is returned in the
EvaluateEmissionForCameraResults structure, which contains the single color field
Edit() is the function that will be called after any changes are made to the light properties. It is expected to update the class members for any subsequent sampling. Note that in more sophisticated lighting examples, this could involve such things as computing a new CDF table for a textured light.