Pointlight shadowmap

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: 390
Joined: Wed Aug 23, 2023 7:22 pm
Contact:

Pointlight shadowmap

Post by Noiecity »

It's far from perfect, but I'm no expert on shaders... I created it with the goal of making a shader that could quickly render cast shadows and be compatible with OpenGL 2.0 and irrlicht 1.9.0:
Image

Code: Select all

// main.cpp - Point Light Shadows Fixed (Viewport & Space Matrix Corrections)
// Compatible with Irrlicht 1.9.0 / C++98 / OpenGL
#include <irrlicht.h>
#include <cmath>
#include <cstring>
#include <cstdio>

#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace gui;

static s32 g_CurrentFace = 0;

// ============================================================================
// Shader Callbacks
// ============================================================================

class DepthPassCallback : public IShaderConstantSetCallBack
{
public:
    vector3df LightPos;
    f32 LightPositionRangeW;

    virtual void OnSetConstants(IMaterialRendererServices* services, s32 userData)
    {
        IVideoDriver* driver = services->getVideoDriver();

        matrix4 world = driver->getTransform(ETS_WORLD);
        matrix4 view  = driver->getTransform(ETS_VIEW);
        matrix4 proj  = driver->getTransform(ETS_PROJECTION);

        services->setVertexShaderConstant("mWorld", world.pointer(), 16);
        services->setVertexShaderConstant("mLightView", view.pointer(), 16);
        services->setVertexShaderConstant("mLightProj", proj.pointer(), 16);
        services->setVertexShaderConstant("mLightPos", reinterpret_cast<f32*>(&LightPos), 3);

        services->setPixelShaderConstant("mLightPositionRangeW", &LightPositionRangeW, 1);
    }
};

class MainPassCallback : public IShaderConstantSetCallBack
{
public:
    SColorf LightColor;
    SColorf AmbientColor;
    vector3df LightPos;
    f32 LightRange;
    f32 ShadowDataR;
    f32 LightPositionRangeW;

    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->setPixelShaderConstant("mLightPos", reinterpret_cast<f32*>(&LightPos), 3);
        services->setPixelShaderConstant("mLightColor", reinterpret_cast<f32*>(&LightColor), 3);
        services->setPixelShaderConstant("mAmbientColor", reinterpret_cast<f32*>(&AmbientColor), 3);
        services->setPixelShaderConstant("mLightRange", &LightRange, 1);
        services->setPixelShaderConstant("mShadowDataR", &ShadowDataR, 1);
        services->setPixelShaderConstant("mLightPositionRangeW", &LightPositionRangeW, 1);

        s32 texUnit = 0;
        services->setPixelShaderConstant("shadowAtlas", &texUnit, 1);
    }
};

// ============================================================================
// Shader Programs (GLSL 1.20)
// ============================================================================

const char* DepthPassVS =
    "#version 120\n"
    "uniform mat4 mWorld;\n"
    "uniform mat4 mLightView;\n"
    "uniform mat4 mLightProj;\n"
    "uniform vec3 mLightPos;\n"
    "\n"
    "varying vec3 vVecToLight;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    vec4 worldPos = mWorld * gl_Vertex;\n"
    "    vVecToLight = worldPos.xyz - mLightPos;\n"
    "    gl_Position = mLightProj * mLightView * worldPos;\n"
    "}\n";

const char* DepthPassPS =
    "#version 120\n"
    "uniform float mLightPositionRangeW;\n"
    "varying vec3 vVecToLight;\n"
    "\n"
    "vec4 EncodeToRGBA8(float v)\n"
    "{\n"
    "    vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * v;\n"
    "    enc = fract(enc);\n"
    "    enc -= enc.yzww * vec4(1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0);\n"
    "    return enc;\n"
    "}\n"
    "\n"
    "void main()\n"
    "{\n"
    "    float distance = length(vVecToLight);\n"
    "    float normalizedDist = distance * mLightPositionRangeW;\n"
    "    gl_FragColor = EncodeToRGBA8(min(normalizedDist, 0.999));\n"
    "}\n";

const char* MainPassVS =
    "#version 120\n"
    "uniform mat4 mWorld;\n"
    "uniform mat4 mWorldViewProj;\n"
    "uniform vec3 mLightPos;\n"
    "\n"
    "varying vec3 vWorldPos;\n"
    "varying vec3 vNormal;\n"
    "varying vec3 vVecToLight;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    vec4 worldPos = mWorld * gl_Vertex;\n"
    "    vWorldPos = worldPos.xyz;\n"
    "    \n"
    "    // FIX: Transform normal to WORLD SPACE using World matrix\n"
    "    // This prevents shadows from deforming when rotating the main camera\n"
    "    vNormal = normalize(mat3(mWorld) * gl_Normal);\n"
    "    \n"
    "    vVecToLight = worldPos.xyz - mLightPos;\n"
    "    gl_Position = mWorldViewProj * gl_Vertex;\n"
    "    gl_FrontColor = gl_Color;\n"
    "}\n";

