Compare commits

...

3 Commits

Author SHA1 Message Date
Jarrod Doyle a5078b0f2b
Use occlusion rays for non-sun tracing
More performant, no longer requires annoying fiddling to check distances, and removes the need for water surfaces in the mesh. Also reduces the number of edge case misses
2025-01-15 20:26:23 +00:00
Jarrod Doyle 4b3016d895
Combine plane facing and initial radius checks 2025-01-14 21:16:25 +00:00
Jarrod Doyle df8cb6f1ca
Fix logging null when failing to find object model 2025-01-14 20:54:21 +00:00
2 changed files with 35 additions and 67 deletions

View File

@ -699,20 +699,11 @@ public class LightMapper
var light = _lights[lightIdx - 1]; var light = _lights[lightIdx - 1];
// Check if plane normal is facing towards the light // If the light is behind the plane we'll never be directly lit by this light.
// If it's not then we're never going to be (directly) lit by this // Additionally, if the distance from the plane is more than the light's radius
// light. // we know no points on the plane will be lit.
var centerDirection = renderPoly.Center - light.Position; var planeDist = MathUtils.DistanceFromPlane(plane, light.Position);
if (Vector3.Dot(plane.Normal, centerDirection) >= 0) if (planeDist <= MathUtils.Epsilon || planeDist > light.Radius)
{
continue;
}
// If there aren't *any* points on the plane that are in range of the light
// then none of the lightmap points will be so we can discard.
// The more compact a map is the less effective this is
var planeDist = Math.Abs(MathUtils.DistanceFromPlane(plane, light.Position));
if (planeDist > light.Radius)
{ {
continue; continue;
} }
@ -739,7 +730,7 @@ public class LightMapper
continue; continue;
} }
if (TraceRay(light.Position, point)) if (!TraceOcclusion(light.Position, point))
{ {
strength += targetWeights[idx] * light.StrengthAtPoint(point, plane, settings.AnimLightCutoff); strength += targetWeights[idx] * light.StrengthAtPoint(point, plane, settings.AnimLightCutoff);
} }
@ -826,81 +817,58 @@ public class LightMapper
{ {
var offset = offsets[i]; var offset = offsets[i];
var pos = basePosition + offset; var pos = basePosition + offset;
// Embree has robustness issues when hitting poly edges which
// results in false misses. To alleviate this we pre-push everything
// slightly towards the center of the poly.
var centerOffset = polyCenter - pos;
if (centerOffset.LengthSquared() > MathUtils.Epsilon)
{
pos += Vector3.Normalize(centerOffset) * MathUtils.Epsilon;
}
// If we can't see our target point from the center of the poly // If we can't see our target point from the center of the poly
// then it's outside the world. We need to clip the point to slightly // then we need to clip the point to slightly inside the poly
// inside the poly and retrace to avoid three problems: // and retrace to avoid two problems:
// 1. Darkened spots from lightmap pixels whose center is outside // 1. Darkened spots from lightmap pixels whose center is outside
// the polygon but is partially contained in the polygon // the polygon but is partially contained in the polygon
// 2. Darkened spots from linear filtering of points outside the // 2. Darkened spots from linear filtering of points outside the
// polygon which have missed // polygon which have missed
// 3. Darkened spots where centers are on the exact edge of a poly var occluded = TraceOcclusion(polyCenter + planeMapper.Normal * 0.25f, pos);
// which can sometimes cause Embree to miss casts if (occluded)
var inPoly = TraceRay(polyCenter + planeMapper.Normal * 0.25f, pos);
if (!inPoly)
{ {
var p2d = planeMapper.MapTo2d(pos); var p2d = planeMapper.MapTo2d(pos);
p2d = MathUtils.ClipPointToPoly2d(p2d, v2ds); p2d = MathUtils.ClipPointToPoly2d(p2d, v2ds);
pos = planeMapper.MapTo3d(p2d); pos = planeMapper.MapTo3d(p2d);
} }
tracePoints[i] = pos; tracePoints[i] = pos;
} }
return tracePoints; return tracePoints;
} }
private bool TraceRay(Vector3 origin, Vector3 target) private bool TraceOcclusion(Vector3 origin, Vector3 target)
{ {
var hitDistanceFromTarget = float.MinValue; var direction = target - origin;
var hitSurfaceType = SurfaceType.Water; var ray = new Ray
while (hitDistanceFromTarget < -MathUtils.Epsilon && hitSurfaceType == SurfaceType.Water)
{ {
var direction = target - origin; Origin = origin,
var hitResult = _scene.Trace(new Ray Direction = Vector3.Normalize(direction),
{ };
Origin = origin,
Direction = Vector3.Normalize(direction), // Epsilon is used here to avoid occlusion when origin lies exactly on a poly
}); return _scene.IsOccluded(new ShadowRay(ray, direction.Length() - MathUtils.Epsilon));
hitDistanceFromTarget = hitResult.Distance - direction.Length();
hitSurfaceType = _triangleTypeMap[(int)hitResult.PrimId];
origin = hitResult.Position += direction * MathUtils.Epsilon;
}
// A large epsilon is used here to fix shadow acne on sloped surfaces :)
return Math.Abs(hitDistanceFromTarget) < 10 * MathUtils.Epsilon;
} }
// TODO: Can this be merged with the above? // TODO: direction should already be normalised here
private bool TraceSunRay(Vector3 origin, Vector3 direction) private bool TraceSunRay(Vector3 origin, Vector3 direction)
{ {
// Avoid self intersection // Avoid self intersection
origin += direction * MathUtils.Epsilon; origin += direction * MathUtils.Epsilon;
var hitSurfaceType = SurfaceType.Water; var hitResult = _scene.Trace(new Ray
while (hitSurfaceType == SurfaceType.Water)
{ {
var hitResult = _scene.Trace(new Ray Origin = origin,
{ Direction = Vector3.Normalize(direction),
Origin = origin, });
Direction = Vector3.Normalize(direction),
}); if (hitResult)
{
hitSurfaceType = _triangleTypeMap[(int)hitResult.PrimId]; return _triangleTypeMap[(int)hitResult.PrimId] == SurfaceType.Sky;
origin = hitResult.Position += direction * MathUtils.Epsilon;
} }
return false;
return hitSurfaceType == SurfaceType.Sky;
} }
private void SetAnimLightCellMaps() private void SetAnimLightCellMaps()

