Skip to main content
GameDev.net gamedev.net
🔒 Locked

Need help with Shadow Mapping

Started by Calneon Mar 16, 2011 at 5:53 PM 11 replies 1.7k views
Original Post
Calneon
Calneon
I'm very new to graphics programming/HLSL and I'm trying to implement shadow mapping into my engine. I am trying to follow this article but it's very hard without a sample to help. I understand the idea of comparing the depth of the pixel being rendered and the depth on the shadow map, but something is going wrong somewhere. I can render a shadow map:

Wkjrr.jpg


But this is the final render I get:

55qp9.jpg


I think something is going wrong when I transform the pixel location to a position relative to the light source, I still haven't really got my head around world view and projection matricies so I think I'm going wrong there somewhere. Here is the shader code:


float4 shadowWVP;

shadowWVP = mul(input.position, worldMatrix);
shadowWVP = mul(shadowWVP, lightViewMatrix);
shadowWVP = mul(shadowWVP, projectionMatrix);

output.ShadowSamplePos = mul( float4(input.position*100), shadowWVP);

output.RealDist = output.ShadowSamplePos.z / 100.0f;
output.Pos3D = input.position;


Where lightViewMatrix is the view matrix of the light where the shadow depth is rendered from.


float4 Shadow = 1.0f;


float2 screencoords = ( ( input.screenPos.xy / input.screenPos.w ) + 1.0f ) * 0.5f;
screencoords.y = 1.0f - screencoords.y;

//Sample the shadow-space depth from the scene rendered
// in shadow space:
float currentDepth = ShadowDepthTexture.Sample(ShadowSampler, screencoords);

//We apply a little bias to the depth values so that our geometry
// doesn't, for the most part, shadow itself.
if (!((input.RealDist -1.0f/100.0f) <= currentDepth))
{
Shadow.rgb *= 0.2f;
}





Colour *= Shadow;

Colour = saturate(Colour);

return Colour;



Any tips would be greatly appreciated!
Aqua Costa
Aqua Costa
Is the projectionMatrixthe light projection matrix? or the regular camera projection matrix?
And why do you multiply the input position * 100 and the divide the shadow sample position by 100?
Calneon
Calneon

Is the projectionMatrixthe light projection matrix? or the regular camera projection matrix?
And why do you multiply the input position * 100 and the divide the shadow sample position by 100?


The projection matrix is the camera projection matrix, which is the same as the light projection matrix. I get the view matrix to render the depth texture by moving the camera to the position of the light and getting the new view matrix, the projection matrix doesn't change, does that matter?

I was following this article, where ShadowSamplePosition is divided by maxDepth, but he doesn't say what maxDepth is, which might be a problem.
Aqua Costa
Aqua Costa
Do you use z/w depth in the shadow map?
Calneon
Calneon

Do you use z/w depth in the shadow map?

Yeah:


pixel OUT;

float normDepth = IN.viewdepth / 100.0f; //(1000.0f - 0.1f);

OUT.color = float4( normDepth, normDepth, normDepth, normDepth );

return OUT;
Aqua Costa
Aqua Costa
Looks like you are storing view space depth, but you should store z/w depth... Can you post your complete shadow map vertex and pixel shaders???
Calneon
Calneon

Looks like you are storing view space depth, but you should store z/w depth... Can you post your complete shadow map vertex and pixel shaders???


fragment VS( vertex IN )
{
fragment OUT;

OUT.position = mul( float4(IN.position, 1), WVP );

OUT.viewdepth = OUT.position.z;

return OUT;
}
//-----------------------------------------------------------------------------
pixel PS( fragment IN )
{
pixel OUT;

float normDepth = IN.viewdepth / 100.0f; //(1000.0f - 0.1f);

OUT.color = float4( normDepth, normDepth, normDepth, normDepth );

return OUT;}



Thanks for helping .

EDIT: I just changed it to


fragment VS( vertex IN )
{
fragment OUT;

OUT.position = mul( float4(IN.position, 1), WVP );

OUT.Pos2D = OUT.position.xyz;

return OUT;
}
//-----------------------------------------------------------------------------
pixel PS( fragment IN )
{
pixel OUT;

float4 cDiffuse = (float4)0;
cDiffuse.rgba = IN.Pos2D.z / 100.0f;

OUT.color = cDiffuse;

return OUT;
}



And there's no difference, actually I can change the output to be a flat texture and nothing will change.
Aqua Costa
Aqua Costa
You should create the shadow map like this:

