Module E - Stencilling

Shadows

Direct3D | Sample 10 | Display | Scene
Thing | Box | PrimitiveSet | Mesh | Exercise



A stencil is a two-dimensional template that restricts drawing to portions of a canvas.  The stencil when placed on the canvas hides the regions to be excluded from drawing.  The openings or cutouts in the stencil expose the drawable regions.  A stencil's cutouts are not restricted in their geometry to any simple shape and often take quite complex shapes. 

On modern graphics hardware, the stencil buffer is a region of memory that controls pixel-by-pixel drawing to the backbuffer.  Typically, the stencil buffer shares the same part of memory as the depth buffer.  Combined use of the stencil buffer with the depth buffer makes effects such as shadowing possible. 

The stencil buffer has the same resolution as the depth buffer and the backbuffer.  The ij-th entry in the stencil buffer corresponds to the ij-th entry in the backbuffer and the ij-th entry in the depth buffer.  While the contents of the depth buffer are updated automatically, the contents of the stencil buffer are under control of the application.  An application can use the contents of the stencil and depth buffers to draw pixels onto the backbuffer selectively. 

For a stencil-enabled application to draw a pixel colour to the backbuffer, the application must pass the stencil test for that particular pixel.  The stencil test compares two values: the current value in the stencil buffer and a reference value set by the application.  The comparison criteria that are available for stencil tests are listed in the following table. 

ComparisonDescription
=stencil value is equal to the reference value
!=stencil value is not equal to the reference value
<stencil value is less than the reference value
<=stencil value is less than or equal to the reference value
>stencil value is greater than the reference value
>=stencil value is greater than or equal to the reference value
falsenever pass the test
truealways pass the test

The comparison operands can be bit-masked to accomodate several stencil values per-pixel.  The full stencil test takes the form

 (ref & mask) comparison operator (value & mask) 
where ref is the reference value set by the application, value is the current stencil value for the pixel as stored in the stencil buffer, and mask is the bit-mask applicable to both the reference value and the current stencil value before determining the result of the test. 

An application can also update per-pixel values in the stencil buffer during the drawing process.  The update may depend upon whether

  • both the depth test and stencil test passed,
  • the stencil test failed, or
  • the depth test failed.

The types of update operations typically available are listed in the following table.

UpdateDescription
keepkeep the pixel's stencil value
zerozero the pixel's stencil value
replacereplace the pixel's stencil value with the reference value
incrementincrement the pixel's stencil value
decrementdecrement the pixel's stencil value

Any per-pixel change to the stencil buffer can itself be mediated by a mask.  This mask is called the write mask for the stencil buffer. 


Direct3D

Setting Up the Stencil Buffer

Direct3D sets up the stencil buffer at the same time as it retrieves an interface to the display device.  Direct3D retrieves the interface through the CreateDevice() function.  This function takes as one of its parameters the address of a D3DPRESENT_PARAMETERS struct.  The member of this struct that holds the depth and stencil buffer description is

 D3DPRESENT_PARAMETERS d3dpp;
 d3dpp.AutoDepthStencilFormat = depth and stencil format description;

The range of available depth and stencil formats depends upon the selected display adapter, the selected device type, and the selected pixel format for the backbuffer. 

The surface formats supported by Direct3D for the depth and stencil buffer include:

  • D3DFMT_D15S1 - 16-bit buffer with 15 bits for the depth buffer and 1 bit for the stencil buffer,
  • D3DFMT_D24S8 - 32-bit buffer with 24 bits for the depth buffer and 8 bits for the stencil buffer,
  • D3DFMT_D24X4S4 - 32-bit buffer with 24 bits for the depth buffer and 4 bits for the stencil buffer,
  • D3DFMT_D24FS8 - 32-bit buffer with 24 bits for the depth buffer in floating point and 8 bits for the stencil buffer,

Direct3D reports the availability of a requested depth and stencil format through the CheckDeviceFormat() function on the Direct3D object.  This function takes six parameters:

  • the number identifying the selected display adapter,
  • the selected device type - either D3DDEVTYPE_HAL (hardware abstraction layer) or D3DDEVTYPE_REF (software emulation),
  • the selected pixel format of the backbuffer,
  • how the resource type will be used - D3DUSAGE_DEPTHSTENCIL,
  • the type of resource - D3DRTYPE_SURFACE,
  • the surface format requested.

This function returns D3D_OK if the requested format is available on the selected display adapter, using the selected device type, and at the selected pixel format for the backbuffer. 

Stencil Control

Direct3D clears the stencil buffer through the Clear() function on the display device.  This function takes the D3DCLEAR_STENCIL flag as its third parameter and an integer value as the sixth parameter and sets all of the values for all entries in the stencil buffer to the integer value. 

Direct3D provides stencil control through the SetRenderState() function on the display device.  This function takes two parameters: an enumerated type that identifies the control parameter to be set and a value that identifies the setting for that parameter. 

For example, the call on the display device to enable stencilling is

 SetRenderState(D3DRS_STENCILENABLE, true) 
and the call to disable stencilling is
 SetRenderState(D3DRS_STENCILENABLE, false) 