// MODIFIED: Smooth attenuation (radial blur from center to edge)
const char* MainPassPS =
    "#version 120\n"
    "\n"
    "uniform vec3 mLightPos;\n"
    "uniform vec3 mLightColor;\n"
    "uniform vec3 mAmbientColor;\n"
    "uniform float mLightRange;\n"
    "uniform float mShadowDataR;\n"
    "uniform float mLightPositionRangeW;\n"
    "uniform sampler2D shadowAtlas;\n"
    "\n"
    "varying vec3 vWorldPos;\n"
    "varying vec3 vNormal;\n"
    "varying vec3 vVecToLight;\n"
    "\n"
    "float DecodeFromRGBA8(vec4 rgba)\n"
    "{\n"
    "    return dot(rgba, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/16581375.0));\n"
    "}\n"
    "\n"
    "vec2 DirectionToAtlasUV(vec3 dir)\n"
    "{\n"
    "    vec3 absDir = abs(dir);\n"
    "    float maxAxis;\n"
    "    float faceIndex;\n"
    "    vec2 faceUV;\n"
    "    \n"
    "    if (absDir.x >= absDir.y && absDir.x >= absDir.z)\n"
    "    {\n"
    "        maxAxis = absDir.x;\n"
    "        if (dir.x > 0.0)\n"
    "        {\n"
    "            faceIndex = 0.0; // +X\n"
    "            faceUV = vec2(-dir.z, dir.y);\n"
    "        }\n"
    "        else\n"
    "        {\n"
    "            faceIndex = 1.0; // -X\n"
    "            faceUV = vec2(dir.z, dir.y);\n"
    "        }\n"
    "    }\n"
    "    else if (absDir.y >= absDir.x && absDir.y >= absDir.z)\n"
    "    {\n"
    "        maxAxis = absDir.y;\n"
    "        if (dir.y > 0.0)\n"
    "        {\n"
    "            faceIndex = 2.0; // +Y\n"
    "            faceUV = vec2(dir.x, -dir.z); // FIX: Corrected Left-Hand inversion\n"
    "        }\n"
    "        else\n"
    "        {\n"
    "            faceIndex = 3.0; // -Y\n"
    "            faceUV = vec2(dir.x, dir.z);  // FIX: Corrected Left-Hand inversion\n"
    "        }\n"
    "    }\n"
    "    else\n"
    "    {\n"
    "        maxAxis = absDir.z;\n"
    "        if (dir.z > 0.0)\n"
    "        {\n"
    "            faceIndex = 4.0; // +Z\n"
    "            faceUV = vec2(dir.x, dir.y);\n"
    "        }\n"
    "        else\n"
    "        {\n"
    "            faceIndex = 5.0; // -Z\n"
    "            faceUV = vec2(-dir.x, dir.y);\n"
    "        }\n"
    "    }\n"
    "    \n"
    "    faceUV = faceUV / maxAxis;\n"
    "    faceUV = faceUV * 0.5 + 0.5;\n"
    "    \n"
    "    faceUV = clamp(faceUV, 0.002, 0.998);\n"
    "    \n"
    "    float col = mod(faceIndex, 3.0);\n"
    "    float row = 1.0 - floor(faceIndex / 3.0);\n"
    "    \n"
    "    vec2 atlasUV;\n"
    "    atlasUV.x = (col + faceUV.x) / 3.0;\n"
    "    atlasUV.y = (row + faceUV.y) / 2.0;\n"
    "    \n"
    "    return atlasUV;\n"
    "}\n"
    "\n"
    "float SampleDistance(vec3 vec)\n"
    "{\n"
    "    vec2 atlasUV = DirectionToAtlasUV(vec);\n"
    "    vec4 rgba = texture2D(shadowAtlas, atlasUV);\n"
    "    return DecodeFromRGBA8(rgba);\n"
    "}\n"
    "\n"
    "void main()\n"
    "{\n"
    "    vec3 N = normalize(vNormal);\n"
    "    vec3 L = mLightPos - vWorldPos;\n"
    "    float distToLight = length(L);\n"
    "    L = normalize(L);\n"
    "    \n"
    "    float NdotL = max(0.0, dot(N, L));\n"
    "    \n"
    "    if (NdotL <= 0.0) {\n"
    "        gl_FragColor = vec4(mAmbientColor * gl_Color.rgb, gl_Color.a);\n"
    "        return;\n"
    "    }\n"
    "\n"
    "    // --- NEW SMOOTH ATTENUATION (radial blur from center to edge) ---\n"
    "    float t = clamp(distToLight / mLightRange, 0.0, 1.0);\n"
    "    float attenuation = (1.0 - t) * (1.0 - t);  // Quadratic falloff, exactly zero at radius\n"
    "    \n"
    "    float bias = 0.002 + 0.006 * (1.0 - NdotL);\n"
    "    float mydist = length(vVecToLight) * mLightPositionRangeW;\n"
    "    mydist -= bias;\n"
    "    \n"
    "    float shadowMapDepth = SampleDistance(vVecToLight);\n"
    "    float shadowAtten = (shadowMapDepth < mydist) ? mShadowDataR : 1.0;\n"
    "    \n"
    "    vec3 lighting = mAmbientColor + (NdotL * mLightColor * attenuation * shadowAtten);\n"
    "    gl_FragColor = vec4(lighting * gl_Color.rgb, gl_Color.a);\n"
    "}\n";

// ============================================================================
// Helpers
// ============================================================================

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 Execution
// ============================================================================