fragment VS( vertex IN )
{
fragment OUT;

OUT.position = mul( float4(IN.position, 1), WVP );

return OUT;
}
//-----------------------------------------------------------------------------
pixel PS( fragment IN )
{
pixel OUT;

float normDepth = IN.position.z / IN.position.w;

OUT.color = float4( normDepth, normDepth, normDepth, normDepth );

return OUT;}


And you should then perform shadow mapping like this:

In vertex shader:

float4 shadowWVP;
shadowWVP = mul(input.position, worldMatrix);
shadowWVP = mul(shadowWVP, lightViewMatrix);
shadowWVP = mul(shadowWVP, projectionMatrix);
out.shadowMapTexC = shadowMapWVP;


In pixel shader:

in.shadowMapTexC.xyz /= in.shadowMapTexC.w;
// Transform from NDC space to texture space.
in.shadowMapTexC.x = +0.5f*in.shadowMapTexC.x + 0.5f;
in.shadowMapTexC.y = -0.5f*in.shadowMapTexC.y + 0.5f;
// Depth in NDC space.
float depth = in.shadowMapTexC.z;
// Sample the texture using the projective tex-coords.
float4 c = gShadowMap.Sample(gSampler, in.shadowMapTexC.xy);

float shadowFactor = 0.0f;
if(depth <= c + gBias)
{
shadowFactor = 1.0f;
}


Then you can implement PCF.
Calneon
Calneon
Thanks for the help so far, it's really appreciated. Still not working properly though. If I change the comparison to this:



float shadowFactor = 1.0f;
if(currentDepth >= depth + -0.975f)
{
shadowFactor = 0.2f;
}



I get this:

jO9wC.jpg


Which at least shows that the shadow map is working, though the depth value seems to be the same everywhere.

I'm just going to post my whole shader code, maybe I've just made a stupid mistake somewhere...

Shadow map pass.

float4x4 WV : WorldViewMatrix;
float4x4 WVP : WorldViewProjMatrix;

//-----------------------------------------------------------------------------
struct vertex
{
float3 position : POSITION;
float2 texcoord : TEXCOORD;
float3 normal : NORMAL;
};
//-----------------------------------------------------------------------------
struct fragment
{
float4 position : SV_Position;
};
//-----------------------------------------------------------------------------
struct pixel
{
float4 color : SV_Target0;
};
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Vertex Shader
//-----------------------------------------------------------------------------
fragment VS( vertex IN )
{
fragment OUT;

OUT.position = mul( float4(IN.position, 1), WVP );

return OUT;
}
//-----------------------------------------------------------------------------
pixel PS( fragment IN )
{
pixel OUT;

float normDepth = IN.position.z / IN.position.w;
OUT.color = float4( normDepth, normDepth, normDepth, normDepth );

return OUT;
}
//-----------------------------------------------------------------------------
technique10 T0
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VS() ) );
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PS() ) );
}
}



Main shader pass.
/*******************************************************************
* Defines
*******************************************************************/
#define NUM_LIGHTS 4

/*******************************************************************
* Globals
*******************************************************************/
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
Texture2D shaderTexture;
float3 cameraPosition;
float4 diffuseColour;
float3 lightDirection;
float4 specularColour;
float4 ambientColour;
float specularPower;
float4 pointLightDiffuseColour[NUM_LIGHTS];
float3 pointLightPosition[NUM_LIGHTS];

Texture2D OcclusionTexture;
Texture2D ShadowDepthTexture;

matrix lightViewMatrix;

bool enableDirectionalLight;
bool enablePointLights;
bool enableTextures;
bool enableSSAO;

/*******************************************************************
* Sample States
*******************************************************************/
SamplerState SampleType
{
// Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};

SamplerState OcclusionSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};

SamplerState ShadowSampler
{
Filter = MIN_MAG_MIP_POINT;
AddressU = Clamp;
AddressV = Clamp;
};

/*******************************************************************
* Typedefs
*******************************************************************/
struct VertexInputType
{
float4 position : POSITION;
float2 diffuseTex : TEXCOORD0;
float3 normal : NORMAL;
};

struct PixelInputType
{
float4 position : SV_POSITION;
float2 diffuseTex : TEXCOORD0;
float4 screenPos : TEXCOORD1;
float3 normal : NORMAL;
float3 viewDirection : TEXCOORD2;
float3 pointLightPos1 : TEXCOORD3;
float3 pointLightPos2 : TEXCOORD4;
float3 pointLightPos3 : TEXCOORD5;
float3 pointLightPos4 : TEXCOORD6;
float4 shadowMapTexC : TEXCOORD7;
};

