
Code: Select all
// main.cpp - Shadow mapping for Irrlicht 1.9 / D3D9 / C++98
//
// Two-pass shadow mapping with PCF soft shadows:
// Pass 1: Render depth from light's perspective, encode depth to RGBA8
// Pass 2: Render scene with per-pixel lighting and shadow sampling
#include <irrlicht.h>
#include <cmath>
#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace gui;
// ============================================================================
// Shader Callbacks
// ============================================================================
class DepthPassCallback : public IShaderConstantSetCallBack
{
public:
matrix4 LightViewProj;
virtual void OnSetConstants(IMaterialRendererServices* services, s32 userData)
{
IVideoDriver* driver = services->getVideoDriver();
matrix4 world = driver->getTransform(ETS_WORLD);
services->setVertexShaderConstant("mWorld", world.pointer(), 16);
services->setVertexShaderConstant("mLightViewProj", LightViewProj.pointer(), 16);
}
};
class MainPassCallback : public IShaderConstantSetCallBack
{
public:
SColorf LightColor;
SColorf AmbientColor;
vector3df LightDir;
matrix4 LightViewProj;
vector2df ShadowTexelSize;
virtual void OnSetConstants(IMaterialRendererServices* services, s32 userData)
{
IVideoDriver* driver = services->getVideoDriver();
matrix4 world = driver->getTransform(ETS_WORLD);
matrix4 view = driver->getTransform(ETS_VIEW);
matrix4 projection = driver->getTransform(ETS_PROJECTION);
matrix4 wvp = projection * view * world;
services->setVertexShaderConstant("mWorld", world.pointer(), 16);
services->setVertexShaderConstant("mWorldViewProj", wvp.pointer(), 16);
services->setVertexShaderConstant("mLightViewProj", LightViewProj.pointer(), 16);
services->setPixelShaderConstant("mLightDir", reinterpret_cast<f32*>(&LightDir), 3);
services->setPixelShaderConstant("mLightColor", reinterpret_cast<f32*>(&LightColor), 3);
services->setPixelShaderConstant("mAmbientColor", reinterpret_cast<f32*>(&AmbientColor), 3);
services->setPixelShaderConstant("mShadowTexelSize", reinterpret_cast<f32*>(&ShadowTexelSize), 2);
s32 texUnit = 0;
services->setPixelShaderConstant("depthTexture", &texUnit, 1);
}
};
// ============================================================================
// Shader Programs
// ============================================================================
// Vertex shader for depth pass - transforms vertices to light space
const char* DepthPassVS =
"float4x4 mWorld;\n"
"float4x4 mLightViewProj;\n"
"\n"
"struct VS_IN { float4 pos : POSITION; };\n"
"struct VS_OUT { float4 pos : POSITION; float depth : TEXCOORD0; };\n"
"\n"
"VS_OUT main(VS_IN input)\n"
"{\n"
" VS_OUT output;\n"
" float4 worldPos = mul(input.pos, mWorld);\n"
" output.pos = mul(worldPos, mLightViewProj);\n"
" output.depth = output.pos.z / output.pos.w;\n"
" return output;\n"
"}\n";
// Pixel shader for depth pass - encodes depth to RGBA8 for precision
const char* DepthPassPS =
"float4 EncodeFloatRGBA(float v)\n"
"{\n"
" float4 enc = frac(v * float4(1.0, 255.0, 65025.0, 16581375.0));\n"
" enc -= enc.yzww * float4(1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0);\n"
" return enc;\n"
"}\n"
"\n"
"float4 main(float depth : TEXCOORD0) : COLOR\n"
"{\n"
" return EncodeFloatRGBA(saturate(depth));\n"
"}\n";
// Vertex shader for main pass - transforms vertices and passes data to pixel shader
const char* MainPassVS =
"float4x4 mWorld;\n"
"float4x4 mWorldViewProj;\n"
"float4x4 mLightViewProj;\n"
"\n"
"struct VS_IN\n"
"{\n"
" float4 pos : POSITION;\n"
" float3 norm : NORMAL;\n"
" float4 color : COLOR0;\n"
"};\n"
"\n"
"struct VS_OUT\n"
"{\n"
" float4 pos : POSITION;\n"
" float4 worldPos : TEXCOORD0;\n"
" float3 normal : TEXCOORD1;\n"
" float4 lightSpacePos : TEXCOORD2;\n"
" float4 color : COLOR0;\n"
"};\n"
"\n"
"VS_OUT main(VS_IN input)\n"
"{\n"
" VS_OUT output;\n"
" float4 worldPos = mul(input.pos, mWorld);\n"
" output.pos = mul(input.pos, mWorldViewProj);\n"
" output.worldPos = worldPos;\n"
" output.normal = input.norm;\n"
" output.lightSpacePos = mul(worldPos, mLightViewProj);\n"
" output.color = input.color;\n"
" return output;\n"
"}\n";
// Pixel shader for main pass - per-pixel lighting with PCF soft shadows
const char* MainPassPS =
"float3 mLightColor;\n"
"float3 mAmbientColor;\n"
"float3 mLightDir;\n"
"float2 mShadowTexelSize;\n"
"\n"
"sampler2D depthTexture : register(s0) = sampler_state\n"
"{\n"
" MinFilter = POINT;\n"
" MagFilter = POINT;\n"
" MipFilter = NONE;\n"
" AddressU = CLAMP;\n"
" AddressV = CLAMP;\n"
"};\n"
"\n"
"float DecodeFloatRGBA(float4 rgba)\n"
"{\n"
" return dot(rgba, float4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/16581375.0));\n"
"}\n"
"\n"
"float SampleShadow(float2 uv, float currentDepth, float bias)\n"
"{\n"
" float4 packed = tex2D(depthTexture, uv);\n"
" float closestDepth = DecodeFloatRGBA(packed);\n"
" return step(currentDepth, closestDepth + bias);\n"
"}\n"
"\n"
"float4 main(float4 worldPos : TEXCOORD0,\n"
" float3 normal : TEXCOORD1,\n"
" float4 lightSpacePos : TEXCOORD2,\n"
" float4 color : COLOR0) : COLOR\n"
"{\n"
" float3 n = normalize(normal);\n"
" float3 L = normalize(-mLightDir);\n"
" float diffuse = max(0.0, dot(n, L));\n"
"\n"
" float4 projCoords = lightSpacePos / lightSpacePos.w;\n"
" float2 shadowUV = projCoords.xy * 0.5 + 0.5;\n"
" shadowUV.y = 1.0 - shadowUV.y;\n"
"\n"
" shadowUV += mShadowTexelSize * 0.5;\n"
"\n"
" float shadow = 1.0;\n"
" if (projCoords.z >= 0.0 && projCoords.z <= 1.0)\n"
" {\n"
" if (shadowUV.x >= 0.0 && shadowUV.x <= 1.0)\n"
" {\n"
" if (shadowUV.y >= 0.0 && shadowUV.y <= 1.0)\n"
" {\n"
" float currentDepth = projCoords.z;\n"
"\n"
" float ndotl = max(0.0, dot(n, L));\n"
" float bias = 0.0015 + (1.0 - ndotl) * 0.0045;\n"
"\n"
" float2 o = mShadowTexelSize;\n"
" float sum = 0.0;\n"
" sum += SampleShadow(shadowUV + float2(-o.x, -o.y), currentDepth, bias);\n"
" sum += SampleShadow(shadowUV + float2( 0.0, -o.y), currentDepth, bias);\n"
" sum += SampleShadow(shadowUV + float2( o.x, -o.y), currentDepth, bias);\n"
" sum += SampleShadow(shadowUV + float2(-o.x, 0.0), currentDepth, bias);\n"
" sum += SampleShadow(shadowUV, currentDepth, bias);\n"
" sum += SampleShadow(shadowUV + float2( o.x, 0.0), currentDepth, bias);\n"
" sum += SampleShadow(shadowUV + float2(-o.x, o.y), currentDepth, bias);\n"
" sum += SampleShadow(shadowUV + float2( 0.0, o.y), currentDepth, bias);\n"
" sum += SampleShadow(shadowUV + float2( o.x, o.y), currentDepth, bias);\n"
" shadow = sum / 9.0;\n"
" }\n"
" }\n"
" }\n"
"\n"
" float3 lighting = mAmbientColor + diffuse * mLightColor * shadow;\n"
" return float4(lighting * color.rgb, color.a);\n"
"}\n";
// ============================================================================
// Helper function to convert char* to wchar_t* for setWindowCaption
// ============================================================================
void setWindowCaption(IrrlichtDevice* device, const char* text)
{
wchar_t wtext[256];
size_t len = strlen(text);
for (size_t i = 0; i < len && i < 255; ++i)
{
wtext[i] = text[i];
}
wtext[len] = 0;
device->setWindowCaption(wtext);
}
// ============================================================================
// Main Entry Point
// ============================================================================
int main()
{
IrrlichtDevice* device = createDevice(
EDT_DIRECT3D9,
dimension2d<u32>(800, 600),
32, false, false, false, 0);
if (!device)
return 1;
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* gui = device->getGUIEnvironment();
// Setup FPS counter
u32 lastFPS = 0;
char windowTitle[256];
// Main camera (player view) - moved closer to see shadow details
ICameraSceneNode* mainCam = smgr->addCameraSceneNodeFPS(0, 100.0f, 0.1f);
mainCam->setPosition(vector3df(0, 3, -5));
mainCam->setTarget(vector3df(0, 0, 1));
mainCam->setNearValue(0.1f);
mainCam->setFarValue(100.0f);
// Light camera (directional light / sun)
ICameraSceneNode* lightCam = smgr->addCameraSceneNode(0);
lightCam->setNearValue(0.5f);
lightCam->setFarValue(20.0f); // Reduced far plane
lightCam->setUpVector(vector3df(0, 1, 0));
// Orthographic projection for directional light (Sun)
// Reduced ortho size to focus on the cubes and sphere (higher shadow resolution)
matrix4 lightProj;
lightProj.buildProjectionMatrixOrthoLH(8.0f, 8.0f, 0.5f, 20.0f); // Smaller area = more detail
lightCam->setProjectionMatrix(lightProj, true);
// Create scene objects - moved closer together
IMesh* planeMesh = smgr->getGeometryCreator()->createPlaneMesh(
dimension2d<f32>(5.0f, 5.0f), dimension2d<u32>(1, 1));
ISceneNode* ground = smgr->addMeshSceneNode(planeMesh);
ground->setPosition(vector3df(0, -1.5f, 0));
ground->getMaterial(0).DiffuseColor = SColor(255, 180, 180, 180);
ground->setMaterialFlag(EMF_LIGHTING, false);
planeMesh->drop();
// Cube 1 - left side
ISceneNode* cube1 = smgr->addCubeSceneNode(1.2f);
cube1->setPosition(vector3df(-1.5f, -0.2f, 1.0f));
cube1->getMaterial(0).DiffuseColor = SColor(255, 200, 150, 150);
cube1->setMaterialFlag(EMF_LIGHTING, false);
// Cube 2 - right side
ISceneNode* cube2 = smgr->addCubeSceneNode(1.2f);
cube2->setPosition(vector3df(1.5f, -0.2f, 1.0f));
cube2->getMaterial(0).DiffuseColor = SColor(255, 150, 200, 150);
cube2->setMaterialFlag(EMF_LIGHTING, false);
// Sphere - center, slightly elevated
ISceneNode* sphere = smgr->addSphereSceneNode(0.9f);
sphere->setPosition(vector3df(0.0f, -0.3f, 2.5f));
sphere->getMaterial(0).DiffuseColor = SColor(255, 150, 150, 200);
sphere->setMaterialFlag(EMF_LIGHTING, false);
// Tall pillar to show shadow stretching
ISceneNode* pillar = smgr->addCubeSceneNode(0.8f);
pillar->setPosition(vector3df(0.0f, 0.3f, -0.5f));
pillar->getMaterial(0).DiffuseColor = SColor(255, 200, 200, 100);
pillar->setMaterialFlag(EMF_LIGHTING, false);
// Visual representation of light position (the sun's position in space)
ISceneNode* lightMarker = smgr->addSphereSceneNode(0.2f);
lightMarker->setMaterialFlag(EMF_LIGHTING, false);
lightMarker->getMaterial(0).EmissiveColor = SColor(255, 255, 255, 0);
ISceneNode* shadowNodes[] = { ground, cube1, cube2, sphere, pillar };
s32 shadowCount = 5;
// Create shadow map texture - higher resolution for better close-up quality
const u32 SHADOW_SIZE = 2048; // Increased for better detail when close
ITexture* depthTex = driver->addRenderTargetTexture(
dimension2d<u32>(SHADOW_SIZE, SHADOW_SIZE), "depthMap", ECF_A8R8G8B8);
if (!depthTex)
{
device->drop();
return 1;
}
// Assign shadow map to all objects
for (s32 i = 0; i < shadowCount; ++i)
{
shadowNodes[i]->getMaterial(0).setTexture(0, depthTex);
shadowNodes[i]->getMaterial(0).setFlag(EMF_BILINEAR_FILTER, false);
shadowNodes[i]->getMaterial(0).setFlag(EMF_TRILINEAR_FILTER, false);
}
// Load shaders
IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
DepthPassCallback depthCB;
MainPassCallback mainCB;
s32 depthMat = gpu->addHighLevelShaderMaterial(
DepthPassVS, "main", EVST_VS_3_0,
DepthPassPS, "main", EPST_PS_3_0,
&depthCB, EMT_SOLID, 0);
s32 mainMat = gpu->addHighLevelShaderMaterial(
MainPassVS, "main", EVST_VS_3_0,
MainPassPS, "main", EPST_PS_3_0,
&mainCB, EMT_SOLID, 0);
if (depthMat < 0 || mainMat < 0)
{
device->drop();
return 1;
}
// Scene center for light orbit
const vector3df sceneCenter(0.0f, 0.0f, 1.0f);
f32 angle = 0.0f;
// Main loop
while (device->run())
{
angle += 0.005f; // Slower rotation to observe shadows
// Rotating directional light (sun)
// The light comes from this direction, like sunlight
vector3df sunDir(0.8f * cosf(angle), -0.6f, 0.6f * sinf(angle));
sunDir.normalize();
// Position light camera for shadow map rendering
// The camera is placed opposite to the light direction
vector3df lightPos = sceneCenter - sunDir * 10.0f; // Closer to scene
lightMarker->setPosition(lightPos);
lightCam->setPosition(lightPos);
lightCam->setTarget(sceneCenter);
matrix4 lightViewProj = lightCam->getProjectionMatrix() * lightCam->getViewMatrix();
depthCB.LightViewProj = lightViewProj;
// PASS 1: Render shadow map from light's perspective
driver->setRenderTarget(depthTex, true, true, SColor(255, 255, 255, 255));
smgr->setActiveCamera(lightCam);
for (s32 i = 0; i < shadowCount; ++i)
shadowNodes[i]->setMaterialType((E_MATERIAL_TYPE)depthMat);
smgr->drawAll();
// PASS 2: Render scene from main camera with shadows
driver->setRenderTarget(0, false, false, SColor(0, 0, 0, 0));
smgr->setActiveCamera(mainCam);
driver->beginScene(true, true, SColor(255, 80, 80, 100));
mainCB.LightViewProj = lightViewProj;
mainCB.LightDir = sunDir;
mainCB.LightColor = SColorf(1.0f, 0.95f, 0.8f); // Warm sunlight
mainCB.AmbientColor = SColorf(0.12f, 0.12f, 0.15f);
mainCB.ShadowTexelSize = vector2df(1.0f / (f32)SHADOW_SIZE, 1.0f / (f32)SHADOW_SIZE);
for (s32 i = 0; i < shadowCount; ++i)
shadowNodes[i]->setMaterialType((E_MATERIAL_TYPE)mainMat);
smgr->drawAll();
gui->drawAll();
driver->endScene();
// Update window title with FPS and info
u32 currentFPS = driver->getFPS();
if (lastFPS != currentFPS)
{
sprintf(windowTitle, "Directional Light Shadow Map - FPS: %u | Ortho Size: 8.0", currentFPS);
setWindowCaption(device, windowTitle);
lastFPS = currentFPS;
}
}
device->drop();
return 0;
}