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:
#include <stdio.h> #include <mi/shader.h> int mypoint_version(void) {return(1);} 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:
#include <stdio.h> #include <mi/shader.h> int myspot_version(void) {return(1);} miBoolean myspot( miColor *result, miState *state, struct soft_light *paras) { miScalar d, t; miScalar inner, outer; d = mi_vector_dot(&state->dir, mi_eval_vector(¶s->direction)); if (d <= 0) return(miFALSE); mi_query(miQ_LIGHT_SPREAD, state, 0, &outer); if (d < outer) return(miFALSE); *result = *mi_eval_color (¶s->color); inner = *mi_eval_scalar(¶s->inner); if (d < inner) { t = (outer - d) / (outer - inner); result->r *= t; result->g *= t; result->b *= t; } return(result->r < .005 && result->g < .005 && result->b < .005 ? (miBoolean)2 : mi_trace_shadow(result, state)); }
Again, miFALSE is returned if no illumination takes place, and miTRUE otherwise. 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.
If the light shader is also to be used for a visible area light source, it needs to check whether state- >type is different from miRAY_LIGHT. If so, a ray has intersected the the area light source directly. In this case, no shadow rays have to be checked, and the light color can directly be assigned to result.