/*******************************************************************
* Vertex Shader
*******************************************************************/
PixelInputType LightVertexShader(VertexInputType input)
{
PixelInputType output;
float4 worldPosition;

// shadow stuff
// --------------

float4 shadowWVP;

shadowWVP = mul(input.position, worldMatrix);
shadowWVP = mul(shadowWVP, lightViewMatrix);
shadowWVP = mul(shadowWVP, projectionMatrix);

output.shadowMapTexC = shadowWVP;

// --------------

// change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;

// calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);

// store the texture coordinates for the pixel shader.
output.diffuseTex = input.diffuseTex;

output.screenPos = output.position;

// calculate the normal vector against the world matrix only.
output.normal = mul(input.normal, (float3x3)worldMatrix);

// normalize the normal vector.
output.normal = normalize(output.normal);

// calculate the position of the vertex in the world.
worldPosition = mul(input.position, worldMatrix);

// determine the viewing direction based on the position of the camera and the position of the vertex in the world.
output.viewDirection = cameraPosition.xyz - worldPosition.xyz;

// normalize the viewing direction vector.
output.viewDirection = normalize(output.viewDirection);

// Determine the light positions based on the position of the lights and the position of the vertex in the world.
output.pointLightPos1 = pointLightPosition[0].xyz - worldPosition.xyz;
output.pointLightPos2 = pointLightPosition[1].xyz - worldPosition.xyz;
output.pointLightPos3 = pointLightPosition[2].xyz - worldPosition.xyz;
output.pointLightPos4 = pointLightPosition[3].xyz - worldPosition.xyz;

// Normalize the light position vectors.
output.pointLightPos1 = normalize(output.pointLightPos1);
output.pointLightPos2 = normalize(output.pointLightPos2);
output.pointLightPos3 = normalize(output.pointLightPos3);
output.pointLightPos4 = normalize(output.pointLightPos4);

return output;
}

/*******************************************************************
* Pixel Shader
*******************************************************************/
float4 LightPixelShader(PixelInputType input) : SV_Target
{
float4 textureColour;
float3 lightDir;
float lightIntensity;
float3 reflection;
float4 specular;
float SSAO = 0.0f;
float4 Colour = ambientColour;
float4 light = 0.0f;

float pointLightIntensity1, pointLightIntensity2, pointLightIntensity3, pointLightIntensity4;
float4 pointLightColour1, pointLightColour2, pointLightColour3, pointLightColour4;

// Point Lights
// --------------------------

if (enablePointLights){
// Calculate the different amounts of light on this pixel based on the positions of the lights.
pointLightIntensity1 = saturate(dot(input.normal, input.pointLightPos1));
pointLightIntensity2 = saturate(dot(input.normal, input.pointLightPos2));
pointLightIntensity3 = saturate(dot(input.normal, input.pointLightPos3));
pointLightIntensity4 = saturate(dot(input.normal, input.pointLightPos4));

// Determine the diffuse color amount of each of the four lights.
pointLightColour1 = pointLightDiffuseColour[0] * pointLightIntensity1;
pointLightColour2 = pointLightDiffuseColour[1] * pointLightIntensity2;
pointLightColour3 = pointLightDiffuseColour[2] * pointLightIntensity3;
pointLightColour4 = pointLightDiffuseColour[3] * pointLightIntensity4;
}

// SSAO
// --------------------------

if (enableSSAO){
float2 screencoords = ( ( input.screenPos.xy / input.screenPos.w ) + 1.0f ) * 0.5f;
screencoords.y = 1.0f - screencoords.y;

// Blur
float tx = 1.0f / 800.0f;
float ty = 1.0f / 600.0f;

const float2 avKernel[8] =
{
float2( -1.5f * tx, -0.5f * ty ),
float2( 0.5f * tx, 1.5f * ty ),
float2( 1.5f * tx, 0.5f * ty ),
float2( -0.5f * tx, 1.5f * ty ),
float2( 0.0f * tx, 0.0f * ty ),
float2( 1.5f * tx, 0.5f * ty ),
float2( -0.5f * tx, 1.5f * ty ),
float2( 0.0f * tx, 0.0f * ty ),
};

for ( int i = 0; i < 8; i++ )
SSAO += OcclusionTexture.Sample( OcclusionSampler, screencoords.xy + avKernel.xy ).r;

SSAO /= 8.0f;

// No blur
SSAO = OcclusionTexture.Sample( OcclusionSampler, screencoords.xy ).r;
}

// Shadow Mapping
// --------------------------

input.shadowMapTexC.xyz /= input.shadowMapTexC.w;

// Transform from NDC space to texture space.
input.shadowMapTexC.x = +0.5f*input.shadowMapTexC.x + 0.5f;
input.shadowMapTexC.y = -0.5f*input.shadowMapTexC.y + 0.5f;

float depth = input.shadowMapTexC.z;
float currentDepth = ShadowDepthTexture.Sample(ShadowSampler, input.shadowMapTexC.xy);

float shadowFactor = 1.0f;
if(currentDepth >= depth + -0.975f)
{
shadowFactor = 0.2f;
}

// Sample the texture
// --------------------------

// sample the texture pixel at this location.
textureColour = shaderTexture.Sample(SampleType, input.diffuseTex);

// Directional Lighting
// --------------------------

if (enableDirectionalLight)
{
// invert the light direction for calculations.
lightDir = -lightDirection;

// calculate the amount of light on this pixel.
lightIntensity = saturate(dot(input.normal, lightDir));

if (lightIntensity > 0.0f)
{
// calculate the reflection vector based on the light intensity, normal vector, and light direction.
reflection = normalize(2 * lightIntensity * input.normal - lightDir);

// determine the amount of specular light based on the reflection vector, viewing direction, and specular power.
specular = pow(saturate(dot(reflection, input.viewDirection)), specularPower);
}
}

// Final result
// --------------------------
if (enablePointLights){
light += (pointLightColour1 + pointLightColour2 + pointLightColour3 + pointLightColour4);
}

if (enableDirectionalLight){
light += (lightIntensity * diffuseColour);
}

Colour *= (light + 1.0f);
Colour = saturate(Colour);

if (enableTextures){
Colour *= textureColour;
}

Colour *= shadowFactor;

Colour = saturate(Colour);

if (enableSSAO){
Colour += SSAO * 1.0f;
}

Colour = saturate(Colour);

return Colour;
}