int main()
{
    IrrlichtDevice* device = createDevice(
        EDT_OPENGL,
        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();

    u32 lastFPS = 0;
    char windowTitle[256];

    ICameraSceneNode* mainCam = smgr->addCameraSceneNodeFPS(0, 100.0f, 0.1f);
    mainCam->setPosition(vector3df(0, 6, -12));
    mainCam->setTarget(vector3df(0, 0, 0));
    mainCam->setNearValue(0.1f);
    mainCam->setFarValue(100.0f);

    ICameraSceneNode* lightCam = smgr->addCameraSceneNode(0);
    lightCam->setNearValue(0.1f);
    lightCam->setFarValue(20.0f);

    IMesh* planeMesh = smgr->getGeometryCreator()->createPlaneMesh(
        dimension2d<f32>(20.0f, 20.0f), dimension2d<u32>(1, 1));

    ISceneNode* ground = smgr->addMeshSceneNode(planeMesh);
    ground->setPosition(vector3df(0, -2.0f, 0));
    ground->getMaterial(0).DiffuseColor = SColor(255, 240, 240, 240);
    ground->setMaterialFlag(EMF_LIGHTING, false);
    planeMesh->drop();

    ISceneNode* cube1 = smgr->addCubeSceneNode(2.0f);
    cube1->setPosition(vector3df(-3.0f, -1.0f, 1.0f));
    cube1->getMaterial(0).DiffuseColor = SColor(255, 230, 100, 100);
    cube1->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* cube2 = smgr->addCubeSceneNode(1.5f);
    cube2->setPosition(vector3df(3.0f, -1.2f, -1.0f));
    cube2->getMaterial(0).DiffuseColor = SColor(255, 100, 210, 100);
    cube2->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* sphere1 = smgr->addSphereSceneNode(1.0f, 32);
    sphere1->setPosition(vector3df(0.0f, -1.0f, 3.5f));
    sphere1->getMaterial(0).DiffuseColor = SColor(255, 100, 100, 230);
    sphere1->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* pillar = smgr->addCubeSceneNode(1.0f);
    pillar->setPosition(vector3df(0.0f, 0.5f, -1.0f));
    pillar->getMaterial(0).DiffuseColor = SColor(255, 200, 200, 100);
    pillar->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* lightMarker = smgr->addSphereSceneNode(0.2f, 16);
    lightMarker->setMaterialFlag(EMF_LIGHTING, false);
    lightMarker->getMaterial(0).EmissiveColor = SColor(255, 255, 255, 255);

    ISceneNode* shadowNodes[] = { ground, cube1, cube2, sphere1, pillar };
    const s32 shadowCount = 5;

    const u32 FACE_SIZE = 512;
    const u32 ATLAS_WIDTH = FACE_SIZE * 3;
    const u32 ATLAS_HEIGHT = FACE_SIZE * 2;

    ITexture* shadowAtlas = driver->addRenderTargetTexture(
        dimension2d<u32>(ATLAS_WIDTH, ATLAS_HEIGHT), "shadowAtlas", ECF_A8R8G8B8);

    if (!shadowAtlas)
    {
        device->drop();
        return 1;
    }

    for (s32 i = 0; i < shadowCount; ++i)
    {
        shadowNodes[i]->getMaterial(0).setTexture(0, shadowAtlas);
        shadowNodes[i]->getMaterial(0).setFlag(EMF_BILINEAR_FILTER, false);
        shadowNodes[i]->getMaterial(0).setFlag(EMF_TRILINEAR_FILTER, false);
        shadowNodes[i]->getMaterial(0).setFlag(EMF_ANISOTROPIC_FILTER, false);
    }

    IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
    DepthPassCallback depthCB;
    MainPassCallback mainCB;

    s32 depthMaterials[6];
    for (int f = 0; f < 6; f++)
    {
        depthMaterials[f] = gpu->addHighLevelShaderMaterial(
            DepthPassVS, "main", EVST_VS_2_0,
            DepthPassPS, "main", EPST_PS_2_0,
            &depthCB, EMT_SOLID, f);
    }

    s32 mainMat = gpu->addHighLevelShaderMaterial(
        MainPassVS, "main", EVST_VS_2_0,
        MainPassPS, "main", EPST_PS_2_0,
        &mainCB, EMT_SOLID, 0);

    bool shadersOK = true;
    for (int f = 0; f < 6; f++)
        if (depthMaterials[f] < 0) shadersOK = false;
    if (mainMat < 0) shadersOK = false;

    if (!shadersOK)
    {
        device->drop();
        return 1;
    }

    vector3df lightPos(0.0f, 4.0f, 0.0f);
    f32 lightRange = 16.0f;
    f32 time = 0.0f;

    rect<s32> faceViewports[6] = {
        rect<s32>(0, 0, FACE_SIZE, FACE_SIZE),
        rect<s32>(FACE_SIZE, 0, FACE_SIZE * 2, FACE_SIZE),
        rect<s32>(FACE_SIZE * 2, 0, FACE_SIZE * 3, FACE_SIZE),
        rect<s32>(0, FACE_SIZE, FACE_SIZE, FACE_SIZE * 2),
        rect<s32>(FACE_SIZE, FACE_SIZE, FACE_SIZE * 2, FACE_SIZE * 2),
        rect<s32>(FACE_SIZE * 2, FACE_SIZE, FACE_SIZE * 3, FACE_SIZE * 2)
    };

    vector3df targets[6] = {
        vector3df( 1, 0, 0), vector3df(-1, 0, 0),
        vector3df( 0, 1, 0), vector3df( 0,-1, 0),
        vector3df( 0, 0, 1), vector3df( 0, 0,-1)
    };

    vector3df upVectors[6] = {
        vector3df(0, 1, 0),  vector3df(0, 1, 0),
        vector3df(0, 0,-1),  vector3df(0, 0, 1),
        vector3df(0, 1, 0),  vector3df(0, 1, 0)
    };

    matrix4 lightProj;
    lightProj.buildProjectionMatrixPerspectiveFovLH(90.0f * core::DEGTORAD, 1.0f, 0.1f, lightRange);
    lightCam->setProjectionMatrix(lightProj);

    while (device->run())
    {
        time += 0.005f;

        lightPos.X = 5.0f * cosf(time * 0.7f);
        lightPos.Z = 5.0f * sinf(time * 0.7f);
        lightPos.Y = 3.0f + sinf(time * 0.5f) * 1.5f;
        lightMarker->setPosition(lightPos);

        depthCB.LightPos = lightPos;
        depthCB.LightPositionRangeW = 1.0f / lightRange;

        lightMarker->setVisible(false);

        // PASS 1: Write depth values to RenderTarget Atlas
        driver->setRenderTarget(shadowAtlas, true, true, SColor(255, 255, 255, 255));
        smgr->setActiveCamera(lightCam);

        for (int face = 0; face < 6; face++)
        {
            // FIX: Set viewport directly on the driver (not on camera)
            driver->setViewPort(faceViewports[face]);

            g_CurrentFace = face;

            lightCam->setPosition(lightPos);
            lightCam->setTarget(lightPos + targets[face]);
            lightCam->setUpVector(upVectors[face]);
            lightCam->updateAbsolutePosition();

            for (s32 i = 0; i < shadowCount; ++i)
                shadowNodes[i]->setMaterialType((E_MATERIAL_TYPE)depthMaterials[face]);

            smgr->drawAll();
        }

        // PASS 2: Main rendering to screen
        driver->setViewPort(rect<s32>(0, 0, 800, 600));
        lightMarker->setVisible(true);

        driver->setRenderTarget(0, false, false, SColor(0, 0, 0, 0));
        smgr->setActiveCamera(mainCam);
        driver->beginScene(true, true, SColor(255, 40, 40, 50));

        mainCB.LightPos = lightPos;
        mainCB.LightColor = SColorf(1.0f, 0.95f, 0.85f);
        mainCB.AmbientColor = SColorf(0.05f, 0.05f, 0.08f);
        mainCB.LightRange = lightRange;
        mainCB.ShadowDataR = 0.20f;
        mainCB.LightPositionRangeW = 1.0f / lightRange;

        for (s32 i = 0; i < shadowCount; ++i)
            shadowNodes[i]->setMaterialType((E_MATERIAL_TYPE)mainMat);

        smgr->drawAll();
        gui->drawAll();
        driver->endScene();

        u32 currentFPS = driver->getFPS();
        if (lastFPS != currentFPS)
        {
            sprintf(windowTitle, "Point Light Cubemap Shadows - Fixed - FPS: %u", currentFPS);
            setWindowCaption(device, windowTitle);
            lastFPS = currentFPS;
        }
    }

    device->drop();
    return 0;
}
Irrlicht is love, Irrlicht is life, long live to Irrlicht
Noiecity
Posts: 390
Joined: Wed Aug 23, 2023 7:22 pm
Contact:

Re: Pointlight shadowmap

Post by Noiecity »

Gouraud shading instead of per pixel light:

Code: Select all

// main.cpp - Point Light Soft Shadows with Radial Fade Gouraud Shading (OpenGL 2.0 / C++98)
#include <irrlicht.h>
#include <cmath>
#include <cstring>
#include <cstdio>

#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace gui;

static s32 g_CurrentFace = 0;

// ============================================================================
// Shader Callbacks
// ============================================================================

class DepthPassCallback : public IShaderConstantSetCallBack
{
public:
    vector3df LightPos;
    f32 LightPositionRangeW;
    f32 NormalOffset;
    f32 NearPlane;
    f32 FarPlane;

    DepthPassCallback() : LightPos(0,0,0), LightPositionRangeW(0.0f), NormalOffset(0.1f), NearPlane(0.5f), FarPlane(20.0f) {}

    virtual void OnSetConstants(IMaterialRendererServices* services, s32 userData)
    {
        IVideoDriver* driver = services->getVideoDriver();

        matrix4 world = driver->getTransform(ETS_WORLD);
        matrix4 view  = driver->getTransform(ETS_VIEW);
        matrix4 proj  = driver->getTransform(ETS_PROJECTION);

        services->setVertexShaderConstant("mWorld", world.pointer(), 16);
        services->setVertexShaderConstant("mLightView", view.pointer(), 16);
        services->setVertexShaderConstant("mLightProj", proj.pointer(), 16);
        services->setVertexShaderConstant("mLightPos", reinterpret_cast<f32*>(&LightPos), 3);
        services->setPixelShaderConstant("mLightPositionRangeW", &LightPositionRangeW, 1);
        services->setVertexShaderConstant("mNormalOffset", &NormalOffset, 1);
        services->setPixelShaderConstant("mNearPlane", &NearPlane, 1);
        services->setPixelShaderConstant("mFarPlane", &FarPlane, 1);
    }
};

class MainPassCallback : public IShaderConstantSetCallBack
{
public:
    SColorf LightColor;
    SColorf AmbientColor;
    vector3df LightPos;
    f32 LightRange;
    f32 ShadowDataR;
    f32 LightPositionRangeW;
    f32 DepthBias;
    f32 NearPlane;
    f32 FarPlane;
    f32 InnerRadius;
    f32 OuterRadius;

    MainPassCallback() : LightColor(1,1,1), AmbientColor(0,0,0), LightPos(0,0,0),
        LightRange(1.0f), ShadowDataR(0.0f), LightPositionRangeW(0.0f), DepthBias(0.06f),
        NearPlane(0.5f), FarPlane(20.0f), InnerRadius(0.15f), OuterRadius(0.85f) {}

    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("mLightPos", reinterpret_cast<f32*>(&LightPos), 3);
        services->setVertexShaderConstant("mLightColor", reinterpret_cast<f32*>(&LightColor), 3);
        services->setVertexShaderConstant("mAmbientColor", reinterpret_cast<f32*>(&AmbientColor), 3);
        services->setVertexShaderConstant("mLightRange", &LightRange, 1);

        services->setPixelShaderConstant("mShadowDataR", &ShadowDataR, 1);
        services->setPixelShaderConstant("mLightPositionRangeW", &LightPositionRangeW, 1);
        services->setPixelShaderConstant("mDepthBias", &DepthBias, 1);
        services->setPixelShaderConstant("mNearPlane", &NearPlane, 1);
        services->setPixelShaderConstant("mFarPlane", &FarPlane, 1);
        services->setPixelShaderConstant("mInnerRadius", &InnerRadius, 1);
        services->setPixelShaderConstant("mOuterRadius", &OuterRadius, 1);

        f32 texUnit = 0.0f;
        services->setPixelShaderConstant("shadowAtlas", &texUnit, 1);
    }
};