View File

@ -43,9 +43,8 @@ public class MeshBuilder
var cellIdxOffset = 0; var cellIdxOffset = 0;
for (var polyIdx = 0; polyIdx < numPolys; polyIdx++) for (var polyIdx = 0; polyIdx < numPolys; polyIdx++)
{ {
// There's 3 types of poly that we need to include in the mesh: // There's 2 types of poly that we need to include in the mesh:
// - Terrain // - Terrain
// - Water surfaces
// - Door vision blockers // - Door vision blockers
// //
// Door vision blockers are the interesting one. They're not RenderPolys at all, just flagged Polys. // Door vision blockers are the interesting one. They're not RenderPolys at all, just flagged Polys.
@ -56,7 +55,8 @@ public class MeshBuilder
} }
else if (polyIdx < numRenderPolys) else if (polyIdx < numRenderPolys)
{ {
primType = SurfaceType.Water; // we no longer want water polys :)
continue;
} }
else if ((cell.Flags & 8) != 0) else if ((cell.Flags & 8) != 0)
{ {
@ -118,7 +118,7 @@ public class MeshBuilder
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName); var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
if (modelPath == null) if (modelPath == null)
{ {
Log.Warning("Failed to find model file: {Path}", modelPath); Log.Warning("Failed to find model file: {Name}", modelName);
continue; continue;
} }