Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

Basic shadow mapping

Started by Thergothon 4 replies 1.3k views
Original Post
Thergothon
Thergothon
Im trying to get a basic shadow mapping implementation working, but I'm having problems. I'm fairly sure the error is with the texture projection, but I can't figure out where (it will probably turn out to be an embarrassingly simple error). Anyway, here is the code, I apologize for the awful coding style, I'm more concerned with getting it working than anything else at this point. The last two functions in the source code is where all the rendering stuff is done, but I included the rest of the code just in case. Thanks guys. main.cpp

#include <windows.h>
#include <windowsx.h>
#include <string>

#include <d3d9.h>
#include <d3dx9.h>

const int SHADOWMAP_SIZE = 1024;
#define SHADOWMAP_FORMAT D3DFMT_R32F

HWND g_hWnd;
HINSTANCE g_instance;

ID3DXMesh* g_teapot;
ID3DXEffect* g_shadow;

IDirect3D9* g_d3d;
IDirect3DDevice9* g_d3dDevice;
D3DPRESENT_PARAMETERS g_d3dpp;

D3DXMATRIX g_world;
D3DXMATRIX g_viewProj;
D3DXMATRIX g_lightViewProj;
D3DXMATRIX g_lightProjTex;

D3DXVECTOR4 g_lightPos;
D3DXVECTOR4 g_ambient;
D3DXVECTOR4 g_diffuse;

IDirect3DTexture9* g_shadowMap;
IDirect3DSurface9* g_shadowMapSurface;
IDirect3DSurface9* g_shadowMapZBuffer;

IDirect3DSurface9* g_oldSurface;
IDirect3DSurface9* g_oldZBuffer;

bool CreateAppWindow(std::string appName);
bool CreateD3D();
void CloseD3D();

bool CreateResources();
void ReleaseResources();
bool CreateMesh();
bool CreateShaders();
bool CreateShadowMap();

void DrawShadowMapPreview();
void DrawShadowMap();
void DrawScene();

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {

	return DefWindowProc(hWnd, msg, wParam, lParam);
}

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

int WINAPI WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdLine, int showCmd) {

	std::string appName = "Arena";

	if(!CreateAppWindow(appName)) 
		return 0;

	if(!CreateD3D()) {
		UnregisterClass(appName.c_str(), instance);
		return 0;
	}

	if(!CreateResources()) {
		ReleaseResources();
		CloseD3D();
		UnregisterClass(appName.c_str(), instance);
		return 0;
	}

	MSG msg;

	bool done = false;
	while(!done) {

		while(PeekMessage(&msg, g_hWnd, 0, 0, PM_REMOVE)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		if(GetAsyncKeyState(VK_ESCAPE))
			done = true;

		//start render
		if(g_d3dDevice->BeginScene()) {

			if(FAILED(g_shadow->SetTechnique("Shader")))
				MessageBox(g_hWnd, "Error: Couldnt set technique: \"ShadowMap\"", "", MB_OK);

			g_shadow->SetMatrix("world", &g_world);
			g_shadow->SetMatrix("viewProj", &g_viewProj);

			if(FAILED(g_shadow->Begin(0, 0)))
				MessageBox(g_hWnd, "Error: Couldnt begin shader: ShadowMap", "", MB_OK);

			DrawShadowMap();

			DrawScene();

			g_shadow->End();

			DrawShadowMapPreview();

			g_d3dDevice->EndScene();
		}

		g_d3dDevice->Present(0, 0, 0, 0);

	}

	ReleaseResources();
	CloseD3D();

	UnregisterClass(appName.c_str(), instance);
	return 0;
}

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

bool CreateAppWindow(std::string appName) {

	g_instance = GetModuleHandle(NULL);

	//fill in window data structure
	WNDCLASSEX wc = {sizeof(WNDCLASSEX),  //size
				     CS_CLASSDC, //share device context between windows
					 WndProc, //window procedure
					 0, //no extra bytes
					 0, //no extra bytes
                     g_instance, //instance of window
					 NULL, //no icon
					 LoadCursor(NULL, IDC_ARROW), //default arrow
					 NULL, //no background
					 NULL, //no menu
					 appName.c_str(), //class name
					 NULL}; //no small icon

	//register the window with windows
	if(!RegisterClassEx(&wc)) {
		return false;
	}

	//create the window
	g_hWnd = CreateWindow(appName.c_str(), //class name
		                  appName.c_str(), //window title name
						  WS_OVERLAPPEDWINDOW, //just a border, caption and close button
						  100, //100 x indent 
						  100, //100 y indent
 						  640,
						  480,
						  GetDesktopWindow(), //parent window
						  NULL, //no menu
						  g_instance, //application instance
						  NULL); //no user data

	//if handle is null, then creation failed
	if(!g_hWnd) {
		return false;
	}

	//display the window
	ShowWindow(g_hWnd, SW_SHOW);

	return true;

}

bool CreateResources() {

	g_shadow = 0;
	g_teapot = 0;

	g_shadowMap = 0;
	g_shadowMapSurface = 0;
	g_shadowMapZBuffer = 0;

	if(!CreateMesh())
		return false;

	if(!CreateShaders()) 
		return false;

	if(!CreateShadowMap())
		return false;

	return true;
}

bool CreateMesh() {

	LPD3DXBUFFER adj, mat;
	DWORD num;

	if(FAILED(D3DXLoadMeshFromX("teapot.x",
		D3DXMESH_MANAGED, g_d3dDevice, &adj, &mat, 0, #, &g_teapot))) {
		return false;
	}

	adj->Release();
	mat->Release();

	return true;
}

bool CreateShaders() {

	if(FAILED(D3DXCreateEffectFromFile(g_d3dDevice, 
		"shadowMap.fx", 0, 0, 0, 0, &g_shadow, 0))) {
		MessageBox(g_hWnd, "Error: shadowMap.fx", "", MB_OK);
		return false;
	}

	return true;
}

void ReleaseResources() {

	if(g_shadowMap)
		g_shadowMap->Release();

	if(g_shadowMapSurface)
		g_shadowMapSurface->Release();

	if(g_shadowMapZBuffer)
		g_shadowMapZBuffer->Release();

	if(g_shadow)
		g_shadow->Release();

	if(g_teapot)
		g_teapot->Release();

}

bool CreateD3D() {

	//create the d3d interface
	if(!(g_d3d = Direct3DCreate9(D3D_SDK_VERSION))) {
		return false;
	}

	//fill out present parameters struct
	ZeroMemory(&g_d3dpp, sizeof(g_d3dpp));

	g_d3dpp.Windowed = true;

	g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

	g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	g_d3dpp.EnableAutoDepthStencil = true;
	g_d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	g_d3dpp.hDeviceWindow = g_hWnd;

	//create the device
    if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, //default display device
		                          D3DDEVTYPE_HAL, //hardware abstraction
								  g_hWnd, //window handle
                                  D3DCREATE_HARDWARE_VERTEXPROCESSING, //shaders on
                                  &g_d3dpp, //present parameters
								  &g_d3dDevice))) { //device
        return false;
	}

	return true;
}