class MarkerCallback : public IShaderConstantSetCallBack
{
public:
    vector3df LightPos;
    SColorf LightColor;
    SColorf AmbientColor;
    f32 LightRange;

    MarkerCallback() : LightPos(0,0,0), LightColor(1,1,1), AmbientColor(0.1f,0.1f,0.1f), LightRange(10.0f) {}

    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("mWorldViewProj", wvp.pointer(), 16);
        services->setVertexShaderConstant("mLightPos", reinterpret_cast<f32*>(&LightPos), 3);
        services->setVertexShaderConstant("mLightColor", reinterpret_cast<f32*>(&LightColor), 3);
        services->setVertexShaderConstant("mAmbientColor", reinterpret_cast<f32*>(&AmbientColor), 3);
        services->setVertexShaderConstant("mLightRange", &LightRange, 1);
    }
};

// ============================================================================
// Shader Programs (GLSL 1.10 - OpenGL 2.0 compatible)
// ============================================================================

const char* DepthPassVS =
    "#version 110\n"
    "uniform mat4 mWorld;\n"
    "uniform mat4 mLightView;\n"
    "uniform mat4 mLightProj;\n"
    "uniform vec3 mLightPos;\n"
    "uniform float mNormalOffset;\n"
    "\n"
    "varying vec3 vVecToLight;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    vec4 worldPos = mWorld * gl_Vertex;\n"
    "    \n"
    "    // Transform normal to world space without using mat3() for GLSL 1.10\n"
    "    vec3 worldNormal;\n"
    "    worldNormal.x = dot(mWorld[0].xyz, gl_Normal);\n"
    "    worldNormal.y = dot(mWorld[1].xyz, gl_Normal);\n"
    "    worldNormal.z = dot(mWorld[2].xyz, gl_Normal);\n"
    "    worldNormal = normalize(worldNormal);\n"
    "    \n"
    "    vec3 lightDir = normalize(mLightPos - worldPos.xyz);\n"
    "    float slopeScale = 1.0 - abs(dot(worldNormal, lightDir));\n"
    "    slopeScale = slopeScale * slopeScale;\n"
    "    float offset = mNormalOffset * (1.0 + slopeScale * 4.0);\n"
    "    worldPos.xyz += worldNormal * offset;\n"
    "    \n"
    "    vVecToLight = worldPos.xyz - mLightPos;\n"
    "    gl_Position = mLightProj * mLightView * worldPos;\n"
    "}\n";

