
tested on irrlicht 1.9.0 svn trunk
Code: Select all
#include <irrlicht.h>
#include <cstdio>
#include <cmath>
#include <vector>
#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
struct VertexLight
{
vector3df color;
int count;
VertexLight() : color(0, 0, 0), count(0) {}
};
SColorf computeLighting(const vector3df& point, const vector3df& normal,
const vector3df& lightPos, f32 lightRadius,
const SColorf& lightColor, f32 ambientFactor)
{
vector3df toLight = lightPos - point;
f32 dist = toLight.getLength();
if (dist > lightRadius)
return SColorf(0, 0, 0);
f32 attenuation = 1.0f - (dist / lightRadius);
attenuation = core::clamp(attenuation, 0.0f, 1.0f);
toLight.normalize();
f32 diff = normal.dotProduct(toLight);
if (diff < 0.0f)
diff = 0.0f;
SColorf result;
result.r = lightColor.r * diff * attenuation;
result.g = lightColor.g * diff * attenuation;
result.b = lightColor.b * diff * attenuation;
result.a = 1.0f;
result.r += lightColor.r * ambientFactor;
result.g += lightColor.g * ambientFactor;
result.b += lightColor.b * ambientFactor;
return result;
}
// Helper to add a subdivided face to a mesh
static void addSubdividedFace(SMesh* mesh,
const vector3df& origin,
const vector3df& u,
const vector3df& v,
u32 segU,
u32 segV)
{
SMeshBuffer* buf = new SMeshBuffer();
const u32 vertexCount = (segU + 1) * (segV + 1);
const u32 indexCount = segU * segV * 6;
buf->Vertices.reallocate(vertexCount);
buf->Vertices.set_used(vertexCount);
buf->Indices.reallocate(indexCount);
buf->Indices.set_used(indexCount);
vector3df normal = u.crossProduct(v);
normal.normalize();
u32 vi = 0;
for (u32 y = 0; y <= segV; ++y)
{
f32 fy = (f32)y / (f32)segV;
for (u32 x = 0; x <= segU; ++x)
{
f32 fx = (f32)x / (f32)segU;
S3DVertex& vert = buf->Vertices[vi++];
vert.Pos = origin + (u * fx) + (v * fy);
vert.Normal = normal;
vert.Color = SColor(255, 255, 255, 255);
vert.TCoords.set(fx, fy);
}
}
u32 ii = 0;
for (u32 y = 0; y < segV; ++y)
{
for (u32 x = 0; x < segU; ++x)
{
u32 i0 = y * (segU + 1) + x;
u32 i1 = i0 + 1;
u32 i2 = i0 + (segU + 1);
u32 i3 = i2 + 1;
buf->Indices[ii++] = i0;
buf->Indices[ii++] = i1;
buf->Indices[ii++] = i2;
buf->Indices[ii++] = i1;
buf->Indices[ii++] = i3;
buf->Indices[ii++] = i2;
}
}
buf->recalculateBoundingBox();
mesh->addMeshBuffer(buf);
buf->drop();
}
static IMesh* createSubdividedPlaneMesh(f32 width, f32 depth, u32 segX, u32 segZ)
{
SMesh* mesh = new SMesh();
const f32 hx = width * 0.5f;
const f32 hz = depth * 0.5f;
addSubdividedFace(
mesh,
vector3df(-hx, 0.0f, hz),
vector3df(width, 0.0f, 0.0f),
vector3df(0.0f, 0.0f, -depth),
segX, segZ
);
mesh->recalculateBoundingBox();
return mesh;
}
static IMesh* createSubdividedCubeMesh(const vector3df& size, u32 seg)
{
SMesh* mesh = new SMesh();
const f32 hx = size.X * 0.5f;
const f32 hy = size.Y * 0.5f;
const f32 hz = size.Z * 0.5f;
// Front (z positive)
addSubdividedFace(mesh, vector3df(-hx, -hy, hz), vector3df(size.X, 0.0f, 0.0f), vector3df(0.0f, size.Y, 0.0f), seg, seg);
// Back (z negative)
addSubdividedFace(mesh, vector3df( hx, -hy, -hz), vector3df(-size.X, 0.0f, 0.0f), vector3df(0.0f, size.Y, 0.0f), seg, seg);
// Right (x positive)
addSubdividedFace(mesh, vector3df( hx, -hy, hz), vector3df(0.0f, 0.0f, -size.Z), vector3df(0.0f, size.Y, 0.0f), seg, seg);
// Left (x negative)
addSubdividedFace(mesh, vector3df(-hx, -hy, -hz), vector3df(0.0f, 0.0f, size.Z), vector3df(0.0f, size.Y, 0.0f), seg, seg);
// Top (y positive)
addSubdividedFace(mesh, vector3df(-hx, hy, hz), vector3df(size.X, 0.0f, 0.0f), vector3df(0.0f, 0.0f, -size.Z), seg, seg);
// Bottom (y negative)
addSubdividedFace(mesh, vector3df(-hx, -hy, -hz), vector3df(size.X, 0.0f, 0.0f), vector3df(0.0f, 0.0f, size.Z), seg, seg);
mesh->recalculateBoundingBox();
return mesh;
}
int main()
{
IrrlichtDevice* device = createDevice(EDT_OPENGL, dimension2d<u32>(800, 600), 16, false, false, false, 0);
if (!device) return 1;
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
device->setWindowCaption(L"Point Light Raytracing - Shadows");
ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.0f, 0.2f);
camera->setPosition(vector3df(0, 5, -15));
camera->setTarget(vector3df(0, 2, 0));
smgr->setActiveCamera(camera);
// Create subdivided meshes
IMesh* groundMesh = createSubdividedPlaneMesh(40.0f, 40.0f, 80, 80);
IMeshSceneNode* groundNode = smgr->addMeshSceneNode(groundMesh);
groundNode->setPosition(vector3df(0, -0.5f, 0)); // plane at y = -0.5
groundNode->setMaterialFlag(EMF_LIGHTING, false);
groundNode->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
groundNode->setID(1 << 0);
printf("Ground triangles: %d\n", 80 * 80 * 2);
groundMesh->drop();
IMesh* cubeMesh = createSubdividedCubeMesh(vector3df(2.0f, 2.0f, 2.0f), 12);
IMeshSceneNode* cubeNode = smgr->addMeshSceneNode(cubeMesh);
cubeNode->setPosition(vector3df(2, 1, 0)); // bottom at y=0
cubeNode->setMaterialFlag(EMF_LIGHTING, false);
cubeNode->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
cubeNode->setID(1 << 1);
printf("Cube triangles: %d\n", 6 * 12 * 12 * 2);
cubeMesh->drop();
IMesh* sphere1Mesh = smgr->getGeometryCreator()->createSphereMesh(1.5f, 24, 24);
IMeshSceneNode* sphere1Node = smgr->addMeshSceneNode(sphere1Mesh);
sphere1Node->setPosition(vector3df(20, 1.5f, 0)); // bottom at y=0
sphere1Node->setMaterialFlag(EMF_LIGHTING, false);
sphere1Node->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
sphere1Node->setID(1 << 2);
printf("Sphere1 triangles: %d\n", 2 * 24 * 24);
sphere1Mesh->drop();
IMesh* sphere2Mesh = smgr->getGeometryCreator()->createSphereMesh(2.0f, 24, 24);
IMeshSceneNode* sphere2Node = smgr->addMeshSceneNode(sphere2Mesh);
sphere2Node->setPosition(vector3df(80, 2.0f, 0)); // bottom at y=0
sphere2Node->setMaterialFlag(EMF_LIGHTING, false);
sphere2Node->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
sphere2Node->setID(1 << 3);
printf("Sphere2 triangles: %d\n", 2 * 24 * 24);
sphere2Mesh->drop();
// Create triangle selectors for each object (required for accurate ray tests)
ITriangleSelector* groundSel = smgr->createTriangleSelector(groundMesh, groundNode);
groundNode->setTriangleSelector(groundSel);
groundSel->drop();
ITriangleSelector* cubeSel = smgr->createTriangleSelector(cubeMesh, cubeNode);
cubeNode->setTriangleSelector(cubeSel);
cubeSel->drop();
ITriangleSelector* sphere1Sel = smgr->createTriangleSelector(sphere1Mesh, sphere1Node);
sphere1Node->setTriangleSelector(sphere1Sel);
sphere1Sel->drop();
ITriangleSelector* sphere2Sel = smgr->createTriangleSelector(sphere2Mesh, sphere2Node);
sphere2Node->setTriangleSelector(sphere2Sel);
sphere2Sel->drop();
// Light parameters
vector3df lightPos(0, 5, 0);
f32 lightRadius = 80.0f;
SColorf lightColor(1.0f, 0.95f, 0.9f);
f32 ambient = 0.2f;
// Light marker (hidden during ray tests)
ISceneNode* lightMarker = smgr->addSphereSceneNode(0.3f);
lightMarker->setPosition(lightPos);
lightMarker->setMaterialFlag(EMF_LIGHTING, false);
lightMarker->getMaterial(0).EmissiveColor = SColor(255, 255, 255, 0);
lightMarker->setID(0);
ISceneCollisionManager* collMan = smgr->getSceneCollisionManager();
IMeshSceneNode* nodes[4] = { groundNode, cubeNode, sphere1Node, sphere2Node };
s32 nodeIDs[4] = { 1 << 0, 1 << 1, 1 << 2, 1 << 3 };
u32 then = device->getTimer()->getTime();
f32 angle = 0.0f;
while (device->run())
{
u32 now = device->getTimer()->getTime();
f32 delta = (now - then) / 1000.0f;
then = now;
angle += delta * 0.5f;
lightPos.X = 4.0f * sinf(angle);
lightPos.Z = 4.0f * cosf(angle);
lightPos.Y = 5.0f + 1.5f * sinf(angle * 1.7f);
lightMarker->setPosition(lightPos);
// Hide light marker during ray tests
lightMarker->setVisible(false);
// Process each object
for (u32 n = 0; n < 4; ++n)
{
IMeshSceneNode* meshNode = nodes[n];
if (!meshNode) continue;
IMesh* mesh = meshNode->getMesh();
if (!mesh) continue;
// Bitmask that excludes the current node (only other objects block light)
s32 otherIDsMask = 0;
for (u32 m = 0; m < 4; ++m) {
if (m != n) otherIDsMask |= nodeIDs[m];
}
matrix4 worldTransform = meshNode->getAbsoluteTransformation();
for (u32 mbIndex = 0; mbIndex < mesh->getMeshBufferCount(); ++mbIndex)
{
IMeshBuffer* mb = mesh->getMeshBuffer(mbIndex);
if (!mb) continue;
S3DVertex* vertices = (S3DVertex*)mb->getVertices();
u32 vertexCount = mb->getVertexCount();
u16* indices = mb->getIndices();
u32 indexCount = mb->getIndexCount();
if (!vertices || !indices) continue;
std::vector<S3DVertex> newVertices(vertices, vertices + vertexCount);
std::vector<VertexLight> accum(vertexCount);
u32 triangleCount = indexCount / 3;
for (u32 tri = 0; tri < triangleCount; ++tri)
{
u32 i0 = indices[tri * 3 + 0];
u32 i1 = indices[tri * 3 + 1];
u32 i2 = indices[tri * 3 + 2];
vector3df v0 = vertices[i0].Pos;
vector3df v1 = vertices[i1].Pos;
vector3df v2 = vertices[i2].Pos;
worldTransform.transformVect(v0);
worldTransform.transformVect(v1);
worldTransform.transformVect(v2);
vector3df center = (v0 + v1 + v2) / 3.0f;
vector3df normal = (v1 - v0).crossProduct(v2 - v0);
normal.normalize();
if (center.getDistanceFrom(lightPos) > lightRadius)
continue;
// Ray from light to triangle center
line3df ray(lightPos, center);
vector3df hitPoint;
triangle3df hitTriangle;
bool collision = collMan->getSceneNodeAndCollisionPointFromRay(
ray, hitPoint, hitTriangle, otherIDsMask, 0, false);
// If another object blocks the ray, this triangle is in shadow
if (collision)
continue;
// Transform normals to world space
vector3df n0 = vertices[i0].Normal;
vector3df n1 = vertices[i1].Normal;
vector3df n2 = vertices[i2].Normal;
matrix4 normalMat = worldTransform;
normalMat.setTranslation(vector3df(0,0,0));
normalMat.transformVect(n0);
normalMat.transformVect(n1);
normalMat.transformVect(n2);
n0.normalize(); n1.normalize(); n2.normalize();
SColorf c0 = computeLighting(v0, n0, lightPos, lightRadius, lightColor, ambient);
SColorf c1 = computeLighting(v1, n1, lightPos, lightRadius, lightColor, ambient);
SColorf c2 = computeLighting(v2, n2, lightPos, lightRadius, lightColor, ambient);
accum[i0].color += vector3df(c0.r, c0.g, c0.b); accum[i0].count++;
accum[i1].color += vector3df(c1.r, c1.g, c1.b); accum[i1].count++;
accum[i2].color += vector3df(c2.r, c2.g, c2.b); accum[i2].count++;
}
// Apply vertex colors
for (u32 i = 0; i < vertexCount; ++i)
{
if (accum[i].count > 0)
{
vector3df avg = accum[i].color / (f32)accum[i].count;
avg.X = core::clamp(avg.X, 0.0f, 1.0f);
avg.Y = core::clamp(avg.Y, 0.0f, 1.0f);
avg.Z = core::clamp(avg.Z, 0.0f, 1.0f);
SColor col(255, (u32)(avg.X*255), (u32)(avg.Y*255), (u32)(avg.Z*255));
newVertices[i].Color = col;
}
else
{
SColorf ambColF;
ambColF.r = lightColor.r * ambient;
ambColF.g = lightColor.g * ambient;
ambColF.b = lightColor.b * ambient;
ambColF.a = 1.0f;
newVertices[i].Color = ambColF.toSColor();
}
}
for (u32 i = 0; i < vertexCount; ++i)
vertices[i] = newVertices[i];
mb->setDirty();
}
}
lightMarker->setVisible(true);
driver->beginScene(true, true, SColor(255, 50, 50, 80));
smgr->drawAll();
driver->endScene();
}
device->drop();
return 0;
}To speed up the calculation, I recommend compiling Irrlicht with GCC using -ftree-vectorize and -msse4.2 (or you can use -msse2, which was more common in 2003–2005), so that you can vectorize multiple instructions with 32-bit floats into 128-bit instructions.
/*Burningsvideo would run almost twice as fast if it used Q8.8 (16-bit integers), and even faster if it utilized a tile-based system and multithreading to take advantage of the L1 cache...*/
The ray-tracing system depends on the amount of geometry present; the more geometry, the higher the shadow quality.
The floor has about 12,000 triangles, the cube about 1,700 triangles, and the spheres about 1,100 triangles.
I've tested it on Linux, and it seems to work, though it might need further testing.
Personal note: It's not worth it; it's better to stick with shadow volumes