Introduce separate GI directory
This commit is contained in:
238
Source/Shaders/GI/GlobalSurfaceAtlas.hlsl
Normal file
238
Source/Shaders/GI/GlobalSurfaceAtlas.hlsl
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "./Flax/Common.hlsl"
|
||||
#include "./Flax/Collisions.hlsl"
|
||||
|
||||
// This must match C++
|
||||
#define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling
|
||||
#define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4
|
||||
#define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile
|
||||
#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling
|
||||
#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes)
|
||||
|
||||
struct GlobalSurfaceTile
|
||||
{
|
||||
float4 AtlasRectUV;
|
||||
float4x4 WorldToLocal;
|
||||
float3 ViewBoundsSize;
|
||||
};
|
||||
|
||||
struct GlobalSurfaceObject
|
||||
{
|
||||
float3 BoundsPosition;
|
||||
float BoundsRadius;
|
||||
float4x4 WorldToLocal;
|
||||
float3 Extent;
|
||||
uint TileOffsets[6];
|
||||
uint DataSize; // count of float4s for object+tiles
|
||||
};
|
||||
|
||||
float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer<float4> objects, uint objectAddress)
|
||||
{
|
||||
// This must match C++
|
||||
return objects.Load(objectAddress + 0);
|
||||
}
|
||||
|
||||
uint LoadGlobalSurfaceAtlasObjectDataSize(Buffer<float4> objects, uint objectAddress)
|
||||
{
|
||||
// This must match C++
|
||||
return asuint(objects.Load(objectAddress + 1).w);
|
||||
}
|
||||
|
||||
GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer<float4> objects, uint objectAddress)
|
||||
{
|
||||
// This must match C++
|
||||
float4 vector0 = objects.Load(objectAddress + 0);
|
||||
float4 vector1 = objects.Load(objectAddress + 1);
|
||||
float4 vector2 = objects.Load(objectAddress + 2);
|
||||
float4 vector3 = objects.Load(objectAddress + 3);
|
||||
float4 vector4 = objects.Load(objectAddress + 4);
|
||||
float4 vector5 = objects.Load(objectAddress + 5); // w unused
|
||||
GlobalSurfaceObject object = (GlobalSurfaceObject)0;
|
||||
object.BoundsPosition = vector0.xyz;
|
||||
object.BoundsRadius = vector0.w;
|
||||
object.WorldToLocal[0] = float4(vector2.xyz, 0.0f);
|
||||
object.WorldToLocal[1] = float4(vector3.xyz, 0.0f);
|
||||
object.WorldToLocal[2] = float4(vector4.xyz, 0.0f);
|
||||
object.WorldToLocal[3] = float4(vector2.w, vector3.w, vector4.w, 1.0f);
|
||||
object.Extent = vector5.xyz;
|
||||
uint vector1x = asuint(vector1.x);
|
||||
uint vector1y = asuint(vector1.y);
|
||||
uint vector1z = asuint(vector1.z);
|
||||
object.DataSize = asuint(vector1.w);
|
||||
object.TileOffsets[0] = vector1x & 0xffff;
|
||||
object.TileOffsets[1] = vector1x >> 16;
|
||||
object.TileOffsets[2] = vector1y & 0xffff;
|
||||
object.TileOffsets[3] = vector1y >> 16;
|
||||
object.TileOffsets[4] = vector1z & 0xffff;
|
||||
object.TileOffsets[5] = vector1z >> 16;
|
||||
return object;
|
||||
}
|
||||
|
||||
GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer<float4> objects, uint tileAddress)
|
||||
{
|
||||
// This must match C++
|
||||
float4 vector0 = objects.Load(tileAddress + 0);
|
||||
float4 vector1 = objects.Load(tileAddress + 1);
|
||||
float4 vector2 = objects.Load(tileAddress + 2);
|
||||
float4 vector3 = objects.Load(tileAddress + 3);
|
||||
float4 vector4 = objects.Load(tileAddress + 4); // w unused
|
||||
GlobalSurfaceTile tile = (GlobalSurfaceTile)0;
|
||||
tile.AtlasRectUV = vector0.xyzw;
|
||||
tile.WorldToLocal[0] = float4(vector1.xyz, 0.0f);
|
||||
tile.WorldToLocal[1] = float4(vector2.xyz, 0.0f);
|
||||
tile.WorldToLocal[2] = float4(vector3.xyz, 0.0f);
|
||||
tile.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f);
|
||||
tile.ViewBoundsSize = vector4.xyz;
|
||||
return tile;
|
||||
}
|
||||
|
||||
// Global Surface Atlas data for a constant buffer
|
||||
struct GlobalSurfaceAtlasData
|
||||
{
|
||||
float3 ViewPos;
|
||||
float Padding0;
|
||||
float Padding1;
|
||||
float Resolution;
|
||||
float ChunkSize;
|
||||
uint ObjectsCount;
|
||||
};
|
||||
|
||||
float3 SampleGlobalSurfaceAtlasTex(Texture2D atlas, float2 atlasUV, float4 bilinearWeights)
|
||||
{
|
||||
float4 sampleX = atlas.GatherRed(SamplerLinearClamp, atlasUV);
|
||||
float4 sampleY = atlas.GatherGreen(SamplerLinearClamp, atlasUV);
|
||||
float4 sampleZ = atlas.GatherBlue(SamplerLinearClamp, atlasUV);
|
||||
return float3(dot(sampleX, bilinearWeights), dot(sampleY, bilinearWeights), dot(sampleZ, bilinearWeights));
|
||||
}
|
||||
|
||||
float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold)
|
||||
{
|
||||
// Tile normal weight based on the sampling angle
|
||||
float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal));
|
||||
float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal));
|
||||
normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD);
|
||||
if (normalWeight <= 0.0f)
|
||||
return 0;
|
||||
|
||||
// Get tile UV and depth at the world position
|
||||
float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz;
|
||||
float tileDepth = tilePosition.z / tile.ViewBoundsSize.z;
|
||||
float2 tileUV = saturate((tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f);
|
||||
tileUV.y = 1.0 - tileUV.y;
|
||||
float2 atlasUV = tileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy;
|
||||
|
||||
// Calculate bilinear weights
|
||||
float2 bilinearWeightsUV = frac(atlasUV * data.Resolution + 0.5f);
|
||||
float4 bilinearWeights;
|
||||
bilinearWeights.x = (1.0 - bilinearWeightsUV.x) * (bilinearWeightsUV.y);
|
||||
bilinearWeights.y = (bilinearWeightsUV.x) * (bilinearWeightsUV.y);
|
||||
bilinearWeights.z = (bilinearWeightsUV.x) * (1 - bilinearWeightsUV.y);
|
||||
bilinearWeights.w = (1 - bilinearWeightsUV.x) * (1 - bilinearWeightsUV.y);
|
||||
|
||||
// Tile depth weight based on sample position occlusion
|
||||
float4 tileZ = depth.Gather(SamplerLinearClamp, atlasUV, 0.0f);
|
||||
float depthThreshold = 2.0f * surfaceThreshold / tile.ViewBoundsSize.z;
|
||||
float4 depthVisibility = 1.0f;
|
||||
UNROLL
|
||||
for (uint i = 0; i < 4; i++)
|
||||
{
|
||||
depthVisibility[i] = 1.0f - saturate((abs(tileDepth - tileZ[i]) - depthThreshold) / (0.5f * depthThreshold));
|
||||
if (tileZ[i] >= 1.0f)
|
||||
depthVisibility[i] = 0.0f;
|
||||
}
|
||||
float sampleWeight = normalWeight * dot(depthVisibility, bilinearWeights);
|
||||
if (sampleWeight <= 0.0f)
|
||||
return 0;
|
||||
bilinearWeights = depthVisibility * bilinearWeights;
|
||||
//bilinearWeights = normalize(bilinearWeights);
|
||||
|
||||
// Sample atlas texture
|
||||
float3 sampleColor = SampleGlobalSurfaceAtlasTex(atlas, atlasUV, bilinearWeights);
|
||||
|
||||
//return float4(sampleWeight.xxx, sampleWeight);
|
||||
return float4(sampleColor.rgb * sampleWeight, sampleWeight);
|
||||
//return float4(normalWeight.xxx, sampleWeight);
|
||||
}
|
||||
|
||||
// Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction).
|
||||
// surfaceThreshold - Additional threshold (in world-units) between object or tile size compared with input data (error due to SDF or LOD incorrect appearance)
|
||||
float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBuffer chunks, Buffer<float4> culledObjects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold = 20.0f)
|
||||
{
|
||||
float4 result = float4(0, 0, 0, 0);
|
||||
|
||||
// Snap to the closest chunk to get culled objects
|
||||
uint3 chunkCoord = (uint3)clamp(floor((worldPosition - data.ViewPos) / data.ChunkSize + (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)), 0, GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION - 1);
|
||||
uint chunkAddress = (chunkCoord.z * (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION) + chunkCoord.y * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION + chunkCoord.x) * 4;
|
||||
uint objectsStart = chunks.Load(chunkAddress);
|
||||
if (objectsStart == 0)
|
||||
{
|
||||
// Empty chunk
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read objects counter
|
||||
float4 chunkHeader = culledObjects[objectsStart];
|
||||
objectsStart++;
|
||||
uint objectsCount = asuint(chunkHeader.x);
|
||||
if (objectsCount > data.ObjectsCount) // Prevents crashing - don't know why the data is invalid here (rare issue when moving fast though scene with terrain)
|
||||
return result;
|
||||
|
||||
// Loop over culled objects inside the chunk
|
||||
LOOP
|
||||
for (uint objectIndex = 0; objectIndex < objectsCount; objectIndex++)
|
||||
{
|
||||
// Cull point vs sphere
|
||||
uint objectAddress = objectsStart;
|
||||
float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(culledObjects, objectAddress);
|
||||
uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(culledObjects, objectAddress);
|
||||
objectsStart += objectSize;
|
||||
if (distance(objectBounds.xyz, worldPosition) > objectBounds.w)
|
||||
continue;
|
||||
GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(culledObjects, objectAddress);
|
||||
float3 localPosition = mul(float4(worldPosition, 1), object.WorldToLocal).xyz;
|
||||
float3 localExtent = object.Extent + surfaceThreshold;
|
||||
if (any(localPosition > localExtent) || any(localPosition < -localExtent))
|
||||
continue;
|
||||
|
||||
// Remove the scale vector from the transformation matrix
|
||||
float3x3 worldToLocal = (float3x3)object.WorldToLocal;
|
||||
float scaleX = length(worldToLocal[0]);
|
||||
float scaleY = length(worldToLocal[1]);
|
||||
float scaleZ = length(worldToLocal[2]);
|
||||
float3 invScale = float3(
|
||||
scaleX > 0.00001f ? 1.0f / scaleX : 0.0f,
|
||||
scaleY > 0.00001f ? 1.0f / scaleY : 0.0f,
|
||||
scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f);
|
||||
worldToLocal[0] *= invScale.x;
|
||||
worldToLocal[1] *= invScale.y;
|
||||
worldToLocal[2] *= invScale.z;
|
||||
|
||||
// Sample tiles based on the directionality
|
||||
float3 localNormal = normalize(mul(worldNormal, worldToLocal));
|
||||
float3 localNormalSq = localNormal * localNormal;
|
||||
uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1];
|
||||
if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0)
|
||||
{
|
||||
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
|
||||
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
|
||||
}
|
||||
tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3];
|
||||
if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0)
|
||||
{
|
||||
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
|
||||
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
|
||||
}
|
||||
tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5];
|
||||
if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0)
|
||||
{
|
||||
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
|
||||
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize result
|
||||
result.rgb /= max(result.a, 0.0001f);
|
||||
|
||||
return result;
|
||||
}
|
||||
275
Source/Shaders/GI/GlobalSurfaceAtlas.shader
Normal file
275
Source/Shaders/GI/GlobalSurfaceAtlas.shader
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||
|
||||
// Diffuse-only lighting
|
||||
#define NO_SPECULAR
|
||||
|
||||
#include "./Flax/Common.hlsl"
|
||||
#include "./Flax/Math.hlsl"
|
||||
#include "./Flax/LightingCommon.hlsl"
|
||||
#include "./Flax/GlobalSignDistanceField.hlsl"
|
||||
#include "./Flax/GI/GlobalSurfaceAtlas.hlsl"
|
||||
|
||||
META_CB_BEGIN(0, Data)
|
||||
float3 ViewWorldPos;
|
||||
float ViewNearPlane;
|
||||
float Padding00;
|
||||
uint CulledObjectsCapacity;
|
||||
float LightShadowsStrength;
|
||||
float ViewFarPlane;
|
||||
float4 ViewFrustumWorldRays[4];
|
||||
GlobalSDFData GlobalSDF;
|
||||
GlobalSurfaceAtlasData GlobalSurfaceAtlas;
|
||||
LightData Light;
|
||||
META_CB_END
|
||||
|
||||
struct AtlasVertexIput
|
||||
{
|
||||
float2 Position : POSITION0;
|
||||
float2 TileUV : TEXCOORD0;
|
||||
uint TileAddress : TEXCOORD1;
|
||||
};
|
||||
|
||||
struct AtlasVertexOutput
|
||||
{
|
||||
float4 Position : SV_Position;
|
||||
float2 TileUV : TEXCOORD0;
|
||||
nointerpolation uint TileAddress : TEXCOORD1;
|
||||
};
|
||||
|
||||
// Vertex shader for Global Surface Atlas rendering (custom vertex buffer to render per-tile)
|
||||
META_VS(true, FEATURE_LEVEL_SM5)
|
||||
META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
|
||||
META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
|
||||
META_VS_IN_ELEMENT(TEXCOORD, 1, R32_UINT, 0, ALIGN, PER_VERTEX, 0, true)
|
||||
AtlasVertexOutput VS_Atlas(AtlasVertexIput input)
|
||||
{
|
||||
AtlasVertexOutput output;
|
||||
output.Position = float4(input.Position, 1, 1);
|
||||
output.TileUV = input.TileUV;
|
||||
output.TileAddress = input.TileAddress;
|
||||
return output;
|
||||
}
|
||||
|
||||
// Pixel shader for Global Surface Atlas software clearing
|
||||
META_PS(true, FEATURE_LEVEL_SM5)
|
||||
void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out float4 RT1 : SV_Target2, out float4 RT2 : SV_Target3)
|
||||
{
|
||||
Light = float4(0, 0, 0, 0);
|
||||
RT0 = float4(0, 0, 0, 0);
|
||||
RT1 = float4(0, 0, 0, 0);
|
||||
RT2 = float4(1, 0, 0, 0);
|
||||
}
|
||||
|
||||
#ifdef _PS_DirectLighting
|
||||
|
||||
#include "./Flax/GBuffer.hlsl"
|
||||
#include "./Flax/Matrix.hlsl"
|
||||
#include "./Flax/Lighting.hlsl"
|
||||
|
||||
// GBuffer+Depth at 0-3 slots
|
||||
Buffer<float4> GlobalSurfaceAtlasObjects : register(t4);
|
||||
Texture3D<float> GlobalSDFTex[4] : register(t5);
|
||||
Texture3D<float> GlobalSDFMip[4] : register(t9);
|
||||
|
||||
// Pixel shader for Global Surface Atlas shading with direct light contribution
|
||||
META_PS(true, FEATURE_LEVEL_SM5)
|
||||
META_PERMUTATION_1(RADIAL_LIGHT=0)
|
||||
META_PERMUTATION_1(RADIAL_LIGHT=1)
|
||||
float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target
|
||||
{
|
||||
// Load current tile info
|
||||
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.TileAddress);
|
||||
float2 atlasUV = input.TileUV * tile.AtlasRectUV.zw + tile.AtlasRectUV.xy;
|
||||
|
||||
// Load GBuffer sample from atlas
|
||||
GBufferData gBufferData = (GBufferData)0;
|
||||
GBufferSample gBuffer = SampleGBuffer(gBufferData, atlasUV);
|
||||
BRANCH
|
||||
if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT)
|
||||
{
|
||||
// Skip unlit pixels
|
||||
discard;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reconstruct world-space position manually (from uv+depth within a tile)
|
||||
float tileDepth = SampleZ(atlasUV);
|
||||
//float tileNear = -GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET;
|
||||
//float tileFar = tile.ViewBoundsSize.z + 2 * GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET;
|
||||
//gBufferData.ViewInfo.zw = float2(tileFar / (tileFar - tileNear), (-tileFar * tileNear) / (tileFar - tileNear) / tileFar);
|
||||
//gBufferData.ViewInfo.zw = float2(1, 0);
|
||||
//float tileLinearDepth = LinearizeZ(gBufferData, tileDepth);
|
||||
float3 tileSpacePos = float3(input.TileUV.x - 0.5f, 0.5f - input.TileUV.y, tileDepth);
|
||||
float3 gBufferTilePos = tileSpacePos * tile.ViewBoundsSize;
|
||||
float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal);
|
||||
gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz;
|
||||
|
||||
// Calculate shadowing
|
||||
float3 L = Light.Direction;
|
||||
#if RADIAL_LIGHT
|
||||
float3 toLight = Light.Position - gBuffer.WorldPos;
|
||||
float toLightDst = length(toLight);
|
||||
if (toLightDst >= Light.Radius)
|
||||
{
|
||||
// Skip texels outside the light influence range
|
||||
discard;
|
||||
return 0;
|
||||
}
|
||||
L = toLight / toLightDst;
|
||||
#else
|
||||
float toLightDst = GLOBAL_SDF_WORLD_SIZE;
|
||||
#endif
|
||||
float4 shadowMask = 1;
|
||||
if (Light.CastShadows > 0)
|
||||
{
|
||||
float NoL = dot(gBuffer.Normal, L);
|
||||
float shadowBias = 10.0f;
|
||||
float bias = 2 * shadowBias * saturate(1 - NoL) + shadowBias;
|
||||
BRANCH
|
||||
if (NoL > 0)
|
||||
{
|
||||
// TODO: try using shadow map for on-screen pixels
|
||||
// TODO: try using cone trace with Global SDF for smoother shadow (eg. for sun shadows or for area lights)
|
||||
|
||||
// Shot a ray from texel into the light to see if there is any occluder
|
||||
GlobalSDFTrace trace;
|
||||
trace.Init(gBuffer.WorldPos + gBuffer.Normal * shadowBias, L, bias, toLightDst - bias);
|
||||
GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace);
|
||||
shadowMask = hit.IsHit() ? LightShadowsStrength : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
shadowMask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate lighting
|
||||
#if RADIAL_LIGHT
|
||||
bool isSpotLight = Light.SpotAngles.x > -2.0f;
|
||||
#else
|
||||
bool isSpotLight = false;
|
||||
#endif
|
||||
float4 light = GetLighting(ViewWorldPos, Light, gBuffer, shadowMask, RADIAL_LIGHT, isSpotLight);
|
||||
|
||||
return light;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(_CS_CullObjects)
|
||||
|
||||
#include "./Flax/Collisions.hlsl"
|
||||
|
||||
RWByteAddressBuffer RWGlobalSurfaceAtlasChunks : register(u0);
|
||||
RWBuffer<float4> RWGlobalSurfaceAtlasCulledObjects : register(u1);
|
||||
Buffer<float4> GlobalSurfaceAtlasObjects : register(t0);
|
||||
|
||||
// Compute shader for culling objects into chunks
|
||||
META_CS(true, FEATURE_LEVEL_SM5)
|
||||
[numthreads(GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE, GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE, GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE)]
|
||||
void CS_CullObjects(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
|
||||
{
|
||||
uint3 chunkCoord = DispatchThreadId;
|
||||
uint chunkAddress = (chunkCoord.z * (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION) + chunkCoord.y * GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION + chunkCoord.x) * 4;
|
||||
if (chunkAddress == 0)
|
||||
return; // Skip chunk at 0,0,0 (used for counter)
|
||||
float3 chunkMin = GlobalSurfaceAtlas.ViewPos + (chunkCoord - (GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION * 0.5f)) * GlobalSurfaceAtlas.ChunkSize;
|
||||
float3 chunkMax = chunkMin + GlobalSurfaceAtlas.ChunkSize;
|
||||
|
||||
// Count objects data size in this chunk (amount of float4s)
|
||||
uint objectsSize = 0, objectAddress = 0, objectsCount = 0;
|
||||
// TODO: maybe cache 20-30 culled object indices in thread memory to skip culling them again when copying data (maybe reude chunk size to get smaller objects count per chunk)?
|
||||
LOOP
|
||||
for (uint objectIndex = 0; objectIndex < GlobalSurfaceAtlas.ObjectsCount; objectIndex++)
|
||||
{
|
||||
float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(GlobalSurfaceAtlasObjects, objectAddress);
|
||||
uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(GlobalSurfaceAtlasObjects, objectAddress);
|
||||
if (BoxIntersectsSphere(chunkMin, chunkMax, objectBounds.xyz, objectBounds.w))
|
||||
{
|
||||
objectsSize += objectSize;
|
||||
objectsCount++;
|
||||
}
|
||||
objectAddress += objectSize;
|
||||
}
|
||||
if (objectsSize == 0)
|
||||
{
|
||||
// Empty chunk
|
||||
RWGlobalSurfaceAtlasChunks.Store(chunkAddress, 0);
|
||||
return;
|
||||
}
|
||||
objectsSize++; // Include objects count before actual objects data
|
||||
|
||||
// Allocate object data size in the buffer
|
||||
uint objectsStart;
|
||||
RWGlobalSurfaceAtlasChunks.InterlockedAdd(0, objectsSize, objectsStart);
|
||||
if (objectsStart + objectsSize > CulledObjectsCapacity)
|
||||
{
|
||||
// Not enough space in the buffer
|
||||
RWGlobalSurfaceAtlasChunks.Store(chunkAddress, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write object data start
|
||||
RWGlobalSurfaceAtlasChunks.Store(chunkAddress, objectsStart);
|
||||
|
||||
// Write objects count before actual objects data
|
||||
RWGlobalSurfaceAtlasCulledObjects[objectsStart] = float4(asfloat(objectsCount), 0, 0, 0);
|
||||
objectsStart++;
|
||||
|
||||
// Copy objects data in this chunk
|
||||
objectAddress = 0;
|
||||
LOOP
|
||||
for (uint objectIndex = 0; objectIndex < GlobalSurfaceAtlas.ObjectsCount; objectIndex++)
|
||||
{
|
||||
float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(GlobalSurfaceAtlasObjects, objectAddress);
|
||||
uint objectSize = LoadGlobalSurfaceAtlasObjectDataSize(GlobalSurfaceAtlasObjects, objectAddress);
|
||||
if (BoxIntersectsSphere(chunkMin, chunkMax, objectBounds.xyz, objectBounds.w))
|
||||
{
|
||||
for (uint i = 0; i < objectSize; i++)
|
||||
{
|
||||
RWGlobalSurfaceAtlasCulledObjects[objectsStart + i] = GlobalSurfaceAtlasObjects[objectAddress + i];
|
||||
}
|
||||
objectsStart += objectSize;
|
||||
}
|
||||
objectAddress += objectSize;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _PS_Debug
|
||||
|
||||
Texture3D<float> GlobalSDFTex[4] : register(t0);
|
||||
Texture3D<float> GlobalSDFMip[4] : register(t4);
|
||||
ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8);
|
||||
Buffer<float4> GlobalSurfaceAtlasCulledObjects : register(t9);
|
||||
Texture2D GlobalSurfaceAtlasDepth : register(t10);
|
||||
Texture2D GlobalSurfaceAtlasTex : register(t11);
|
||||
|
||||
// Pixel shader for Global Surface Atlas debug drawing
|
||||
META_PS(true, FEATURE_LEVEL_SM5)
|
||||
float4 PS_Debug(Quad_VS2PS input) : SV_Target
|
||||
{
|
||||
#if 0
|
||||
// Preview Global Surface Atlas texture
|
||||
return float4(GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0).rgb, 1);
|
||||
#endif
|
||||
|
||||
// Shot a ray from camera into the Global SDF
|
||||
GlobalSDFTrace trace;
|
||||
float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz;
|
||||
viewRay = normalize(viewRay - ViewWorldPos);
|
||||
trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane);
|
||||
trace.NeedsHitNormal = true;
|
||||
GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace);
|
||||
if (!hit.IsHit())
|
||||
return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1);
|
||||
//return float4(hit.HitNormal * 0.5f + 0.5f, 1);
|
||||
|
||||
// Sample Global Surface Atlas at the hit location
|
||||
float surfaceThreshold = hit.HitCascade * 10.0f + 20.0f; // Scale the threshold based on the hit cascade (less precision)
|
||||
float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold);
|
||||
return float4(surfaceColor.rgb, 1);
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user