void CloseD3D() {
	if(g_d3dDevice) 
		g_d3dDevice->Release();

	if(g_d3d)
		g_d3d->Release();
}

bool CreateShadowMap() {

	if(FAILED(g_d3dDevice->CreateTexture(SHADOWMAP_SIZE,
		SHADOWMAP_SIZE, 1, D3DUSAGE_RENDERTARGET, SHADOWMAP_FORMAT, 
		D3DPOOL_DEFAULT, &g_shadowMap, NULL))) {
			MessageBox(g_hWnd, "Error: couldnt create shadow map texture", "", MB_OK);
			return false;
	}
	
	if(FAILED(g_shadowMap->GetSurfaceLevel(0, &g_shadowMapSurface))) {
		MessageBox(g_hWnd, "Error: couldnt get render surface from shadow map texture", "", MB_OK);
		return false;
	}
		
	if(FAILED(g_d3dDevice->CreateDepthStencilSurface(SHADOWMAP_SIZE,
		SHADOWMAP_SIZE, D3DFMT_D24S8, D3DMULTISAMPLE_NONE, 0,
		TRUE, &g_shadowMapZBuffer, NULL))) {
			MessageBox(g_hWnd, "Error: couldnt create shadow map depth surface", "", MB_OK);
			return false;
	}

	return true;
}

void DrawShadowMapPreview() {

	//create quad for shadow map preview
	struct VERTEX {
		float x, y, z, rhw;
		float tu, tv;
	};

	float size = 100.0f;

	VERTEX quad[4] = {
		{size, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f},
		{size, size, 0.0f, 1.0f, 1.0f, 1.0f},
		{0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
		{0.0f, size, 0.0f, 1.0f, 0.0f, 1.0f},
	};

	//set up texture stages
	g_d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
	g_d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

	//render
	g_d3dDevice->SetTexture(0, g_shadowMap);
	g_d3dDevice->SetVertexShader(0);
	g_d3dDevice->SetPixelShader(0);
	g_d3dDevice->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
	g_d3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad, sizeof(VERTEX));

}