Stencil Test

The stencil test determines whether or not the pipeline writes anything to the backbuffer.  The four elements of this test are the reference value, the stencil value for the pixel in question, the mask, and the comparion operator.  The application sets the reference value, the mask, and the comparison operator; but cannot set the stencil value for any pixel directly (except by clearing the stencil buffer completely).

Reference Value

The call on the display device to set the reference value for the stencil test to 1 is

 SetRenderState(D3DRS_STENCILREF, 0x1) 

The default setting is 0x0. 

Stencil Mask

The call to set the bit-mask for the stencil test to expose all bits is

 SetRenderState(D3DRS_STENCILMASK, 0xffffffff) 

This is the default setting.

Comparison Operator

The call to set the comparison operator for the stencil test to equality is

 SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL) 

Direct3D supports the following comparison operators.

ComparisonD3DCMP Symbolic NameDescription
=D3DCMP_EQUALstencil value is equal to the reference value
!=D3DCMP_NOTEQUALstencil value is not equal to the reference value
<D3DCMP_LESSstencil value is less than the reference value
<=D3DCMP_LESSEQUALstencil value is less than or equal to the reference value
>D3DCMP_GREATERstencil value is greater than the reference value
>=D3DCMP_GREATEREQUALstencil value is greater than or equal to the reference value
falseD3DCMP_NEVERnever pass the test
trueD3DCMP_ALWAYSalways pass the test

Initializing the Stencil Buffer

In order to initialize select parts of the stencil buffer, the application must draw to those parts.  To avoid updating the backbuffer during such initial drawing, we can set the blending factors to

 SetRenderState(D3DRS_ALPHABLENDING, true) 
 SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); 
 SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

Direct3D supports the following update operations to the values in the stencil buffer.

UpdateFlagDescription
keepD3DSTENCILOP_KEEPkeep the pixel's stencil value
zeroD3DSTENCILOP_ZEROzero the pixel's stencil value
replaceD3DSTENCILOP_REPLACEreplace the pixel's stencil value with the reference value
incrementD3DSTENCILOP_INCRincrement the pixel's stencil value
decrementD3DSTENCILOP_DECRdecrement the pixel's stencil value

The call to keep the stencil value when the stencil test fails is

 SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP) 

The call to keep the stencil value when the depth test fails is

 SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP) 

The call to replace the stencil value with the reference value when both the depth and stencil tests succeed is

 SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE) 

Using the Stencil Buffer

The render state settings for drawing differ from those for initializing the stencil buffer.  In order to write data to the backbuffer, the blending factors need to be changed.

 SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); 
 SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

Blending the incoming colour with the colour in the backbuffer prevents z-fighting.  Z-fighting is an artifact that appears when two different surfaces with the same depth values in the depth buffer compete, the pixel on one surface prevailing at one instant and the the pixel on the competing surface prevailing at another instant to cause annoying flicker. 

In order to write data to the backbuffer only where the stencil has been marked, reset the test condition to equality. 

The call to set the comparison operator for the stencil test to equality is

 SetRenderState(D3DRS_STENCILREF, D3DCMP_EQUAL) 

In order to prevent writing data to the backbuffer more than once during the drawing process, set the update value for the stencil test to

 SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR) 

Stencil Write Mask

The call to set the bit-mask for writing to the stencil is

 SetRenderState(D3DRS_STENCILWMASK, 0xffffffff) 

This is the default setting.


Sample 10

Sample 10 uses stencilling to create shadows on the box objects in the scene.  Each box object is capable of casting a shadow.  Each box object consists of six planes, each of which is capable of accepting a shadow.  Shadowing on or casting of shadows by the more complex objects is not supported in this sample. 

The shadows associated with only a point light source in the upper-right corner are shown below.

The shadows associated with only a directional overhead light are shown below.

The shadows associated with the point and directional lights sources as well as a spot light source in the upper-left corner are shown below.

Note how the shadows do not extend beyond the primitives that define the plane of projection and how the overlapping shadows do not show any change in intensity within the regions of overlap.

Avoiding the extension of shadows beyond the primitives that define the plane of projection is achieved by initializing the stencil buffer to expose only those pixel that belong to the primitives themselves.  The initialized stencil buffer prevents subsequent drawing to pixels that do not belong to the primitives that define the plane of projection under consideration. 

Avoiding an increase in shadow intensity wherever shadows overlap is achieved by updating the stencil buffer every time a shadow is drawn for a particular pixel.  Any subsequent attempt to draw a shadow on the same pixel fails because the stencil tests fails for the updated value.


Display

The Display class includes four new queries to extract lighting data and four new modifiers to handle the stencilling operations. 


 class Display : public IDisplay {

     LPDIRECT3D9 d3d;             // Direct3D interface
     LPDIRECT3DDEVICE9 d3dd;      // Direct3D device
     D3DPRESENT_PARAMETERS d3dpp; // parameters for creating/restoring D3D 
                                  // device
     int display;                 // display adapter identifier
     int width;                   // width of the client area
     int height;                  // height of the client area
     int mode;                    // display adapter mode ID
     D3DFORMAT format;            // display adapter pixel format
     LPD3DXFONT font;             // Direct3D font
     int titleBar;                // approximate height of the title bar

     int    keys;                 // keys currently pressed by the user
     Timer* timer;                // fps timer

     Display &operator=(const Display &x);
     Display(const Display &x);

     void  initialize(HWND hwnd);
     void  release();

   protected:
     BOOL create(HWND hwnd);
     void destroy(HWND hwnd);

   public:
     Display();
     virtual ~Display();

     // before application window creation
     int  adapterCount() const;
     int  displayDescription(int id, char* desc) const;
     int  formatCount();
     int  modeCount(int id, int k) const;
     BOOL getMode(int id, int ir, int k, DWORD& data, char* line);
     void setProperties(int id, int data, HWND hwnd);
     BOOL wndParam(DWORD& w, DWORD& h, DWORD& ex, DWORD& st);

     // after application window creation
     BOOL setup(HWND hwnd);
     void suspend();
     BOOL restore(HWND hwnd);
     void wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
     void alphaBlending(BOOL on);
     void biLinearFiltering(BOOL on);
     void anisotropicFiltering(BOOL on);
     void mipmapLinearFiltering(BOOL on);
     void textureClamp(BOOL on);
     void textureBorder(BOOL on);
     void update();
     void beginDraw(const Vector& p, const Vector& h, const Vector& u);
     void* displayDevice() const { return (void*)d3dd; }
     void* adapterFormat() const { return (void*)&format; }
     BOOL fullscreen() const { return display != RUN_IN_WINDOW; }
     void endDraw(const Vector& p, const Vector& h, const Vector& u);
     int  noLights() const;
     int  lightType(int i) const;
     Vector lightPosition(int i) const;
     Vector lightDirection(int i) const;
     void stencillingOn();
     void beginCreatingStencil();
     void beginStencilledDrawing();
     void stencillingOff();
 };

setup()

The Display object initializes the display adapter to include a stencil buffer through the setup() function


 BOOL Display::setup(HWND hwnd) {

     BOOL rc = FALSE;
     UINT adapter;

     // store the D3D presentation parameters
     ZeroMemory(&d3dpp, sizeof d3dpp);
     //d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
     d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
     d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
     d3dpp.BackBufferCount = 1;
     d3dpp.EnableAutoDepthStencil = TRUE;
     if (display == RUN_IN_WINDOW) {
         d3dpp.Windowed = TRUE;
         adapter = D3DADAPTER_DEFAULT;
     }
     else {
         D3DDISPLAYMODE d3ddm;
         if (FAILED(d3d->EnumAdapterModes(display, format, mode, &d3ddm))) 
             error("Unable to get new display mode");
         else {
             d3dpp.BackBufferWidth  = d3ddm.Width;
             d3dpp.BackBufferHeight = d3ddm.Height;
             d3dpp.BackBufferFormat = d3ddm.Format;
             d3dpp.FullScreen_RefreshRateInHz = d3ddm.RefreshRate;
             adapter = display;
         }
     }

     // If all else fails, use the REF (software emulation) device rather
     // than the HAL (hardware accelerated) device, and use a 16-bit
     // depth buffer, hoping that it will work. (If it doesn't, we
     // are out of luck anyway.)
     D3DDEVTYPE devtype = D3DDEVTYPE_REF;          // REF or HAL
     d3dpp.AutoDepthStencilFormat = D3DFMT_D16;  // Z-buffer depth

     // Now, let's see if we can find better choices:

     if (D3D_OK == d3d->CheckDeviceFormat(adapter,
      D3DDEVTYPE_HAL, format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE,
      D3DFMT_D24S8)) {
         d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
         devtype = D3DDEVTYPE_HAL;
     }
     else if (D3D_OK == d3d->CheckDeviceFormat(adapter,
      D3DDEVTYPE_HAL, format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE,
      D3DFMT_D24X4S4)) {
         d3dpp.AutoDepthStencilFormat = D3DFMT_D24X4S4;
         devtype = D3DDEVTYPE_HAL;
     }
     else if (D3D_OK == d3d->CheckDeviceFormat(adapter,
      D3DDEVTYPE_HAL, format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE,
      D3DFMT_D15S1)) {
         d3dpp.AutoDepthStencilFormat = D3DFMT_D15S1;
         devtype = D3DDEVTYPE_HAL;
     }
     else if (D3D_OK == d3d->CheckDeviceFormat(adapter,
      D3DDEVTYPE_REF, format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE,
      D3DFMT_D32)) {
         d3dpp.AutoDepthStencilFormat = D3DFMT_D32;
         devtype = D3DDEVTYPE_REF;
     }
     // retrieve capabilities of selected adapter
     D3DCAPS9 caps;
     d3d->GetDeviceCaps(adapter, devtype, &caps);

     // create the interface to the D3D device
     if (FAILED(d3d->CreateDevice(adapter, devtype, hwnd,
      D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dd)))
         error("Unable to create D3D device", hwnd);
     else if (FAILED(d3dd->SetSamplerState(0, D3DSAMP_MAXANISOTROPY,
      caps.MaxAnisotropy - 1))) {
         release();
         error("Unable to set up anisotropic filtering", hwnd);
     }
     else {
         // initialize the display device
         initialize(hwnd);
         timer->setup(hwnd);
         rc = TRUE;
     }

     return rc;
 }

noLights()

The Display object returns the number of lights that are currently on through the noLights() function


 int Display::noLights() const {

     BOOL on;
     int n = 0;
     D3DLIGHT9 lt;
     int maxLights = 3;

     for (int i = 0; i < maxLights; i++) {
         if (D3D_OK == d3dd->GetLight(i, <) &&
          D3D_OK == d3dd->GetLightEnable(i, &on) && on) { 
             n++;
         }
     }

     return n;
 }

lightType()

The Display object returns the type of the i-th light that is currently on through the lightType() function


 int Display::lightType(int i) const {

     BOOL on;
     int n = 0, t = -1;
     D3DLIGHT9 lt;
     int maxLights = 3;

     for (int j = 0; j < maxLights; j++) {
         if (D3D_OK == d3dd->GetLight(j, <) &&
          D3D_OK == d3dd->GetLightEnable(j, &on) && on) {
             if (n == i) {
                 t = lt.Type == D3DLIGHT_DIRECTIONAL ? 2 : 
                     lt.Type == D3DLIGHT_POINT ? 0 :
                     lt.Type == D3DLIGHT_SPOT ? 1 : -1;
                 j = maxLights;
             }
             else
                 n++;
         }
     }

     return t;
 }

lightPosition()

The Display object returns the position of the i-th light that is currently on through the lightPosition() function


 Vector Display::lightPosition(int i) const {

     BOOL on;
     int n = 0;
     D3DLIGHT9 lt;
     Vector position;
     int maxLights = 3;

     for (int j = 0; j < maxLights; j++) {
         if (D3D_OK == d3dd->GetLight(j, <) &&
          D3D_OK == d3dd->GetLightEnable(j, &on) && on) {
             if (n == i) {
                 if (lt.Type == D3DLIGHT_POINT || lt.Type == D3DLIGHT_SPOT) 
                     position = Vector(lt.Position.x, lt.Position.y,
                      lt.Position.z);
                 j = maxLights;
             }
             else
                 n++;
         }
     }

     return position;
 }

lightDirection()

The Display object returns the direction of the i-th light that is currently on through the lightDirection() function


 Vector Display::lightDirection(int i) const {

     BOOL on;
     int n = 0;
     D3DLIGHT9 lt;
     Vector direction;
     int maxLights = 3;

     for (int j = 0; j < maxLights; j++) {
         if (D3D_OK == d3dd->GetLight(j, <) &&
          D3D_OK == d3dd->GetLightEnable(j, &on) && on) {
             if (n == i) {
                 if (lt.Type == D3DLIGHT_SPOT || lt.Type == D3DLIGHT_DIRECTIONAL) 
                     direction = Vector(lt.Direction.x, lt.Direction.y,
                      lt.Direction.z);
                 j = maxLights;
             }
             else
                 n++;
         }
     }

     return direction;
 }

stencillingOn()

The Display object initializes the stencilling operations through the stencillingOn() function.  This function sets the render state values for the stencilling operations as a whole.  These state values did not change throughout the process.


 void Display::stencillingOn() {

     // enable stencilling
     d3dd->SetRenderState(D3DRS_STENCILENABLE,    true);
     // disable masking
     d3dd->SetRenderState(D3DRS_STENCILMASK,      0xffffffff);
     d3dd->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
     // retain stencil contents on failure
     d3dd->SetRenderState(D3DRS_STENCILZFAIL,     D3DSTENCILOP_KEEP); 
     d3dd->SetRenderState(D3DRS_STENCILFAIL,      D3DSTENCILOP_KEEP);
     // enable alpha-blending
     d3dd->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
 }

beginCreatingStencil()

The Display object re-initializes the stencil buffer for creating a stencil through the beginCreatingStencil() function.  This function clears the stencil buffer, sets the blending coefficients to keep the contents of the backbuffer intact, and defines the elements of the stencil test during the creation of the stencil.  The stencil test always passes, the reference value is 0x1, ahd the update operation when the depth test passes is to replace the stencil value with the reference value.


 void Display::beginCreatingStencil() {

     // enable z-buffering while creating stencil
     d3dd->SetRenderState(D3DRS_ZENABLE,          true);
     // ensure no change to contents of backbuffer while creating stencil
     d3dd->SetRenderState(D3DRS_SRCBLEND,         D3DBLEND_ZERO);
     d3dd->SetRenderState(D3DRS_DESTBLEND,        D3DBLEND_ONE);
     // always pass the stencil test
     d3dd->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_ALWAYS);
     // set ref value for replace process
     d3dd->SetRenderState(D3DRS_STENCILREF,       0x1);
     // if depth and stencil tests pass, replace stencil value with ref value 
     d3dd->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_REPLACE);
     // initialize the stencil values to 0x0 throughout
     d3dd->Clear(0, 0, D3DCLEAR_STENCIL, 0, 0, 0);
 }

After drawing with these settings, those pixels that have passed the depth test will be marked with the reference value in the stencil buffer.

beginStencilledDrawing()

The Display object prepares the stencil buffer for drawing to pixels marked with the reference value through the beginStencilledDrawing() function.  This function disables the depth test and allows blending with the contents of the backbuffer.


 void Display::beginStencilledDrawing() {

     // pass stencil test only if value is the ref value
     d3dd->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_EQUAL);
     // if depth and stencil tests pass, increment stencil value by 1
     d3dd->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_INCR);
     // if tests pass, blend with contents of the backbuffer
     d3dd->SetRenderState(D3DRS_SRCBLEND,         D3DBLEND_SRCALPHA);
     d3dd->SetRenderState(D3DRS_DESTBLEND,        D3DBLEND_INVSRCALPHA); 
     // disable z-buffering
     d3dd->SetRenderState(D3DRS_ZENABLE,          false);
 }

The stencil value for every pixel that is updated is incremented so that any subsequent attempt to update it will fail the stencil test.

stencillingOff()

The Display object concludes the stencilling operations through the stencillingOff() function.  This function disables stencilling and alpha blending, and re-enables z-buffering.


 void Display::stencillingOff() {

     // disable stencilling
     d3dd->SetRenderState(D3DRS_STENCILENABLE,    false);
     // disable alpha-blending
     d3dd->SetRenderState(D3DRS_ALPHABLENDENABLE, false); 
     // enable z-buffering
     d3dd->SetRenderState(D3DRS_ZENABLE,          true);
 }


Scene

The Scene class includes a private member function that manages the shadowing operations. 


 class Scene {

     GameContext& context;     // context for the scene
     Audio& audio;             // audio for the scene objects

     int keys;                 // keys currently pressed
     int noThings;             // number of things in the scene
     int prevUpdate;           // time of the last update
     Thing* thing[MAX_THINGS]; // pointers to the thing objects

     Scene(const Scene& s);
     Scene& operator=(const Scene& s);

     void drawShadows();

   public:
     Scene(GameContext& c, Audio& a);
     ~Scene();
     BOOL setup(int now, HWND hwnd);
     Thing* select(int i) { return i >= 0 && i < noThings ? thing[i] : NULL; } 
     void suspend();
     BOOL restore(int now, HWND hwnd);
     void wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
     void update(int now);
     void createReflection(const Vector& viewpoint, const Vector& top);
     void draw(const Vector& viewpoint);
 };

draw()

The Scene object accesses shadowing operations through the draw() function.  The Scene object commences the shadowing operations only after having drawn all of the things in the scene to the backbuffer.


 void Scene::draw(const Vector& viewpoint) {

     context.biLinearFiltering(TRUE);
     context.mipmapLinearFiltering(TRUE);
     if (context.keyPressed('Q')) context.textureClamp(TRUE);
     if (context.keyPressed('Z')) context.textureBorder(TRUE); 

     // draw all of the objects in the scene
     //
     for (int i = 0; i < noThings; i++)
         if (thing[i])
             thing[i]->draw();
     context.textureClamp(FALSE);
     context.mipmapLinearFiltering(FALSE);
     context.biLinearFiltering(FALSE);

     // draw the shadows created by the objects in the scene
     //
     if (context.keyPressed('T')) drawShadows();
 }

drawShadows()

The Scene object performs the shadowing operations through the drawShadows() function


 void Scene:: drawShadows() {

     Matrix shadow;    // shadow matrix
     int type;         // light type
     Vector direction; // light direction
     Vector position;  // light position

     context.stencillingOn();
     int noLights = context.noLights();
     for (int j = 0; j < noThings; j++) {
         if (thing[j]) {
             int noPlanes = thing[j]->noShadowablePlanes();
             for (int k = 0; k < noPlanes; k++) {
                 // identify the destination plane
                 Plane plane = thing[j]->plane(k);
                 // create the stencil for the occupied region
                 // of the destination plane
                 context.beginCreatingStencil();
                 thing[j]->draw(k);
                 // draw shadows from all lights and objects onto the
                 // stencilled region of the destination plane
                 context.beginStencilledDrawing();
                 for (int i = 0; i < noLights; i++) {
                     type      = context.lightType(i);
                     direction = context.lightDirection(i);
                     position  = context.lightPosition(i);
                     if (type == DIRECTIONAL_TYPE &&
                      dot(plane.n, direction) < 0.0f) {
                         // light is directional and shining onto the
                         // projection plane
                         shadow = Shadow(type, -direction, plane);
                         for (int m = 0; m < noThings; m++)
                             if (m != j && thing[m] &&
                              thing[m]->isInFrontOf(plane, direction))
                                 thing[m]->drawShadow(shadow);
                     }
                     else if ((type == POINT_TYPE || type == SPOT_TYPE) 
                      && dot(plane.n, position) > - plane.d) {
                         // light is point or spot and shining onto the
                         // projection plane
                         shadow = Shadow(type, position, plane);
                         for (int m = 0; m < noThings; m++)
                             if (m != j && thing[m] &&
                                 thing[m]->isBetween(position, plane))
                                 thing[m]->drawShadow(shadow);
                     }
                 }
             }
         }
     }
     context.stencillingOff();
 }


Thing

The Thing class includes six member functions that handle the creation and drawing of planar shadows.


 class Thing {

     int mVertices;               // maximum number of vertices
     int nVertices;               // number of vertices
     Vertex* vertex;              // vertices
     int mIndices;                // maximum number of indices
     int nIndices;                // number of indices
     int* index;                  // indices
     int type;                    // primitive type
     int nPrimitives;             // number of primitives
     Colour colour;               // material colour
     char* texFile;               // texture file
     char* uexFile;               // second texture file
     Matrix world;                // world transformation
     Vector centroid;             // centroid

     PrimitiveSet* primitiveSet;  // visual representation

     Thing(const Thing &);            // intentionally privatized ...
     Thing &operator=(const Thing &); // ... to prevent copies

     void init(int prType, int nPr, Colour clr, const char* tFile,
      const char* uFile, int maxVertices, int maxIndices);

   protected:
     virtual void add(const Vertex& v);
     virtual Vector position() const { return centroid; }
     virtual PrimitiveSet* createPrimitive(GameContext& c, int t, int n,
      Colour clr);
     const Matrix& worldTransform() const { return world; }
     PrimitiveSet* primitive() const { return primitiveSet; }

   public:
     Thing();
     Thing(int prType, int nPr, Colour clr = Colour(0, 0, 0, 1),
      const char* tFile = NULL, const char* uFile = NULL,
      int maxVertices = MAX_VERTICES, int maxIndices = MAX_VERTICES);
     Thing(int nSubsets, int nPr, const Colour* clr);
     Thing(const char* modelFile, const char* = NULL);
     virtual ~Thing();
     virtual BOOL setup(GameContext& c, HWND hwnd);
     virtual void suspend();
     virtual void rotatex(float rad);
     virtual void rotatey(float rad);
     virtual void rotatez(float rad);
     virtual void scale(float s);
     virtual void move(float x, float y, float z);
     virtual Vector transform(const Vector& v) const { return v * world; } 
     virtual void draw();
     virtual void draw(int i) {}
     virtual int  noShadowablePlanes() const { return 0; }
     virtual Plane plane(int i) const { return Plane(); }
     virtual void drawShadow(const Matrix& s) {}
     virtual BOOL isBetween(const Vector& x, const Plane& p);
     virtual BOOL isInFrontOf(const Plane& p, const Vector& d);
 };

The first four member functions are stubs that simply exclude the thing from accepting shadows or obstructing light to create shadows. 

isBetween()

The Thing object determines if the object in its entirety lies in between a specified point and a specified plane through the inBetween() function.


 BOOL Thing::isBetween(const Vector& x, const Plane& p) {

     BOOL rc = dot(x, p.n) > - p.d; // in +ve p half-space

     for (int i = 0; i < nVertices && rc; i++) {
         Vector v = Vector(vertex[i].x, vertex[i].y, vertex[i].z); 
         if (dot(transform(v), p.n) < - p.d)
             // this vertex is in -ve p half-space, so
             rc = FALSE;
     }

     return rc;
 }

isInFrontOf

The Thing object determines if the object in its entirety lies in front of a specified plane through the isInFrontOf() function.


 BOOL Thing::isInFrontOf(const Plane& p, const Vector& d) {

     BOOL rc = dot(d, p.n) < 0.0f; // directed to front of plane p 

     for (int i = 0; i < nVertices && rc; i++) {
         Vector v = Vector(vertex[i].x, vertex[i].y, vertex[i].z);
         if (dot(transform(v), p.n) < - p.d)
             rc = FALSE;
     }

     return rc;
 }


PrimitiveSet

The PrimitiveSet class now includes an instance variable that holds the colour of the shadow to be cast by a primitive set and a member function that draws that shadow. 


 class PrimitiveSet {

     LPDIRECT3DDEVICE9 d3dd;     // points to interface to display device 
     LPDIRECT3DVERTEXBUFFER9 vb; // Vertex buffer (stores vertices)
     LPDIRECT3DINDEXBUFFER9 ib;  // Index buffer (points to vertices)
     D3DPRIMITIVETYPE type;      // e.g. point, triangle strip, etc.
     int nVertices;              // number of vertices
     int nPrimitives;            // number of primitives
     D3DMATERIAL9 mat;           // material it is made of
     D3DMATERIAL9 shd;           // shadow material
     LPDIRECT3DTEXTURE9 tex;     // a texture spread over the surface
     LPDIRECT3DTEXTURE9 tx2;     // 2nd texture spread over the surface

     PrimitiveSet(const PrimitiveSet& v);
     PrimitiveSet& operator=(const PrimitiveSet& v);

   protected:
     virtual void release();

   public:
     PrimitiveSet(GameContext& c, int t, int n, Colour clr);
     virtual ~PrimitiveSet();
     virtual BOOL setup(const Vertex* v, int nVertices, const int*,
      int nIndices, const char* texFile, const char* tx2File, HWND hwnd);
     virtual void draw(void* world);
     virtual void drawShadow(void* world);
 };

Constructor

The PrimitiveSet constructor initializes the material describing the shadow to black and sets its transparency to 50%.


 PrimitiveSet::PrimitiveSet(GameContext& c, int t, int n, Colour clr) :
 d3dd(LPDIRECT3DDEVICE9(c.displayDevice())) {

     switch (t) {
       case POINT_LIST:     type = D3DPT_POINTLIST;     break;
       case LINE_LIST:      type = D3DPT_LINELIST;      break;
       case LINE_STRIP:     type = D3DPT_LINESTRIP;     break;
       case TRIANGLE_LIST:  type = D3DPT_TRIANGLELIST;  break;
       case TRIANGLE_STRIP: type = D3DPT_TRIANGLESTRIP; break;
       case TRIANGLE_FAN:   type = D3DPT_TRIANGLEFAN;   break;
     }
     nPrimitives = n;
     vb          = NULL;
     ib          = NULL;
     tex         = NULL;
     tx2         = NULL;

     // make a shiny material of the specified color
     ZeroMemory(&mat, sizeof(mat));
     mat.Ambient  = D3DXCOLOR(clr.r*0.7f, clr.g*0.7f, clr.b*0.7f, clr.a);
     mat.Diffuse  = D3DXCOLOR(clr.r, clr.g, clr.b, clr.a); // reflected
     mat.Specular = D3DXCOLOR(1.f, 1.f, 1.f, clr.a);  // shine from lights 
     mat.Power    = 100; // 0 if it shouldn't be shiny

     // make a shadow material of the specified color
     ZeroMemory(&shd, sizeof(shd));
     shd.Diffuse  = D3DXCOLOR(0.f, 0.f, 0.f, 0.25f); // reflected
 }

drawShadow

The PrimitiveSet object draws the shadow to the backbuffer through the drawShadow() function. 


 void PrimitiveSet::drawShadow(void* world) {

     d3dd->SetStreamSource(0, vb, 0, sizeof(Vertex));
     d3dd->SetTransform(D3DTS_WORLD, (D3DXMATRIX*)world);
     d3dd->SetMaterial(&shd);
     d3dd->SetIndices(ib);
     d3dd->DrawIndexedPrimitive(type, 0, 0, nVertices, 0, nPrimitives); 
 }

The world transformation received by this function includes the transformation that projects the vertices from world space onto the plane of projection. 


Mesh

The Mesh class now includes an instance variable that holds the colour of the shadow and member functions that draw the a subset of the mesh and the shadow cast by the mesh. 


 class Mesh {

    LPDIRECT3DDEVICE9 d3dd;  // points to interface to display device
    DWORD nSubsets;          // number of subsets
    int nFaces;              // number of triangles
    LPD3DXMESH mesh;         // set of vertices, indices, attributes
    D3DMATERIAL9* mat;       // materials it is made of
    LPDIRECT3DTEXTURE9* tex; // textures spread over its surfaces
    int type;                // internally generated type
    D3DMATERIAL9 shd;        // shadow material

    Mesh(const Mesh& v);
    Mesh& operator=(const Mesh& v);

  public:
    Mesh(GameContext& c, int s, int n, const Colour* clr);
    Mesh(GameContext& c, int t, const Colour& clr);
    Mesh(GameContext& c, const char* file);
    ~Mesh();
    BOOL setup(const Vertex* v, int nVertices, const int*, int nIndices,
     const int*, char* texFile[], HWND hwnd);
    BOOL setup(const float* dimension, const int* partition, HWND hwnd); 
    BOOL setup(const char* x, HWND hwnd);
    void draw(void* world);
    void release();
    void draw(void* world, int i);
    void drawShadow(void* world);
};

Constructors