const char* DepthPassPS =
    "#version 110\n"
    "uniform float mLightPositionRangeW;\n"
    "uniform float mNearPlane;\n"
    "uniform float mFarPlane;\n"
    "varying vec3 vVecToLight;\n"
    "\n"
    "vec4 EncodeToRGBA8(float v)\n"
    "{\n"
    "    vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * v;\n"
    "    enc = fract(enc);\n"
    "    enc -= enc.yzww * vec4(1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0);\n"
    "    return enc;\n"
    "}\n"
    "\n"
    "void main()\n"
    "{\n"
    "    float distance = length(vVecToLight);\n"
    "    float normalizedDist = (distance - mNearPlane) / (mFarPlane - mNearPlane);\n"
    "    normalizedDist = clamp(normalizedDist, 0.0, 1.0);\n"
    "    gl_FragColor = EncodeToRGBA8(min(normalizedDist, 0.999));\n"
    "}\n";

// 100% Gouraud emulation in vertex. Shadows will be applied later to Diffuse.
const char* MainPassVS =
    "#version 110\n"
    "uniform mat4 mWorld;\n"
    "uniform mat4 mWorldViewProj;\n"
    "uniform vec3 mLightPos;\n"
    "uniform vec3 mLightColor;\n"
    "uniform vec3 mAmbientColor;\n"
    "uniform float mLightRange;\n"
    "\n"
    "varying vec4 vAmbientColor;\n" // Base + ambient color
    "varying vec4 vDiffuseColor;\n" // Illuminated color (will be shadowed)
    "varying vec3 vVecToLight;\n"   // Used ONLY to calculate shadow in Fragment
    "varying float vDistToLight;\n" // Used ONLY to calculate shadow in Fragment
    "\n"
    "void main()\n"
    "{\n"
    "    vec4 worldPos = mWorld * gl_Vertex;\n"
    "    \n"
    "    // Transform normal to world space in strict GLSL 1.10\n"
    "    vec3 worldNormal;\n"
    "    worldNormal.x = dot(mWorld[0].xyz, gl_Normal);\n"
    "    worldNormal.y = dot(mWorld[1].xyz, gl_Normal);\n"
    "    worldNormal.z = dot(mWorld[2].xyz, gl_Normal);\n"
    "    worldNormal = normalize(worldNormal);\n"
    "    \n"
    "    vec3 lightDir = mLightPos - worldPos.xyz;\n"
    "    float distToLight = length(lightDir);\n"
    "    \n"
    "    if (distToLight > 0.0) {\n"
    "        lightDir = lightDir / distToLight;\n"
    "    }\n"
    "    \n"
    "    // --- PURE GOURAUD CALCULATION (Everything calculated here, nothing in pixel shader) ---\n"
    "    float NdotL = max(0.0, dot(worldNormal, lightDir));\n"
    "    float attenuation = max(0.0, 1.0 - (distToLight / mLightRange));\n"
    "    \n"
    "    vec3 ambient = mAmbientColor * gl_Color.rgb;\n"
    "    vec3 diffuse = mLightColor * NdotL * attenuation * gl_Color.rgb;\n"
    "    \n"
    "    vAmbientColor = vec4(ambient, gl_Color.a);\n"
    "    vDiffuseColor = vec4(diffuse, 0.0);\n"
    "    \n"
    "    // Vectors needed for Shadow Map lookup later\n"
    "    vVecToLight = worldPos.xyz - mLightPos;\n"
    "    vDistToLight = distToLight;\n"
    "    \n"
    "    gl_Position = mWorldViewProj * gl_Vertex;\n"
    "}\n";