void DrawShadowMap() {

	//set up light matrices
	D3DXMATRIX lightView, lightProj;

	D3DXMatrixLookAtLH(&lightView, &D3DXVECTOR3(5.0f, 5.0f, 5.0f),
		&D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f));

	D3DXMatrixPerspectiveFovLH(&lightProj, 0.5f, 1.0f, 1.0f, 100.0f);

	D3DXMatrixIdentity(&g_world);

	g_lightViewProj = lightView * lightProj;

	//upload matrices
	g_shadow->SetMatrix("lightViewProj", &(g_world * g_lightViewProj));
	g_shadow->CommitChanges();

	//switch render targets
	g_d3dDevice->GetRenderTarget(0, &g_oldSurface);
	g_d3dDevice->GetDepthStencilSurface(&g_oldZBuffer);
	g_d3dDevice->SetRenderTarget(0, g_shadowMapSurface);
	g_d3dDevice->SetDepthStencilSurface(g_shadowMapZBuffer);

	g_d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);

	g_shadow->BeginPass(0);
	g_teapot->DrawSubset(0);
	g_shadow->EndPass();

	//restore render target
	g_d3dDevice->SetRenderTarget(0, g_oldSurface);
	g_d3dDevice->SetDepthStencilSurface(g_oldZBuffer);


}

void DrawScene() {

	//set up matrices
	D3DXMatrixIdentity(&g_world);

	D3DXMATRIX view, proj;

	D3DXMatrixLookAtLH(&view, &D3DXVECTOR3(5.0f, 5.0f, -5.0f),
		&D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f));

	D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI/4, 1.33f, 1.0f, 100.0f);

	g_viewProj = view * proj;

	//light projection matrix
	float offset = 0.5f + (0.5f/(float)SHADOWMAP_SIZE);
	D3DXMATRIX textureAdjust(0.5f,    0.0f,  0.0f, 0.0f,
				             0.0f,   -0.5f,  0.0f, 0.0f,
						     0.0f,    0.0f,  0.0f, 0.0f,
						     offset, offset, 0.0f, 1.0f);

	g_lightProjTex = g_world * g_lightViewProj * textureAdjust;

	//upload matrices
	g_shadow->SetMatrix("world", &g_world);
	g_shadow->SetMatrix("viewProj", &g_viewProj);
	g_shadow->SetMatrix("lightProjTex", &g_lightProjTex);

	g_shadow->SetVector("lightPos", &D3DXVECTOR4(5.0f, 5.0f, 5.0f, 0.0f));
	g_shadow->SetVector("diffuse", &D3DXVECTOR4(0.8f, 0.0f, 0.0f, 1.0f));
	g_shadow->SetVector("ambient", &D3DXVECTOR4(0.2f, 0.0f, 0.0f, 1.0f));

	g_shadow->SetTexture("ShadowMap", g_shadowMap);

	g_shadow->CommitChanges();

	//create ground plane
	struct VERTEX {
		float x, y, z;
		float nx, ny, nz;
	};

	VERTEX quad[4] = {
		{20.0f, -10.0f, 20.0f, 0.0f, 1.0f, 0.0f},
		{20.0f, -10.0f, -20.0f, 0.0f, 1.0f, 0.0f},
		{-20.0f, -10.0f, 20.0f, 0.0f, 1.0f, 0.0f},
		{-20.0f, -10.0f, -20.0f, 0.0f, 1.0f, 0.0f}
	};

	g_d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff000000, 1.0f, 0);

	g_shadow->BeginPass(1);
	g_teapot->DrawSubset(0);

	g_d3dDevice->SetFVF(D3DFVF_XYZ | D3DFVF_NORMAL);
	g_d3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad, sizeof(VERTEX));

	g_shadow->EndPass();

}

shadowMap.fx

//camera matrices
float4x4 world;
float4x4 viewProj;

//light matrix
float4x4 lightViewProj;

//light projected texture adjustment matrix
float4x4 lightProjTex; 

//colours for diffuse lighting
float4 ambient;
float4 diffuse;

//position of diffuse point light
float3 lightPos;

//------------ Shadow map texture sampler --------------------

texture ShadowMap;
sampler ShadowMapSampler = sampler_state {
    Texture = <ShadowMap>;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    MipFilter = NONE;
    AddressU = CLAMP;
    AddressV = CLAMP;
    AddressW = CLAMP;
};

//------------ Shadow map creation shaders -------------------

struct createIn {
    float3 pos: POSITION;
};

struct createOut {
    float4 pos: POSITION;
    float depth: TEXCOORD0;
};

void CreateShadowMapVS(in createIn input, out createOut output) {
    //transform position into light space
    output.pos = mul(float4(input.pos, 1.0), lightViewProj);

    //perspective divide pos.z to get depth
    output.depth = output.pos.z/output.pos.w;
}

