mirror of
https://github.com/ZDoom/ZDRay.git
synced 2024-11-12 23:54:27 +00:00
- add indirect light pass
This commit is contained in:
parent
ef3257e062
commit
63715b855d
7 changed files with 216 additions and 69 deletions
|
@ -309,8 +309,11 @@ struct LevelTraceHit
|
|||
{
|
||||
kexVec3 start;
|
||||
kexVec3 end;
|
||||
surface_t *hitSurface;
|
||||
float fraction;
|
||||
|
||||
surface_t *hitSurface;
|
||||
int indices[3];
|
||||
float b, c;
|
||||
};
|
||||
|
||||
struct FLevel
|
||||
|
@ -357,6 +360,7 @@ struct FLevel
|
|||
// Dlight helpers
|
||||
|
||||
TArray<kexVec3> MeshVertices;
|
||||
TArray<int> MeshUVIndex;
|
||||
TArray<unsigned int> MeshElements;
|
||||
TArray<int> MeshSurfaces;
|
||||
std::unique_ptr<TriangleMeshShape> CollisionMesh;
|
||||
|
|
|
@ -358,6 +358,24 @@ LevelTraceHit FLevel::Trace(const kexVec3 &startVec, const kexVec3 &endVec)
|
|||
trace.start = startVec;
|
||||
trace.end = endVec;
|
||||
trace.fraction = hit.fraction;
|
||||
trace.hitSurface = (trace.fraction < 1.0f) ? surfaces[hit.surface].get() : nullptr;
|
||||
if (trace.fraction < 1.0f)
|
||||
{
|
||||
int elementIdx = hit.triangle * 3;
|
||||
trace.hitSurface = surfaces[MeshSurfaces[hit.triangle]].get();
|
||||
trace.indices[0] = MeshUVIndex[MeshElements[elementIdx]];
|
||||
trace.indices[1] = MeshUVIndex[MeshElements[elementIdx + 1]];
|
||||
trace.indices[2] = MeshUVIndex[MeshElements[elementIdx + 2]];
|
||||
trace.b = hit.b;
|
||||
trace.c = hit.c;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace.hitSurface = nullptr;
|
||||
trace.indices[0] = 0;
|
||||
trace.indices[1] = 0;
|
||||
trace.indices[2] = 0;
|
||||
trace.b = 0.0f;
|
||||
trace.c = 0.0f;
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
TriangleMeshShape::TriangleMeshShape(const kexVec3 *vertices, int num_vertices, const unsigned int *elements, int num_elements, const int *surfaces)
|
||||
: vertices(vertices), num_vertices(num_vertices), elements(elements), num_elements(num_elements), surfaces(surfaces)
|
||||
TriangleMeshShape::TriangleMeshShape(const kexVec3 *vertices, int num_vertices, const unsigned int *elements, int num_elements)
|
||||
: vertices(vertices), num_vertices(num_vertices), elements(elements), num_elements(num_elements)
|
||||
{
|
||||
int num_triangles = num_elements / 3;
|
||||
if (num_triangles <= 0)
|
||||
|
@ -94,8 +94,6 @@ TraceHit TriangleMeshShape::find_first_hit(TriangleMeshShape *shape, const kexVe
|
|||
}
|
||||
}
|
||||
|
||||
if (hit.surface != -1)
|
||||
hit.surface = shape->surfaces[hit.surface / 3];
|
||||
return hit;
|
||||
}
|
||||
|
||||
|
@ -193,7 +191,8 @@ bool TriangleMeshShape::find_any_hit(TriangleMeshShape *shape, const RayBBox &ra
|
|||
{
|
||||
if (shape->is_leaf(a))
|
||||
{
|
||||
return intersect_triangle_ray(shape, ray, a) < 1.0f;
|
||||
float baryB, baryC;
|
||||
return intersect_triangle_ray(shape, ray, a, baryB, baryC) < 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -212,11 +211,14 @@ void TriangleMeshShape::find_first_hit(TriangleMeshShape *shape, const RayBBox &
|
|||
{
|
||||
if (shape->is_leaf(a))
|
||||
{
|
||||
float t = intersect_triangle_ray(shape, ray, a);
|
||||
float baryB, baryC;
|
||||
float t = intersect_triangle_ray(shape, ray, a, baryB, baryC);
|
||||
if (t < hit->fraction)
|
||||
{
|
||||
hit->fraction = t;
|
||||
hit->surface = shape->nodes[a].element_index;
|
||||
hit->triangle = shape->nodes[a].element_index / 3;
|
||||
hit->b = baryB;
|
||||
hit->c = baryC;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -232,7 +234,7 @@ bool TriangleMeshShape::overlap_bv_ray(TriangleMeshShape *shape, const RayBBox &
|
|||
return IntersectionTest::ray_aabb(ray, shape->nodes[a].aabb) == IntersectionTest::overlap;
|
||||
}
|
||||
|
||||
float TriangleMeshShape::intersect_triangle_ray(TriangleMeshShape *shape, const RayBBox &ray, int a)
|
||||
float TriangleMeshShape::intersect_triangle_ray(TriangleMeshShape *shape, const RayBBox &ray, int a, float &barycentricB, float &barycentricC)
|
||||
{
|
||||
const int start_element = shape->nodes[a].element_index;
|
||||
|
||||
|
@ -243,49 +245,6 @@ float TriangleMeshShape::intersect_triangle_ray(TriangleMeshShape *shape, const
|
|||
shape->vertices[shape->elements[start_element + 2]]
|
||||
};
|
||||
|
||||
#if 1
|
||||
|
||||
kexVec3 e1 = p[1] - p[0];
|
||||
kexVec3 e2 = p[2] - p[0];
|
||||
|
||||
kexVec3 triangleNormal = kexVec3::Cross(e1, e2);
|
||||
float dist = kexVec3::Dot(p[0], triangleNormal);
|
||||
float distA = kexVec3::Dot(ray.start, triangleNormal) - dist;
|
||||
float distB = kexVec3::Dot(ray.end, triangleNormal) - dist;
|
||||
|
||||
if (distA * distB >= 0.0f)
|
||||
return 1.0f;
|
||||
|
||||
// Backface check
|
||||
//if (distA < 0.0f)
|
||||
// return 1.0f;
|
||||
|
||||
float projLength = distA - distB;
|
||||
float distance = (distA) / (projLength);
|
||||
if (distance > 1.0f)
|
||||
return 1.0f;
|
||||
|
||||
float edgeTolerance = triangleNormal.LengthSq() * -0.0001f;
|
||||
kexVec3 point = ray.start * (1.0f - distance) + ray.end * distance;
|
||||
|
||||
kexVec3 v0p = p[0] - point;
|
||||
kexVec3 v1p = p[1] - point;
|
||||
kexVec3 cp0 = kexVec3::Cross(v0p, v1p);
|
||||
if (kexVec3::Dot(cp0, triangleNormal) < edgeTolerance)
|
||||
return 1.0f;
|
||||
|
||||
kexVec3 v2p = p[2] - point;
|
||||
kexVec3 cp1 = kexVec3::Cross(v1p, v2p);
|
||||
if (kexVec3::Dot(cp1, triangleNormal) < edgeTolerance)
|
||||
return 1.0f;
|
||||
|
||||
kexVec3 cp2 = kexVec3::Cross(v2p, v0p);
|
||||
if (kexVec3::Dot(cp2, triangleNormal) < edgeTolerance)
|
||||
return 1.0f;
|
||||
|
||||
return distance;
|
||||
|
||||
#else
|
||||
// Moeller–Trumbore ray-triangle intersection algorithm:
|
||||
|
||||
kexVec3 D = ray.end - ray.start;
|
||||
|
@ -329,12 +288,14 @@ float TriangleMeshShape::intersect_triangle_ray(TriangleMeshShape *shape, const
|
|||
return 1.0f;
|
||||
|
||||
float t = kexVec3::Dot(e2, Q) * inv_det;
|
||||
if (t > FLT_EPSILON)
|
||||
return t;
|
||||
if (t <= FLT_EPSILON)
|
||||
return 1.0f;
|
||||
|
||||
// No hit, no win
|
||||
return 1.0f;
|
||||
#endif
|
||||
// Return hit location on triangle in barycentric coordinates
|
||||
barycentricB = u;
|
||||
barycentricC = v;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
bool TriangleMeshShape::sweep_overlap_bv_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a, const kexVec3 &target)
|
||||
|
|
|
@ -39,7 +39,9 @@ public:
|
|||
struct TraceHit
|
||||
{
|
||||
float fraction = 1.0f;
|
||||
int surface = -1;
|
||||
int triangle = -1;
|
||||
float b = 0.0f;
|
||||
float c = 0.0f;
|
||||
};
|
||||
|
||||
class CollisionBBox : public kexBBox
|
||||
|
@ -80,7 +82,7 @@ public:
|
|||
class TriangleMeshShape
|
||||
{
|
||||
public:
|
||||
TriangleMeshShape(const kexVec3 *vertices, int num_vertices, const unsigned int *elements, int num_elements, const int *surfaces);
|
||||
TriangleMeshShape(const kexVec3 *vertices, int num_vertices, const unsigned int *elements, int num_elements);
|
||||
|
||||
int get_min_depth() const;
|
||||
int get_max_depth() const;
|
||||
|
@ -113,7 +115,6 @@ public:
|
|||
const int num_vertices = 0;
|
||||
const unsigned int *elements = nullptr;
|
||||
int num_elements = 0;
|
||||
const int *surfaces = nullptr;
|
||||
|
||||
std::vector<Node> nodes;
|
||||
int root = -1;
|
||||
|
@ -128,7 +129,7 @@ private:
|
|||
static void find_first_hit(TriangleMeshShape *shape1, const RayBBox &ray, int a, TraceHit *hit);
|
||||
|
||||
inline static bool overlap_bv_ray(TriangleMeshShape *shape, const RayBBox &ray, int a);
|
||||
inline static float intersect_triangle_ray(TriangleMeshShape *shape, const RayBBox &ray, int a);
|
||||
inline static float intersect_triangle_ray(TriangleMeshShape *shape, const RayBBox &ray, int a, float &barycentricB, float &barycentricC);
|
||||
|
||||
inline static bool sweep_overlap_bv_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a, const kexVec3 &target);
|
||||
inline static float sweep_intersect_triangle_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a, const kexVec3 &target);
|
||||
|
|
|
@ -464,19 +464,18 @@ void kexLightmapBuilder::TraceSurface(surface_t *surface)
|
|||
bShouldLookupTexture = true;
|
||||
}
|
||||
|
||||
kexMath::Clamp(c, 0, 1);
|
||||
colorSamples[i * LIGHTMAP_MAX_SIZE + j] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// SVE redraws the scene for lightmaps, so for optimizations,
|
||||
// tell the engine to ignore this surface if completely black
|
||||
if (bShouldLookupTexture == false)
|
||||
/*if (bShouldLookupTexture == false)
|
||||
{
|
||||
surface->lightmapNum = -1;
|
||||
return;
|
||||
}
|
||||
else
|
||||
else*/
|
||||
{
|
||||
int x = 0, y = 0;
|
||||
int width = surface->lightmapDims[0];
|
||||
|
@ -534,6 +533,126 @@ void kexLightmapBuilder::TraceSurface(surface_t *surface)
|
|||
}
|
||||
}
|
||||
|
||||
static float RadicalInverse_VdC(uint32_t bits)
|
||||
{
|
||||
bits = (bits << 16u) | (bits >> 16u);
|
||||
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
|
||||
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
||||
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
||||
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
||||
return float(bits) * 2.3283064365386963e-10f; // / 0x100000000
|
||||
}
|
||||
|
||||
static kexVec2 Hammersley(uint32_t i, uint32_t N)
|
||||
{
|
||||
return kexVec2(float(i) / float(N), RadicalInverse_VdC(i));
|
||||
}
|
||||
|
||||
static kexVec3 ImportanceSampleGGX(kexVec2 Xi, kexVec3 N, float roughness)
|
||||
{
|
||||
float a = roughness * roughness;
|
||||
|
||||
float phi = 2.0f * M_PI * Xi.x;
|
||||
float cosTheta = sqrt((1.0f - Xi.y) / (1.0f + (a*a - 1.0f) * Xi.y));
|
||||
float sinTheta = sqrt(1.0f - cosTheta * cosTheta);
|
||||
|
||||
// from spherical coordinates to cartesian coordinates
|
||||
kexVec3 H(std::cos(phi) * sinTheta, std::sin(phi) * sinTheta, cosTheta);
|
||||
|
||||
// from tangent-space vector to world-space sample vector
|
||||
kexVec3 up = std::abs(N.z) < 0.999f ? kexVec3(0.0f, 0.0f, 1.0f) : kexVec3(1.0f, 0.0f, 0.0f);
|
||||
kexVec3 tangent = kexVec3::Normalize(kexVec3::Cross(up, N));
|
||||
kexVec3 bitangent = kexVec3::Cross(N, tangent);
|
||||
|
||||
kexVec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
|
||||
return kexVec3::Normalize(sampleVec);
|
||||
}
|
||||
|
||||
void kexLightmapBuilder::TraceIndirectLight(surface_t *surface)
|
||||
{
|
||||
if (surface->lightmapNum == -1)
|
||||
return;
|
||||
|
||||
int sampleWidth = surface->lightmapDims[0];
|
||||
int sampleHeight = surface->lightmapDims[1];
|
||||
|
||||
kexVec3 normal = surface->plane.Normal();
|
||||
|
||||
uint16_t *currentTexture = &indirectoutput[surface->lightmapNum * textureWidth * textureHeight * 3];
|
||||
|
||||
for (int i = 0; i < sampleHeight; i++)
|
||||
{
|
||||
for (int j = 0; j < sampleWidth; j++)
|
||||
{
|
||||
kexVec3 pos = surface->lightmapOrigin + normal +
|
||||
(surface->lightmapSteps[0] * (float)j) +
|
||||
(surface->lightmapSteps[1] * (float)i);
|
||||
|
||||
const int SAMPLE_COUNT = 128;// 1024;
|
||||
|
||||
float totalWeight = 0.0f;
|
||||
kexVec3 c(0.0f, 0.0f, 0.0f);
|
||||
|
||||
for (int i = 0; i < SAMPLE_COUNT; i++)
|
||||
{
|
||||
kexVec2 Xi = Hammersley(i, SAMPLE_COUNT);
|
||||
kexVec3 H = ImportanceSampleGGX(Xi, normal, 1.0f);
|
||||
kexVec3 L = kexVec3::Normalize(H * (2.0f * kexVec3::Dot(normal, H)) - normal);
|
||||
|
||||
float NdotL = std::max(kexVec3::Dot(normal, L), 0.0f);
|
||||
if (NdotL > 0.0f)
|
||||
{
|
||||
tracedTexels++;
|
||||
LevelTraceHit hit = map->Trace(pos, pos + L * 1000.0f);
|
||||
if (hit.fraction < 1.0f)
|
||||
{
|
||||
kexVec3 surfaceLight;
|
||||
if (hit.hitSurface->bSky)
|
||||
{
|
||||
surfaceLight = { 0.5f, 0.5f, 0.5f };
|
||||
}
|
||||
else
|
||||
{
|
||||
float u =
|
||||
hit.hitSurface->lightmapCoords[hit.indices[0] * 2] * (1.0f - hit.b - hit.c) +
|
||||
hit.hitSurface->lightmapCoords[hit.indices[1] * 2] * hit.b +
|
||||
hit.hitSurface->lightmapCoords[hit.indices[2] * 2] * hit.c;
|
||||
|
||||
float v =
|
||||
hit.hitSurface->lightmapCoords[hit.indices[0] * 2 + 1] * (1.0f - hit.b - hit.c) +
|
||||
hit.hitSurface->lightmapCoords[hit.indices[1] * 2 + 1] * hit.b +
|
||||
hit.hitSurface->lightmapCoords[hit.indices[2] * 2 + 1] * hit.c;
|
||||
|
||||
int hitTexelX = clamp((int)(u * textureWidth + 0.5f), 0, textureWidth - 1);
|
||||
int hitTexelY = clamp((int)(v * textureHeight + 0.5f), 0, textureHeight - 1);
|
||||
|
||||
uint16_t *hitTexture = textures[hit.hitSurface->lightmapNum].data();
|
||||
uint16_t *hitPixel = hitTexture + (hitTexelX + hitTexelY * textureWidth) * 3;
|
||||
|
||||
float attenuation = (1.0f - hit.fraction);
|
||||
surfaceLight.x = halfToFloat(hitPixel[0]) * attenuation;
|
||||
surfaceLight.y = halfToFloat(hitPixel[1]) * attenuation;
|
||||
surfaceLight.z = halfToFloat(hitPixel[2]) * attenuation;
|
||||
}
|
||||
c += surfaceLight * NdotL;
|
||||
}
|
||||
totalWeight += NdotL;
|
||||
}
|
||||
}
|
||||
|
||||
c = c / totalWeight;
|
||||
|
||||
// convert RGB to bytes
|
||||
int tx = j + surface->lightmapOffs[0];
|
||||
int ty = i + surface->lightmapOffs[1];
|
||||
uint16_t *pixel = currentTexture + (tx + ty * textureWidth) * 3;
|
||||
pixel[0] = floatToHalf(c.x);
|
||||
pixel[1] = floatToHalf(c.y);
|
||||
pixel[2] = floatToHalf(c.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void kexLightmapBuilder::LightSurface(const int surfid)
|
||||
{
|
||||
BuildSurfaceParams(surfaces[surfid].get());
|
||||
|
@ -552,6 +671,21 @@ void kexLightmapBuilder::LightSurface(const int surfid)
|
|||
}
|
||||
}
|
||||
|
||||
void kexLightmapBuilder::LightIndirect(const int surfid)
|
||||
{
|
||||
TraceIndirectLight(surfaces[surfid].get());
|
||||
|
||||
int numsurfs = surfaces.size();
|
||||
int lastproc = processed * 100 / numsurfs;
|
||||
processed++;
|
||||
int curproc = processed * 100 / numsurfs;
|
||||
if (lastproc != curproc || processed == 1)
|
||||
{
|
||||
float remaining = (float)processed / (float)numsurfs;
|
||||
printf("%i%c surfaces done\r", (int)(remaining * 100.0f), '%');
|
||||
}
|
||||
}
|
||||
|
||||
void kexLightmapBuilder::CreateLightmaps(FLevel &doomMap)
|
||||
{
|
||||
map = &doomMap;
|
||||
|
@ -577,6 +711,29 @@ void kexLightmapBuilder::CreateLightmaps(FLevel &doomMap)
|
|||
});
|
||||
|
||||
printf("Texels traced: %i \n\n", tracedTexels);
|
||||
|
||||
printf("------------- Tracing indirect -------------\n");
|
||||
|
||||
indirectoutput.resize(textures.size() * textureWidth * textureHeight * 3);
|
||||
|
||||
tracedTexels = 0;
|
||||
processed = 0;
|
||||
kexWorker::RunJob(surfaces.size(), [=](int id) {
|
||||
LightIndirect(id);
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < textures.size(); i++)
|
||||
{
|
||||
uint16_t *tex = textures[i].data();
|
||||
uint16_t *indirect = &indirectoutput[i * textureWidth * textureHeight * 3];
|
||||
int count = textureWidth * textureHeight * 3;
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
tex[j] = floatToHalf(halfToFloat(tex[j]) + halfToFloat(indirect[j]));
|
||||
}
|
||||
}
|
||||
|
||||
printf("Texels traced: %i \n\n", tracedTexels);
|
||||
}
|
||||
|
||||
void kexLightmapBuilder::SetupLightCellGrid()
|
||||
|
@ -585,8 +742,8 @@ void kexLightmapBuilder::SetupLightCellGrid()
|
|||
float blockWorldSize = LIGHTCELL_BLOCK_SIZE * LIGHTCELL_SIZE;
|
||||
grid.x = static_cast<int>(std::floor(worldBBox.min.x / blockWorldSize));
|
||||
grid.y = static_cast<int>(std::floor(worldBBox.min.y / blockWorldSize));
|
||||
grid.width = static_cast<int>(std::ceil(worldBBox.max.x / blockWorldSize + 1.0f) - 1.0f) - grid.x;
|
||||
grid.height = static_cast<int>(std::ceil(worldBBox.max.y / blockWorldSize + 1.0f) - 1.0f) - grid.y;
|
||||
grid.width = static_cast<int>(std::ceil(worldBBox.max.x / blockWorldSize)) - grid.x;
|
||||
grid.height = static_cast<int>(std::ceil(worldBBox.max.y / blockWorldSize)) - grid.y;
|
||||
grid.blocks.resize(grid.width * grid.height);
|
||||
}
|
||||
|
||||
|
@ -641,7 +798,7 @@ void kexLightmapBuilder::LightBlock(int id)
|
|||
{
|
||||
// Allocate space for the cells
|
||||
block.z = static_cast<int>(std::floor(minFloor / LIGHTCELL_SIZE));
|
||||
block.layers = static_cast<int>(std::ceil(maxCeiling / LIGHTCELL_SIZE + 1.0f) - 1.0f) - block.z;
|
||||
block.layers = static_cast<int>(std::ceil(maxCeiling / LIGHTCELL_SIZE)) - block.z;
|
||||
block.cells.Resize(LIGHTCELL_BLOCK_SIZE * LIGHTCELL_BLOCK_SIZE * block.layers);
|
||||
|
||||
// Ray trace the cells
|
||||
|
|
|
@ -79,12 +79,15 @@ private:
|
|||
|
||||
void BuildSurfaceParams(surface_t *surface);
|
||||
void TraceSurface(surface_t *surface);
|
||||
void TraceIndirectLight(surface_t *surface);
|
||||
void SetupLightCellGrid();
|
||||
void LightBlock(int blockid);
|
||||
void LightSurface(const int surfid);
|
||||
void LightIndirect(const int surfid);
|
||||
|
||||
FLevel *map;
|
||||
std::vector<std::vector<uint16_t>> textures;
|
||||
std::vector<uint16_t> indirectoutput;
|
||||
std::vector<std::vector<int>> allocBlocks;
|
||||
int numTextures = 0;
|
||||
int extraSamples = 2;
|
||||
|
|
|
@ -397,7 +397,10 @@ void CreateSurfaces(FLevel &doomMap)
|
|||
unsigned int pos = doomMap.MeshVertices.Size();
|
||||
|
||||
for (int j = 0; j < numVerts; j++)
|
||||
{
|
||||
doomMap.MeshVertices.Push(s->verts[j]);
|
||||
doomMap.MeshUVIndex.Push(j);
|
||||
}
|
||||
|
||||
if (s->type == ST_FLOOR || s->type == ST_CEILING)
|
||||
{
|
||||
|
@ -431,5 +434,5 @@ void CreateSurfaces(FLevel &doomMap)
|
|||
}
|
||||
}
|
||||
|
||||
doomMap.CollisionMesh = std::make_unique<TriangleMeshShape>(&doomMap.MeshVertices[0], doomMap.MeshVertices.Size(), &doomMap.MeshElements[0], doomMap.MeshElements.Size(), &doomMap.MeshSurfaces[0]);
|
||||
doomMap.CollisionMesh = std::make_unique<TriangleMeshShape>(&doomMap.MeshVertices[0], doomMap.MeshVertices.Size(), &doomMap.MeshElements[0], doomMap.MeshElements.Size());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue