
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;
}