]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - r_shadow.c
366
[xonotic/darkplaces.git] / r_shadow.c
index 2779a310075d149acf878ba885af6cf4e8c72259..f9abefbd0d965c27cc8a6968c4330b64ce24283b 100644 (file)
@@ -7,23 +7,25 @@ as far as the light's radius, if the light has a radius at all), capped at
 both front and back to avoid any problems (extrusion from dark faces also
 works but has a different set of problems)
 
-This is rendered using Carmack's Reverse technique, in which backfaces behind
-zbuffer (zfail) increment the stencil, and frontfaces behind zbuffer (zfail)
-decrement the stencil, the result is a stencil value of zero where shadows
-did not intersect the visible geometry, suitable as a stencil mask for
-rendering lighting everywhere but shadow.
-
-In our case we use a biased stencil clear of 128 to avoid requiring the
-stencil wrap extension (but probably should support it), and to address
-Creative's patent on this sort of technology we also draw the frontfaces
-first, and backfaces second (decrement, increment).
+This is normally rendered using Carmack's Reverse technique, in which
+backfaces behind zbuffer (zfail) increment the stencil, and frontfaces behind
+zbuffer (zfail) decrement the stencil, the result is a stencil value of zero
+where shadows did not intersect the visible geometry, suitable as a stencil
+mask for rendering lighting everywhere but shadow.
+
+In our case to hopefully avoid the Creative Labs patent, we draw the backfaces
+as decrement and the frontfaces as increment, and we redefine the DepthFunc to
+GL_LESS (the patent uses GL_GEQUAL) which causes zfail when behind surfaces
+and zpass when infront (the patent draws where zpass with a GL_GEQUAL test),
+additionally we clear stencil to 128 to avoid the need for the unclamped
+incr/decr extension (not related to patent).
 
 Patent warning:
-This algorithm may be covered by Creative's patent (US Patent #6384822)
-on Carmack's Reverse paper (which I have not read), however that patent
-seems to be about drawing a stencil shadow from a model in an otherwise
-unshadowed scene, where as realtime lighting technology draws light where
-shadows do not lie.
+This algorithm may be covered by Creative's patent (US Patent #6384822),
+however that patent is quite specific about increment on backfaces and
+decrement on frontfaces where zpass with GL_GEQUAL depth test, which is
+opposite this implementation and partially opposite Carmack's Reverse paper
+(which uses GL_LESS, but increments on backfaces and decrements on frontfaces).
 
 
 
@@ -53,7 +55,8 @@ NormalMap) if supported by hardware; in our case there is support for cards
 which are incapable of DOT3, the quality is quite poor however.  Additionally
 it is desirable to have specular evaluation per pixel, per vertex
 normalization of specular halfangle vectors causes noticable distortion but
-is unavoidable on hardware without GL_ARB_fragment_program.
+is unavoidable on hardware without GL_ARB_fragment_program or
+GL_ARB_fragment_shader.
 
 
 
@@ -61,18 +64,29 @@ Terminology: Normalization CubeMap
 A cubemap containing normalized dot3-encoded (vectors of length 1 or less
 encoded as RGB colors) for any possible direction, this technique allows per
 pixel calculation of incidence vector for per pixel lighting purposes, which
-would not otherwise be possible per pixel without GL_ARB_fragment_program.
+would not otherwise be possible per pixel without GL_ARB_fragment_program or
+GL_ARB_fragment_shader.
 
 
 
-Terminology: 2D Attenuation Texturing
+Terminology: 2D+1D Attenuation Texturing
 A very crude approximation of light attenuation with distance which results
 in cylindrical light shapes which fade vertically as a streak (some games
 such as Doom3 allow this to be rotated to be less noticable in specific
 cases), the technique is simply modulating lighting by two 2D textures (which
 can be the same) on different axes of projection (XY and Z, typically), this
