Problem
Although you have mastered the basic computer real-time illumination, you should note that the light source has not shot a shadow. This is because pixel shader calculates illumination based on the angle between light and normal. Until now, pixel shader has not considered the objects between light and pixels.
Shadow ing technology on my website (http: // www. riemers. net), this technology can generate correct shadows for a light source, but you want to use the deferred rendering method.
Solution
An excellent way to add shadow to a scene is the shadow ing technology, which I have described in detail on my website (http://www.riemers.net ).
In short, the shadow ing technology compares the real distance between each pixel and the light source with the distance it looks like from the light source. If the actual distance is greater than the distance from the light source, there must be an object between the camera and the pixel, so this pixel is not illuminated.
To allow such a comparison, the scene should first be drawn from the light source, so that the distance from the pixel to the light source can be stored in a texture.
If you want to implement the deferred version of shadow ing technology, you must first generate a depth map for each light source. After this texture is completed, the comparison of shadow mappings can be applied to step 2 of deferrded rendering.
Working Principle
This tutorial is based on the previous tutorial. The first step of the previous tutorial remains unchanged because you still need the color, normal, and depth values of all pixels on the screen.
Generate shadow Paster xNa Code
Before obtaining the illumination value of a light source in step 2, you need to generate a shadow map for the light source. This shadow map contains the distance between the scene and the light source. You also need to add a rendering target and two variables: one storage distance and one black image to reset the other:
Rendertarget2d shadowtarget; texture2d shadowmap; texture2d blackimage;
Initialize the rendering target and black image in the loadcontent method:
Shadowtarget = new rendertarget2d (device, width, height, 1, surfaceformat. Single); blackimage = new texture2d (device, width, height, 1, textureusage. None, surfaceformat. Color );
Find the generateshadingmap method. This method uses Alpha hybrid to mix the illumination values of all light sources and add them to a texture. This step is not required, because you need to generate a new shadow texture between the two blending operations, which will cause the shading texture to be mixed into the shading texture.
You do not need to use Alpha mixing. After adding the illumination values of each light source, you will save the shading texture. However, you must first Delete the shading texture using a black image:
Private texture2d generateshadingmap () {shadingmap = blackimage; For (INT I = 0; I <numberoflights; I ++) {rendershadowmap (spotlights [I]); addlight (spotlights [I]);} return shadingtarget. gettexture ();}
Call the rendershadowmap method for each light source. This method stores the shadowmap of the light source in the shadowmap variable. Based on this shadow texture, the light source adds its illumination value to the shading texture.
Shadow maps must contain distance values from the light source. Therefore, each light source needs to define the view and projection matrix, which needs to expand the spotlight structure:
Public struct spotlight {public vector3 position; public float strength; Public vector3 ction; public float coneangle; public float conedecay; Public matrix viewmatrix; Public matrix projectionmatrix ;}
For a spotlight, it is very easy to define these two matrices:
Spotlights [I]. viewmatrix = matrix. createlookat (lightposition, lightposition + lightdirection, lightup); float viewangle = (float) math. ACOs (spotlights [I]. coneangle); spotlights [I]. projectionmatrix = matrix. createperspectivefieldofview (coneangle * 2.0f, 1.0f, 0.5f, 1000.0f );
Viewangle is based on the spotlight light cone. In this way, the rendering target area can be optimized.
The rendershadowmap method transmits these matrices to the shadowmap effect (defined below. Then, the scene is drawn from the camera's perspective, and the distance is saved to the shadowmap texture.
Private void rendershadowmap (spotlight) {Device. setrendertarget (0, shadowtarget); implements tshadowmap. currenttechnique = effectshadowmap. techniques ["shadowmap"]; ffectshadowmap. parameters ["xview"]. setvalue (spotlight. viewmatrix); implements tshadowmap. parameters ["xprojection"]. setvalue (spotlight. projectionmatrix); renderscene (effectshadowmap); device. setrendertarget (0, null); shadowmap = shadowtarget. gettexture ();}
HLSL code
HLSL code is simple. Because 3D scenes need to be converted to 2D screen coordinates, effect needs the world, view, and projection matrices. To match the renderscene method, you also need to set a texture, so effect also contains the xtexture variable, although this variable is not used here
Float4x4 xworld; float4x4 xview; float4x4 xprojection; texture xtexture; struct detail {float4 position: position; float4 screenpos: texcoord1 ;}; struct pixeltoframe {float4 color: color0 ;}
Vertex shader needs to calculate 2D screen coordinates. The screen coordinates also contain depth values for pixel shader output. Because the position semantics cannot be accessed in pixel shader, You need to copy it to the screenpos variable.
Vertex shader is very simple because it converts 3d positions to 2D screen positions:
Partition partition (float4 inpos: position0, float3 innormal: normal0) {partition output = (partition) 0; float4x4 previewprojection = MUL (xview, xprojection); float4x4 preworldviewprojection = MUL (xworld, previewprojection); output. position = MUL (inpos, preworldviewprojection); output. screenpos = output. position; return output ;}
Pixel shader accepts screen coordinates. Because this is a homogeneous vector, you must divide the first three components by the fourth one before use. Pixel shader generates deep value output.
Pixeltoframe mypixelshader (vertextopixel pSIN): color0 {pixeltoframe output = (pixeltoframe) 0; output. color. r = pSIN. screenpos. Z/pSIN. screenpos. W; return output ;}
The following is the definition of technique:
Technique shadowmap {pass pass0 {vertexshader = compile vs_2_0 myvertexshader (); pixelshader = compile ps_2_0 mypixelshader ();}}
Add shadow ing xNa code based on illumination Calculation
Call the addlight method after the shadow is drawn. This method first enables shadingtarget and deferredspotlight technique. This method is the same as technique in the previous tutorial, except for some minor changes. At the end of each light source, the current content of shadingtarget is saved to the shadingmap texture. Use the xpreviusshadingmapcontents variable to pass this content to the next light source, and you need to pass the shadow texture.
Private void addlight (spotlight) {Device. setrendertarget (0, shadingtarget); optional t2lights. currenttechnique = effect2lights. techniques ["deferredspotlight"]; effect2lights. parameters ["xpreviusshadingcontents"]. setvalue (shadingmap); optional t2lights. parameters ["xnormalmap"]. setvalue (normalmap); extends t2lights. parameters ["xdepthmap"]. setvalue (depthmap); effect2lights. parameters ["xshadowmap"]. setvalue (shadowmap); optional t2lights. parameters ["xlightposition"]. setvalue (spotlight. position); optional t2lights. parameters ["xlightstrength"]. setvalue (spotlight. strength); required t2lights. parameters ["xconedirection"]. setvalue (spotlight. direction); optional t2lights. parameters ["xconeangle"]. setvalue (spotlight. coneangle); optional t2lights. parameters ["xconedecay"]. setvalue (spotlight. conedecay); matrix viewprojinv = matrix. invert (fpscam. viewmatrix * fpscam. projectionmatrix); optional t2lights. parameters ["xviewprojectioninv"]. setvalue (viewprojinv); effect2lights. parameters ["xlightviewprojection"]. setvalue (spotlight. viewmatrix * spotlight. projectionmatrix); optional t2lights. begin (); foreach (effectpass pass in effect2lights. currenttechnique. passes) {pass. begin (); device. vertexdeclaration = fsvertexdeclaration; device. drawuserprimitives <vertexpositiontexture> (primitivetype. trianglestrip, fsvertices, 0, 2); pass. end ();} else t2lights. end (); device. setrendertarget (0, null); shadingmap = shadingtarget. gettexture ();}
The HLSL Code ensures that effect can accept new textures:
Texture xshadowmap; sampler scheme = sampler_state {texture = <xshadowmap>; magfilter = Linear; minfilter = Linear; mipfilter = Linear; addressu = mirror; addressv = mirror;}; texture scheme; sampler previussampler = sampler_state {texture = <xpreviusshadingcontents>; magfilter = Linear; minfilter = Linear; mipfilter = Linear; addressu = mirror; addressv = mirror ;};
The only change is in pixel shader. You want to get the real distance between the vertex and the light source and the distance stored in the shadow map. If the distance stored in the shadow is less than the actual distance, it indicates that an object is between the light source and the pixel, and this pixel is not directed by the current light source. To find the real distance, you need to convert the 3D position through the viewprojection matrix of illumination. After dividing by its homogeneous component (W, the fourth component), the Z component of the distance can be easily used.
// Find screen position as seen by the light float4 lightscreenpos = MUL (worldpos, xlightviewprojection); lightscreenpos/= lightscreenpos. W;
Then, you want to get the distance stored in the shadow texture. First, you need to know where to sample the shadow texture and map the lightscreenpos component from the screen position range of [-] to the texture coordinate range of [], which is the same as in the previous tutorial:
// Find sample position in shadow map float2 lightsamplepos; lightsamplepos. x = lightscreenpos. X/2.0f + 0.5f; lightsamplepos. Y = (-lightscreenpos. Y/2.0f + 0.5f );
Now you can sample the depth values stored in the shadow texture. Check whether the distance is smaller than the actual distance, indicating whether the pixel is illuminated by the current light source:
// Determine shadowing criteria float realdistancetolight = lightscreenpos. Z; float condition = tex2d (gradient, lightsamplepos); bool shadowcondition = inflow <= realdistancetolight-1.0f/1001_f;
Finally, the shadowcondition and conecondition are used to determine whether a pixel is illuminated. This shading value is added to the original value in shadingmap, which is executed in the last line of code in pixel shader.
The following is the complete pixel shader code:
Pixeltoframe mypixelshader (vertextopixel pSIN): color0 {pixeltoframe output = (pixeltoframe) 0; // sample normal from normal map float3 normal = tex2d (normalmapsampler, pSIN. texcoord ). RGB; normal = normal * 2.0f-1.0f; normal = normalize (normal); // sample depth from depth map float depth = tex2d (depthmapsampler, pSIN. texcoord ). r; // create screen position float4 screenpos; screenpos. X = pSIN. texcoord. X * 2.0f-1.0f; screenpos. y =-(pSIN. texcoord. y * 2.0f-1.0f); screenpos. z = depth; screenpos. W = 1.0f; // transform to 3D position float4 worldpos = MUL (screenpos, xviewprojectioninv); worldpos/= worldpos. w; // find screen position as seen by the light float4 lightscreenpos = MUL (worldpos, xlightviewprojection); lightscreenpos/= lightscreenpos. w; // find sample position in shadow map float2 lightsamplepo S; lightsamplepos. X = lightscreenpos. x/2.0f + 0.5f; lightsamplepos. y = (-lightscreenpos. y/2.0f + 0.5f); // determine shadowing criteria float realdistancetolight = lightscreenpos. z; float condition = tex2d (shadowmapsampler, lightsamplepos); bool shadowcondition = condition <= realdistancetolight-1.0f/100366f; // determine cone criteria float3 lightdirection = normalize (wor Ldpos-xlightposition); float conedot = dot (lightdirection, normalize (xconedirection); bool conecondition = conedot> = xconeangle; // calculate shading float shading = 0; if (conecondition &&! Shadowcondition) {float coneattenuation = POW (conedot, xconedecay); shading = dot (normal,-lightdirection); shading * = xlightstrength; shading * = coneattenuation ;} float4 previous = tex2d (previussampler, pSIN. texcoord); output. color = previous + shading; return output ;}Code
This tutorial uses the same code as 6-10, but the generateshadingmap, rendershadowmap, and addlight methods have changed, and these methods have been written before.