const char* MainPassPS =
    "#version 110\n"
    "\n"
    "uniform float mShadowDataR;\n"
    "uniform float mDepthBias;\n"
    "uniform float mNearPlane;\n"
    "uniform float mFarPlane;\n"
    "uniform float mInnerRadius;\n"
    "uniform float mOuterRadius;\n"
    "uniform sampler2D shadowAtlas;\n"
    "\n"
    "varying vec4 vAmbientColor;\n"
    "varying vec4 vDiffuseColor;\n"
    "varying vec3 vVecToLight;\n"
    "varying float vDistToLight;\n"
    "\n"
    "float DecodeFromRGBA8(vec4 rgba)\n"
    "{\n"
    "    return dot(rgba, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/16581375.0));\n"
    "}\n"
    "\n"
    "vec2 DirectionToAtlasUV(vec3 dir)\n"
    "{\n"
    "    vec3 absDir = abs(dir);\n"
    "    float maxAxis;\n"
    "    float faceIndex;\n"
    "    vec2 faceUV;\n"
    "    \n"
    "    if (absDir.x >= absDir.y && absDir.x >= absDir.z)\n"
    "    {\n"
    "        maxAxis = absDir.x;\n"
    "        if (dir.x > 0.0) { faceIndex = 0.0; faceUV = vec2(-dir.z, dir.y); }\n"
    "        else             { faceIndex = 1.0; faceUV = vec2(dir.z, dir.y); }\n"
    "    }\n"
    "    else if (absDir.y >= absDir.x && absDir.y >= absDir.z)\n"
    "    {\n"
    "        maxAxis = absDir.y;\n"
    "        if (dir.y > 0.0) { faceIndex = 2.0; faceUV = vec2(dir.x, -dir.z); }\n"
    "        else             { faceIndex = 3.0; faceUV = vec2(dir.x, dir.z); }\n"
    "    }\n"
    "    else\n"
    "    {\n"
    "        maxAxis = absDir.z;\n"
    "        if (dir.z > 0.0) { faceIndex = 4.0; faceUV = vec2(dir.x, dir.y); }\n"
    "        else             { faceIndex = 5.0; faceUV = vec2(-dir.x, dir.y); }\n"
    "    }\n"
    "    \n"
    "    faceUV = faceUV / maxAxis;\n"
    "    faceUV = faceUV * 0.5 + 0.5;\n"
    "    faceUV = clamp(faceUV, 0.01, 0.99);\n"
    "    \n"
    "    float col = mod(faceIndex, 3.0);\n"
    "    float row = 1.0 - floor(faceIndex / 3.0);\n"
    "    \n"
    "    vec2 atlasUV;\n"
    "    atlasUV.x = (col + faceUV.x) / 3.0;\n"
    "    atlasUV.y = (row + faceUV.y) / 2.0;\n"
    "    \n"
    "    return atlasUV;\n"
    "}\n"
    "\n"
    "void main()\n"
    "{\n"
    "    // This shader DOES NOT CALCULATE ANY LIGHTING.\n"
    "    // It only generates the shadow and applies it to the Gouraud lighting that already comes from the Vertex.\n"
    "    \n"
    "    float distance = vDistToLight;\n"
    "    float shadowFactor = 1.0;\n"
    "    \n"
    "    if (distance > mNearPlane && distance < mFarPlane)\n"
    "    {\n"
    "        float mydist = (distance - mNearPlane) / (mFarPlane - mNearPlane);\n"
    "        mydist = clamp(mydist, 0.0, 1.0);\n"
    "        mydist -= mDepthBias;\n"
    "        \n"
    "        vec2 atlasUV = DirectionToAtlasUV(vVecToLight);\n"
    "        vec4 rgba = texture2D(shadowAtlas, atlasUV);\n"
    "        float shadowMapDepth = DecodeFromRGBA8(rgba);\n"
    "        \n"
    "        float distFactor = distance * 0.1;\n"
    "        float softRange = 0.03 + distFactor * 0.02;\n"
    "        float shadowDiff = shadowMapDepth - mydist;\n"
    "        shadowFactor = smoothstep(-softRange, softRange, shadowDiff);\n"
    "    }\n"
    "    else if (distance >= mFarPlane)\n"
    "    {\n"
    "        shadowFactor = mShadowDataR;\n"
    "    }\n"
    "    \n"
    "    float radialDistance = distance / mFarPlane;\n"
    "    radialDistance = clamp(radialDistance, 0.0, 1.0);\n"
    "    \n"
    "    float radialFade;\n"
    "    if (radialDistance <= mInnerRadius)\n"
    "    {\n"
    "        radialFade = 1.0;\n"
    "    }\n"
    "    else if (radialDistance >= mOuterRadius)\n"
    "    {\n"
    "        radialFade = shadowFactor;\n"
    "    }\n"
    "    else\n"
    "    {\n"
    "        float t = (radialDistance - mInnerRadius) / (mOuterRadius - mInnerRadius);\n"
    "        t = smoothstep(0.0, 1.0, t);\n"
    "        radialFade = mix(1.0, shadowFactor, t);\n"
    "    }\n"
    "    \n"
    "    // Shadow ONLY darkens the diffuse light (D3D9 Gouraud Shader style)\n"
    "    // Ambient light remains untouched.\n"
    "    vec3 finalColor = vAmbientColor.rgb + (vDiffuseColor.rgb * radialFade);\n"
    "    \n"
    "    gl_FragColor = vec4(finalColor, vAmbientColor.a);\n"
    "}\n";