-is the best technique available without 3D Attenuation Texturing or
-GL_ARB_fragment_program technology.
+is the second best technique available without 3D Attenuation Texturing,
+GL_ARB_fragment_program or GL_ARB_fragment_shader technology.
+
+
+
+Terminology: 2D+1D Inverse Attenuation Texturing
+A clever method described in papers on the Abducted engine, this has a squared
+distance texture (bright on the outside, black in the middle), which is used
+twice using GL_ADD blending, the result of this is used in an inverse modulate
+(GL_ONE_MINUS_DST_ALPHA, GL_ZERO) to implement the equation
+lighting*=(1-((X*X+Y*Y)+(Z*Z))) which is spherical (unlike 2D+1D attenuation
+texturing).
 
 
 
@@ -91,8 +105,13 @@ diffuse lighting if 3D Attenuation Textures are already used.
 
 Terminology: Light Cubemap Filtering
 A technique for modeling non-uniform light distribution according to
-direction, for example projecting a stained glass window image onto a wall,
-this is done by texturing the lighting with a cubemap.
+direction, for example a lantern may use a cubemap to describe the light
+emission pattern of the cage around the lantern (as well as soot buildup
+discoloring the light in certain areas), often also used for softened grate
+shadows and light shining through a stained glass window (done crudely by
+texturing the lighting with a cubemap), another good example would be a disco
+light.  This technique is used heavily in many games (Doom3 does not support
+this however).
 
 
 
@@ -101,14 +120,18 @@ A technique for modeling shadowing of light passing through translucent
 surfaces, allowing stained glass windows and other effects to be done more
 elegantly than possible with Light Cubemap Filtering by applying an occluder
 texture to the lighting combined with a stencil light volume to limit the lit
-area (this allows evaluating multiple translucent occluders in a scene).
+area, this technique is used by Doom3 for spotlights and flashlights, among
+other things, this can also be used more generally to render light passing
+through multiple translucent occluders in a scene (using a light volume to
+describe the area beyond the occluder, and thus mask off rendering of all
+other areas).
 
 
 
 Terminology: Doom3 Lighting
 A combination of Stencil Shadow Volume, Per Pixel Lighting, Normalization
-CubeMap, 2D Attenuation Texturing, and Light Filtering, as demonstrated by
-the (currently upcoming) game Doom3.
+CubeMap, 2D+1D Attenuation Texturing, and Light Projection Filtering, as
+demonstrated by the game Doom3.
 */
 
 #include "quakedef.h"
@@ -923,15 +946,15 @@ void R_Shadow_RenderVolume(int numvertices, int numtriangles, const float *verte
        GL_LockArrays(0, numvertices);
        if (r_shadowstage == R_SHADOWSTAGE_STENCIL)
        {
-               // increment stencil if backface is behind depthbuffer
+               // decrement stencil if backface is behind depthbuffer
                qglCullFace(GL_BACK); // quake is backwards, this culls front faces
-               qglStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
+               qglStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
                R_Mesh_Draw(0, numvertices, numtriangles, element3i);
                c_rt_shadowmeshes++;
                c_rt_shadowtris += numtriangles;
-               // decrement stencil if frontface is behind depthbuffer
+               // increment stencil if frontface is behind depthbuffer
                qglCullFace(GL_FRONT); // quake is backwards, this culls back faces
-               qglStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
+               qglStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
        }
        R_Mesh_Draw(0, numvertices, numtriangles, element3i);
        c_rt_shadowmeshes++;
@@ -1089,7 +1112,7 @@ void R_Shadow_Stage_StencilShadowVolumes(void)
        //}
        //else
        //      qglDisable(GL_POLYGON_OFFSET_FILL);
-       qglDepthFunc(GL_GEQUAL);
+       qglDepthFunc(GL_LESS);
        qglCullFace(GL_FRONT); // quake is backwards, this culls back faces
        qglEnable(GL_STENCIL_TEST);
        qglStencilFunc(GL_ALWAYS, 128, ~0);
@@ -1100,10 +1123,10 @@ void R_Shadow_Stage_StencilShadowVolumes(void)
                qglEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);
                qglActiveStencilFaceEXT(GL_BACK); // quake is backwards, this is front faces
                qglStencilMask(~0);
