Light shaders are called from other shaders by sampling a light using the mi_sample_light or mi_trace_light functions, which perform some calculations and then call the given light shader, or directly if a ray hits a source. mi_sample_light may also request that it is called more than once if an area light source is to be sampled. For an example for using mi_sample_light, see the section on material shaders above. mi_trace_light performs less exact shading for area lights, and is provided for backwards compatibility only.
The light shader computes the amount of light contributed by the
light source to a previous intersection point, stored in
state→point
. The calculation may be based on the
direction state→dir
to that point, and the
distance state→dist
from the light source to that ray. There may also
be shader parameters that specify directional and distance
attenuation. Directional lights have no location;
state→dist
is undefined in this case.
Light shaders are also responsible for shadow casting. Shadows are computed by finding all objects that are in the path of the light from the light source to the illuminated intersection point. This is done in the light shader by casting "shadow rays" after the standard light color computation including attenuation is finished. Shadow rays are cast from the light source back towards the illuminated point (or vice versa if shadow segment mode is enabled), in the same direction of the light ray. Every time an occluding object is found, that object's shadow shader is called, if it has one, which reduces the amount of light based on the object's transparency and color. If an occluding object is found that has no shadow shader, it is assumed to be opaque, so no light from the light source can reach the illuminated point. For details on shadow shaders, see the next section.
Here is an example for a simple point light that supports no attenuation, but casts shadows:
struct mypoint { miColor color; }; miBoolean mypoint( register miColor *result, register miState *state, register struct mypoint *paras) { *result = *mi_eval_color(¶s->color); return(mi_trace_shadow(result, state)); }
The shader parameters are assumed to contain the light color. The shadows are calculated simply by giving the shadow shaders of all occluding objects the chance to reduce the light from the light source by calling mi_trace_shadow. The shader returns miTRUE if some light reaches the illuminated point.
There is a useful trick that can improve performance significantly: the light shader should trace shadow rays only if the contribution from the light is greater than some threshold, for example because distance or angle attenuation has left so little of the light color (less than 1/256, for example) that it does not matter whether this contribution is counted or not. If this is the case, the shader might skip shadow calculation (significantly increasing speed in a complex scene) and return (miBoolean)2 to indicate that if this is a small area light source, there is no point in continuing to sample it because all the other samples are not going to make a contribution either. Returning 2 does not work well for large area light sources because some parts of the light may be closer than others and may exceed the threshold for returning early. Consider the following alternate shader body:
{ *result = *mi_eval_color(¶s->color); apply_distance_attenuation(result); apply_angle_attenuation(result); if (result->r < .005 && result->g < .005 && result->b < .005) return((miBoolean)2); else return(mi_trace_shadow(result, state)); }
The point light can be turned into a spot light by adding directional attenuation parameters for the inner and outer cones and a spot direction parameter to the shader parameters, and change the shader to reduce the light intensity if the illuminated point falls between the inner and outer cones, and turns the light off if it does not fall into the outer cone at all:
struct mib_light_spot { miColor color; /* color of light source */ miBoolean shadow; /* light casts shadows */ miScalar factor; /* makes opaque objects transparent */ miBoolean atten; /* distance attenuation */ miScalar start, stop; /* if atten, distance range */ miScalar cone; /* inner solid cone */ }; DLLEXPORT miBoolean mib_light_spot( register miColor *result, register miState *state, register struct mib_light_spot *paras) { register miScalar d, t, start, stop, cone; miScalar spread; miVector ldir, dir; miTag ltag; *result = *mi_eval_color(¶s->color); if (state->type != miRAY_LIGHT) /* visible area light*/ return(miTRUE); /*angle atten*/ ltag = ((miInstance *)mi_db_access(state->light_instance))->item; mi_db_unpin(state->light_instance); mi_query(miQ_LIGHT_DIRECTION, state, ltag, &ldir); mi_vector_to_light(state, &dir, &state->dir); mi_vector_normalize(&dir); d = mi_vector_dot(&dir, &ldir); if (d <= 0) return(miFALSE); mi_query(miQ_LIGHT_SPREAD, state, ltag, &spread); if (d < spread) return(miFALSE); cone = *mi_eval_scalar(¶s->cone); if (d < cone) { t = 1 - (d - cone) / (spread - cone); result->r *= t; result->g *= t; result->b *= t; } if (*mi_eval_scalar(¶s->atten)) { /* dist atten*/ stop = *mi_eval_scalar(¶s->stop); if (state->dist >= stop) return(miFALSE); start = *mi_eval_scalar(¶s->start); if (state->dist > start && fabs(stop - start) > EPS) { t = 1 - (state->dist - start) / (stop - start); result->r *= t; result->g *= t; result->b *= t; } } if (*mi_eval_boolean(¶s->shadow)) { /* shadows: */ d = *mi_eval_scalar(¶s->factor); if (d < 1) { miColor filter; filter.r = filter.g = filter.b = filter.a = 1; /* opaque */ if (!mi_trace_shadow(&filter,state) || BLACK(filter)) { result->r *= d; result->g *= d; result->b *= d; if (d == 0) return(miFALSE); } else { /* transparent*/ double omf = 1 - d; result->r *= d + omf * filter.r; result->g *= d + omf * filter.g; result->b *= d + omf * filter.b; } } } return(miTRUE); }
This shader performs both distance attenuation (it illuminates only between the start and stop parameters if defined) and angle attenuation (full energy up to cone, then a linear falloff to the spread of the light source definition, accessed with mi_query). Outside spread the shader will not even get called (this is why spread is a light definition parameter, not a shader parameter - mental ray needs it reject light sources during sampling). The shader can also handle visible light sources: it returns its light color if the ray type is not equal to miRAY_LIGHT. Finally, the shader casts shadow rays if they are enabled to reduce the returned light if there are occluding objects.
Note that none of these light shaders takes the normal at the
illuminated point into account; the light shader is merely
responsible for calculating the amount of light that reaches that
point. The material shader (or
other shader) that sampled the light must use the dot_nd
value returned by mi_sample_light, and its own shader
parameters such as the diffuse color, to calculate the actual
fraction of light reflected by the material. Note also that
state→instance
is not the instance of the light
but of the illuminated point; use
state→light_instance
instead.
mental ray 3.1 also supports geometric area light sources, which behave like the fixed four area light source types (rectangle, disc, sphere, and cylinder) except that the light shape is defined by an object instance. All points on the surface of the object are emitting light uniformly. For every light sample, mental ray constructs an intersection point with the object, and then calls the light shader. The light shader now has two sets of parameters:
state→point
and the
other intersection-related state variables describe the illuminated
point.state→child
state contains the
intersection information for the light object, just like a material
shader would receive it, including texture coordinates if available. For
example, state→child→point
contains the
chosen light-emitting intersection point on the light object. Note
that the child state's local space is the object's internal space,
not the light's internal space; it is recommended that the instance
that connects the light and the light object uses an identity
transformation.If the light object is uniformly emitting light from all points on its surface, no modifications to the light shader are required to work with geometric area light sources. Note that geometric area light source are supported only for point lights, but not spot or infinite lights.
mental ray
supports an additional user-defined area light source, which leaves
sample point selection completely to the light shader. The shader is
expected to select points on its surface, cast shadow rays at these
points if required, and return the illumination from that point.
mental ray treats such lights as zero-dimension point lights, without
any further information on origin and direction. Instead, the light
shader modifies the values state→org
and
state→dir
as necessary, including any dependent values
like state→dist
and state→dot_nd
.
The shader is called
repeatedly until it returns (miBoolean)2. A call counter
is provided in state->count, which is 0 for the first
call, and counts up to 65536 (normally a few or a few tens of calls
are considered sufficient).
mental ray also supports light profiles, such as IES or Eulumdat light profiles, that are supplied by physical lamp vendors. A light profile can be attached to light objects in the scene as a property, or may be passed directly to light shaders which attenuate the light color (which is not provided by the profile) by the profile's directional attenuation specification. This is done by the mi_lightprofile_sample shader interface function, which requires a profile argument that is taken from a shader parameter:
typedef struct { miColor color; /* color of light source */ miTag profile; /* tag of ies profile to use */ } photometric_light_t; DLLEXPORT int photometric_light_version(void) {return(1);} DLLEXPORT miBoolean photometric_light( miColor *result, miState *state, photometric_light_t *paras) { miTag lp_tag = *mi_eval_tag(¶s->profile); miScalar factor = mi_lightprofile_sample(state, lp_tag, miTRUE); factor *= 1.0 / (state->dist * state->dist); *result = *mi_eval_color(¶s->color); result->r *= factor; result->g *= factor; result->b *= factor; return(mi_trace_shadow(result, state)); }
This shader performs physically correct inverse-square distance falloff, and also takes care of shadows. Here is a scene example that uses this shader:
declare shader color "photometric_light" ( color "color", lightprofile "profile") version 1 end declare lightprofile "myprof" format ies hermite 1 file "/usr/local/ies/profile.ies" end lightprofile light "mylight" "photometric_light" ( "color" 100 100 100, "profile" "myprof") ... end light
The light color is chosen large because of the inverse-square falloff; it depends on the distance from the light to the illuminated point. This scene fragment assumes that an IES light profile file, as provided by the lamp vendor, exists in /usr/local/ies/profile.ies.
Copyright © 1986-2009 by mental images GmbH