The Mesh constructors initialize the material describing the shadow itself and sets its transparency to 50%.


 Mesh::Mesh(GameContext& c, int s, int n, const Colour* clr) :
 d3dd(LPDIRECT3DDEVICE9(c.displayDevice())), nSubsets(s), nFaces(n) {

     mesh = NULL;
     mat  = new D3DMATERIAL9[nSubsets];
     tex  = new LPDIRECT3DTEXTURE9[nSubsets];

     for (int i = 0; i < nSubsets; i++) {
         // make a shiny material of the specified color
         ZeroMemory(&mat[i], sizeof D3DMATERIAL9);
         mat[i].Ambient  = D3DXCOLOR(clr[i].r*0.7f, clr[i].g*0.7f,
          clr[i].b*0.7f, clr[i].a);
         mat[i].Diffuse  = D3DXCOLOR(clr[i].r, clr[i].g, clr[i].b,
          clr[i].a); // reflected
         mat[i].Specular = D3DXCOLOR(1.f, 1.f, 1.f, clr[i].a);  // shine
         mat[i].Power    = 100; // 0 if it shouldn't be shiny
         // texture object created later
         tex[i] = NULL;
     }

     // make a shadow material of the specified color
     ZeroMemory(&shd, sizeof(shd));
     shd.Diffuse  = D3DXCOLOR(0.f, 0.f, 0.f, 0.5f); // reflected
 }

 // This constructor stores 1 material property for a mesh to be generated
 // at setup
 //
 Mesh::Mesh(GameContext& c, int t, const Colour& clr) :
  d3dd(LPDIRECT3DDEVICE9(c.displayDevice())), nSubsets(1), nFaces(0) {

     mesh = NULL;
     mat  = new D3DMATERIAL9[1];
     tex  = new LPDIRECT3DTEXTURE9[1];
     type = t;

     // make a shiny material of the specified color
     ZeroMemory(&mat[0], sizeof D3DMATERIAL9);
     mat[0].Ambient  = D3DXCOLOR(clr.r*0.7f, clr.g*0.7f, clr.b*0.7f, clr.a); 
     mat[0].Diffuse  = D3DXCOLOR(clr.r, clr.g, clr.b, clr.a); // reflected
     mat[0].Specular = D3DXCOLOR(1.f, 1.f, 1.f, clr.a);  // shine
     mat[0].Power    = 100; // 0 if it shouldn't be shiny
     // no texture object
     tex[0] = NULL;

     // make a shadow material of the specified color
     ZeroMemory(&shd, sizeof(shd));
     shd.Diffuse  = D3DXCOLOR(0.f, 0.f, 0.f, 0.5f); // reflected
 }

 // This constructor extracts the material properties from a x file and
 // stores them for the mesh to be generated at setup
 //
 Mesh::Mesh(GameContext& c, const char* file) :
  d3dd(LPDIRECT3DDEVICE9(c.displayDevice())) {

     mesh = NULL;
     LPD3DXBUFFER mtrl = NULL;

     if (FAILED(D3DXLoadMeshFromX(file, 0, d3dd, NULL, &mtrl, 0,
      &nSubsets, &mesh))) {
         error("Failed to retrieve mesh from x file");
     }
     else {
         D3DXMATERIAL* matl;
         mat  = new D3DMATERIAL9[nSubsets];
         tex  = new LPDIRECT3DTEXTURE9[nSubsets];
         matl = (D3DXMATERIAL*)mtrl->GetBufferPointer();

         for (int i = 0; i < nSubsets; i++) {
             ZeroMemory(&mat[i], sizeof D3DMATERIAL9);
             mat[i].Ambient.r  = matl[i].MatD3D.Diffuse.r * 0.7f;
             mat[i].Ambient.g  = matl[i].MatD3D.Diffuse.g * 0.7f;
             mat[i].Ambient.b  = matl[i].MatD3D.Diffuse.b * 0.7f;
             mat[i].Ambient.a  = matl[i].MatD3D.Diffuse.a;
             mat[i].Diffuse    = matl[i].MatD3D.Diffuse;
             mat[i].Specular   = matl[i].MatD3D.Specular;
             mat[i].Power      = matl[i].MatD3D.Power;
             // texture object created later
             tex[i] = NULL;
         }
         mtrl->Release();
         mesh->Release();
         mesh = NULL;
     }

     // make a shadow material of the specified color
     ZeroMemory(&shd, sizeof(shd));
     shd.Diffuse  = D3DXCOLOR(0.f, 0.f, 0.f, 0.5f); // reflected
 }

draw

The Mesh object draws the primitives in one subset of the mesh through the draw( , ) function. 


 void Mesh::draw(void* world, int i) {

     d3dd->SetTransform(D3DTS_WORLD, (D3DXMATRIX*)world); 

     d3dd->SetMaterial(&mat[i]);
     if (tex[i])d3dd->SetTexture(0, tex[i]);
     mesh->DrawSubset(i);
     if (tex[i])d3dd->SetTexture(0, NULL);

     // reset to default format -
     // DrawSubset may have changed the format
     d3dd->SetFVF(D3DFVF_VERTEX);
 }

The second parameter received by this function identifies the zero-based index of the subset to be drawn. 

drawShadow

The Mesh object draws the shadow casts by the primitives in the mesh through the drawShadow() function. 


 void Mesh::drawShadow(void* world) {

     d3dd->SetTransform(D3DTS_WORLD, (D3DXMATRIX*)world); 
     d3dd->SetMaterial(&shd);

     // draw each subset
     for (int i = 0; i < nSubsets; i++)
         mesh->DrawSubset(i);

     // reset to default format -
     // DrawSubset may have changed the format
     d3dd->SetFVF(D3DFVF_VERTEX);
 }

The parameter received by this function includes the transformation that casts the vertices onto the projection plane. 


Exercise and Research

  • Read pages 131-150 in Luna,
  • Read the DirectX Documentation on stencil buffer techniques.




   Printer Friendly Version of this Page print this page     Top  Go Back to the Top of this Page
Previous Reading  Previous: Mirrors Next: Vertex Shaders   Next Reading


  Designed by Chris Szalwinski   Copying From This Site