1327 lines
44 KiB
C++
1327 lines
44 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Embree.h"
|
|
#include "LightingSystem.h"
|
|
#include "UnrealLightmass.h"
|
|
#include "HAL/PlatformTime.h"
|
|
|
|
// #pragma optimize( "", off )
|
|
// #define EMBREE_INLINE
|
|
|
|
#define EMBREE_INLINE FORCEINLINE
|
|
|
|
#if USE_EMBREE
|
|
|
|
namespace Lightmass
|
|
{
|
|
|
|
volatile int64 GEmbreeAllocatedSpace = 0;
|
|
|
|
void FEmbreeTransmissionAccumulator::Resolve(FLinearColor& FinalColor, float tCollide)
|
|
{
|
|
FinalColor = FLinearColor::White;
|
|
|
|
for (int32 i = 0; i < Colors.Num(); ++i)
|
|
{
|
|
const FLinearColor& Color = Colors[i];
|
|
if (Color.A < tCollide)
|
|
{
|
|
FinalColor.R *= Color.R;
|
|
FinalColor.G *= Color.G;
|
|
FinalColor.B *= Color.B;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FEmbreeTransmissionAccumulator::Resolve(FLinearColor& FinalColor)
|
|
{
|
|
FinalColor = FLinearColor::White;
|
|
|
|
for (int32 i = 0; i < Colors.Num(); ++i)
|
|
{
|
|
const FLinearColor& Color = Colors[i];
|
|
FinalColor.R *= Color.R;
|
|
FinalColor.G *= Color.G;
|
|
FinalColor.B *= Color.B;
|
|
}
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
FEmbreeContext::FEmbreeContext(
|
|
const FStaticLightingMesh* InShadowMesh,
|
|
const FStaticLightingMesh* InMappingMesh,
|
|
uint32 InTraceFlags,
|
|
bool InFindClosestIntersection,
|
|
bool InCalculateTransmission,
|
|
bool InDirectShadowingRay
|
|
) :
|
|
ShadowMesh(InShadowMesh),
|
|
MappingMesh(InMappingMesh),
|
|
TraceFlags(InTraceFlags),
|
|
bFindClosestIntersection(InFindClosestIntersection),
|
|
bCalculateTransmission(InCalculateTransmission),
|
|
bDirectShadowingRay(InDirectShadowingRay),
|
|
bStaticAndOpaqueOnly((TraceFlags & LIGHTRAY_STATIC_AND_OPAQUEONLY) != 0),
|
|
bTwoSidedCollision(!InDirectShadowingRay),
|
|
bFlipSidedness((TraceFlags & LIGHTRAY_FLIP_SIDEDNESS) != 0),
|
|
ElementIndex(-1),
|
|
TextureCoordinates(0, 0),
|
|
LightmapCoordinates(0, 0)
|
|
{
|
|
}
|
|
#else
|
|
FEmbreeRay::FEmbreeRay(
|
|
const FStaticLightingMesh* InShadowMesh,
|
|
const FStaticLightingMesh* InMappingMesh,
|
|
uint32 InTraceFlags,
|
|
bool InFindClosestIntersection,
|
|
bool InCalculateTransmission,
|
|
bool InDirectShadowingRay
|
|
) :
|
|
ShadowMesh(InShadowMesh),
|
|
MappingMesh(InMappingMesh),
|
|
TraceFlags(InTraceFlags),
|
|
bFindClosestIntersection(InFindClosestIntersection),
|
|
bCalculateTransmission(InCalculateTransmission),
|
|
bDirectShadowingRay(InDirectShadowingRay),
|
|
bStaticAndOpaqueOnly((TraceFlags & LIGHTRAY_STATIC_AND_OPAQUEONLY) != 0),
|
|
bTwoSidedCollision(!InDirectShadowingRay),
|
|
bFlipSidedness((TraceFlags & LIGHTRAY_FLIP_SIDEDNESS) != 0),
|
|
ElementIndex(-1),
|
|
TextureCoordinates(0, 0),
|
|
LightmapCoordinates(0, 0)
|
|
{
|
|
u = v = 0;
|
|
time = 0;
|
|
mask = 0xFFFFFFFF;
|
|
geomID = -1;
|
|
instID = -1;
|
|
primID = -1;
|
|
}
|
|
|
|
FEmbreeRay4::FEmbreeRay4(
|
|
const FStaticLightingMesh* InShadowMesh,
|
|
const FStaticLightingMesh* InMappingMesh,
|
|
uint32 InTraceFlags,
|
|
bool InFindClosestIntersection,
|
|
bool InCalculateTransmission,
|
|
bool InDirectShadowingRay
|
|
) :
|
|
ShadowMesh(InShadowMesh),
|
|
MappingMesh(InMappingMesh),
|
|
TraceFlags(InTraceFlags),
|
|
bFindClosestIntersection(InFindClosestIntersection),
|
|
bCalculateTransmission(InCalculateTransmission),
|
|
bDirectShadowingRay(InDirectShadowingRay),
|
|
bStaticAndOpaqueOnly((TraceFlags & LIGHTRAY_STATIC_AND_OPAQUEONLY) != 0),
|
|
bTwoSidedCollision(!InDirectShadowingRay),
|
|
bFlipSidedness((TraceFlags & LIGHTRAY_FLIP_SIDEDNESS) != 0)
|
|
{
|
|
for (int32 i = 0; i < 4; i++)
|
|
{
|
|
u[i] = v[i] = 0;
|
|
time[i] = 0;
|
|
mask[i] = 0xFFFFFFFF;
|
|
geomID[i] = -1;
|
|
instID[i] = -1;
|
|
primID[i] = -1;
|
|
|
|
ElementIndex[i] = -1,
|
|
TextureCoordinates[i] = FVector2f(0, 0);
|
|
LightmapCoordinates[i] = FVector2f(0, 0);
|
|
}
|
|
}
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
struct FEmbreeFilterProcessor
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
FEmbreeFilterProcessor(
|
|
FEmbreeContext& InEmbreeContext,
|
|
RTCRay& InRay,
|
|
RTCHit& InHit,
|
|
int*& InValidMask,
|
|
const FEmbreeGeometry& InGeo
|
|
) :
|
|
EmbreeContext(InEmbreeContext),
|
|
EmbreeRay(InRay),
|
|
EmbreeHit(InHit),
|
|
ValidMask(InValidMask),
|
|
Geo(InGeo),
|
|
bCoordsDirty(true)
|
|
#else
|
|
FEmbreeFilterProcessor(FEmbreeRay& InRay, const FEmbreeGeometry& InGeo) : Ray(InRay), Geo(InGeo), bCoordsDirty(true)
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
{
|
|
Mesh = Geo.Mesh;
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
Desc = Geo.TriangleDescs[EmbreeHit.primID];
|
|
if (EmbreeHit.instID[0] != RTC_INVALID_GEOMETRY_ID)
|
|
{
|
|
// if instancing is used, material evaluation is deferred here
|
|
Mesh = Geo.ParentAggregateMesh->StaticMeshInstancesToMappings[EmbreeHit.instID[0]]->Mesh;
|
|
#else
|
|
Desc = Geo.TriangleDescs[Ray.primID];
|
|
if (Ray.instID != -1)
|
|
{
|
|
// if instancing is used, material evaluation is deferred here
|
|
Mesh = Geo.ParentAggregateMesh->StaticMeshInstancesToMappings[Ray.instID]->Mesh;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
int32 ElementIndex = Desc.ElementIndex;
|
|
Desc.CastShadow = Mesh->IsElementCastingShadow(ElementIndex);
|
|
Desc.StaticAndOpaqueMask = !Mesh->IsMasked(ElementIndex) && !Mesh->IsTranslucent(ElementIndex) && !Mesh->bMovable;;
|
|
Desc.TwoSidedMask = Mesh->IsTwoSided(ElementIndex) || Mesh->IsCastingShadowAsTwoSided();
|
|
Desc.Translucent = Mesh->IsTranslucent(ElementIndex);
|
|
Desc.SurfaceDomain = Mesh->IsSurfaceDomain(ElementIndex);
|
|
Desc.IndirectlyShadowedOnly = Mesh->IsIndirectlyShadowedOnly(ElementIndex);
|
|
Desc.Masked = Mesh->IsMasked(ElementIndex);
|
|
Desc.CastShadowAsMasked = Mesh->IsCastingShadowsAsMasked(ElementIndex);
|
|
}
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
s = 1 - EmbreeHit.u - EmbreeHit.v;
|
|
#else
|
|
s = 1 - Ray.u - Ray.v;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
FEmbreeContext& EmbreeContext;
|
|
RTCRay& EmbreeRay;
|
|
RTCHit& EmbreeHit;
|
|
int*& ValidMask;
|
|
#else
|
|
FEmbreeRay& Ray;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
const FEmbreeGeometry& Geo;
|
|
FEmbreeTriangleDesc Desc;
|
|
const FStaticLightingMesh* Mesh;
|
|
|
|
float s; // (s,u,v) : Barycentric Weights
|
|
int32 Index0;
|
|
int32 Index1;
|
|
int32 Index2;
|
|
|
|
FVector2f TextureCoordinates; // Material Coordinates
|
|
|
|
bool bCoordsDirty;
|
|
|
|
void UpdateCoordinates();
|
|
|
|
// This is called when everything succeeds and the ray is the final collision.
|
|
void UpdateRay();
|
|
|
|
EMBREE_INLINE void Invalidate()
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
ValidMask[0] = 0;
|
|
#else
|
|
Ray.geomID = -1;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
|
|
EMBREE_INLINE bool IsBackFace() const
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
return EmbreeRay.dir_x * -EmbreeHit.Ng_x + EmbreeRay.dir_y * -EmbreeHit.Ng_y + EmbreeRay.dir_z * -EmbreeHit.Ng_z < 0;
|
|
#else
|
|
return Ray.dir[0] * Ray.Ng[0] + Ray.dir[1] * Ray.Ng[1] + Ray.dir[2] * Ray.Ng[2] < 0;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
|
|
|
|
EMBREE_INLINE bool HitRejectedByStaticAndOpaqueOnlyTest() const
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
return EmbreeContext.bStaticAndOpaqueOnly && !Desc.StaticAndOpaqueMask;
|
|
#else
|
|
return Ray.bStaticAndOpaqueOnly && !Desc.StaticAndOpaqueMask;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
|
|
EMBREE_INLINE bool HitRejectedByBackFaceCullingTest() const
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
if (!EmbreeContext.bTwoSidedCollision && !Desc.TwoSidedMask)
|
|
{
|
|
bool bIsBackFace = IsBackFace();
|
|
if (EmbreeContext.bFlipSidedness ? !bIsBackFace : bIsBackFace)
|
|
#else
|
|
if (!Ray.bTwoSidedCollision && !Desc.TwoSidedMask)
|
|
{
|
|
bool bIsBackFace = IsBackFace();
|
|
if (Ray.bFlipSidedness ? !bIsBackFace : bIsBackFace)
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determine ray interaction with HLODs (hierarchical LODs):
|
|
*
|
|
* A
|
|
* / \
|
|
* B E
|
|
* / \ / \
|
|
* C D F G
|
|
*
|
|
* Above is a HLOD tree where A is tier 2 HLOD, B and E are tier 1 HLODs. C,D,F and G are LOD0 nodes.
|
|
* Node range indices are assigned by a depth-first traversal beginning at the largest HLOD, i.e. node A,
|
|
* as this allows each HLOD to know the contained children for later rejection. Leaf nodes are always LOD0s.
|
|
*
|
|
* Stored HLOD data per node:
|
|
* HLODTreeIndex: Unique index assigned to this tree of nodes.
|
|
* HLODRange: Range of nodes that make up this HLOD node (self-inclusive).
|
|
* HLODRangeStart: The index within the tree of this node.
|
|
* HLODRangeEnd: The index within the tree of this node's final child.
|
|
*
|
|
* @return true if the ray is rejected.
|
|
*/
|
|
EMBREE_INLINE bool HitRejectedByHLODTest() const
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
const FStaticLightingMesh* MappingMesh = EmbreeContext.MappingMesh;
|
|
#else
|
|
const FStaticLightingMesh* MappingMesh = Ray.MappingMesh;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
const uint32 InvalidIndex = 0xFFFF;
|
|
uint32 GeoHLODTreeIndex = (Mesh->GetLODIndices() & 0xFFFF0000) >> 16;
|
|
uint32 RayHLODTreeIndex = MappingMesh ? (MappingMesh->GetLODIndices() & 0xFFFF0000) >> 16 : InvalidIndex;
|
|
|
|
bool bRejectHit = false;
|
|
|
|
// If either Geo or Ray is a HLOD (0xFFFF being invalid HLOD)
|
|
if (GeoHLODTreeIndex != InvalidIndex || RayHLODTreeIndex != InvalidIndex)
|
|
{
|
|
uint32 GeoHLODRange = Mesh->GetHLODRange();
|
|
uint32 GeoHLODRangeStart = GeoHLODRange & 0xFFFF;
|
|
uint32 GeoHLODRangeEnd = (GeoHLODRange & 0xFFFF0000) >> 16;
|
|
|
|
uint32 RayHLODRange = MappingMesh ? MappingMesh->GetHLODRange() : 0;
|
|
uint32 RayHLODRangeStart = RayHLODRange & 0xFFFF;
|
|
uint32 RayHLODRangeEnd = (RayHLODRange & 0xFFFF0000) >> 16;
|
|
|
|
// Different rules if nodes are within the same HLOD tree
|
|
if (GeoHLODTreeIndex != RayHLODTreeIndex)
|
|
{
|
|
// Allow other meshes to interact with this tree's LOD0 nodes, else reject
|
|
if (GeoHLODRangeStart != GeoHLODRangeEnd)
|
|
{
|
|
bRejectHit = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Allow shadowing within HLOD tree if:
|
|
// * Ray and geo are same node, i.e. self-shadowing
|
|
// * Geo is LOD0 and not a child of Ray node
|
|
bool bIsRaySameNodeAsGeo = GeoHLODRange == RayHLODRange;
|
|
bool bIsGeoLOD0 = GeoHLODRangeStart == GeoHLODRangeEnd;
|
|
bool bIsGeoOutsideRayRange = GeoHLODRangeStart < RayHLODRangeStart || GeoHLODRangeStart > RayHLODRangeEnd;
|
|
|
|
if (!(bIsRaySameNodeAsGeo || (bIsGeoLOD0 && bIsGeoOutsideRayRange)))
|
|
{
|
|
bRejectHit = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bRejectHit;
|
|
}
|
|
|
|
EMBREE_INLINE bool HitRejectedByLODIndexTest() const
|
|
{
|
|
uint32 GeoMeshLODIndex = Mesh->GetLODIndices() & 0xFFFF;
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
const FStaticLightingMesh* MappingMesh = EmbreeContext.MappingMesh;
|
|
#else
|
|
const FStaticLightingMesh* MappingMesh = Ray.MappingMesh;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
// Only shadows from appropriate mesh LODs.
|
|
if (MappingMesh)
|
|
{
|
|
// If it is not from the same mesh, then only LOD 0 can cast shadow.
|
|
if (MappingMesh->MeshIndex != Mesh->MeshIndex)
|
|
{
|
|
return GeoMeshLODIndex != 0;
|
|
}
|
|
else
|
|
{
|
|
// If it is from the same mesh, then only same LOD can cast shadow.
|
|
return (MappingMesh->GetLODIndices() & 0xFFFF) != GeoMeshLODIndex;
|
|
}
|
|
}
|
|
|
|
// If the ray didn't originate from a mesh, only intersect against LOD0
|
|
return GeoMeshLODIndex != 0;
|
|
}
|
|
|
|
EMBREE_INLINE bool HitRejectedBySelfShadowTest() const
|
|
{
|
|
// No self shadows, or only self shadow
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
if ((Mesh == EmbreeContext.ShadowMesh && ((Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWDISABLE) || (EmbreeContext.TraceFlags & LIGHTRAY_SELFSHADOWDISABLE)))
|
|
|| (EmbreeContext.bDirectShadowingRay && Desc.IndirectlyShadowedOnly)
|
|
|| (Mesh != EmbreeContext.ShadowMesh && (Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWONLY)))
|
|
#else
|
|
if ((Mesh == Ray.ShadowMesh && ((Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWDISABLE) || (Ray.TraceFlags & LIGHTRAY_SELFSHADOWDISABLE)))
|
|
|| (Ray.bDirectShadowingRay && Desc.IndirectlyShadowedOnly)
|
|
|| (Mesh != Ray.ShadowMesh && (Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWONLY)))
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
EMBREE_INLINE bool HitRejectedByAlphaTestTest()
|
|
{
|
|
UpdateCoordinates();
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
if (Desc.Masked || (EmbreeContext.bDirectShadowingRay && Desc.CastShadowAsMasked))
|
|
#else
|
|
if (Desc.Masked || (Ray.bDirectShadowingRay && Desc.CastShadowAsMasked))
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
{
|
|
return !Mesh->EvaluateMaskedCollision(TextureCoordinates, Desc.ElementIndex);
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
void FEmbreeFilterProcessor::UpdateCoordinates()
|
|
{
|
|
if (bCoordsDirty)
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
unsigned int InstanceID = EmbreeHit.instID[0];
|
|
unsigned int PrimID = EmbreeHit.primID;
|
|
float HitU = EmbreeHit.u;
|
|
float HitV = EmbreeHit.v;
|
|
#else
|
|
unsigned int InstanceID = Ray.instID;
|
|
unsigned int PrimID = Ray.primID;
|
|
float HitU = Ray.u;
|
|
float HitV = Ray.v;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
if (InstanceID == RTC_INVALID_GEOMETRY_ID)
|
|
{
|
|
Mesh->GetTriangleIndices(PrimID, Index0, Index1, Index2);
|
|
}
|
|
else
|
|
{
|
|
Mesh->GetInstanceableStaticMesh()->GetNonTransformedTriangleIndices(PrimID, Index0, Index1, Index2);
|
|
}
|
|
|
|
const FVector2f& UV1 = Geo.UVs[Index0];
|
|
const FVector2f& UV2 = Geo.UVs[Index1];
|
|
const FVector2f& UV3 = Geo.UVs[Index2];
|
|
TextureCoordinates = UV1 * s + UV2 * HitU + UV3 * HitV;
|
|
|
|
bCoordsDirty = false;
|
|
}
|
|
}
|
|
|
|
// This is called when everything succeeds and the ray is the final collision.
|
|
void FEmbreeFilterProcessor::UpdateRay()
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
// ElementIndex
|
|
EmbreeContext.ElementIndex = Desc.ElementIndex;
|
|
|
|
if (EmbreeContext.bFindClosestIntersection)
|
|
{
|
|
//@todo - only lookup and interpolate UV's if needed
|
|
UpdateCoordinates();
|
|
|
|
// TextureCoordinates
|
|
EmbreeContext.TextureCoordinates = TextureCoordinates;
|
|
|
|
// LightmapCoordinates
|
|
const FVector2f& LightmapUV1 = Geo.LightmapUVs[Index0];
|
|
const FVector2f& LightmapUV2 = Geo.LightmapUVs[Index1];
|
|
const FVector2f& LightmapUV3 = Geo.LightmapUVs[Index2];
|
|
EmbreeContext.LightmapCoordinates = LightmapUV1 * s + LightmapUV2 * EmbreeHit.u + LightmapUV3 * EmbreeHit.v;
|
|
#else
|
|
// ElementIndex
|
|
Ray.ElementIndex = Desc.ElementIndex;
|
|
|
|
if (Ray.bFindClosestIntersection)
|
|
{
|
|
//@todo - only lookup and interpolate UV's if needed
|
|
UpdateCoordinates();
|
|
|
|
// TextureCoordinates
|
|
Ray.TextureCoordinates = TextureCoordinates;
|
|
|
|
// LightmapCoordinates
|
|
const FVector2f& LightmapUV1 = Geo.LightmapUVs[Index0];
|
|
const FVector2f& LightmapUV2 = Geo.LightmapUVs[Index1];
|
|
const FVector2f& LightmapUV3 = Geo.LightmapUVs[Index2];
|
|
Ray.LightmapCoordinates = LightmapUV1 * s + LightmapUV2 * Ray.u + LightmapUV3 * Ray.v;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
else if (bCoordsDirty)
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
unsigned int InstanceID = EmbreeHit.instID[0];
|
|
unsigned int PrimID = EmbreeHit.primID;
|
|
#else
|
|
unsigned int InstanceID = Ray.instID;
|
|
unsigned int PrimID = Ray.primID;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
if (InstanceID == RTC_INVALID_GEOMETRY_ID)
|
|
{
|
|
Mesh->GetTriangleIndices(PrimID, Index0, Index1, Index2);
|
|
}
|
|
else
|
|
{
|
|
Mesh->GetInstanceableStaticMesh()->GetNonTransformedTriangleIndices(PrimID, Index0, Index1, Index2);
|
|
}
|
|
}
|
|
|
|
// Transmission : updated outside of this scope.
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
// Warning: EmbreeFilterFunc must only modify FEmbreeContext outputs!
|
|
void EmbreeFilterFunc(const RTCFilterFunctionNArguments* args)
|
|
{
|
|
int* EmbreeValid = args->valid;
|
|
FEmbreeGeometry& EmbreeGeom = *(FEmbreeGeometry*)args->geometryUserPtr;
|
|
FEmbreeContext& EmbreeContext = *(FEmbreeContext*)args->context;
|
|
|
|
// We expect single-ray packets here since we use rtcIntersect1/rtcOccluded1.
|
|
check(args->N == 1u);
|
|
|
|
// Ignore invalid rays.
|
|
if (EmbreeValid[0] != -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RTCRay& EmbreeRay = *(RTCRay*)args->ray;
|
|
RTCHit& EmbreeHit = *(RTCHit*)args->hit;
|
|
|
|
FEmbreeFilterProcessor Proc(EmbreeContext, EmbreeRay, EmbreeHit, EmbreeValid, EmbreeGeom);
|
|
checkSlow(Proc.Geo.GeomID == Proc.EmbreeHit.geomID);
|
|
#else
|
|
// Warning: EmbreeFilterFunc must only modify RTCRay outputs! Nothing else is copied back to FEmbreeRay4.
|
|
void EmbreeFilterFunc(void* UserPtr, RTCRay& InRay)
|
|
{
|
|
FEmbreeFilterProcessor Proc((FEmbreeRay&)InRay, *(FEmbreeGeometry*)UserPtr);
|
|
checkSlow(Proc.Geo.GeomID == Proc.Ray.geomID);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
if (!Proc.Desc.CastShadow)
|
|
{
|
|
Proc.Invalidate();
|
|
return;
|
|
}
|
|
|
|
// appLineCheckTriangleSOA
|
|
if (Proc.HitRejectedByStaticAndOpaqueOnlyTest() ||
|
|
Proc.HitRejectedByBackFaceCullingTest() ||
|
|
Proc.HitRejectedByLODIndexTest() ||
|
|
Proc.HitRejectedByHLODTest())
|
|
{
|
|
Proc.Invalidate();
|
|
return;
|
|
}
|
|
|
|
// Only collide with surface domain materials
|
|
if (!Proc.Desc.SurfaceDomain)
|
|
{
|
|
Proc.Invalidate();
|
|
return;
|
|
}
|
|
|
|
// No collision with translucent primitives.
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
if (Proc.Desc.Translucent && !(Proc.EmbreeContext.bDirectShadowingRay && Proc.Desc.CastShadowAsMasked))
|
|
{
|
|
if (Proc.EmbreeContext.bCalculateTransmission)
|
|
{
|
|
Proc.UpdateCoordinates();
|
|
|
|
// Accumulate the total transmission along the ray
|
|
// The result is order independent so the intersections don't have to be strictly front to back
|
|
Proc.EmbreeContext.TransmissionAcc.Push(Proc.Mesh->EvaluateTransmission(Proc.TextureCoordinates, Proc.Desc.ElementIndex), Proc.EmbreeRay.tfar);
|
|
}
|
|
#else
|
|
if (Proc.Desc.Translucent && !(Proc.Ray.bDirectShadowingRay && Proc.Desc.CastShadowAsMasked))
|
|
{
|
|
if (Proc.Ray.bCalculateTransmission)
|
|
{
|
|
Proc.UpdateCoordinates();
|
|
|
|
// Accumulate the total transmission along the ray
|
|
// The result is order independent so the intersections don't have to be strictly front to back
|
|
Proc.Ray.TransmissionAcc.Push(Proc.Mesh->EvaluateTransmission(Proc.TextureCoordinates, Proc.Desc.ElementIndex), Proc.Ray.tfar);
|
|
}
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
Proc.Invalidate();
|
|
return;
|
|
}
|
|
|
|
// No self shadows, or only self shadow
|
|
if (Proc.HitRejectedBySelfShadowTest())
|
|
{
|
|
Proc.Invalidate();
|
|
return;
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
if (Proc.EmbreeContext.bFindClosestIntersection)
|
|
#else
|
|
if (Proc.Ray.bFindClosestIntersection)
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
{
|
|
if (Proc.HitRejectedByAlphaTestTest())
|
|
{
|
|
Proc.Invalidate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Ray Properties need to be updated only once everything has been validated.
|
|
// Otherwise, after a valid collision, a failed collision could be tested which must not change any property.
|
|
Proc.UpdateRay();
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION < 3
|
|
void EmbreeFilterFunc4(const void* valid, void* UserPtr, RTCRay4& InRay)
|
|
{
|
|
FEmbreeRay4& EmbreeRay4 = (FEmbreeRay4&)InRay;
|
|
|
|
const FEmbreeGeometry* TestGeometry = (FEmbreeGeometry*)UserPtr;
|
|
|
|
for (int32 i = 0; i < 4; i++)
|
|
{
|
|
if (EmbreeRay4.primID[i] != uint32(-1) && EmbreeRay4.geomID[i] == TestGeometry->GeomID)
|
|
{
|
|
FEmbreeRay SingleRay = EmbreeRay4.BuildSingleRay(i);
|
|
EmbreeFilterFunc(UserPtr, SingleRay);
|
|
EmbreeRay4.SetFromSingleRay(SingleRay, i);
|
|
}
|
|
}
|
|
}
|
|
#endif // USE_EMBREE_MAJOR_VERSION < 3
|
|
|
|
FEmbreeGeometry::FEmbreeGeometry(
|
|
RTCDevice EmbreeDevice,
|
|
RTCScene EmbreeScene,
|
|
const FBoxSphereBounds3f& ImportanceBounds,
|
|
const FStaticLightingMesh* InMesh,
|
|
const FStaticLightingMapping* InMapping,
|
|
bool bUseForInstancing
|
|
) :
|
|
Mesh(InMesh),
|
|
Mapping(InMapping),
|
|
SurfaceArea(0),
|
|
SurfaceAreaWithinImportanceVolume(0),
|
|
bHasShadowCastingPrimitives(false)
|
|
{
|
|
if (bUseForInstancing)
|
|
{
|
|
check(Mesh->GetInstanceableStaticMesh() != nullptr);
|
|
}
|
|
|
|
const FStaticLightingTextureMapping* TextureMapping = Mapping ? Mapping->GetTextureMapping() : NULL;
|
|
|
|
TriangleDescs.AddZeroed(Mesh->NumTriangles);
|
|
UVs.AddZeroed(Mesh->NumVertices);
|
|
LightmapUVs.AddZeroed(Mesh->NumVertices);
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
RTCGeometry EmbreeGeom = rtcNewGeometry(EmbreeDevice, RTC_GEOMETRY_TYPE_TRIANGLE);
|
|
rtcSetGeometryBuildQuality(EmbreeGeom, RTC_BUILD_QUALITY_MEDIUM);
|
|
rtcSetGeometryTimeStepCount(EmbreeGeom, 1);
|
|
|
|
FVector4f* Vertices = (FVector4f*) rtcSetNewGeometryBuffer(
|
|
EmbreeGeom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 4 * sizeof(float), Mesh->NumVertices);
|
|
int32* Indices = (int32*) rtcSetNewGeometryBuffer(
|
|
EmbreeGeom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, 3 * sizeof(int32), Mesh->NumTriangles);
|
|
#else
|
|
GeomID = rtcNewTriangleMesh(EmbreeScene, RTC_GEOMETRY_STATIC, Mesh->NumTriangles, Mesh->NumVertices);
|
|
|
|
FVector4f* Vertices = (FVector4f*) rtcMapBuffer(EmbreeScene, GeomID, RTC_VERTEX_BUFFER);
|
|
int32* Indices = (int32*) rtcMapBuffer(EmbreeScene, GeomID, RTC_INDEX_BUFFER);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
for (int32 TriangleIndex = 0;TriangleIndex < Mesh->NumTriangles;TriangleIndex++)
|
|
{
|
|
int32 I0 = 0, I1 = 0, I2 = 0;
|
|
FStaticLightingVertex V0, V1, V2;
|
|
int32 ElementIndex;
|
|
|
|
if (bUseForInstancing)
|
|
{
|
|
Mesh->GetInstanceableStaticMesh()->GetNonTransformedTriangleIndices(TriangleIndex, I0, I1, I2);
|
|
Mesh->GetInstanceableStaticMesh()->GetNonTransformedTriangle(TriangleIndex, V0, V1, V2, ElementIndex);
|
|
}
|
|
else
|
|
{
|
|
Mesh->GetTriangleIndices(TriangleIndex, I0, I1, I2);
|
|
Mesh->GetTriangle(TriangleIndex, V0, V1, V2, ElementIndex);
|
|
}
|
|
|
|
// Compute the triangle's normal.
|
|
const FVector4f TriangleNormal = (V2.WorldPosition - V0.WorldPosition) ^ (V1.WorldPosition - V0.WorldPosition);
|
|
// Compute the triangle area.
|
|
const float TriangleArea = TriangleNormal.Size3() * 0.5f;
|
|
|
|
FEmbreeTriangleDesc& Desc = TriangleDescs[TriangleIndex];
|
|
Desc.ElementIndex = ElementIndex;
|
|
Desc.CastShadow = false;
|
|
|
|
if (!bUseForInstancing)
|
|
{
|
|
// When instancing is not used, evaluate material properties here
|
|
// Otherwise, defer material evaluation until ray intersection
|
|
Desc.CastShadow = TriangleArea > TRIANGLE_AREA_THRESHOLD && Mesh->IsElementCastingShadow(ElementIndex);
|
|
Desc.StaticAndOpaqueMask = !Mesh->IsMasked(ElementIndex) && !Mesh->IsTranslucent(ElementIndex) && !Mesh->bMovable;;
|
|
Desc.TwoSidedMask = Mesh->IsTwoSided(ElementIndex) || Mesh->IsCastingShadowAsTwoSided();
|
|
Desc.Translucent = Mesh->IsTranslucent(ElementIndex);
|
|
Desc.SurfaceDomain = Mesh->IsSurfaceDomain(ElementIndex);
|
|
Desc.IndirectlyShadowedOnly = Mesh->IsIndirectlyShadowedOnly(ElementIndex);
|
|
Desc.Masked = Mesh->IsMasked(ElementIndex);
|
|
Desc.CastShadowAsMasked = Mesh->IsCastingShadowsAsMasked(ElementIndex);
|
|
}
|
|
|
|
if (TriangleArea > TRIANGLE_AREA_THRESHOLD && (bUseForInstancing || Desc.CastShadow))
|
|
{
|
|
Indices[TriangleIndex * 3 + 0] = I0;
|
|
Indices[TriangleIndex * 3 + 1] = I1;
|
|
Indices[TriangleIndex * 3 + 2] = I2;
|
|
bHasShadowCastingPrimitives = true;
|
|
}
|
|
else // Otherwise map an degenerated triangle to reduce intersections
|
|
{
|
|
Indices[TriangleIndex * 3 + 0] = I0;
|
|
Indices[TriangleIndex * 3 + 1] = I0;
|
|
Indices[TriangleIndex * 3 + 2] = I0;
|
|
}
|
|
|
|
Vertices[I0] = V0.WorldPosition;
|
|
Vertices[I1] = V1.WorldPosition;
|
|
Vertices[I2] = V2.WorldPosition;
|
|
|
|
UVs[I0] = V0.TextureCoordinates[Mesh->TextureCoordinateIndex];
|
|
UVs[I1] = V1.TextureCoordinates[Mesh->TextureCoordinateIndex];
|
|
UVs[I2] = V2.TextureCoordinates[Mesh->TextureCoordinateIndex];
|
|
if (TextureMapping)
|
|
{
|
|
LightmapUVs[I0] = V0.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
|
|
LightmapUVs[I1] = V1.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
|
|
LightmapUVs[I2] = V2.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
|
|
}
|
|
|
|
if (!bUseForInstancing)
|
|
{
|
|
SurfaceArea += TriangleArea;
|
|
|
|
// Sum the total triangle area of everything in the aggregate mesh within the importance volume,
|
|
// if any vertex is contained or if there is no importance volume at all
|
|
if (ImportanceBounds.SphereRadius < DELTA ||
|
|
ImportanceBounds.GetBox().IsInside(V0.WorldPosition) ||
|
|
ImportanceBounds.GetBox().IsInside(V1.WorldPosition) ||
|
|
ImportanceBounds.GetBox().IsInside(V2.WorldPosition))
|
|
{
|
|
SurfaceAreaWithinImportanceVolume += TriangleArea;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
rtcCommitGeometry(EmbreeGeom);
|
|
GeomID = rtcAttachGeometry(EmbreeScene, EmbreeGeom);
|
|
rtcReleaseGeometry(EmbreeGeom);
|
|
|
|
check(rtcGetDeviceError(EmbreeDevice) == RTC_ERROR_NONE);
|
|
#else
|
|
rtcUnmapBuffer(EmbreeScene, GeomID, RTC_VERTEX_BUFFER);
|
|
rtcUnmapBuffer(EmbreeScene, GeomID, RTC_INDEX_BUFFER);
|
|
|
|
check(rtcDeviceGetError(EmbreeDevice) == RTC_NO_ERROR);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
|
|
void CalculateSurfaceArea(const FStaticLightingMesh* Mesh, const FBoxSphereBounds3f& ImportanceBounds, float& SurfaceArea, float& SurfaceAreaWithinImportanceVolume)
|
|
{
|
|
SurfaceArea = 0.0f;
|
|
SurfaceAreaWithinImportanceVolume = 0.0f;
|
|
|
|
for (int32 TriangleIndex = 0; TriangleIndex < Mesh->NumTriangles; TriangleIndex++)
|
|
{
|
|
int32 I0 = 0, I1 = 0, I2 = 0;
|
|
FStaticLightingVertex V0, V1, V2;
|
|
int32 ElementIndex;
|
|
|
|
Mesh->GetTriangleIndices(TriangleIndex, I0, I1, I2);
|
|
Mesh->GetTriangle(TriangleIndex, V0, V1, V2, ElementIndex);
|
|
|
|
// Compute the triangle's normal.
|
|
const FVector4f TriangleNormal = (V2.WorldPosition - V0.WorldPosition) ^ (V1.WorldPosition - V0.WorldPosition);
|
|
// Compute the triangle area.
|
|
const float TriangleArea = TriangleNormal.Size3() * 0.5f;
|
|
|
|
SurfaceArea += TriangleArea;
|
|
|
|
// Sum the total triangle area of everything in the aggregate mesh within the importance volume,
|
|
// if any vertex is contained or if there is no importance volume at all
|
|
if (ImportanceBounds.SphereRadius < DELTA ||
|
|
ImportanceBounds.GetBox().IsInside(V0.WorldPosition) ||
|
|
ImportanceBounds.GetBox().IsInside(V1.WorldPosition) ||
|
|
ImportanceBounds.GetBox().IsInside(V2.WorldPosition))
|
|
{
|
|
SurfaceAreaWithinImportanceVolume += TriangleArea;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
bool EmbreeMemoryMonitor(void* ptr, ssize_t bytes, bool post)
|
|
#else
|
|
bool EmbreeMemoryMonitor(const ssize_t bytes, const bool post)
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
{
|
|
FPlatformAtomics::InterlockedAdd(&GEmbreeAllocatedSpace, bytes);
|
|
return true;
|
|
}
|
|
|
|
FEmbreeAggregateMesh::FEmbreeAggregateMesh(const FScene& InScene):
|
|
FStaticLightingAggregateMesh(InScene),
|
|
EmbreeDevice(NULL),
|
|
EmbreeScene(NULL),
|
|
TotalNumTriangles(0),
|
|
TotalNumTrianglesInstanced(0)
|
|
{
|
|
EmbreeDevice = InScene.EmbreeDevice;
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
rtcSetDeviceMemoryMonitorFunction(EmbreeDevice, EmbreeMemoryMonitor, NULL);
|
|
|
|
EmbreeScene = rtcNewScene(EmbreeDevice);
|
|
rtcSetSceneBuildQuality(EmbreeScene, RTC_BUILD_QUALITY_MEDIUM);
|
|
check(rtcGetDeviceError(EmbreeDevice) == RTC_ERROR_NONE);
|
|
#else
|
|
rtcDeviceSetMemoryMonitorFunction(EmbreeDevice, EmbreeMemoryMonitor);
|
|
|
|
uint32 AlgorithmFlags = RTC_INTERSECT1;
|
|
|
|
if (InScene.GeneralSettings.bUseEmbreePacketTracing)
|
|
{
|
|
AlgorithmFlags |= RTC_INTERSECT4;
|
|
}
|
|
|
|
EmbreeScene = rtcDeviceNewScene(EmbreeDevice, RTC_SCENE_STATIC, (RTCAlgorithmFlags)AlgorithmFlags);
|
|
check(rtcDeviceGetError(EmbreeDevice) == RTC_NO_ERROR);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
if (InScene.GeneralSettings.bUseEmbreePacketTracing)
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
ssize_t SupportsPacketTracing = rtcGetDeviceProperty(EmbreeDevice, RTC_DEVICE_PROPERTY_NATIVE_RAY4_SUPPORTED);
|
|
check(SupportsPacketTracing);
|
|
#else
|
|
#if RTCORE_VERSION_MAJOR >= 2 && RTCORE_VERSION_MINOR >= 14
|
|
ssize_t SupportsPacketTracing = rtcDeviceGetParameter1i(EmbreeDevice, RTC_CONFIG_INTERSECT4);
|
|
check(SupportsPacketTracing);
|
|
#endif
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
}
|
|
|
|
FEmbreeAggregateMesh::~FEmbreeAggregateMesh()
|
|
{
|
|
for (int32 i = 0; i < MeshInfos.Num(); i++)
|
|
{
|
|
delete MeshInfos[i];
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
rtcReleaseScene(EmbreeScene);
|
|
#else
|
|
rtcDeleteScene(EmbreeScene);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
|
|
/**
|
|
* Merges a mesh into the shadow mesh.
|
|
* @param Mesh - The mesh the triangle comes from.
|
|
*/
|
|
void FEmbreeAggregateMesh::AddMesh(const FStaticLightingMesh* Mesh, const FStaticLightingMapping* Mapping)
|
|
{
|
|
// Only use shadow casting meshes.
|
|
if( Mesh->LightingFlags&GI_INSTANCE_CASTSHADOW )
|
|
{
|
|
SceneBounds = SceneBounds + Mesh->BoundingBox;
|
|
|
|
if (Scene.GeneralSettings.bUseEmbreeInstancing && Mesh->GetInstanceableStaticMesh() != nullptr)
|
|
{
|
|
const FStaticMeshStaticLightingMesh* StaticMeshInstance = Mesh->GetInstanceableStaticMesh();
|
|
|
|
const FStaticMeshLOD* LOD = &StaticMeshInstance->StaticMesh->GetLOD(StaticMeshInstance->GetMeshLODIndex());
|
|
|
|
if (!StaticMeshGeometries.Find(LOD))
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
RTCScene MeshScene = rtcNewScene(EmbreeDevice);
|
|
rtcSetSceneBuildQuality(MeshScene, RTC_BUILD_QUALITY_MEDIUM);
|
|
#else
|
|
RTCScene MeshScene = rtcDeviceNewScene(EmbreeDevice, RTC_SCENE_STATIC, RTC_INTERSECT1);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
FEmbreeGeometry* Geo = new FEmbreeGeometry(EmbreeDevice, MeshScene, Scene.GetImportanceBounds(), Mesh, Mapping, true);
|
|
Geo->ParentAggregateMesh = this;
|
|
StaticMeshGeometries.Add(LOD, FEmbreeStaticMeshGeometry { MeshScene, Geo });
|
|
MeshInfos.Add(Geo);
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
rtcSetGeometryIntersectFilterFunction(rtcGetGeometry(MeshScene, Geo->GeomID), EmbreeFilterFunc);
|
|
rtcSetGeometryOccludedFilterFunction(rtcGetGeometry(MeshScene, Geo->GeomID), EmbreeFilterFunc);
|
|
|
|
rtcSetGeometryUserData(rtcGetGeometry(MeshScene, Geo->GeomID), Geo);
|
|
|
|
rtcCommitScene(MeshScene);
|
|
#else
|
|
rtcSetIntersectionFilterFunction(MeshScene, Geo->GeomID, EmbreeFilterFunc);
|
|
rtcSetOcclusionFilterFunction(MeshScene, Geo->GeomID, EmbreeFilterFunc);
|
|
|
|
rtcSetIntersectionFilterFunction4(MeshScene, Geo->GeomID, EmbreeFilterFunc4);
|
|
rtcSetOcclusionFilterFunction4(MeshScene, Geo->GeomID, EmbreeFilterFunc4);
|
|
|
|
rtcSetUserData(MeshScene, Geo->GeomID, Geo);
|
|
|
|
rtcCommit(MeshScene);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
bHasShadowCastingPrimitives |= Geo->bHasShadowCastingPrimitives;
|
|
|
|
TotalNumTriangles += Mesh->NumTriangles;
|
|
}
|
|
else
|
|
{
|
|
TotalNumTrianglesInstanced += Mesh->NumTriangles;
|
|
}
|
|
|
|
// Sum the total triangle area of everything in the aggregate mesh
|
|
float SurfaceArea, SurfaceAreaWithinImportanceVolume;
|
|
CalculateSurfaceArea(Mesh, Scene.GetImportanceBounds(), SurfaceArea, SurfaceAreaWithinImportanceVolume);
|
|
SceneSurfaceArea += SurfaceArea;
|
|
SceneSurfaceAreaWithinImportanceVolume += SurfaceAreaWithinImportanceVolume;
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
RTCGeometry EmbreeGeom = rtcNewGeometry(EmbreeDevice, RTC_GEOMETRY_TYPE_INSTANCE);
|
|
rtcSetGeometryInstancedScene(EmbreeGeom, StaticMeshGeometries[LOD].MeshScene);
|
|
rtcSetGeometryTimeStepCount(EmbreeGeom, 1);
|
|
rtcCommitGeometry(EmbreeGeom);
|
|
unsigned int InstID = rtcAttachGeometry(EmbreeScene, EmbreeGeom);
|
|
rtcSetGeometryUserData(EmbreeGeom, (void*)Mapping);
|
|
rtcSetGeometryTransform(EmbreeGeom, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, (const float*)StaticMeshInstance->LocalToWorld.M);
|
|
rtcReleaseGeometry(EmbreeGeom);
|
|
check(rtcGetDeviceError(EmbreeDevice) == RTC_ERROR_NONE);
|
|
#else
|
|
auto InstID = rtcNewInstance(EmbreeScene, StaticMeshGeometries[LOD].MeshScene);
|
|
rtcSetUserData(EmbreeScene, InstID, (void*)Mapping);
|
|
rtcSetTransform(EmbreeScene, InstID, RTC_MATRIX_COLUMN_MAJOR_ALIGNED16, (const float*)StaticMeshInstance->LocalToWorld.M);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
if (StaticMeshInstancesToMappings.Num() < (int32)InstID + 1)
|
|
{
|
|
StaticMeshInstancesToMappings.SetNum(InstID + 1);
|
|
}
|
|
StaticMeshInstancesToMappings[InstID] = Mapping;
|
|
|
|
}
|
|
else
|
|
{
|
|
FEmbreeGeometry* Geo = new FEmbreeGeometry(EmbreeDevice, EmbreeScene, Scene.GetImportanceBounds(), Mesh, Mapping, false);
|
|
MeshInfos.Add(Geo);
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
rtcSetGeometryUserData(rtcGetGeometry(EmbreeScene, Geo->GeomID), Geo);
|
|
rtcSetGeometryIntersectFilterFunction(rtcGetGeometry(EmbreeScene, Geo->GeomID), EmbreeFilterFunc);
|
|
rtcSetGeometryOccludedFilterFunction(rtcGetGeometry(EmbreeScene, Geo->GeomID), EmbreeFilterFunc);
|
|
#else
|
|
rtcSetUserData(EmbreeScene, Geo->GeomID, Geo);
|
|
rtcSetIntersectionFilterFunction(EmbreeScene, Geo->GeomID, EmbreeFilterFunc);
|
|
rtcSetOcclusionFilterFunction(EmbreeScene, Geo->GeomID, EmbreeFilterFunc);
|
|
|
|
rtcSetIntersectionFilterFunction4(EmbreeScene, Geo->GeomID, EmbreeFilterFunc4);
|
|
rtcSetOcclusionFilterFunction4(EmbreeScene, Geo->GeomID, EmbreeFilterFunc4);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
bHasShadowCastingPrimitives |= Geo->bHasShadowCastingPrimitives;
|
|
|
|
// Sum the total triangle area of everything in the aggregate mesh
|
|
SceneSurfaceArea += Geo->SurfaceArea;
|
|
SceneSurfaceAreaWithinImportanceVolume += Geo->SurfaceAreaWithinImportanceVolume;
|
|
TotalNumTriangles += Mesh->NumTriangles;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FEmbreeAggregateMesh::PrepareForRaytracing()
|
|
{
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
rtcCommitScene(EmbreeScene);
|
|
check(rtcGetDeviceError(EmbreeDevice) == RTC_ERROR_NONE);
|
|
#else
|
|
rtcCommit(EmbreeScene);
|
|
check(rtcDeviceGetError(EmbreeDevice) == RTC_NO_ERROR);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
const float Buildtime = FPlatformTime::Seconds() - StartTime;
|
|
UE_LOG(LogLightmass, Log, TEXT("Embree Build %.1fs"), Buildtime);
|
|
}
|
|
|
|
void FEmbreeAggregateMesh::DumpStats() const
|
|
{
|
|
int64 MeshInfoSize = sizeof(FEmbreeGeometry) * MeshInfos.Num();
|
|
int64 UVSize = 0;
|
|
int64 LightmapUV = 0;
|
|
|
|
for (int32 i = 0; i < MeshInfos.Num(); i++)
|
|
{
|
|
const FEmbreeGeometry* Geo = MeshInfos[i];
|
|
UVSize += Geo->UVs.GetAllocatedSize();
|
|
LightmapUV += Geo->LightmapUVs.GetAllocatedSize();
|
|
}
|
|
|
|
UE_LOG(LogLightmass, Log, TEXT("\n"));
|
|
UE_LOG(LogLightmass, Log, TEXT("Collision Mesh Overview:"));
|
|
if (Scene.GeneralSettings.bUseEmbreeInstancing)
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("Num Triangles : %d (Instanced to %d)"), TotalNumTriangles, TotalNumTriangles + TotalNumTrianglesInstanced);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLightmass, Log, TEXT("Num Triangles : %d"), TotalNumTriangles);
|
|
}
|
|
UE_LOG(LogLightmass, Log, TEXT("MeshInfos : %7.1fMb"), MeshInfoSize / 1048576.0f);
|
|
UE_LOG(LogLightmass, Log, TEXT("UVs : %7.1fMb"), UVSize / 1048576.0f);
|
|
UE_LOG(LogLightmass, Log, TEXT("LightmapUVs : %7.1fMb"), LightmapUV / 1048576.0f);
|
|
UE_LOG(LogLightmass, Log, TEXT("Embree Used Memory : %7.1fMb"), GEmbreeAllocatedSpace / 1048576.0f);
|
|
UE_LOG(LogLightmass, Log, TEXT("\n"));
|
|
}
|
|
|
|
bool FEmbreeAggregateMesh::IntersectLightRay(
|
|
const FLightRay& LightRay,
|
|
bool bFindClosestIntersection,
|
|
bool bCalculateTransmission,
|
|
bool bDirectShadowingRay,
|
|
FCoherentRayCache& CoherentRayCache,
|
|
FLightRayIntersection& ClosestIntersection) const
|
|
{
|
|
LIGHTINGSTAT(FScopedRDTSCTimer RayTraceTimer(bFindClosestIntersection ? CoherentRayCache.FirstHitRayTraceTime : CoherentRayCache.BooleanRayTraceTime);)
|
|
bFindClosestIntersection ? CoherentRayCache.NumFirstHitRaysTraced++ : CoherentRayCache.NumBooleanRaysTraced++;
|
|
// Calculating transmission requires finding the closest intersection for now
|
|
//@todo - allow boolean visibility tests while calculating transmission
|
|
checkSlow(!bCalculateTransmission || bFindClosestIntersection);
|
|
|
|
ClosestIntersection.bIntersects = false;
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
FEmbreeContext EmbreeContext(
|
|
LightRay.Mesh,
|
|
LightRay.Mapping ? LightRay.Mapping->Mesh : NULL,
|
|
LightRay.TraceFlags,
|
|
bFindClosestIntersection,
|
|
bCalculateTransmission,
|
|
bDirectShadowingRay);
|
|
#if USE_EMBREE_MAJOR_VERSION >= 4
|
|
rtcInitRayQueryContext(&EmbreeContext);
|
|
#else
|
|
rtcInitIntersectContext(&EmbreeContext);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 4
|
|
|
|
RTCRayHit EmbreeRayHit;
|
|
EmbreeRayHit.ray.org_x = LightRay.Start.X;
|
|
EmbreeRayHit.ray.org_y = LightRay.Start.Y;
|
|
EmbreeRayHit.ray.org_z = LightRay.Start.Z;
|
|
EmbreeRayHit.ray.tnear = 0.0f;
|
|
|
|
EmbreeRayHit.ray.dir_x = LightRay.Direction.X;
|
|
EmbreeRayHit.ray.dir_y = LightRay.Direction.Y;
|
|
EmbreeRayHit.ray.dir_z = LightRay.Direction.Z;
|
|
EmbreeRayHit.ray.tfar = LightRay.Length;
|
|
|
|
EmbreeRayHit.ray.time = 0.0f;
|
|
EmbreeRayHit.ray.mask = 0xFFFFFFFF;
|
|
EmbreeRayHit.ray.flags = 0u;
|
|
|
|
EmbreeRayHit.hit.u = EmbreeRayHit.hit.v = 0.0f;
|
|
|
|
EmbreeRayHit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
|
|
#else
|
|
FEmbreeRay EmbreeRay(
|
|
LightRay.Mesh,
|
|
LightRay.Mapping ? LightRay.Mapping->Mesh : NULL,
|
|
LightRay.TraceFlags,
|
|
bFindClosestIntersection,
|
|
bCalculateTransmission,
|
|
bDirectShadowingRay);
|
|
|
|
EmbreeRay.org[0] = LightRay.Start.X;
|
|
EmbreeRay.org[1] = LightRay.Start.Y;
|
|
EmbreeRay.org[2] = LightRay.Start.Z;
|
|
EmbreeRay.dir[0] = LightRay.Direction.X;
|
|
EmbreeRay.dir[1] = LightRay.Direction.Y;
|
|
EmbreeRay.dir[2] = LightRay.Direction.Z;
|
|
EmbreeRay.tnear = 0;
|
|
EmbreeRay.tfar = LightRay.Length;
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
if (bFindClosestIntersection)
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
#if USE_EMBREE_MAJOR_VERSION >= 4
|
|
RTCIntersectArguments args;
|
|
rtcInitIntersectArguments(&args);
|
|
args.context = &EmbreeContext;
|
|
rtcIntersect1(EmbreeScene, &EmbreeRayHit, &args);
|
|
#else
|
|
rtcIntersect1(EmbreeScene, &EmbreeContext, &EmbreeRayHit);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 4
|
|
EmbreeRayHit.hit.Ng_x = -EmbreeRayHit.hit.Ng_x;
|
|
EmbreeRayHit.hit.Ng_y = -EmbreeRayHit.hit.Ng_y;
|
|
EmbreeRayHit.hit.Ng_z = -EmbreeRayHit.hit.Ng_z;
|
|
#else
|
|
rtcIntersect(EmbreeScene, EmbreeRay);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
}
|
|
else
|
|
{
|
|
#if USE_EMBREE_MAJOR_VERSION >= 4
|
|
RTCOccludedArguments args;
|
|
rtcInitOccludedArguments(&args);
|
|
args.context = &EmbreeContext;
|
|
rtcOccluded1(EmbreeScene, &EmbreeRayHit.ray, &args);
|
|
#elif USE_EMBREE_MAJOR_VERSION == 3
|
|
rtcOccluded1(EmbreeScene, &EmbreeContext, &EmbreeRayHit.ray);
|
|
#else
|
|
rtcOccluded(EmbreeScene, EmbreeRay);
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 4
|
|
}
|
|
|
|
#if USE_EMBREE_MAJOR_VERSION >= 3
|
|
if (EmbreeRayHit.ray.tfar >= 0.0f && EmbreeRayHit.hit.geomID != -1 && EmbreeRayHit.hit.primID != -1)
|
|
{
|
|
FMinimalStaticLightingVertex EmbreeVertex;
|
|
EmbreeVertex.WorldPosition = LightRay.Start + LightRay.Direction * EmbreeRayHit.ray.tfar;
|
|
|
|
EmbreeVertex.TextureCoordinates[0] = EmbreeContext.TextureCoordinates;
|
|
EmbreeVertex.TextureCoordinates[1] = EmbreeContext.LightmapCoordinates;
|
|
|
|
if (EmbreeRayHit.hit.instID[0] == -1)
|
|
{
|
|
const FEmbreeGeometry& Geo = *(FEmbreeGeometry*)rtcGetGeometryUserData(rtcGetGeometry(EmbreeScene, EmbreeRayHit.hit.geomID));
|
|
EmbreeVertex.WorldTangentZ = FVector3f(EmbreeRayHit.hit.Ng_x, EmbreeRayHit.hit.Ng_y, EmbreeRayHit.hit.Ng_z).GetSafeNormal();
|
|
ClosestIntersection = FLightRayIntersection(true, EmbreeVertex, Geo.Mesh, Geo.Mapping, EmbreeContext.ElementIndex);
|
|
}
|
|
else
|
|
{
|
|
FStaticLightingMapping* Mapping = (FStaticLightingMapping*)rtcGetGeometryUserData(rtcGetGeometry(EmbreeScene, EmbreeRayHit.hit.instID[0]));
|
|
FVector3f GeometryNormal(EmbreeRayHit.hit.Ng_x, EmbreeRayHit.hit.Ng_y, EmbreeRayHit.hit.Ng_z);
|
|
EmbreeVertex.WorldTangentZ = Mapping->Mesh->GetInstanceableStaticMesh()->LocalToWorldInverseTranspose.TransformVector(GeometryNormal).GetSafeNormal();
|
|
ClosestIntersection = FLightRayIntersection(true, EmbreeVertex, Mapping->Mesh, Mapping, EmbreeContext.ElementIndex);
|
|
}
|
|
EmbreeContext.TransmissionAcc.Resolve(ClosestIntersection.Transmission, EmbreeRayHit.ray.tfar);
|
|
}
|
|
else
|
|
{
|
|
EmbreeContext.TransmissionAcc.Resolve(ClosestIntersection.Transmission);
|
|
}
|
|
#else
|
|
if (EmbreeRay.geomID != -1 && EmbreeRay.primID != -1)
|
|
{
|
|
FMinimalStaticLightingVertex EmbreeVertex;
|
|
EmbreeVertex.WorldPosition = LightRay.Start + LightRay.Direction * EmbreeRay.tfar;
|
|
|
|
EmbreeVertex.TextureCoordinates[0] = EmbreeRay.TextureCoordinates;
|
|
EmbreeVertex.TextureCoordinates[1] = EmbreeRay.LightmapCoordinates;
|
|
|
|
if (EmbreeRay.instID == -1)
|
|
{
|
|
const FEmbreeGeometry& Geo = *(FEmbreeGeometry*)rtcGetUserData(EmbreeScene, EmbreeRay.geomID);
|
|
EmbreeVertex.WorldTangentZ = FVector3f(EmbreeRay.Ng[0], EmbreeRay.Ng[1], EmbreeRay.Ng[2]).GetSafeNormal();
|
|
ClosestIntersection = FLightRayIntersection(true, EmbreeVertex, Geo.Mesh, Geo.Mapping, EmbreeRay.ElementIndex);
|
|
}
|
|
else
|
|
{
|
|
FStaticLightingMapping* Mapping = (FStaticLightingMapping*)rtcGetUserData(EmbreeScene, EmbreeRay.instID);
|
|
FVector3f GeometryNormal(EmbreeRay.Ng[0], EmbreeRay.Ng[1], EmbreeRay.Ng[2]);
|
|
EmbreeVertex.WorldTangentZ = Mapping->Mesh->GetInstanceableStaticMesh()->LocalToWorldInverseTranspose.TransformVector(GeometryNormal).GetSafeNormal();
|
|
ClosestIntersection = FLightRayIntersection(true, EmbreeVertex, Mapping->Mesh, Mapping, EmbreeRay.ElementIndex);
|
|
}
|
|
EmbreeRay.TransmissionAcc.Resolve(ClosestIntersection.Transmission, EmbreeRay.tfar);
|
|
}
|
|
else
|
|
{
|
|
EmbreeRay.TransmissionAcc.Resolve(ClosestIntersection.Transmission);
|
|
}
|
|
#endif // USE_EMBREE_MAJOR_VERSION >= 3
|
|
|
|
return ClosestIntersection.bIntersects;
|
|
}
|
|
|
|
FEmbreeVerifyAggregateMesh::FEmbreeVerifyAggregateMesh(const FScene& InScene) :
|
|
Super(InScene),
|
|
DefaultAggregate(InScene),
|
|
EmbreeAggregate(InScene),
|
|
TransmissionMismatchCount(0),
|
|
TransmissionEqualCount(0),
|
|
CheckEqualCount(0),
|
|
CheckMismatchCount(0)
|
|
{
|
|
}
|
|
|
|
void FEmbreeVerifyAggregateMesh::AddMesh(const FStaticLightingMesh* Mesh, const FStaticLightingMapping* Mapping)
|
|
{
|
|
DefaultAggregate.AddMesh(Mesh, Mapping);
|
|
EmbreeAggregate.AddMesh(Mesh, Mapping);
|
|
|
|
// Update properties affected by AddMesh
|
|
bHasShadowCastingPrimitives = DefaultAggregate.bHasShadowCastingPrimitives;
|
|
SceneBounds = DefaultAggregate.SceneBounds;
|
|
SceneSurfaceArea = DefaultAggregate.SceneSurfaceArea;
|
|
SceneSurfaceAreaWithinImportanceVolume = DefaultAggregate.SceneSurfaceAreaWithinImportanceVolume;
|
|
}
|
|
|
|
void FEmbreeVerifyAggregateMesh::ReserveMemory(int32 NumMeshes, int32 NumVertices, int32 NumTriangles)
|
|
{
|
|
DefaultAggregate.ReserveMemory(NumMeshes, NumVertices, NumTriangles);
|
|
EmbreeAggregate.ReserveMemory(NumMeshes, NumVertices, NumTriangles);
|
|
}
|
|
|
|
void FEmbreeVerifyAggregateMesh::PrepareForRaytracing()
|
|
{
|
|
DefaultAggregate.PrepareForRaytracing();
|
|
EmbreeAggregate.PrepareForRaytracing();
|
|
}
|
|
|
|
void FEmbreeVerifyAggregateMesh::DumpStats() const
|
|
{
|
|
DefaultAggregate.DumpStats();
|
|
EmbreeAggregate.DumpStats();
|
|
}
|
|
|
|
void FEmbreeVerifyAggregateMesh::DumpCheckStats() const
|
|
{
|
|
DefaultAggregate.DumpCheckStats();
|
|
EmbreeAggregate.DumpCheckStats();
|
|
|
|
UE_LOG(LogLightmass, Display, TEXT("\n\n"));
|
|
UE_LOG(LogLightmass, Display, TEXT("============================================================"));
|
|
|
|
float r = TransmissionMismatchCount > 0 ? (TransmissionMismatchCount / (float)(TransmissionEqualCount + TransmissionMismatchCount)) : 0;
|
|
UE_LOG(LogLightmass, Log, TEXT("Embree transmission divergence : %d / %d [%.7f]"), TransmissionMismatchCount, TransmissionEqualCount + TransmissionMismatchCount, r);
|
|
|
|
r = CheckMismatchCount > 0 ? (CheckMismatchCount / (float)(CheckEqualCount + CheckMismatchCount)) : 0;
|
|
UE_LOG(LogLightmass, Log, TEXT("Embree check divergence : %d / %d [%.7f]"), CheckMismatchCount, CheckEqualCount + CheckMismatchCount, r);
|
|
|
|
UE_LOG(LogLightmass, Display, TEXT("============================================================"));
|
|
UE_LOG(LogLightmass, Display, TEXT("\n\n"));
|
|
}
|
|
|
|
bool FEmbreeVerifyAggregateMesh::VerifyTransmissions(const FLightRayIntersection& EmbreeIntersection, const FLightRayIntersection& ClosestIntersection)
|
|
{
|
|
const_cast<float&>(ClosestIntersection.Transmission.A) = 1.f;
|
|
return EmbreeIntersection.Transmission.Equals(ClosestIntersection.Transmission, 0.01f);
|
|
}
|
|
|
|
bool FEmbreeVerifyAggregateMesh::VerifyChecks(const FLightRayIntersection& EmbreeIntersection, const FLightRayIntersection& ClosestIntersection, bool bFindClosestIntersection)
|
|
{
|
|
if (EmbreeIntersection.bIntersects != ClosestIntersection.bIntersects)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (bFindClosestIntersection && EmbreeIntersection.bIntersects)
|
|
{
|
|
if (EmbreeIntersection.ElementIndex != ClosestIntersection.ElementIndex)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const_cast<FVector4f::FReal&>(EmbreeIntersection.IntersectionVertex.WorldPosition.W) = const_cast<FVector4f::FReal&>(ClosestIntersection.IntersectionVertex.WorldPosition.W) = 1;
|
|
if (!EmbreeIntersection.IntersectionVertex.WorldPosition.Equals(ClosestIntersection.IntersectionVertex.WorldPosition, .1f))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const_cast<FVector4f::FReal&>(EmbreeIntersection.IntersectionVertex.WorldTangentZ.W) = const_cast<FVector4f::FReal&>(ClosestIntersection.IntersectionVertex.WorldTangentZ.W) = 0;
|
|
if (!EmbreeIntersection.IntersectionVertex.WorldTangentZ.Equals(ClosestIntersection.IntersectionVertex.WorldTangentZ, .01f))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FVector4f EmbreeCoord = FVector4f(EmbreeIntersection.IntersectionVertex.TextureCoordinates[0].X, EmbreeIntersection.IntersectionVertex.TextureCoordinates[0].Y,
|
|
EmbreeIntersection.IntersectionVertex.TextureCoordinates[1].X, EmbreeIntersection.IntersectionVertex.TextureCoordinates[1].Y);
|
|
FVector4f ClosestCoord = FVector4f(ClosestIntersection.IntersectionVertex.TextureCoordinates[0].X, ClosestIntersection.IntersectionVertex.TextureCoordinates[0].Y,
|
|
ClosestIntersection.IntersectionVertex.TextureCoordinates[1].X, ClosestIntersection.IntersectionVertex.TextureCoordinates[1].Y);
|
|
|
|
if (!EmbreeCoord.Equals(ClosestCoord, .01f))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FEmbreeVerifyAggregateMesh::IntersectLightRay(
|
|
const FLightRay& LightRay,
|
|
bool bFindClosestIntersection,
|
|
bool bCalculateTransmission,
|
|
bool bDirectShadowingRay,
|
|
FCoherentRayCache& CoherentRayCache,
|
|
FLightRayIntersection& ClosestIntersection) const
|
|
{
|
|
DefaultAggregate.IntersectLightRay(LightRay, bFindClosestIntersection, bCalculateTransmission, bDirectShadowingRay, CoherentRayCache, ClosestIntersection);
|
|
|
|
FLightRayIntersection EmbreeIntersection;
|
|
EmbreeAggregate.IntersectLightRay(LightRay, bFindClosestIntersection, bCalculateTransmission, bDirectShadowingRay, CoherentRayCache, EmbreeIntersection);
|
|
|
|
if (bCalculateTransmission)
|
|
{
|
|
if (VerifyTransmissions(EmbreeIntersection, ClosestIntersection))
|
|
{
|
|
FPlatformAtomics::InterlockedIncrement(&TransmissionEqualCount);
|
|
}
|
|
else
|
|
{
|
|
FPlatformAtomics::InterlockedIncrement(&TransmissionMismatchCount);
|
|
}
|
|
}
|
|
|
|
if (VerifyChecks(EmbreeIntersection, ClosestIntersection, bFindClosestIntersection))
|
|
{
|
|
FPlatformAtomics::InterlockedIncrement(&CheckEqualCount);
|
|
}
|
|
else
|
|
{
|
|
FPlatformAtomics::InterlockedIncrement(&CheckMismatchCount);
|
|
}
|
|
|
|
return ClosestIntersection.bIntersects;
|
|
}
|
|
|
|
} // namespace
|
|
#endif // USE_EMBREE
|