const char* MarkerVS =
    "#version 110\n"
    "uniform mat4 mWorldViewProj;\n"
    "uniform vec3 mLightPos;\n"
    "uniform vec3 mLightColor;\n"
    "uniform vec3 mAmbientColor;\n"
    "uniform float mLightRange;\n"
    "\n"
    "varying vec4 vColor;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    vec4 worldPos = gl_Vertex;\n"
    "    \n"
    "    vec3 lightDir = mLightPos - worldPos.xyz;\n"
    "    float distToLight = length(lightDir);\n"
    "    \n"
    "    float attenuation = 1.0;\n"
    "    if (distToLight > 0.001)\n"
    "    {\n"
    "        attenuation = max(0.0, 1.0 - (distToLight / mLightRange));\n"
    "    }\n"
    "    \n"
    "    vec3 lighting = mAmbientColor + mLightColor * attenuation;\n"
    "    vColor = vec4(lighting * gl_Color.rgb, gl_Color.a);\n"
    "    \n"
    "    gl_Position = mWorldViewProj * gl_Vertex;\n"
    "}\n";

const char* MarkerPS =
    "#version 110\n"
    "varying vec4 vColor;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    gl_FragColor = vColor;\n"
    "}\n";

// ============================================================================
// Helpers
// ============================================================================

void setWindowCaption(IrrlichtDevice* device, const char* text)
{
    wchar_t wtext[256];
    size_t len = strlen(text);
    size_t i;
    for (i = 0; i < len && i < 255; ++i)
        wtext[i] = (wchar_t)text[i];
    wtext[i] = 0;
    device->setWindowCaption(wtext);
}

// ============================================================================
// Main Execution
// ============================================================================

