/* 
 * Copyright (c) 1998 Thomas E. Burge.  All rights reserved.  
 * 
 *
 * FILE:  tebSpotLight.sl
 *
 *
 * DESCRIPTION:  
 *    A general spotlight lightsource shader that also includes 
 *    parameters that are loosely based on the Alias's PowerAnimator.  
 *
 *
 *    Quick Reference to Parameters:
 *    
 *        float   intensity = 1.0;
 *        color   lightcolor = color (1.0,1.0,1.0);
 *        float   coneangle = radians(30.0);
 *        float   coneangledegrees = 0.0;
 *        float   conedeltaangle = radians(5.0);
 *        float   conedeltaangledegrees = 0.0;
 *        point   from = point "shader" (0.0,0.0,0.0);
 *        point   to = point "shader" (0.0,0.0,1.0);
 *        float   decay = 2.0;
 *        float   beamdistribution = 2.0;
 *        string  shadowmap = "";
 *        float   shadowmapopacity = 1.0;
 *        float   shadowblur = 1.0;
 *        float   shadowsamples = 16;
 *        float   shadowbias = 0.0;
 *        string  lightstencil = "";
 *        string  lightsecondstencil = "";
 *        float   lightinvertstencil = 0;
 *        string  lightgel = "";
 *        string  lightsecondgel = "";
 *        float   lightimageconeangle = 0.0;
 *        float   lightimageconeangledegrees = 0.0;
 *        float   lightimageblur = 1.0;
 *        float   lightimageblurangle = 0.0;
 *        float   lightimageblurangledegrees = 0.0;
 *        point   lightup = point "shader" (0.0,1.0,0.0);
 *        float   lightimagerotate = 0.0;
 *        float   lightimagerotatedegrees = 0.0;
 *        float   lightimagelerp = 0.0;
 *        float   lightcutoff = 0.0;
 *
 *    Notes:
 *        When creating shadow maps with prman, create zfiles with 
 *        x and y pixel resolutions that are powers of two.  The aspect 
 *        ratio should be 1 and the following RIB statements should 
 *        appear in the RIB file creating the zfile:
 *
 *                Display ".z" "zfile" "z"
 *                Hider "hidden" "jitter" [0]
 *                PixelFilter "box" 1 1
 *                PixelSamples 1 1
 *                ShadingRate 1
 *        
 *        Attempt to tighten the fov of the spotlight to include only 
 *        those objects casting shadows and in shadow.
 *
 *    References:
 *          [ALIA96]  Alias|Wavefront, Rendering in Alias, 
 *                    110 Richmond St. East, Toronto, Ontario, 
 *                    Canada, M5C 1P1, 1996, pp. 84-95.
 *          [PIXA89]  Pixar, The RenderMan Interface, Version 3.1, 
 *                    Richmond, CA, pp. 160-165, September 1989.  
 *          [PIXA96]  Siggraph '96 RenderMan Birds of a Feather meeting,
 *                    New Orleans, August, 1996.
 *          [UPST89]  Upstill, Steve, The Renderman Companion: A 
 *                    Programmer's Guide to Realistic Computer Graphics,
 *                    Addison Wesley, 1989. 
 *
 */
#include "tebLightCommon.h"


