Sunlight shadow map Shader

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Noiecity
Posts: 384
Joined: Wed Aug 23, 2023 7:22 pm
Contact:

Sunlight shadow map Shader

Post by Noiecity »

Image

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;
}
Irrlicht is love, Irrlicht is life, long live to Irrlicht
Post Reply