int main()
{
    IrrlichtDevice* device = createDevice(
        EDT_OPENGL,
        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();

    u32 lastFPS = 0;
    char windowTitle[256];

    ICameraSceneNode* mainCam = smgr->addCameraSceneNodeFPS(0, 100.0f, 0.1f);
    mainCam->setPosition(vector3df(0, 6, -12));
    mainCam->setTarget(vector3df(0, 0, 0));
    mainCam->setNearValue(0.1f);
    mainCam->setFarValue(100.0f);
    mainCam->setFOV(54.0f*0.0174f);

    ICameraSceneNode* lightCam = smgr->addCameraSceneNode(0);
    lightCam->setNearValue(0.3f);
    lightCam->setFarValue(20.0f);

    IMesh* planeMesh = smgr->getGeometryCreator()->createPlaneMesh(
        dimension2d<f32>(4.0f, 4.0f), dimension2d<u32>(4, 4));

    ISceneNode* ground = smgr->addMeshSceneNode(planeMesh);
    ground->setPosition(vector3df(0, -2.0f, 0));
    ground->getMaterial(0).DiffuseColor = SColor(255, 240, 240, 240);
    ground->setMaterialFlag(EMF_LIGHTING, false);
    planeMesh->drop();

    ISceneNode* cube1 = smgr->addCubeSceneNode(2.0f);
    cube1->setPosition(vector3df(-3.0f, -1.0f, 1.0f));
    cube1->getMaterial(0).DiffuseColor = SColor(255, 230, 100, 100);
    cube1->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* cube2 = smgr->addCubeSceneNode(1.5f);
    cube2->setPosition(vector3df(3.0f, -1.2f, -1.0f));
    cube2->getMaterial(0).DiffuseColor = SColor(255, 100, 210, 100);
    cube2->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* sphere1 = smgr->addSphereSceneNode(1.0f, 32);
    sphere1->setPosition(vector3df(0.0f, -1.0f, 3.5f));
    sphere1->getMaterial(0).DiffuseColor = SColor(255, 100, 100, 230);
    sphere1->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* pillar = smgr->addCubeSceneNode(1.0f);
    pillar->setPosition(vector3df(0.0f, 0.5f, -1.0f));
    pillar->getMaterial(0).DiffuseColor = SColor(255, 200, 200, 100);
    pillar->setMaterialFlag(EMF_LIGHTING, false);

    ISceneNode* lightMarker = smgr->addSphereSceneNode(0.3f, 16);
    lightMarker->setMaterialFlag(EMF_LIGHTING, false);
    lightMarker->getMaterial(0).EmissiveColor = SColor(255, 255, 255, 0);
    lightMarker->getMaterial(0).DiffuseColor = SColor(255, 255, 255, 100);

    ISceneNode* shadowNodes[] = { ground, cube1, cube2, sphere1, pillar };
    const s32 shadowCount = 5;

    const u32 FACE_SIZE = 256;
    const u32 ATLAS_WIDTH = FACE_SIZE * 3;
    const u32 ATLAS_HEIGHT = FACE_SIZE * 2;

    ITexture* shadowAtlas = driver->addRenderTargetTexture(
        dimension2d<u32>(ATLAS_WIDTH, ATLAS_HEIGHT), "shadowAtlas", ECF_A8R8G8B8);

    if (!shadowAtlas)
    {
        device->drop();
        return 1;
    }

    for (s32 i = 0; i < shadowCount; ++i)
    {
        shadowNodes[i]->getMaterial(0).setTexture(0, shadowAtlas);
        shadowNodes[i]->getMaterial(0).setFlag(EMF_BILINEAR_FILTER, true);
        shadowNodes[i]->getMaterial(0).setFlag(EMF_TRILINEAR_FILTER, false);
        shadowNodes[i]->getMaterial(0).setFlag(EMF_ANISOTROPIC_FILTER, false);
    }

    IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
    DepthPassCallback depthCB;
    MainPassCallback mainCB;
    MarkerCallback markerCB;

    s32 depthMaterials[6];
    for (int f = 0; f < 6; f++)
    {
        depthMaterials[f] = gpu->addHighLevelShaderMaterial(
            DepthPassVS, "main", EVST_VS_2_0,
            DepthPassPS, "main", EPST_PS_2_0,
            &depthCB, EMT_SOLID, f);
    }

    s32 mainMat = gpu->addHighLevelShaderMaterial(
        MainPassVS, "main", EVST_VS_2_0,
        MainPassPS, "main", EPST_PS_2_0,
        &mainCB, EMT_SOLID, 0);

    s32 markerMat = gpu->addHighLevelShaderMaterial(
        MarkerVS, "main", EVST_VS_2_0,
        MarkerPS, "main", EPST_PS_2_0,
        &markerCB, EMT_SOLID, 0);

    bool shadersOK = true;
    for (int f = 0; f < 6; f++)
        if (depthMaterials[f] < 0) shadersOK = false;
    if (mainMat < 0) shadersOK = false;
    if (markerMat < 0) shadersOK = false;

    if (!shadersOK)
    {
        device->drop();
        return 1;
    }

    vector3df lightPos(0.0f, 4.0f, 0.0f);
    f32 lightRange = 16.0f;
    f32 time = 0.0f;

    rect<s32> faceViewports[6] = {
        rect<s32>(0, 0, FACE_SIZE, FACE_SIZE),
        rect<s32>(FACE_SIZE, 0, FACE_SIZE * 2, FACE_SIZE),
        rect<s32>(FACE_SIZE * 2, 0, FACE_SIZE * 3, FACE_SIZE),
        rect<s32>(0, FACE_SIZE, FACE_SIZE, FACE_SIZE * 2),
        rect<s32>(FACE_SIZE, FACE_SIZE, FACE_SIZE * 2, FACE_SIZE * 2),
        rect<s32>(FACE_SIZE * 2, FACE_SIZE, FACE_SIZE * 3, FACE_SIZE * 2)
    };

    vector3df targets[6] = {
        vector3df( 1, 0, 0), vector3df(-1, 0, 0),
        vector3df( 0, 1, 0), vector3df( 0,-1, 0),
        vector3df( 0, 0, 1), vector3df( 0, 0,-1)
    };

    vector3df upVectors[6] = {
        vector3df(0, 1, 0),  vector3df(0, 1, 0),
        vector3df(0, 0,-1),  vector3df(0, 0, 1),
        vector3df(0, 1, 0),  vector3df(0, 1, 0)
    };

    matrix4 lightProj;
    lightProj.buildProjectionMatrixPerspectiveFovLH(90.0f * core::DEGTORAD, 1.0f, 0.3f, lightRange);
    lightCam->setProjectionMatrix(lightProj);

    while (device->run())
    {
        time += 0.005f;

        lightPos.X = 5.0f * cosf(time * 0.7f);
        lightPos.Z = 5.0f * sinf(time * 0.7f);
        lightPos.Y = 3.0f + sinf(time * 0.5f) * 1.5f;

        lightMarker->setPosition(lightPos);
        lightMarker->updateAbsolutePosition();

        depthCB.LightPos = lightPos;
        depthCB.LightPositionRangeW = 1.0f / lightRange;
        depthCB.NormalOffset = 0.1f;
        depthCB.NearPlane = 0.3f;
        depthCB.FarPlane = lightRange;

        lightMarker->setVisible(false);

        driver->setRenderTarget(shadowAtlas, true, true, SColor(255, 255, 255, 255));
        smgr->setActiveCamera(lightCam);

        for (int face = 0; face < 6; face++)
        {
            driver->setViewPort(faceViewports[face]);
            g_CurrentFace = face;

            lightCam->setPosition(lightPos);
            lightCam->setTarget(lightPos + targets[face]);
            lightCam->setUpVector(upVectors[face]);
            lightCam->updateAbsolutePosition();

            for (s32 i = 0; i < shadowCount; ++i)
                shadowNodes[i]->setMaterialType((E_MATERIAL_TYPE)depthMaterials[face]);

            smgr->drawAll();
        }

        driver->setViewPort(rect<s32>(0, 0, 800, 600));

        driver->setRenderTarget(0, false, false, SColor(0, 0, 0, 0));
        smgr->setActiveCamera(mainCam);
        driver->beginScene(true, true, SColor(255, 40, 40, 50));

        mainCB.LightPos = lightPos;
        mainCB.LightColor = SColorf(1.5f, 1.4f, 1.3f);
        mainCB.AmbientColor = SColorf(0.15f, 0.15f, 0.20f);
        mainCB.LightRange = lightRange;
        mainCB.ShadowDataR = 0.35f;
        mainCB.LightPositionRangeW = 1.0f / lightRange;
        mainCB.DepthBias = 0.06f;
        mainCB.NearPlane = 0.3f;
        mainCB.FarPlane = lightRange;

        mainCB.InnerRadius = 0.15f;
        mainCB.OuterRadius = 0.75f;

        for (s32 i = 0; i < shadowCount; ++i)
            shadowNodes[i]->setMaterialType((E_MATERIAL_TYPE)mainMat);

        smgr->drawAll();

        markerCB.LightPos = lightPos;
        markerCB.LightColor = SColorf(1.5f, 1.4f, 0.5f);
        markerCB.AmbientColor = SColorf(0.3f, 0.3f, 0.1f);
        markerCB.LightRange = lightRange;

        lightMarker->setVisible(true);
        lightMarker->setMaterialType((E_MATERIAL_TYPE)markerMat);
        lightMarker->render();

        gui->drawAll();
        driver->endScene();

        u32 currentFPS = driver->getFPS();
        if (lastFPS != currentFPS)
        {
            sprintf(windowTitle, "Point Light Gouraud Shadows - Radial Fade - FPS: %u", currentFPS);
            setWindowCaption(device, windowTitle);
            lastFPS = currentFPS;
        }
    }

    device->drop();
    return 1;
}
Irrlicht is love, Irrlicht is life, long live to Irrlicht
Post Reply