light tebSpotLight( 
		   /* For standard spotlight parameters refer to [UPST89]
                    *    page 340 for setting intensity, lightcolor, 
                    *    coneangle and conedeltaangle.  
                    */
		   uniform float   intensity = 1.0;
		   uniform color   lightcolor = color (1.0,1.0,1.0);

		   /* Cannot set coneangle wider than 90 degrees. */
		   uniform float   coneangle = radians(30.0);

		   /* Override coneangle by specifying the angle in
                    *    degrees.  If you use this parameter and want to
                    *    animate the cone angle down to zero, set coneangle
                    *    to zero so when coneangledegrees is set to zero
                    *    and defaults to using coneangle, a value of zero 
                    *    is present.  
                    */
		   uniform float   coneangledegrees = 0.0;

		   /* Set conedeltaangle from coneangle downto zero. */
		   uniform float   conedeltaangle = radians(5.0);

		   /* Override conedeltaangle by specifying the angle in
                    *    degrees.
                    */
		   uniform float   conedeltaangledegrees = 0.0;

		   /* [UPST89] page 340 has "from" and "to" set to
                    *    (0,0,0) and (0,0,1) values in camera space,
                    *    respectively.  The spec [PIXA89] uses "shader"
                    *    space which is more intuitive.
                    */
		   uniform point   from = point "shader" (0.0,0.0,0.0);
		   uniform point   to = point "shader" (0.0,0.0,1.0);

		   /* Alias PA defaults to decay set to 1 and Alias Sketch!
                    *   to 0.  Sketch! RIBs do not export the value "decay",
                    *   so users should add it in and match decay to the 
                    *   setting in Sketch! where 0=None, 1=Slow, 
                    *   2=Realistic, and 3=Fast.  Sketch! users should also 
                    *   add beamdistribution set to 0.  This setting is based
                    *   on what PowerAnimator likes to do.
                    * I'm going to set the default decay value to follow 
                    *   the square-law falloff with distance from light.  
                    *   This setting makes tebSpotLight default to acting 
                    *   like the standard spotlight shader.
                    * Refer to the [UPST89] page 341.
                    */
		   uniform float   decay = 2.0;

                   /* Alias PA RIBs typically have beamdistribution set to
                    *   zero.  So I believe if Alias Sketch! users set the 
                    *   decay level (refer to the "decay" above) and set
                    *   beamdistribution to zero, they should see their
                    *   RenderMan renderings match their Alias Sketch!
                    *   renderings in light intensity.
                    */
		   uniform float   beamdistribution = 2.0;

		   /* Specify a depth map (zfile) using shadowmap. */
		   uniform string  shadowmap = "";

		   /* Setting shadowmapopacity makes objects that cast 
                    *    shadows act as if they were translucent.
                    *    Setting to zero essentially removes the shadowmap. 
                    */
		   uniform float   shadowmapopacity = 1.0;

		   /* The sampling width for swidth and twidth is set by
                    *    the value given to shadowblur.
                    */
		   uniform float   shadowblur = 1.0;

		   /* Supersample the shadow map the number of times
                    *    shadowsamples is set to.  The value shadowsamples 
                    *    represents a 2D sampling scheme, so it is typically
                    *    set to a squared integer value.
                    */
		   uniform float   shadowsamples = 16;

		   /* The value bias was introduced in PRMan 3.6 and is a 
                    *    parameter given to shadow() to override the 
                    *    options set by bias0 and bias1 that are used to 
                    *    prevent self-shadowing.  Leave bias set to zero 
		    *    and this shader will not use it when calling shadow().
                    */
		   uniform float   shadowbias = 0.0; /* bias0=bias1=0.35 */

		   /* By setting lightstencil to a name of a single channel 
                    *    texture map, the texture blocks light where it is 
                    *    black (0.0,0.0,0.0) and lets light through where 
                    *    it is white (1.0,1.0,1.0).  Using a stencil is like 
                    *    positioning in front of the spotlight a metal
                    *    sheet with a pattern of holes that lets light 
                    *    through according to the pattern of holes.
                    */
		   uniform string  lightstencil = "";

		   /* Set lightimagelerp to blend between lightstencil
                    *    and lightsecondstencil.
                    */
                   uniform string  lightsecondstencil = "";

		   /* If not zero, lightinvertstencil inverts the stencil. */
		   uniform float  lightinvertstencil = 0;

		   /* Give the spotlight different colors depending on a
                    *    texture.  The texture is stretched over the end of
                    *    the cone defining the spotlight.  The center of 
                    *    the texture is the center hotspot of the spotlight.
                    */
                   uniform string  lightgel = "";

		   /* Set lightimagelerp to blend between lightgel
                    *    and lightsecondgel.
                    */
                   uniform string  lightsecondgel = "";

		   /* If not zero, lightstencil and lightgel are mapped
                    *    to a cone angle of lightimageconeangle.   This
                    *    allows the coneangle of the spotlight to change
                    *    while not adjusting the size of the stencil and
                    *    a gel images.  The images remained locked as the
                    *    spot of the spotlight shrinks or grows.
                    * Note that if coneangle is greater than 
                    *    lightimageconeangle, the spotlight is limited
                    *    to lightimageconeangle.
                    */
		   uniform float  lightimageconeangle = 0.0;

		   /* Override lightimageconeangle by specifying the angle in
                    *    degrees.
                    */
		   uniform float   lightimageconeangledegrees = 0.0;

		   /* Amount to blur the stencil and gel by using swidth 
                    *    and twidth.  
                    */
		   uniform float   lightimageblur = 1.0;

		   /* Amount to blur the stencil and gel.  This can soften
                    *    a stencil a bit when set to a low number such as
                    *    0.1 degrees (0.001745 radians).
                    */
		   uniform float   lightimageblurangle = 0.0;

		   /* Override lightimageblurangle by specifying the angle in
                    *    degrees.
                    */
		   uniform float   lightimageblurangledegrees = 0.0;


		   /* By default the direction "up" is considered to be the 
                    *    positive Y direction in shader space.  Set lightup 
                    *    to orient lightstencil and lightgel.
                    */
		   uniform point   lightup = point "shader" (0.0,1.0,0.0);

		   /* You could rotate the stencil and gel images by
                    *    setting lightup, but use lightimagerotate instead.
                    *    This parameter should be more intuitive for 
                    *    rotating counter-clockwise the projected images.
                    */
		   uniform float  lightimagerotate = 0.0;

		   /* Override lightimagerotate by specifying the angle in
                    *    degrees.
                    */
		   uniform float   lightimagerotatedegrees = 0.0;

		   /* If you use the new "disk" filter that comes with PRMan
		    *    3.8, the following comments about large blur do not
		    *    apply anymore.
		    */
		   /* Large lightimageblur and/or lightimageblurangle values
                    *    begins to show artifacts in the image as a the 
                    *    begins to use a low resolution version of the texture
                    *    map.  
                    * If you want the look of a spotlight projecting an image
                    *    going in and out of focus, take the stencil or gel
                    *    image being used and blur it up in PhotoShop.  Then
                    *    assign the blurred stencil or gel to 
                    *    lightsecondstencil or lightsecondgel respectively.  
                    *    Use lightimagelerp from 0.0 to 1.0 to lerp between
                    *    the images to produce an animation of the projected
                    *    image becoming blurry or more in focus.  The value
                    *    0.0 gives only lightstencil and lightgel.  The value
                    *    1.0 gives only lightsecondstencil or lightsecondgel.
                    */
		   uniform float   lightimagelerp = 0.0;

		   /* Cut the lightsource off once it reaches the distance
                    *    lightcutoff.  If lightcutoff is zero it is ignored
                    *    and the decay value listed above determines the
                    *    spotlight's falloff.
                    */
		   uniform float  lightcutoff = 0.0;
		   )
{
   uniform float   cosoutside;
   uniform float   cosinside;
   uniform vector  A;
   uniform vector  UP;
   uniform vector  RIGHT;
   varying float   cosangle;
   varying float   len;
   varying float   nLdotA;
   varying float   attenuation;
   uniform float   lightconeangle;
   uniform float   lightconedeltaangle;
   uniform float   lightimageca;
   uniform float   lightangleblur;
   uniform float   lightrotation;
   uniform float   outsideconeangle;
   uniform float   twicelightconeangle;
   varying float   stencil,stencil2;
   varying color   gel = color (1.0,1.0,1.0);
   varying color   gel2;
   varying float   ss, tt;
   uniform float   costheta, sintheta;
   varying float   x = 0;
   varying float   y = 0;
   uniform float   halfdelta;
   varying float   ds,dt;
#ifdef BMRT
   varying float   dist;
#endif


   /* Calculate a normalized vector pointing straight out of the spotlight. */
   A = normalize(to - from);

   lightconeangle = (coneangledegrees==0.0 
		     ? coneangle : radians(coneangledegrees) );
   lightconedeltaangle = (conedeltaangledegrees==0.0 
			  ? conedeltaangle : radians(conedeltaangledegrees));
   lightrotation = ( lightimagerotatedegrees == 0.0 
		     ? lightimagerotate : radians(lightimagerotatedegrees));
   lightimageca = ( lightimageconeangledegrees == 0.0 
		    ? lightimageconeangle 
		    : radians(lightimageconeangledegrees));
   lightangleblur = ( lightimageblurangledegrees == 0.0 
		    ? lightimageblurangle 
		    : radians(lightimageblurangledegrees));
   
   /* The spotlight will be blocked a angles outside the cone angle of
    *    lightimageconeangle, so find the minimum angle to use.
    */
   if ( lightimageca != 0.0 )
      lightconeangle = min( lightconeangle, lightimageca );

   cosoutside= cos(lightconeangle);
   cosinside = cos(lightconeangle-lightconedeltaangle);

   illuminate( from, A, lightconeangle ) 
   {
      /* In the above illuminate() statement 
       *    "illuminate( from, A, lightconeangle )" the value L is assigned
       *    the value (Ps-from).  Note that if L were assigned a value
       *    while in the illuminate() statement block, that value would
       *    be negated and assigned to a surface shader's L value in
       *    it's illuminance() statement block.  This is true for both
       *    PRMan 3.7 and BMRT 2.3.6beta.
       * This can be seen by placing printf()'s in a light shader and surface
       *    shader.  In the light shader's illuminate statement block  
       *    print L, then set L = L + 1 to each of the three values in the L 
       *    vector.  In a surface shader print L in an illuminance() 
       *    statement block.  Note that the surface shader's L equals the
       *    a negative version of the lightsource's incremented L.
       */

      /* Give Cl an initial color and then alter it along the way. */
      Cl = lightcolor;

      /* When calculating L, prman refers to the point "from" given to 
       *    illuminate() to calculate L = Ps - "from".
       */
      len = length(L);

      /* Since we needed to have "len" anyway use it to normalize nLdotA,
       *    instead of having normalize() recalculate the length again.
       */
      nLdotA = (L.A) / len;
      cosangle = nLdotA;

      /* Typically there is a divide by L.L to do an inverse square for
       *    the falloff of the light source intensity with distance.
       *    Refer to the [UPST89] page 341.  But to handle
       *    the value decay, a division of the value pow(length(L),decay) 
       *    is used.  Alias provides a shader with PowerAnimator (8.2 and
       *    previous releases) called, ari_spotlight that is basically 
       *    the standard spotlight shader with a variable "decay" added 
       *    and Cl divided by pow(length(L),decay).   One thing to note, 
       *    this shader retains the L.L divide even though a division 
       *    involve "decay" was added.  The L.L should be removed to match 
       *    the Alias renderer when the light source medium to long distance 
       *    from the target objects.
       */
      if ( len <= lightcutoff || lightcutoff == 0.0 )
      {
	 attenuation = pow(cosangle, beamdistribution) / pow(len,decay);
	 attenuation *= smoothstep( cosoutside, cosinside, cosangle );
      }
      else
      {
	 attenuation = 0.0;
      }
      
      /* The stencil and gel are set to cover the end of the cone defining
       *    the spotlight.
       */
      if ( lightstencil != "" || lightgel != "" )
      {
	 /* Set UP, RIGHT and A to form an orthonormal basis. */
	 UP = normalize(lightup - from);

	 /* Find direction pointing to the right of the spotlight. */
	 RIGHT = UP^A;

	 /* Set vector UP so that it is perpendicular to A which
	  *    represents the direction of a ray of light shooting straight
          *    out of the spotlight.
          */
	 UP = A^RIGHT;

	 if ( lightimageca == 0.0 )
	 {
	    outsideconeangle = lightconeangle;
	    twicelightconeangle = 2 * lightconeangle;
	 }
	 else
	 {
	    outsideconeangle = lightimageca;
	    twicelightconeangle = 2 * lightimageca;
	 }

	 ss = -asin((L.UP)/len);
	 tt = -asin((L.RIGHT)/len);
         ss += outsideconeangle;
	 tt += outsideconeangle;
	 ss /= twicelightconeangle;
	 tt /= twicelightconeangle;

	 /* Rotate the image about its texturecoordinate center. */
	 if ( lightrotation != 0.0 )
	 {
	    /* Rotate (ss,tt) about the center (0.5,0.5). */
	    costheta = cos(lightrotation);
	    sintheta = sin(lightrotation);
	    x = ss - 0.5;
	    y = tt - 0.5;
            ss = x * costheta - y * sintheta + 0.5;
            tt = x * sintheta + y * costheta + 0.5;
	 }

	 /* Unlike a surface shader that has the sample being calculated
	  *    at the lowest u,v value of a micropolygon and du and dv 
	  *    indicating the sampling area.  This light shader is going to
	  *    actually sample the texturemap at the center of the sample.  
	  * Inshort instead of (s,t) blurring to (s+ds,t+dt), we'll
	  *    do (s-ds/2,t-dt/2) to (s+ds/2,t+dt/2) which
          *    isn't the norm for most shaders.
          *
	  */
	 halfdelta = (lightangleblur / twicelightconeangle);
	 
	 ss -= halfdelta;
	 tt -= halfdelta;
	 ds = dt = 2 * halfdelta;

	 /* Larry added mkmip which allows for a boxfiltered texmap with
	  *    with surrounding black.  If you use that feature, the following
	  *    comments and code no longer apply.
	  */
         /* Handle edge conditions when calculating ss, tt, swidth
          *    twidth for BMRT or when an inverted stencil is being used 
          *    with prman.  The texture() function in BMRT repeats a 
          *    texture when s,t are out of the range [0,1].  PRMan allows a 
          *    texture to have different "wrap modes" for its textures.  
          *    The default is black, which has texture() return zero for 
          *    s,t coordinates outside a [0,1] range.  
          *
          * Cutting down the area around the edges where a texmap is being
          *    accessed may brighten the edge if white in the texmap is
          *    present.  The brighter colors are the result of a texmap's 
          *    edge color getting more emphasis when a black color 
          *    surrounding the texmap is not being averaged in.
	  */
#ifdef BMRT
	 ss = max( 0.0, ss );
	 tt = max( 0.0, tt );
	 dist = ss + ds;
	 if ( dist > 1.0 )
	    ds -= dist - 1.0;
	 dist = tt + dt;
	 if ( dist > 1.0 )
	    dt -= dist - 1.0;
#endif

	 /* If a stencil is not being used, the default value of 1.0 that 
	  *    was given to attenuation when it is declared above will 
          *    be used.
	  */
	 if ( lightstencil != "" 
	      && (lightsecondstencil == "" || lightimagelerp == 0.0))
	 {
	    /* Texture coordinates are in the following order:
             *    (0,0), (1,0), (0,1), and (1,1) where (0,0) is
             *    the lower left and (1,1) is the upper right.
             * Refer to [UPST89] pages 246 and 251 and [PIXA89]
             *    page 39.  Its hard to find a place that mentions 
             *    the order of the four pairs of texture 
             *    coordinates, so I stuck it in here.  It is the
             *    order given to point used in RiTextureCoordinates().
             */
	    stencil = texture( lightstencil, 
			       ss,tt,        /* (0,0) */
			       ss+ds,tt,     /* (1,0) */
			       ss,tt+dt,     /* (0,1) */
			       ss+ds,tt+dt,  /* (1,1) */
			       "swidth", lightimageblur, 
			       "twidth", lightimageblur
#define _USE_DISK_
#ifdef USE_DISK
			       , "filter", "disk"
#endif
			       );

	    /* Invert the stencil value if lightinvertstencil is nonzero. */
	    if ( lightinvertstencil != 0 )
	       stencil = 1.0 - stencil;

	    attenuation *= stencil;
	 }
	 else if ( lightstencil != "" && lightsecondstencil != "" )
	 {
	    stencil = texture( lightstencil, 
			       ss,tt,
			       ss+ds,tt,
			       ss,tt+dt,
			       ss+ds,tt+dt,
			       "swidth", lightimageblur, 
			       "twidth", lightimageblur
			       );
	    stencil2 = texture( lightsecondstencil, 
				ss,tt,
				ss+ds,tt,
				ss,tt+dt,
				ss+ds,tt+dt,
				"swidth", lightimageblur, 
				"twidth", lightimageblur
				);

	    /* Invert the stencil value if lightinvertstencil is nonzero. */
	    if ( lightinvertstencil != 0 )
	    {
	       stencil = 1.0 - stencil;
	       stencil2 = 1.0 - stencil2;
	    }

	    attenuation *= (1.0-lightimagelerp)*stencil 
	       + lightimagelerp*stencil2;
	 }

	 /* If a gel is not being used the lightcolor is left alone and
          *    its default value of (1.0,1.0,1.0) is used unless overridden
          *     by a parameterlist assignment of lightcolor.
	  */
	 if ( lightgel != ""
	      && (lightsecondgel == "" || lightimagelerp == 0.0))
	 {
	    gel = texture( lightgel, 
			   ss,tt, 
			   ss+ds,tt, 
			   ss,tt+dt, 
			   ss+ds,tt+dt,
			   "swidth", lightimageblur, 
			   "twidth", lightimageblur
			   ); 
	    
            /* Scale lightcolor by the color given by gel. */
            Cl *= gel;
	 }
	 else if ( lightgel != "" && lightsecondgel != "" )
	 {
	    gel = texture( lightgel, 
			   ss,tt, 
			   ss+ds,tt, 
			   ss,tt+dt, 
			   ss+ds,tt+dt,
			   "swidth", lightimageblur, 
			   "twidth", lightimageblur
			   ); 
	    gel2 = texture( lightsecondgel, 
			    ss,tt, 
			    ss+ds,tt, 
			    ss,tt+dt, 
			    ss+ds,tt+dt,
			    "swidth", lightimageblur, 
			    "twidth", lightimageblur
			    ); 

            /* Scale lightcolor by the color given by gel. */
            Cl *= (1.0-lightimagelerp)*gel + lightimagelerp*gel2;
	 }
      }
      /* Refer to tebLightCommon.h for the function tebShadow(). */
      attenuation *= tebShadow( shadowmap, Ps, 
				shadowmapopacity, shadowsamples, shadowblur,
				shadowbias );

      /* Set final version of the spotlight color on surface point Ps. */
      Cl = attenuation * intensity * Cl;
   }
}


[Affine Toolkit]
[RIB Utilities] [Bitmap Utilities] [Handy Little Utilities]
[Libraries] [Using the Libraries]