Add light in the scene-Add the shading capability to the deferred Shading Engine

Source: Internet
Author: User
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.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.