float4 CreateShadowMapPS(in float depth: TEXCOORD0) : COLOR {
    return depth;
}  

//------------ Scene render with shadow map ------------------

struct vsIn {
    float3 pos: POSITION;
    float3 norm: NORMAL;
};

struct vsOut {
    float4 final: POSITION;

    float3 pos: TEXCOORD0;
    float3 norm: TEXCOORD1;

    float4 uv: TEXCOORD2;
    float  depth: TEXCOORD3;
};

void VS(in vsIn input, out vsOut output) {

    //transform vertex to world and save for pixel shader
    output.final = mul(float4(input.pos, 1.0), world);
    output.pos = output.final.xyz;    
    
    //transform vertex to screen space
    output.final = mul(output.final, viewProj);

    //transform normal into world space, ignoring translation
    output.norm = normalize(mul(input.norm, (float3x3) world));

    //get shadow map uv coordinates for this vertex
    output.uv = mul(input.pos, lightProjTex);
    
    //get depth of vertex from light source
    float4 pos = mul(input.pos, lightViewProj);
    output.depth = pos.z/pos.w - 0.001;

}

struct psIn {
    float3 pos: TEXCOORD0;
    float3 norm: TEXCOORD1;

    float4 uv: TEXCOORD2;
    float  depth: TEXCOORD3;
};

float4 PS(in psIn input) : COLOR {
   
    //get shadow map sample for depth comparison
    float depth = tex2Dproj(ShadowMapSampler, input.uv).x;

    //set ambient colour here as diffuse lighting may be skipped
    float4 colour = ambient;

    //test whether this pixel is lit or not
    if(input.depth - depth > 0.0) {
        //renormalize 
        input.norm = normalize(input.norm);

        //get vector to the light source
        float3 l = normalize(lightPos - input.pos);

        //get amount of light at surface
        float d = saturate(dot(input.norm, l));

        //add diffuse component to colour
        colour += diffuse*d;
    }

    return colour;

}

//----------------- techniques ----------------------------------

technique Shader {

    pass P0 {
        VertexShader = compile vs_1_1 CreateShadowMapVS();
        PixelShader = compile ps_2_0 CreateShadowMapPS();
    }

    pass P1 {
        Sampler[0] = (ShadowMapSampler);
        VertexShader = compile vs_2_0 VS();
        PixelShader = compile ps_2_0 PS();
    }

}

Taymo
Taymo
Just to let you know your coding style isn't aweful. It's actually consistant and easy to read.
Dev Journal - Ride the Spiralhttp://spiralride.blogspot.com/
bgilb
bgilb
way better then the shit i manage to makeup
Thergothon
Thergothon
Thanks guys, pity the code wasn't working as intended. :)

Yes, wasn't. I just figured out the problem then. And yeah it was embarrassingly simple. All the logic was correct, but I was doing some incorrect transforms in the vertex shader.

where I had:

    //get shadow map uv coordinates for this vertex    output.uv = mul(input.pos, lightProjTex);        //get depth of vertex from light source    float4 pos = mul(input.pos, lightViewProj);


It should have been:

    //get shadow map uv coordinates for this vertex    output.uv = mul(float4(input.pos, 1.0), lightProjTex);        //get depth of vertex from light source    float4 pos = mul(float4(input.pos, 1.0), lightViewProj);


Anyway, cool, it works. I'm a happy camper now.
ZealousEngine
ZealousEngine
noob question - How are you passing that depth texture (from the first pass) to the second pass? I thought if you wanted to share texture data you had to render to a texture, re texture the object, then run the shader again (so it can use the new texture). Can you do all that INBETWEEN pass 1 and 2? I thought it had to be done with two different techniques..
Thergothon
Thergothon
Before the second pass, I upload the texture to the shader and then call commit changes. That way you can change attributes of the shader mid technique, so for instance, if you have a whole heap of objects that have basic diffuse lighting, but different ambient and diffuse colours; then between each draw primitive call, you can set the new colours and call commit changes so that the change takes effect.

Like this:

m_effect->SetMatrix("world", &m_object1->m_world);m_effect->SetVector("ambient", &m_object1->m_ambient);m_effect->SetVector("diffuse", &m_object1->m_diffuse);m_effect->CommitChanges();m_object1->DrawSubset(0);m_effect->SetVector("world", &m_object2->m_world);m_effect->SetVector("ambient", &m_object2->m_ambient);m_effect->SetVector("diffuse", &m_object2->m_diffuse);m_effect->CommitChanges();m_object2->DrawSubset(0);//etc


Of course, this would be done in a loop or whatever, but yeah, you can do the same thing with textures, matrices, floats, whatever.

Topic Locked

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

Sign in to reply to this topic.