/*******************************************************************
* Technique
*******************************************************************/
technique10 LightTechnique
{
pass pass0
{
SetVertexShader(CompileShader(vs_4_0, LightVertexShader()));
SetPixelShader(CompileShader(ps_4_0, LightPixelShader()));
SetGeometryShader(NULL);
}
}
Aqua Costa
Aqua Costa

Which at least shows that the shadow map is working, though the depth value seems to be the same everywhere.

Well that also happens in my engine, I dont know why... but it works...

In the main shader you shouldnt be using the same projection matrix to calculate shadow position WVP and camera position WVP, because the light projection matrix should be orthogonal and camera projection matrix should be Perspective...
Calneon
Calneon

[quote name='Calneon' timestamp='1300329898' post='4786862']
Which at least shows that the shadow map is working, though the depth value seems to be the same everywhere.

Well that also happens in my engine, I dont know why... but it works...

In the main shader you shouldnt be using the same projection matrix to calculate shadow position WVP and camera position WVP, because the light projection matrix should be orthogonal and camera projection matrix should be Perspective...
[/quote]
I read some other threads on this forum and I heard some people say that you should use perspective and not orthogonal. I will try it with an orthogonal projection matrix, but I don't see how that would make a difference if the depth variable is always the same, surely it is meant to change for each pixel based on the pixel's distance from the light. That's how it determines whether the pixel is in shadow or not.

EDIT: Ok, I changed the projection matrix to be orthogonal, and it works! However, it's ridiculously low resolution. What would cause that?

Invrk.jpg
Aqua Costa
Aqua Costa

EDIT: Ok, I changed the projection matrix to be orthogonal, and it works! However, it's ridiculously low resolution. What would cause that?

Firstly you should implement Percentage Closer Filtering when sampling the shadow map...

If that is not enough you can either increase the resolution of your shadow map or decrease the size of your projection matrix...
Calneon
Calneon

[quote name='Calneon' timestamp='1300372441' post='4787015']
EDIT: Ok, I changed the projection matrix to be orthogonal, and it works! However, it's ridiculously low resolution. What would cause that?

Firstly you should implement Percentage Closer Filtering when sampling the shadow map...

If that is not enough you can either increase the resolution of your shadow map or decrease the size of your projection matrix...
[/quote]
Ok I'll give that a try. You've been a great help thank you so much .

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.