-               qglStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
+               qglStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
                qglActiveStencilFaceEXT(GL_FRONT); // quake is backwards, this is back faces
                qglStencilMask(~0);
-               qglStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+               qglStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
        }
        else
        {
@@ -1198,7 +1221,7 @@ void R_Shadow_Stage_Lighting(int stenciltest)
                        qglUniform1fARB(qglGetUniformLocationARB(r_shadow_lightprog, "OffsetMapping_Bias"), r_shadow_glsl_offsetmapping_bias.value);CHECKGLERROR
                }
        }
-       else if (gl_dot3arb && gl_texturecubemap && gl_combine.integer && gl_stencil)
+       else if (gl_dot3arb && gl_texturecubemap && r_textureunits.integer >= 2 && gl_combine.integer && gl_stencil)
                r_shadowstage = R_SHADOWSTAGE_LIGHT_DOT3;
        else
                r_shadowstage = R_SHADOWSTAGE_LIGHT_VERTEX;
@@ -1262,171 +1285,90 @@ void R_Shadow_Stage_End(void)
        r_shadowstage = R_SHADOWSTAGE_NONE;
 }
 
-int R_Shadow_ScissorForBBox(const float *mins, const float *maxs)
+qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs)
 {
        int i, ix1, iy1, ix2, iy2;
-       float x1, y1, x2, y2, x, y, f;
-       vec3_t smins, smaxs;
+       float x1, y1, x2, y2;
        vec4_t v, v2;
-       if (!r_shadow_scissor.integer)
-               return false;
-       // if view is inside the box, just say yes it's visible
+       rmesh_t mesh;
+       mplane_t planes[11];
+       float vertex3f[256*3];
+
+       // if view is inside the light box, just say yes it's visible
        if (BoxesOverlap(r_vieworigin, r_vieworigin, mins, maxs))
        {
                GL_Scissor(r_view_x, r_view_y, r_view_width, r_view_height);
                return false;
        }
-       for (i = 0;i < 3;i++)
+
+       // create a temporary brush describing the area the light can affect in worldspace
+       VectorNegate(frustum[0].normal, planes[ 0].normal);planes[ 0].dist = -frustum[0].dist;
+       VectorNegate(frustum[1].normal, planes[ 1].normal);planes[ 1].dist = -frustum[1].dist;
+       VectorNegate(frustum[2].normal, planes[ 2].normal);planes[ 2].dist = -frustum[2].dist;
+       VectorNegate(frustum[3].normal, planes[ 3].normal);planes[ 3].dist = -frustum[3].dist;
+       VectorNegate(frustum[4].normal, planes[ 4].normal);planes[ 4].dist = -frustum[4].dist;
+       VectorSet   (planes[ 5].normal,  1, 0, 0);         planes[ 5].dist =  maxs[0];
+       VectorSet   (planes[ 6].normal, -1, 0, 0);         planes[ 6].dist = -mins[0];
+       VectorSet   (planes[ 7].normal, 0,  1, 0);         planes[ 7].dist =  maxs[1];
+       VectorSet   (planes[ 8].normal, 0, -1, 0);         planes[ 8].dist = -mins[1];
+       VectorSet   (planes[ 9].normal, 0, 0,  1);         planes[ 9].dist =  maxs[2];
+       VectorSet   (planes[10].normal, 0, 0, -1);         planes[10].dist = -mins[2];
+
+       // turn the brush into a mesh
+       memset(&mesh, 0, sizeof(rmesh_t));
+       mesh.maxvertices = 256;
+       mesh.vertex3f = vertex3f;
+       mesh.epsilon2 = (1.0f / (32.0f * 32.0f));
+       R_Mesh_AddBrushMeshFromPlanes(&mesh, 11, planes);
+
+       // if that mesh is empty, the light is not visible at all
+       if (!mesh.numvertices)
+               return true;
+
+       if (!r_shadow_scissor.integer)
+               return false;
+
+       // if that mesh is not empty, check what area of the screen it covers
+       x1 = y1 = x2 = y2 = 0;
+       v[3] = 1.0f;
+       for (i = 0;i < mesh.numvertices;i++)
        {
-               if (r_viewforward[i] >= 0)
+               VectorCopy(mesh.vertex3f + i * 3, v);
+               GL_TransformToScreen(v, v2);
+               //Con_Printf("%.3f %.3f %.3f %.3f transformed to %.3f %.3f %.3f %.3f\n", v[0], v[1], v[2], v[3], v2[0], v2[1], v2[2], v2[3]);
+               if (i)
                {
-                       v[i] = mins[i];
-                       v2[i] = maxs[i];
+                       if (x1 > v2[0]) x1 = v2[0];
+                       if (x2 < v2[0]) x2 = v2[0];
+                       if (y1 > v2[1]) y1 = v2[1];
+                       if (y2 < v2[1]) y2 = v2[1];
                }
                else
                {
-                       v[i] = maxs[i];
-                       v2[i] = mins[i];
-               }
-       }
-       f = DotProduct(r_viewforward, r_vieworigin) + 1;
-       if (DotProduct(r_viewforward, v2) <= f)
-       {
-               // entirely behind nearclip plane
-               return true;
-       }
-       if (DotProduct(r_viewforward, v) >= f)
-       {
-               // entirely infront of nearclip plane
-               x1 = y1 = x2 = y2 = 0;
-               for (i = 0;i < 8;i++)
-               {
-                       v[0] = (i & 1) ? mins[0] : maxs[0];
-                       v[1] = (i & 2) ? mins[1] : maxs[1];
-                       v[2] = (i & 4) ? mins[2] : maxs[2];
-                       v[3] = 1.0f;
-                       GL_TransformToScreen(v, v2);
-                       //Con_Printf("%.3f %.3f %.3f %.3f transformed to %.3f %.3f %.3f %.3f\n", v[0], v[1], v[2], v[3], v2[0], v2[1], v2[2], v2[3]);
-                       x = v2[0];
-                       y = v2[1];
-                       if (i)
-                       {
-                               if (x1 > x) x1 = x;
-                               if (x2 < x) x2 = x;
-                               if (y1 > y) y1 = y;
-                               if (y2 < y) y2 = y;
-                       }
-                       else
-                       {
-                               x1 = x2 = x;
-                               y1 = y2 = y;
-                       }
+                       x1 = x2 = v2[0];
+                       y1 = y2 = v2[1];
                }
        }
-       else
-       {
-               // clipped by nearclip plane
-               // this is nasty and crude...
-               // create viewspace bbox
-               for (i = 0;i < 8;i++)
-               {
-                       v[0] = ((i & 1) ? mins[0] : maxs[0]) - r_vieworigin[0];
-                       v[1] = ((i & 2) ? mins[1] : maxs[1]) - r_vieworigin[1];
-                       v[2] = ((i & 4) ? mins[2] : maxs[2]) - r_vieworigin[2];
-                       v2[0] = -DotProduct(v, r_viewleft);
-                       v2[1] = DotProduct(v, r_viewup);
-                       v2[2] = DotProduct(v, r_viewforward);
-                       if (i)
-                       {
-                               if (smins[0] > v2[0]) smins[0] = v2[0];
-                               if (smaxs[0] < v2[0]) smaxs[0] = v2[0];
-                               if (smins[1] > v2[1]) smins[1] = v2[1];
-                               if (smaxs[1] < v2[1]) smaxs[1] = v2[1];
-                               if (smins[2] > v2[2]) smins[2] = v2[2];
-                               if (smaxs[2] < v2[2]) smaxs[2] = v2[2];
-                       }
-                       else
-                       {
-                               smins[0] = smaxs[0] = v2[0];
-                               smins[1] = smaxs[1] = v2[1];
-                               smins[2] = smaxs[2] = v2[2];
-                       }
-               }
-               // now we have a bbox in viewspace
-               // clip it to the view plane
-               if (smins[2] < 1)
-                       smins[2] = 1;
-               // return true if that culled the box
-               if (smins[2] >= smaxs[2])
-                       return true;
-               // ok some of it is infront of the view, transform each corner back to
-               // worldspace and then to screenspace and make screen rect
-               // initialize these variables just to avoid compiler warnings
-               x1 = y1 = x2 = y2 = 0;
-               for (i = 0;i < 8;i++)
-               {
-                       v2[0] = (i & 1) ? smins[0] : smaxs[0];
-                       v2[1] = (i & 2) ? smins[1] : smaxs[1];
-                       v2[2] = (i & 4) ? smins[2] : smaxs[2];
-                       v[0] = v2[0] * -r_viewleft[0] + v2[1] * r_viewup[0] + v2[2] * r_viewforward[0] + r_vieworigin[0];
-                       v[1] = v2[0] * -r_viewleft[1] + v2[1] * r_viewup[1] + v2[2] * r_viewforward[1] + r_vieworigin[1];
-                       v[2] = v2[0] * -r_viewleft[2] + v2[1] * r_viewup[2] + v2[2] * r_viewforward[2] + r_vieworigin[2];
-                       v[3] = 1.0f;
-                       GL_TransformToScreen(v, v2);
-                       //Con_Printf("%.3f %.3f %.3f %.3f transformed to %.3f %.3f %.3f %.3f\n", v[0], v[1], v[2], v[3], v2[0], v2[1], v2[2], v2[3]);
-                       x = v2[0];
-                       y = v2[1];
-                       if (i)
-                       {
-                               if (x1 > x) x1 = x;
-                               if (x2 < x) x2 = x;
-                               if (y1 > y) y1 = y;
-                               if (y2 < y) y2 = y;
-                       }
-                       else
-                       {
-                               x1 = x2 = x;
-                               y1 = y2 = y;
-                       }
-               }
-               /*
-               // this code doesn't handle boxes with any points behind view properly
-               x1 = 1000;x2 = -1000;
-               y1 = 1000;y2 = -1000;
-               for (i = 0;i < 8;i++)
-               {
-                       v[0] = (i & 1) ? mins[0] : maxs[0];
-                       v[1] = (i & 2) ? mins[1] : maxs[1];
-                       v[2] = (i & 4) ? mins[2] : maxs[2];
-                       v[3] = 1.0f;
-                       GL_TransformToScreen(v, v2);
-                       //Con_Printf("%.3f %.3f %.3f %.3f transformed to %.3f %.3f %.3f %.3f\n", v[0], v[1], v[2], v[3], v2[0], v2[1], v2[2], v2[3]);
-                       if (v2[2] > 0)
-                       {
-                               x = v2[0];
-                               y = v2[1];
 
-                               if (x1 > x) x1 = x;
-                               if (x2 < x) x2 = x;
-                               if (y1 > y) y1 = y;
-                               if (y2 < y) y2 = y;
-                       }
-               }
-               */
-       }
+       // now convert the scissor rectangle to integer screen coordinates
        ix1 = x1 - 1.0f;
        iy1 = y1 - 1.0f;
        ix2 = x2 + 1.0f;
        iy2 = y2 + 1.0f;
        //Con_Printf("%f %f %f %f\n", x1, y1, x2, y2);
+
+       // clamp it to the screen
        if (ix1 < r_view_x) ix1 = r_view_x;
        if (iy1 < r_view_y) iy1 = r_view_y;
        if (ix2 > r_view_x + r_view_width) ix2 = r_view_x + r_view_width;
        if (iy2 > r_view_y + r_view_height) iy2 = r_view_y + r_view_height;
+
+       // if it is inside out, it's not visible
        if (ix2 <= ix1 || iy2 <= iy1)
                return true;
-       // set up the scissor rectangle
-       GL_Scissor(ix1, vid.realheight - iy2, ix2 - ix1, iy2 - iy1);
+
+       // the light area is visible, set up the scissor rectangle
+       GL_Scissor(ix1, vid.height - iy2, ix2 - ix1, iy2 - iy1);
        //qglScissor(ix1, iy1, ix2 - ix1, iy2 - iy1);
        //qglEnable(GL_SCISSOR_TEST);
        c_rt_scissored++;
@@ -1658,7 +1600,7 @@ void R_Shadow_RenderLighting(int firstvertex, int numvertices, int numtriangles,
                int passes = 0;
                if (r_shadow_glsl.integer && r_shadow_program_light[0])
                        passes++; // GLSL shader path (GFFX5200, Radeon 9500)
-               else if (gl_dot3arb && gl_texturecubemap && gl_combine.integer && gl_stencil)
+               else if (gl_dot3arb && gl_texturecubemap && r_textureunits.integer >= 2 && gl_combine.integer && gl_stencil)
                {
                        // TODO: add direct pants/shirt rendering
                        if (pantstexture && (r_shadow_rtlight->ambientscale + r_shadow_rtlight->diffusescale) * VectorLength2(lightcolorpants) > 0.001)
@@ -2690,7 +2632,7 @@ void R_RTLight_Compile(rtlight_t *rtlight)
        {
                // this variable directs the DrawShadowVolume and DrawLight code to capture into the mesh chain instead of rendering
                r_shadow_compilingrtlight = rtlight;
-               R_Shadow_EnlargeLeafSurfaceBuffer(model->brush.num_leafs, model->brush.num_surfaces);
+               R_Shadow_EnlargeLeafSurfaceBuffer(model->brush.num_leafs, model->num_surfaces);
                model->GetLightInfo(ent, rtlight->shadoworigin, rtlight->radius, rtlight->cullmins, rtlight->cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces);
                numleafpvsbytes = (model->brush.num_leafs + 7) >> 3;
                data = Mem_Alloc(r_shadow_mempool, sizeof(int) * numleafs + numleafpvsbytes + sizeof(int) * numsurfaces);
@@ -2800,15 +2742,15 @@ void R_Shadow_DrawEntityShadow(entity_render_t *ent, rtlight_t *rtlight, int num
                                GL_LockArrays(0, mesh->numverts);
                                if (r_shadowstage == R_SHADOWSTAGE_STENCIL)
                                {
-                                       // increment stencil if backface is behind depthbuffer
+                                       // decrement stencil if backface is behind depthbuffer
                                        qglCullFace(GL_BACK); // quake is backwards, this culls front faces
-                                       qglStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
+                                       qglStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
                                        R_Mesh_Draw(0, mesh->numverts, mesh->numtriangles, mesh->element3i);
                                        c_rtcached_shadowmeshes++;
                                        c_rtcached_shadowtris += mesh->numtriangles;
-                                       // decrement stencil if frontface is behind depthbuffer
+                                       // increment stencil if frontface is behind depthbuffer
                                        qglCullFace(GL_FRONT); // quake is backwards, this culls back faces
-                                       qglStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
+                                       qglStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
                                }
                                R_Mesh_Draw(0, mesh->numverts, mesh->numtriangles, mesh->element3i);
                                c_rtcached_shadowmeshes++;
@@ -2930,7 +2872,7 @@ void R_DrawRTLight(rtlight_t *rtlight, qboolean visible)
        {
                // dynamic light, world available and can receive realtime lighting
                // calculate lit surfaces and leafs
-               R_Shadow_EnlargeLeafSurfaceBuffer(r_refdef.worldmodel->brush.num_leafs, r_refdef.worldmodel->brush.num_surfaces);
+               R_Shadow_EnlargeLeafSurfaceBuffer(r_refdef.worldmodel->brush.num_leafs, r_refdef.worldmodel->num_surfaces);
                r_refdef.worldmodel->GetLightInfo(r_refdef.worldentity, rtlight->shadoworigin, rtlight->radius, rtlight->cullmins, rtlight->cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces);
                leaflist = r_shadow_buffer_leaflist;
                leafpvs = r_shadow_buffer_leafpvs;