Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,27 @@
#pragma only_renderers d3d11 vulkan metal glcore ps5 switch2
#define GROUP_SIZE_X 8
#define GROUP_SIZE_Y 8
Texture2D<float4> g_Source;
RWTexture2D<float4> g_Destination;
SamplerState sampler_g_Source;
uint g_TextureWidth;
uint g_TextureHeight;
// Projects (blits) a face of a cubemap on a 2D texture
#pragma kernel BlitGrayScaleCookie
[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)]
void BlitGrayScaleCookie(in uint2 gidx : SV_DispatchThreadID)
{
if (gidx.x >= g_TextureWidth || gidx.y >= g_TextureHeight)
return;
float2 texCoord = float2((gidx.x + 0.5f) / g_TextureWidth, (gidx.y + 0.5f) / g_TextureHeight);
float c = g_Source.SampleLevel(sampler_g_Source, texCoord, 0).w;
g_Destination[gidx] = float4(c, c, c, 1);
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 557fa399e33bf7647bda5697c5c158df
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,67 @@
#pragma only_renderers d3d11 vulkan metal glcore ps5 switch2
#define GROUP_SIZE_X 8
#define GROUP_SIZE_Y 8
#pragma multi_compile _ GRAYSCALE_BLIT
TextureCube<float4> g_Source;
RWTexture2D<float4> g_Destination;
SamplerState sampler_g_Source;
int g_TextureSize;
int g_Face;
#define PositiveX 0
#define NegativeX 1
#define PositiveY 2
#define NegativeY 3
#define PositiveZ 4
#define NegativeZ 5
// Projects (blits) a face of a cubemap on a 2D texture
#pragma kernel BlitCubemap
[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)]
void BlitCubemap(in uint2 gidx: SV_DispatchThreadID)
{
if (gidx.x >= uint(g_TextureSize) || gidx.y >= uint(g_TextureSize))
return;
float u = (gidx.x + 0.5f) / g_TextureSize;
float v = (gidx.y + 0.5f) / g_TextureSize;
u = 2 * u - 1.0f;
v = 1.0f - 2 * v;
float3 texCoord = 0;
switch (g_Face)
{
case PositiveX:
texCoord = float3(1, v, -u);
break;
case NegativeX:
texCoord = float3(-1, v, u);
break;
case PositiveY:
texCoord = float3(u, 1, -v);
break;
case NegativeY:
texCoord = float3(u, -1, v);
break;
case PositiveZ:
texCoord = float3(u, v, 1);
break;
case NegativeZ:
texCoord = float3(-u, v, -1);
break;
}
float4 c = g_Source.SampleLevel(sampler_g_Source, texCoord, 0);
#if defined(GRAYSCALE_BLIT)
g_Destination[gidx] = float4(c.w, c.w, c.w, 1);
#else
g_Destination[gidx] = float4(c.r, c.g, c.b, 1);
#endif
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5a992812cb320d146a66cc600200cce7
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,296 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ SPARSE_GRID
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/GeometricTools.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
#include "PathTracingCommon.hlsl"
#include "PathTracingRandom.hlsl"
#include "PathTracingLightGrid.hlsl"
#if defined(SHADER_API_MOBILE) && defined(SHADER_API_METAL)
// On pre A9 (iPhone 6S and earlier) devices, the max groupshared size is 16352. So to avoid exceeding that limit, we reduce the group size.
#define GROUP_SIZE_X 4
#define GROUP_SIZE_Y 4
#define GROUP_SIZE_Z 2
#else
#define GROUP_SIZE_X 4
#define GROUP_SIZE_Y 4
#define GROUP_SIZE_Z 4
#endif
#define MAX_RESERVOIRS_PER_VOXEL 64
groupshared float s_SampleCount[GROUP_SIZE_X * GROUP_SIZE_Y * GROUP_SIZE_Z * MAX_RESERVOIRS_PER_VOXEL];
#ifndef PI
#define PI 3.14159265358979323846f
#endif
// Input parameters and lighting state
StructuredBuffer<PTLight> g_LightList;
uint g_NumLights;
uint g_MaxLightsPerCell;
uint g_NumCandidates;
uint g_BuildPass;
// Output indices and grid
RWStructuredBuffer<ThinReservoir> g_LightGridCellsData;
RWStructuredBuffer<uint2> g_LightGrid;
RWStructuredBuffer<uint> g_TotalLightsInGridCount;
float4 PlaneFromNormalAndPosition(float3 normal, float3 pos)
{
return float4(normal, -dot(normal, pos));
}
void GetBoxLightFrustumPlanes(float3 lightOrigin, float3 lightBasisX, float3 lightBasisY, float3 lightBasisZ, float halfSizeX, float halfSizeY, float range, out float4 frustumPlanes[6])
{
// sides along X
frustumPlanes[0] = PlaneFromNormalAndPosition(-lightBasisX, lightOrigin + lightBasisX * halfSizeX);
frustumPlanes[1] = PlaneFromNormalAndPosition(lightBasisX, lightOrigin - lightBasisX * halfSizeX);
// sides along Y
frustumPlanes[2] = PlaneFromNormalAndPosition(-lightBasisY, lightOrigin + lightBasisY * halfSizeY);
frustumPlanes[3] = PlaneFromNormalAndPosition(lightBasisY, lightOrigin - lightBasisY * halfSizeY);
// sides along Z
frustumPlanes[4] = PlaneFromNormalAndPosition(lightBasisZ, lightOrigin);
frustumPlanes[5] = PlaneFromNormalAndPosition(-lightBasisZ, lightOrigin + lightBasisZ * range);
}
void GetPyramidLightFrustumPlanes(float3 lightOrigin, float3 lightBasisX, float3 lightBasisY, float3 lightBasisZ, float halfSizeX, float halfSizeY, float range, out float4 frustumPlanes[6])
{
float2 nX = normalize(float2(1.0, halfSizeX));
float2 nY = normalize(float2(1.0, halfSizeY));
// the plane's normals point towards the interior of the frustum
// Pyramid sides along X
frustumPlanes[0] = PlaneFromNormalAndPosition(nX.y * lightBasisZ + nX.x * lightBasisX, lightOrigin);
frustumPlanes[1] = PlaneFromNormalAndPosition(nX.y * lightBasisZ - nX.x * lightBasisX, lightOrigin);
// Pyramid sides along Y
frustumPlanes[2] = PlaneFromNormalAndPosition(nY.y * lightBasisZ + nY.x * lightBasisY, lightOrigin);
frustumPlanes[3] = PlaneFromNormalAndPosition(nY.y * lightBasisZ - nY.x * lightBasisY, lightOrigin);
// Pyramid near and far planes
frustumPlanes[4] = PlaneFromNormalAndPosition(lightBasisZ, lightOrigin);
frustumPlanes[5] = PlaneFromNormalAndPosition(-lightBasisZ, lightOrigin + lightBasisZ * range);
}
// Estimate the maximum emission in a voxel / Target function for RIS
float3 EstimateEmission(PTLight light, float3 worldPosition)
{
// Evaluate the light's distance attenuation
float distAttenuation = 1.0;
if (light.type != DIRECTIONAL_LIGHT && light.type != ENVIRONMENT_LIGHT && light.type != BOX_LIGHT && light.type != EMISSIVE_MESH)
{
// First compute the point light attenuation
float d = length(light.position - worldPosition);
// To be conservative on the attenuation, we substruct the cell radius of the grid.
// We also substract the radius (of the bounding sphere) of the light
float r = 0.0f;
if (light.type == DISC_LIGHT)
r = light.width;
if (light.type == RECTANGULAR_LIGHT)
r = 0.5f * sqrt(light.width * light.width + light.height * light.height);
// To be conservative on the attenuation, we substruct the cell radius of the grid
d = max(0.01f, d - GetCellSize() - r);
distAttenuation = SmoothWindowedDistanceAttenuation(d * d, rcp(d), light.attenuation.x, light.attenuation.y);
}
// Evaluate the lights's emission + culling by light's bounding shape
const float Deg2Rad = PI * 2.0f / 360.0f;
float3 emission = float3(1.0f, 1.0f, 1.0f);
if (light.type == DIRECTIONAL_LIGHT || light.type == POINT_LIGHT)
{
emission = light.intensity;
}
else if (light.type == ENVIRONMENT_LIGHT || light.type == EMISSIVE_MESH)
{
emission = float3(1.0f, 1.0f, 1.0f);
}
else if (light.type == BOX_LIGHT) // Frustum vs cell AABB culling
{
float4 frustumPlanes[6];
GetBoxLightFrustumPlanes(light.position, light.right, light.up, light.forward, 0.5 * light.width, 0.5 * light.height, light.range, frustumPlanes);
float3 aabbCenter = worldPosition;
float3 aabbExtent = 0.5 * GetCellSize();
bool intersection = IntersectFrustumPlanesAABB(frustumPlanes, aabbCenter, aabbExtent);
emission = intersection ? light.intensity : 0.0f;
}
else if (light.type == PYRAMID_LIGHT) // Frustum vs cell AABB culling
{
float4 frustumPlanes[6];
GetPyramidLightFrustumPlanes(light.position, light.right, light.up, light.forward, 0.5 * light.width, 0.5 * light.height, light.range, frustumPlanes);
float3 aabbCenter = worldPosition;
float3 aabbExtent = 0.5 * GetCellSize();
bool intersection = IntersectFrustumPlanesAABB(frustumPlanes, aabbCenter, aabbExtent);
emission = intersection ? light.intensity : 0.0f;
}
else // SPOT_LIGHT || RECTANGULAR_LIGHT || DISC_LIGHT (cone vs cell's bounding sphere culling)
{
float coneAngle = (light.type == SPOT_LIGHT) ? 0.5 * light.spotAngle.x * Deg2Rad : 0.5f * 180.0f * Deg2Rad;
float4 bSphere = float4(worldPosition, 0.5 * GetCellSize());
bool intersection = IntersectSphericalConeWithSphere(light.position, light.forward, light.range, cos(coneAngle), sin(coneAngle), bSphere);
emission = intersection ? light.intensity : 0.0f;
}
return distAttenuation * emission;
}
float GetLightTargetFunction(float3 pos, uint lightIndex)
{
PTLight light = g_LightList[lightIndex];
float3 emission = EstimateEmission(light, pos);
return Luminance(emission);
}
#pragma kernel BuildConservativeLightGrid
[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, GROUP_SIZE_Z)]
void BuildConservativeLightGrid(in uint3 gidx : SV_DispatchThreadID, in uint threadIndex : SV_GroupIndex)
{
if (gidx.x >= g_GridDimX || gidx.y >= g_GridDimY || gidx.z >= g_GridDimZ)
return;
uint cellIndex = GetCellIndex(gidx);
PathTracingSampler rngState;
rngState.Init(gidx.xy, gidx.z);
float3 pos = GetCellPosition(gidx);
if (g_BuildPass == 0) // compute number of lights overlapping current cell
{
uint lightCount = 0;
for (uint lightIndex = 0; lightIndex < g_NumLights; ++lightIndex)
{
if (GetLightTargetFunction(pos, lightIndex) > 0)
lightCount++;
}
uint unusedOldValue;
InterlockedAdd(g_TotalLightsInGridCount[0], lightCount, unusedOldValue);
g_LightGrid[cellIndex] = uint2(0, lightCount);
}
else // write lights overlapping current cell to g_LightGridCellsData
{
#if SPARSE_GRID
uint lightCount = g_LightGrid[cellIndex].y;
uint firstCellLightDataIndex;
InterlockedAdd(g_TotalLightsInGridCount[0], lightCount, firstCellLightDataIndex);
g_LightGrid[cellIndex].x = firstCellLightDataIndex;
uint cellDataStart = firstCellLightDataIndex;
#else
uint cellDataStart = cellIndex * g_MaxLightsPerCell;
#endif
uint lightDataIndex = 0;
for (uint lightIndex = 0; lightIndex < g_NumLights; ++lightIndex)
{
// Conservative culling mode: if there is a contribution, save the light index / reservoir
// This should be used when the number of candidates equals the number of ligths
if (GetLightTargetFunction(pos, lightIndex) > 0)
{
ThinReservoir reservoir = { lightIndex, 1.0f};
g_LightGridCellsData[cellDataStart + lightDataIndex] = reservoir;
lightDataIndex++;
}
#if !(SPARSE_GRID)
if (lightDataIndex >= g_MaxLightsPerCell)
break;
#endif
}
#if !(SPARSE_GRID)
uint lightCount = lightDataIndex;
g_LightGrid[cellIndex] = uint2(cellDataStart, lightCount);
#endif
}
}
void UpdateReservoir(int reservoirIndex, int lightIndex, float targetFunction, float r)
{
float candidateRISWeight = targetFunction;
ThinReservoir reservoir = g_LightGridCellsData[reservoirIndex];
float totalWeights = reservoir.weight + candidateRISWeight;
if (r * totalWeights < candidateRISWeight)
{
reservoir.lightIndex = lightIndex;
reservoir.weight = totalWeights;
}
g_LightGridCellsData[reservoirIndex] = reservoir;
}
void NormalizeReservoirWeights(uint offset, float3 pos, uint numReservoirs, uint threadIndex)
{
for (uint i = 0; i < numReservoirs; ++i)
{
ThinReservoir reservoir = g_LightGridCellsData[offset + i];
float numSamples = s_SampleCount[threadIndex * MAX_RESERVOIRS_PER_VOXEL + i];
float sampleTargetDensity = GetLightTargetFunction(pos, reservoir.lightIndex);
float risSampleWeight = (reservoir.weight / numSamples) / sampleTargetDensity;
reservoir.weight = risSampleWeight;
g_LightGridCellsData[offset + i] = reservoir;
}
}
#pragma kernel BuildRegirLightGrid
[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, GROUP_SIZE_Z)]
void BuildRegirLightGrid(in uint3 gidx : SV_DispatchThreadID, in uint threadIndex : SV_GroupIndex)
{
if (gidx.x >= g_GridDimX || gidx.y >= g_GridDimY || gidx.z >= g_GridDimZ)
return;
uint cellIndex = GetCellIndex(gidx);
PathTracingSampler rngState;
rngState.Init(gidx.xy, gidx.z);
float3 pos = GetCellPosition(gidx);
uint reservoirIndex = 0;
for (uint lightIndex = 0; lightIndex < g_NumCandidates; ++lightIndex)
{
float targetFunction = GetLightTargetFunction(pos, lightIndex);
// Conservative culling mode: if there is a contribution, save the light index / reservoir
// This should be used when the number of candidates equals the number of ligths
if (targetFunction > 0)
{
ThinReservoir reservoir = { lightIndex, targetFunction };
if (reservoirIndex < g_NumReservoirs)
{
g_LightGridCellsData[cellIndex * g_NumReservoirs + reservoirIndex] = reservoir;
s_SampleCount[threadIndex * MAX_RESERVOIRS_PER_VOXEL + reservoirIndex] = 1.0f;
reservoirIndex++;
}
else
{
// When we run out of free reservoirs, we rely on reservoir sampling to update an existing one
// The reservoirs are stratified based on the lightIndex % g_NumReservoirs
uint reservoirLocalIndex = lightIndex % max(g_NumReservoirs, 1);
uint reservoirGlobalIndex = cellIndex * g_NumReservoirs + reservoirLocalIndex;
float rnd = rngState.GetSample1D(lightIndex);
UpdateReservoir(reservoirGlobalIndex, lightIndex, targetFunction, rnd);
s_SampleCount[threadIndex * MAX_RESERVOIRS_PER_VOXEL + reservoirLocalIndex] += 1;
}
}
}
NormalizeReservoirWeights(cellIndex * g_NumReservoirs, pos, reservoirIndex, threadIndex);
g_LightGrid[cellIndex] = uint2(cellIndex * g_NumReservoirs, reservoirIndex);
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 16e47c1641bd0104e92b624601457bb0
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,195 @@
#pragma only_renderers d3d11 vulkan metal glcore
RWTexture2D<float4> g_TextureInOut;
int g_TextureWidth;
int g_TextureHeight;
float4 g_MultiplicationFactor;
#pragma kernel Multiply
[numthreads(8,8,1)]
void Multiply(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
g_TextureInOut[id.xy] = g_TextureInOut[id.xy] * g_MultiplicationFactor;
}
int g_ChannelIndex;
#pragma kernel BroadcastChannel
[numthreads(8,8,1)]
void BroadcastChannel(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
if (g_ChannelIndex < 0 || g_ChannelIndex > 3)
return;
g_TextureInOut[id.xy] = g_TextureInOut[id.xy][g_ChannelIndex];
}
float g_ChannelValue;
#pragma kernel SetChannel
[numthreads(8,8,1)]
void SetChannel(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
if (g_ChannelIndex < 0 || g_ChannelIndex > 3)
return;
switch (g_ChannelIndex)
{
case 0:
g_TextureInOut[id.xy] = float4(g_ChannelValue, g_TextureInOut[id.xy].g, g_TextureInOut[id.xy].b, g_TextureInOut[id.xy].a);
break;
case 1:
g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_ChannelValue, g_TextureInOut[id.xy].b, g_TextureInOut[id.xy].a);
break;
case 2:
g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_TextureInOut[id.xy].g, g_ChannelValue, g_TextureInOut[id.xy].a);
break;
case 3:
g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_TextureInOut[id.xy].g, g_TextureInOut[id.xy].b, g_ChannelValue);
break;
}
}
Texture2D<float4> g_SourceTexture;
SamplerState g_LinearClampSampler;
RWTexture2D<float4> g_TextureOut;
int g_BoxFilterRadius;
#pragma kernel ReferenceBoxFilter
[numthreads(8,8,1)]
void ReferenceBoxFilter(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
float4 sum = float4(0, 0, 0, 0);
for (int y = -g_BoxFilterRadius; y <= g_BoxFilterRadius; ++y)
{
for (int x = -g_BoxFilterRadius; x <= g_BoxFilterRadius; ++x)
{
float2 uv = (float2(id.xy) + float2(x, y)) / float2(g_TextureWidth, g_TextureHeight);
sum += g_SourceTexture.SampleLevel(g_LinearClampSampler, uv, 0);
}
}
g_TextureOut[id.xy] = sum / float((g_BoxFilterRadius * 2 + 1) * (g_BoxFilterRadius * 2 + 1));
}
#pragma kernel ReferenceBoxFilterBlueChannel
[numthreads(16,8,1)]
void ReferenceBoxFilterBlueChannel(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
float4 color = g_SourceTexture[id.xy];
float sum = 0;
for (int y = -g_BoxFilterRadius; y <= g_BoxFilterRadius; ++y)
{
for (int x = -g_BoxFilterRadius; x <= g_BoxFilterRadius; ++x)
{
float2 uv = (float2(id.xy) + float2(x, y)) / float2(g_TextureWidth, g_TextureHeight);
sum += g_SourceTexture.SampleLevel(g_LinearClampSampler, uv, 0).b;
}
}
float average = sum / float((g_BoxFilterRadius * 2 + 1) * (g_BoxFilterRadius * 2 + 1));
g_TextureOut[id.xy] = float4(color.rg, average, color.a);
}
Texture2D<float4> g_SampleCountInW;
Texture2D<float4> g_VarianceInR;
#pragma kernel StandardError
[numthreads(8,8,1)]
void StandardError(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
float variance = g_VarianceInR[id.xy].r;
float error = sqrt(variance / g_SampleCountInW[id.xy].w);
g_TextureOut[id.xy] = float4(error, error, error, 1.f);
}
Texture2D<float4> g_StandardErrorInR;
Texture2D<float4> g_MeanInR;
float g_StandardErrorThreshold;
#pragma kernel StandardErrorThreshold
[numthreads(8,8,1)]
void StandardErrorThreshold(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
float mean = g_MeanInR[id.xy].r;
float standardError = g_StandardErrorInR[id.xy].r;
bool shouldStop = standardError < mean * g_StandardErrorThreshold || mean < 0.000001f;
g_TextureOut[id.xy] = shouldStop ? float4(0.0f, 0.0f, 0.0f, 1.f) : float4(1.0f, 1.0f, 1.0f, 1.f);
}
RWStructuredBuffer<float> g_OutputBuffer;
int g_X;
int g_Y;
#pragma kernel GetValue
[numthreads(8,8,1)]
void GetValue(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
if (id.x == uint(g_X) && id.y == uint(g_Y))
{
float4 color = g_SourceTexture[id.xy];
g_OutputBuffer[0] = color.r;
g_OutputBuffer[1] = color.g;
g_OutputBuffer[2] = color.b;
g_OutputBuffer[3] = color.a;
}
}
#pragma kernel NormalizeByAlpha
[numthreads(8,8,1)]
void NormalizeByAlpha(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
const float4 color = g_TextureInOut[id.xy];
const float sampleCount = max(color.w, 1.0f);
g_TextureInOut[id.xy] = float4(color.xyzw * (1.f / sampleCount));
}
StructuredBuffer<uint> g_InputBufferSelector; // 0 or 1
int g_InputBufferLength;
StructuredBuffer<uint> g_InputBuffer0;
StructuredBuffer<uint> g_InputBuffer1;
RWStructuredBuffer<uint> g_SelectionOutput;
#pragma kernel SelectGraphicsBuffer
[numthreads(16,1,1)]
void SelectGraphicsBuffer(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_InputBufferLength) || id.y >= 1u || id.z >= 1u)
return;
g_SelectionOutput[id.x] = g_InputBufferSelector[0] == 0u ? g_InputBuffer0[id.x] : g_InputBuffer1[id.x];
}
RWTexture2D<float4> g_DestinationTexture;
int g_SourceX;
int g_SourceY;
int g_SourceWidth;
int g_SourceHeight;
int g_DestinationX;
int g_DestinationY;
#pragma kernel CopyTextureAdditive
[numthreads(8,8,1)]
void CopyTextureAdditive(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_SourceWidth) || id.y >= uint(g_SourceHeight))
return;
uint2 src = id.xy + uint2(uint(g_SourceX), uint(g_SourceY));
uint2 dst = id.xy + uint2(uint(g_DestinationX), uint(g_DestinationY));
g_DestinationTexture[dst] = g_DestinationTexture[dst] + g_SourceTexture[src];
}
#pragma kernel MaskAlphaChannel
[numthreads(8,8,1)]
void MaskAlphaChannel(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
return;
float4 val = g_TextureInOut[id.xy];
val.a = val.a > 0;
g_TextureInOut[id.xy] = val;
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2ba9c897a5bfc8c4e8faefab864e2a59
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,119 @@
#pragma only_renderers d3d11 vulkan metal glcore
#define UNIFIED_RT_BACKEND_COMPUTE
#define UNIFIED_RT_GROUP_SIZE_X 16
#define UNIFIED_RT_GROUP_SIZE_Y 8
#include "ExpansionHelpers.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/CommonStructs.hlsl"
// Compact an instance GBuffer to a buffer of non-empty texel indices.
// The output buffer has non-empty texels indices written into it. The number of texels written is stored in g_CompactedBufferLength.
// g_CompactedBufferLength must be initialized to 0 before calling this kernel.
#pragma kernel CompactGBuffer
[numthreads(64,1,1)]
void CompactGBuffer(uint3 id : SV_DispatchThreadID)
{
CompactGBufferInternal(id.x);
}
uint DivUp(uint x, uint y)
{
return uint((x + (int)y - 1) / (int)y);
}
// Populate the indirect dispatch buffer for the accumulation shader. Dimensions are in threads.
int g_ExpandedTexelSampleWidth;
int g_ThreadGroupSizeX;
RWStructuredBuffer<uint> g_AccumulationDispatchBuffer;
#pragma kernel PopulateAccumulationDispatch
[numthreads(8,1,1)]
void PopulateAccumulationDispatch(uint3 id : SV_DispatchThreadID)
{
if (id.x >= 1)
return;
const uint dimX = uint(g_ExpandedTexelSampleWidth) * g_CompactedGBufferLength[0];
g_AccumulationDispatchBuffer[0] = dimX;
g_AccumulationDispatchBuffer[1] = 1;
g_AccumulationDispatchBuffer[2] = 1;
}
// Populate the indirect dispatch buffer for the clear shader.
RWStructuredBuffer<uint> g_ClearDispatchBuffer;
#pragma kernel PopulateClearDispatch
[numthreads(8,1,1)]
void PopulateClearDispatch(uint3 id : SV_DispatchThreadID)
{
if (id.x >= 1)
return;
const uint dimX = uint(g_ExpandedTexelSampleWidth) * g_CompactedGBufferLength[0];
g_ClearDispatchBuffer[0] = DivUp(dimX, uint(g_ThreadGroupSizeX));
g_ClearDispatchBuffer[1] = 1;
g_ClearDispatchBuffer[2] = 1;
}
// Populate the indirect dispatch buffer for the copy shader.
RWStructuredBuffer<uint> g_CopyDispatchBuffer;
#pragma kernel PopulateCopyDispatch
[numthreads(8,1,1)]
void PopulateCopyDispatch(uint3 id : SV_DispatchThreadID)
{
if (id.x >= 1)
return;
const uint dimX = g_CompactedGBufferLength[0];
g_CopyDispatchBuffer[0] = DivUp(dimX, uint(g_ThreadGroupSizeX));
g_CopyDispatchBuffer[1] = 1;
g_CopyDispatchBuffer[2] = 1;
}
// Populate the indirect dispatch buffer for the reduce shader.
RWStructuredBuffer<uint> g_ReduceDispatchBuffer;
#pragma kernel PopulateReduceDispatch
[numthreads(8,1,1)]
void PopulateReduceDispatch(uint3 id : SV_DispatchThreadID)
{
if (id.x >= 1)
return;
const uint dimX = uint(g_ExpandedTexelSampleWidth) * g_CompactedGBufferLength[0];
g_ReduceDispatchBuffer[0] = DivUp(dimX, uint(g_ThreadGroupSizeX));
g_ReduceDispatchBuffer[1] = 1;
g_ReduceDispatchBuffer[2] = 1;
}
StructuredBuffer<float4> g_SourceBuffer;
RWTexture2D<float4> g_DestinationTexture;
int g_SourceStride;
int g_DestinationX;
int g_DestinationY;
#pragma kernel AdditivelyCopyCompactedTo2D
[numthreads(64,1,1)]
void AdditivelyCopyCompactedTo2D(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_CompactedGBufferLength[0]))
return;
uint linearChunkOffset = g_ChunkOffsetX + g_ChunkOffsetY * uint(g_InstanceWidth);
uint linearTexelIndex = g_CompactedGBuffer[id.x] + linearChunkOffset;
uint2 texelIndex = uint2(linearTexelIndex % uint(g_InstanceWidth), linearTexelIndex / uint(g_InstanceWidth)) + uint2(uint(g_DestinationX), uint(g_DestinationY));
g_DestinationTexture[texelIndex] += g_SourceBuffer[id.x*uint(g_SourceStride)];
}
// Sums the leftmost value of each group to the left
// The input buffer must be a multiple of g_BinaryGroupSize
RWStructuredBuffer<float4> g_Float4Buffer;
#pragma kernel BinaryGroupSumLeft
int g_BinaryBufferSize;
int g_BinaryGroupSize;
[numthreads(64,1,1)]
void BinaryGroupSumLeft(uint3 id : SV_DispatchThreadID)
{
if (id.x % (2 * uint(g_BinaryGroupSize)) != 0 || id.x + uint(g_BinaryGroupSize) >= uint(g_BinaryBufferSize))
return;
g_Float4Buffer[id.x] += g_Float4Buffer[id.x + g_BinaryGroupSize];
}
int g_Float4BufferLength;
#pragma kernel ClearFloat4Buffer
[numthreads(64,1,1)]
void ClearFloat4Buffer(uint3 id : SV_DispatchThreadID)
{
if (id.x >= uint(g_Float4BufferLength))
return;
g_Float4Buffer[id.x] = 0.0f;
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 200e63f12ebe79f4aa306481ce8ce4a4
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,32 @@
#ifndef _PATHTRACING_EXPANSIONHELPERS_HLSL_
#define _PATHTRACING_EXPANSIONHELPERS_HLSL_
#include "PathTracingCommon.hlsl"
RWStructuredBuffer<uint> g_CompactedGBuffer;
RWStructuredBuffer<uint> g_CompactedGBufferLength; // This will contain the number of texels written.
int g_ChunkOffsetX;
int g_ChunkOffsetY;
int g_ChunkSize;
int g_InstanceWidth;
Texture2D<half2> g_UvFallback;
void CompactGBufferInternal(uint index)
{
// The dispatch domain is [0; g_ChunkSize-1] in X
if (index >= (uint)g_ChunkSize)
return;
const uint linearChunkOffset = g_ChunkOffsetX + g_ChunkOffsetY * g_InstanceWidth;
const uint linearDispatch = linearChunkOffset + index;
const uint2 instanceTexelPos = uint2(linearDispatch % g_InstanceWidth, linearDispatch / g_InstanceWidth);
if (g_UvFallback[instanceTexelPos].x < 0)
return;
uint destinationIndex = 0;
InterlockedAdd(g_CompactedGBufferLength[0], 1, destinationIndex);
g_CompactedGBuffer[destinationIndex] = index;
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e287e19aac082a84e9ede659dfde55ee
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,26 @@
#ifndef _PATHTRACING_FETCHTERRAINGEOMETRY_HLSL_
#define _PATHTRACING_FETCHTERRAINGEOMETRY_HLSL_
#include "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/TerrainRayMarching.hlsl"
UnifiedRT::HitGeomAttributes FetchTerrainHitGeomAttributes(UnifiedRT::Hit hit, int terrainIndex)
{
UnifiedRT::TerrainData terrainData = g_TerrainList[terrainIndex];
float numCells = 1.0 / terrainData.invHeightmapWidthInTexels - 1.0;
float2 heightmapUV = hit.uvBarycentrics * numCells;
float3 localPos, localNormal;
ComputeTerrainLocalPosAndNormal(terrainData, terrainIndex, heightmapUV, localPos, localNormal);
UnifiedRT::HitGeomAttributes result = (UnifiedRT::HitGeomAttributes) 0;
result.position = localPos;
result.normal = localNormal;
result.faceNormal = localNormal;
result.uv0 = hit.uvBarycentrics;
result.uv1 = hit.uvBarycentrics;
return result;
}
#endif // _PATHTRACING_FETCHTERRAINGEOMETRY_HLSL_
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7d54f9694e06a3f42b78d1b025fb4aab
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f4d1137d7b0a472438ff16f81047ba11
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,75 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
int g_SampleOffset;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
float g_AOMaxDistance;
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
// setup rng
const uint sampleOffset = g_SampleOffset + localSampleOffset;
PathTracingSampler rngState;
rngState.Init(instanceTexelPos, sampleOffset);
// now sample occlusion with the gBuffer data
UnifiedRT::Ray ray;
float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff);
float3 direction = CosineSample(rngState.GetSample2D(RAND_DIM_SURF_SCATTER), worldNormal);
ray.origin = origin;
ray.direction = direction;
ray.tMin = 0;
ray.tMax = g_AOMaxDistance;
rngState.NextBounce();
float3 occlusion = 0.f;
// trace through potentially several layers of transmissive materials, determining at each hit whether or not to kill the ray
for (uint i = 0; i < MAX_TRANSMISSION_BOUNCES; i++)
{
UnifiedRT::Hit hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, RayMask(true), ray, 0);
if (hitResult.IsValid())
{
UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID);
PTHitGeom geometry = GetHitGeomInfo(instance, hitResult);
geometry.FixNormals(direction);
MaterialProperties material = LoadMaterialProperties(instance, geometry.uv0, geometry.uv1);
// Transmissive material, continue with a probability
if (ShouldTransmitRay(rngState, material))
{
ray.origin = geometry.NextTransmissionRayOrigin();
ray.tMax = g_AOMaxDistance - distance(origin, ray.origin);
rngState.NextBounce();
}
// No transmission, so the ray is occluded
else
{
occlusion = 1.f;
break;
}
}
// Hit nothing, so the ray is not occluded
else
break;
}
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(occlusion, 1.0f);
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1c410815c01ce1d4d82c645c0cd45747
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,132 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma exclude_renderers switch switch2
#pragma multi_compile _ EMISSIVE_SAMPLING_LIGHT EMISSIVE_SAMPLING_BRDF EMISSIVE_SAMPLING_MIS
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
int g_AccumulateDirectional;
int g_SampleOffset;
uint g_ReceiveShadows;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
RWStructuredBuffer<float4> g_ExpandedOutputDirectional;
// Sample a random cosine weighted direction, weight with MIS. Only counts contribution from emissive meshes.
// Assumes 2 sampling strategies, the other being direct sampling of lights (which may or may not be emissive meshes).
void EstimateMISWeightedIrradianceUsingCosineSampling(
UnifiedRT::DispatchInfo dispatchInfo,
UnifiedRT::RayTracingAccelStruct accelStruct,
StructuredBuffer<UnifiedRT::InstanceData> instanceList,
float3 origin,
float3 normal,
inout PathTracingSampler rngState,
inout float4 irradiance,
inout float4 directional)
{
UnifiedRT::Ray ray;
ray.origin = origin;
ray.direction = CosineSample(rngState.GetSample2D(RAND_DIM_SURF_SCATTER), normal);
ray.tMin = 0;
ray.tMax = FLT_MAX;
float sampleDensity = dot(normal, ray.direction) / PI;
float lightDensity = 0.f;
float3 emission = 0.f;
float3 attenuation = 1.0f;
for (uint i = 0; i < MAX_TRANSMISSION_BOUNCES; i++)
{
UnifiedRT::Hit hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, RayMask(true), ray, 0);
if (hitResult.IsValid()) // Hit something, possibly emissive
{
UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID);
PTHitGeom geometry = GetHitGeomInfo(instance, hitResult);
geometry.FixNormals(ray.direction);
MaterialProperties material = LoadMaterialProperties(instance, geometry.uv0, geometry.uv1);
// Transmissive material, either continue or terminate ray, then apply attenuation.
if (ShouldTransmitRay(rngState, material))
{
attenuation *= saturate(material.transmission);
ray.origin = geometry.NextTransmissionRayOrigin();
rngState.NextBounce();
continue;
}
// Hit emissive frontface
else if (!ShouldTreatAsBackface(hitResult, material) && any(material.emission))
{
// The density used to calculate MIS weights should include both the probability of sampling the specific
// point on the emissive mesh, as well as the probability of picking the emissive mesh.
lightDensity = ComputeEmissiveTriangleDensity(instanceList, geometry, hitResult.instanceID, ray.origin);
lightDensity /= GetNumLights(ray.origin);
emission = material.emission;
}
break;
}
else // Hit skybox, so stop
{
GetEnvironmentLightEmissionAndDensity(ray.direction, emission, lightDensity);
break;
}
}
// Tint the emissiom by the transmissive attenuation before accumulating
emission *= attenuation;
// MIS - Cosine weighted sampling
float sampleMISWeight = EmissiveMISWeightForBrdfRay(lightDensity, sampleDensity);
// f(x)/p(x) = (Li * cos(theta)) / (cos(theta) / PI) = Li * PI
float3 e = emission * PI * sampleMISWeight;
irradiance.rgb += e;
directional += float4(ray.direction, 1.f) * Luminance(e);
}
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
const uint sampleOffset = g_SampleOffset + localSampleOffset;
float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff);
float4 irradiance = 0.f;
float4 directional = 0.f;
PathTracingSampler rngState;
rngState.Init(instanceTexelPos, sampleOffset);
SampleLightsOptions options;
options.isDirect = true;
options.receiveShadows = g_ReceiveShadows;
options.shadowRayMask = ShadowRayMask();
options.lightsRenderingLayerMask = 0xFFFFFFFF;
options.numLightCandidates = min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS);
uint numLightsInCell = GetNumLights(origin);
EstimateMISWeightedIrradianceUsingCosineSampling(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, worldNormal, rngState, irradiance, directional);
// Store new accumulated irradiance
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(irradiance.rgb, 1.0);
if (g_AccumulateDirectional > 0)
g_ExpandedOutputDirectional[dispatchInfo.dispatchThreadID.x] += directional;
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5951703b3d165764c95c653e98350884
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,86 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma exclude_renderers switch switch2
#pragma multi_compile _ LIGHT_SAMPLING_UNIFORM LIGHT_SAMPLING_RIS LIGHT_SAMPLING_ROUND_ROBIN
#pragma multi_compile _ EMISSIVE_SAMPLING_LIGHT EMISSIVE_SAMPLING_BRDF EMISSIVE_SAMPLING_MIS
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
int g_AccumulateDirectional;
int g_SampleOffset;
uint g_ReceiveShadows;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
RWStructuredBuffer<float4> g_ExpandedOutputDirectional;
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
const uint sampleOffset = g_SampleOffset + localSampleOffset;
float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff);
float4 irradiance = 0.0f;
float4 directional = 0.0f;
PathTracingSampler rngState;
rngState.Init(instanceTexelPos, sampleOffset);
SampleLightsOptions options;
options.isDirect = true;
options.receiveShadows = g_ReceiveShadows;
options.shadowRayMask = ShadowRayMask();
options.lightsRenderingLayerMask = 0xFFFFFFFF;
options.numLightCandidates = 1;
// TODO: Specialize sampling codepaths for different light types: https://jira.unity3d.com/browse/GFXLIGHT-2260
// Sample the main directional light
if (g_HasMainDirectionalLight)
{
PTLight directionalLight = g_MainDirectionalLight[0];
LightSample directionalLightSample;
SampleLightRadiance(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, worldNormal, options, directionalLight, rngState, directionalLightSample);
float3 eval = ClampedCosine(worldNormal, directionalLightSample.direction) * directionalLightSample.radiance;
irradiance.rgb += eval;
directional += float4(directionalLightSample.direction, 1.f) * Luminance(eval);
}
// Sample the environment
if (g_HasEnvironmentLight)
{
PTLight environmentLight = (PTLight)0;
environmentLight.type = ENVIRONMENT_LIGHT;
environmentLight.castsShadows = 1;
environmentLight.contributesToDirectLighting = 1;
environmentLight.layerMask = UINT_MAX;
LightSample environmentLightSample;
SampleLightRadiance(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, worldNormal, options, environmentLight, rngState, environmentLightSample);
float misWeight = EmissiveMISWeightForLightRay(environmentLightSample.lightType, environmentLightSample.direction, environmentLightSample.risSourcePdf, worldNormal);
float3 eval = misWeight * ClampedCosine(worldNormal, environmentLightSample.direction) * environmentLightSample.radiance;
irradiance.rgb += eval;
directional += float4(environmentLightSample.direction, 1.f) * Luminance(eval);
}
// Store new accumulated irradiance
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(irradiance.rgb, 0.0f);
if (g_AccumulateDirectional > 0)
g_ExpandedOutputDirectional[dispatchInfo.dispatchThreadID.x] += directional;
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f5ba4d424d4566249b3d94cf81a3e916
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,141 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma exclude_renderers switch switch2
#pragma multi_compile _ LIGHT_SAMPLING_UNIFORM LIGHT_SAMPLING_RIS LIGHT_SAMPLING_ROUND_ROBIN
#pragma multi_compile _ EMISSIVE_SAMPLING_LIGHT EMISSIVE_SAMPLING_BRDF EMISSIVE_SAMPLING_MIS
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
uint g_LightIndexInCell;
int g_AccumulateDirectional;
int g_SampleOffset;
uint g_ReceiveShadows;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
RWStructuredBuffer<float4> g_ExpandedOutputDirectional;
// Sample a specific light directly, weight with MIS.
// Assumes 2 sampling strategies, the other being cosine weighted hemisphere sampling.
void EstimateMISWeightedIrradianceForSpecificLightUsingDirectSampling(
UnifiedRT::DispatchInfo dispatchInfo,
UnifiedRT::RayTracingAccelStruct accelStruct,
StructuredBuffer<UnifiedRT::InstanceData> instanceList,
float3 origin,
float3 normal,
SampleLightsOptions options,
uint lightIndexInCell,
inout PathTracingSampler rngState,
inout float4 irradiance,
inout float4 directional,
out LightSample lightSample)
{
float3 sampleRadiance = 0.f;
float3 sampleDirection = 0.f;
float sampleDensity = 0.f;
uint lightIndex = LocalToGlobalLightIndex(origin, lightIndexInCell);
if (SampleLightRadianceRoundRobin(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, normal, options, lightIndex, rngState, lightSample))
{
float sampleMISWeight = EmissiveMISWeightForLightRay(lightSample.lightType, lightSample.direction, lightSample.risSourcePdf, normal);
float3 e = sampleMISWeight * ClampedCosine(normal, lightSample.direction) * lightSample.radiance;
irradiance.rgb += e;
directional += float4(lightSample.direction, 1.f) * Luminance(e);
}
}
// Sample every light directly, weight with MIS.
// Assumes 2 sampling strategies, the other being cosine weighted hemisphere sampling.
void EstimateMISWeightedIrradianceUsingDirectSampling(
UnifiedRT::DispatchInfo dispatchInfo,
UnifiedRT::RayTracingAccelStruct accelStruct,
StructuredBuffer<UnifiedRT::InstanceData> instanceList,
float3 origin,
float3 normal,
SampleLightsOptions options,
inout PathTracingSampler rngState,
inout float4 irradiance,
inout float4 directional,
out LightSample lightSample)
{
float3 sampleRadiance = 0.f;
float3 sampleDirection = 0.f;
float sampleDensity = 0.f;
if (SampleRadianceFromLightGrid(dispatchInfo, accelStruct, instanceList, origin, normal, options, rngState, lightSample))
{
float sampleMISWeight = EmissiveMISWeightForLightRay(lightSample.lightType, lightSample.direction, lightSample.risSourcePdf, normal);
float3 e = sampleMISWeight * ClampedCosine(normal, lightSample.direction) * lightSample.radiance;
irradiance.rgb += e;
directional += float4(lightSample.direction, 1.f) * Luminance(e);
}
}
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
const uint sampleOffset = g_SampleOffset + localSampleOffset;
float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff);
float4 irradiance = 0.f;
float4 directional = 0.f;
PathTracingSampler rngState;
rngState.Init(instanceTexelPos, sampleOffset);
SampleLightsOptions options;
options.isDirect = true;
options.receiveShadows = g_ReceiveShadows;
options.shadowRayMask = ShadowRayMask();
options.lightsRenderingLayerMask = 0xFFFFFFFF;
options.numLightCandidates = min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS);
uint numLightsInCell = GetNumLights(origin);
// Sample a light using direct sampling, weight with MIS.
#ifdef LIGHT_SAMPLING_ROUND_ROBIN
// If we have already sampled every light in the cell, skip this sample to avoid needing to normalize per-light.
[branch] if (g_LightIndexInCell >= numLightsInCell)
{
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(0,0,0,g_LightIndexInCell==0);
return;
}
LightSample lightSample;
EstimateMISWeightedIrradianceForSpecificLightUsingDirectSampling(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, worldNormal, options, g_LightIndexInCell, rngState, irradiance, directional, lightSample);
#else
LightSample lightSample;
EstimateMISWeightedIrradianceUsingDirectSampling(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, worldNormal, options, rngState, irradiance, directional, lightSample);
#endif
// With round-robin, we take (numLights*sampleCount) samples instead of just (sampleCount).
// To keep normalization consistent with the other strategies, we keep the same effective
// sample count in the alpha channel of the output, but divide each sample by (numLights).
#ifdef LIGHT_SAMPLING_ROUND_ROBIN
irradiance /= numLightsInCell;
directional /= numLightsInCell;
#endif
// Store new accumulated irradiance
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(irradiance.rgb,g_LightIndexInCell==0);
if (g_AccumulateDirectional > 0)
g_ExpandedOutputDirectional[dispatchInfo.dispatchThreadID.x] += directional;
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5bbebdef5916845478fcef13fa10d89f
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,23 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
RWStructuredBuffer<float2> g_LightmapSamplesExpanded;
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
float2 uv1 = 0.f;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, worldPosition, worldNormal, worldFaceNormal, uv1);
if (!gotSample)
return;
g_LightmapSamplesExpanded[dispatchInfo.dispatchThreadID.x] = uv1;
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 737a1597422c12547a8714d76a9406ec
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,99 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/TraceRayAndQueryHit.hlsl"
#include "PathTracingRandom.hlsl"
#include "StochasticLightmapSampling.hlsl"
UNIFIED_RT_DECLARE_ACCEL_STRUCT(g_UVAccelStruct);
int g_StochasticAntialiasing;
int g_SuperSampleWidth; // width of the super sampling grid - defines as g_SuperSampleWidth x g_SuperSampleWidth grid of samples
int g_InstanceOffsetX;
int g_InstanceOffsetY;
int g_InstanceWidth;
int g_InstanceHeight;
float g_InstanceWidthScale;
float g_InstanceHeightScale;
int g_ChunkOffsetX;
int g_ChunkOffsetY;
int g_ChunkSize;
int g_ExpandedTexelSampleWidth;
int g_PassSampleCount;
int g_SampleOffset;
Texture2D<half2> g_UvFallback;
RWStructuredBuffer<HitEntry> g_GBuffer;
StructuredBuffer<uint> g_CompactedGBuffer;
StructuredBuffer<uint> g_CompactedGBufferLength;
float2 JitterSample(float2 sample, float2 random01, float jitterAmount)
{
const float2 jitteredSample = sample + jitterAmount * (random01 - 0.5f);
return saturate(jitteredSample);
}
float2 GetSuperSamplingOffset(uint currentSuperSampleIndex, uint superSampleWidth)
{
currentSuperSampleIndex = currentSuperSampleIndex % (superSampleWidth * superSampleWidth);
uint ssX = currentSuperSampleIndex % superSampleWidth;
uint ssY = currentSuperSampleIndex / superSampleWidth;
return (float2(ssX, ssY) + 0.5f) / (float)superSampleWidth;
}
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
// The dispatch domain is [0; (g_ChunkSize * g_ExpandedTexelSampleWidth) - 1] in X
const uint sampleIndex = dispatchInfo.dispatchThreadID.x;
g_GBuffer[sampleIndex].instanceID = -1;
g_GBuffer[sampleIndex].primitiveIndex = -1;
g_GBuffer[sampleIndex].barycentrics = uint2(0,0);
const uint compactedRange = g_CompactedGBufferLength[0] * (uint)g_ExpandedTexelSampleWidth;
if (sampleIndex >= compactedRange)
return;
// check that the sample is needed, g_ExpandedTexelSampleWidth might have more samples than needed as it is power of two
// .. and the last sampling pass might have few remaining samples
uint localSampleIndex = (uint)sampleIndex % (uint)g_ExpandedTexelSampleWidth;
if (localSampleIndex >= (uint)g_PassSampleCount)
return;
uint compactedTexelIndex = sampleIndex / (uint)g_ExpandedTexelSampleWidth;
uint chunkLinearTexelIndex = g_CompactedGBuffer[compactedTexelIndex];
if (chunkLinearTexelIndex >= (uint)g_ChunkSize)
return;
const uint linearChunkOffset = g_ChunkOffsetX + g_ChunkOffsetY * g_InstanceWidth;
const uint linearInstancePos = linearChunkOffset + chunkLinearTexelIndex;
if (linearInstancePos >= (uint)g_InstanceWidth * (uint)g_InstanceHeight)
return;
const uint2 instanceTexelPos = uint2(linearInstancePos % g_InstanceWidth, linearInstancePos / g_InstanceWidth);
const uint2 lightmapTexelPos = uint2(g_InstanceOffsetX, g_InstanceOffsetY) + instanceTexelPos;
const float2 instanceSize = float2(g_InstanceWidth, g_InstanceHeight);
UnifiedRT::RayTracingAccelStruct uvAccelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_UVAccelStruct);
PathTracingSampler rngState;
rngState.Init(lightmapTexelPos, g_SampleOffset + localSampleIndex);
// select a random point in the texel
const float2 random01 = rngState.GetSample2D(RAND_DIM_AA);
const float2 texelSample = g_StochasticAntialiasing == 1 ? random01 : JitterSample(GetSuperSamplingOffset(g_SampleOffset + localSampleIndex, g_SuperSampleWidth), random01, 0.001f); // the jitter is to avoid issues with raytracing watertightness
const float2 instanceScale = float2(g_InstanceWidthScale, g_InstanceHeightScale);
const UnifiedRT::Hit hit = LightmapSampleTexelOffset(instanceTexelPos, texelSample, instanceSize, instanceScale, dispatchInfo, uvAccelStruct, g_UvFallback[instanceTexelPos]);
if (!hit.IsValid())
{
// no intersection found
return;
}
g_GBuffer[sampleIndex].instanceID = hit.instanceID;
g_GBuffer[sampleIndex].primitiveIndex = hit.primitiveIndex;
g_GBuffer[sampleIndex].barycentrics = hit.uvBarycentrics;
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e9f4de6d1e07fb94f9a60d35b618f724
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,138 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ LIGHT_SAMPLING_UNIFORM LIGHT_SAMPLING_RIS LIGHT_SAMPLING_ROUND_ROBIN
#pragma multi_compile _ EMISSIVE_SAMPLING_LIGHT EMISSIVE_SAMPLING_BRDF EMISSIVE_SAMPLING_MIS
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
int g_AccumulateDirectional;
int g_SampleOffset;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
RWStructuredBuffer<float4> g_ExpandedOutputDirectional;
// additional instance flags to deal with lightmap lods
#define CURRENT_LOD_FOR_LIGHTMAP_INSTANCE 8u
#define LOD_ZERO_FOR_LIGHTMAP_INSTANCE 16u
#define CURRENT_LOD_FOR_LIGHTMAP_INSTANCE_SHADOW 32u
#define LOD_ZERO_FOR_LIGHTMAP_INSTANCE_SHADOW 64u
float3 EstimateLightmapRadiance(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::Ray ray, inout PathTracingSampler rngState)
{
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
PathIterator pathIter;
InitPathIterator(pathIter, ray);
// LOD handling: Rays starting from the lightmapped object use an accel struct built with that same LOD level
uint rayLightmapLodMask = CURRENT_LOD_FOR_LIGHTMAP_INSTANCE;
uint shadowRayLightmapLodMask = CURRENT_LOD_FOR_LIGHTMAP_INSTANCE_SHADOW;
int transparencyBounce = 0;
for (int bounceIndex = 0; bounceIndex <= g_BounceCount && transparencyBounce < MAX_TRANSMISSION_BOUNCES; bounceIndex++)
{
// The first path segment is special for the indirect pass - we should not add radiance from the
// environment or emission, as these are already explicitly sampled in the direct pass.
bool isFirstPathSegment = bounceIndex == 0;
uint pathRayMask = RayMask(isFirstPathSegment) | rayLightmapLodMask;
uint traceResult = TraceBounceRay(pathIter, pathRayMask, dispatchInfo, accelStruct, rngState);
if (traceResult == TRACE_HIT)
{
uint hitInstanceMask = UnifiedRT::GetInstance(pathIter.hitResult.instanceID).instanceMask;
// LOD handling: If the ray hits a surface that is not the current lightmap instance,
// then, for the rest of the path, we can replace it in the scene accel struct by one that using lod 0.
if (!(hitInstanceMask & CURRENT_LOD_FOR_LIGHTMAP_INSTANCE))
{
rayLightmapLodMask = LOD_ZERO_FOR_LIGHTMAP_INSTANCE;
shadowRayLightmapLodMask = LOD_ZERO_FOR_LIGHTMAP_INSTANCE_SHADOW;
}
uint shadowRayMask = ShadowRayMask() | shadowRayLightmapLodMask;
if (!isFirstPathSegment)
AddEmissionRadiance(pathIter, accelStruct, g_AccelStructInstanceList, false);
// Check if we should do NEE, respecting the max bounce count
if (bounceIndex < g_BounceCount)
AddRadianceFromDirectIllumination(pathIter, shadowRayMask, dispatchInfo, accelStruct, g_AccelStructInstanceList, rngState, false);
}
if (traceResult == TRACE_MISS)
{
if (!isFirstPathSegment)
AddEnvironmentRadiance(pathIter, false);
break;
}
if (traceResult == TRACE_TRANSMISSION)
{
bounceIndex--;
transparencyBounce++;
pathIter.ray.origin = pathIter.hitGeo.NextTransmissionRayOrigin();
pathIter.throughput *= pathIter.material.transmission;
rngState.NextBounce();
continue;
}
if (!Scatter(pathIter, rngState))
break;
if (bounceIndex >= RUSSIAN_ROULETTE_MIN_BOUNCES)
{
float p = max(pathIter.throughput.x, max(pathIter.throughput.y, pathIter.throughput.z));
if (rngState.GetSample1D(RAND_DIM_RUSSIAN_ROULETTE) > p)
break;
else
pathIter.throughput /= p;
}
rngState.NextBounce();
}
return pathIter.radianceSample;
}
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
// now sample irradiance with the gBuffer data
const uint sampleOffset = g_SampleOffset + localSampleOffset;
PathTracingSampler rngState;
rngState.Init(instanceTexelPos, sampleOffset);
UnifiedRT::Ray ray;
ray.origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff);
ray.direction = CosineSample(rngState.GetSample2D(RAND_DIM_SURF_SCATTER), worldNormal);
ray.tMin = 0;
ray.tMax = Max_float();
rngState.NextBounce();
float3 sampleRadiance = EstimateLightmapRadiance(dispatchInfo, ray, rngState);
const float sampleLuminance = Luminance(sampleRadiance.xyz);
const float reciprocalProbabilityDensityMulipliedByCosine = PI;
// store new accumulated radiance
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(sampleRadiance * reciprocalProbabilityDensityMulipliedByCosine, 1.0f);
// the cosine term from the PDF cancels with the cosine term for the integrand.
if (g_AccumulateDirectional > 0)
g_ExpandedOutputDirectional[dispatchInfo.dispatchThreadID.x] += float4(normalize(ray.direction), 1.f) * sampleLuminance;
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7624adbeb148b5c48ab5ab660cc2602a
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,121 @@
#ifndef _PATHTRACING_LIGHTMAPINTEGRATIONHELPERS_HLSL_
#define _PATHTRACING_LIGHTMAPINTEGRATIONHELPERS_HLSL_
#include "PathTracingCommon.hlsl"
StructuredBuffer<HitEntry> g_GBuffer;
RWStructuredBuffer<uint> g_CompactedGBuffer;
RWStructuredBuffer<uint> g_CompactedGBufferLength; // This will contain the number of texels written.
int g_MaxLocalSampleCount; // Don't take more than this many samples per texel in this dispatch, sometimes the last dispatch will have fewer samples as the expanded sample count may not be a multiple of the total sample count
int g_ExpandedTexelSampleWidth; // The number of samples per texel in the expanded buffers
int g_InstanceWidth;
int g_ChunkOffsetX;
int g_ChunkOffsetY;
int g_InstanceGeometryIndex;
int g_TerrainIndex;
float4x4 g_ShaderLocalToWorld;
float4x4 g_ShaderLocalToWorldNormals;
#ifdef TERRAIN_RAY_MARCHING_ENABLED
bool GetExpandedSampleTerrain(uint dispatchIndex, out float3 worldPosition, out float3 worldNormal, out float3 worldFaceNormal, out float2 uv)
{
if (!g_GBuffer[dispatchIndex].IsValid())
{
worldPosition = worldNormal = worldFaceNormal = 0;
uv = 0;
return false;
}
// Reconstruct UV from quad mesh barycentrics.
// Quad: v0=(0,0), v1=(1,0), v2=(1,1), v3=(0,1)
// Triangle 0: v0,v2,v1 (indices 0,2,1) Triangle 1: v0,v3,v2 (indices 0,3,2)
uint primIndex = g_GBuffer[dispatchIndex].primitiveIndex;
float2 bary = g_GBuffer[dispatchIndex].barycentrics;
float2 p0 = float2(0, 0);
float2 p1 = primIndex == 0 ? float2(1, 1) : float2(0, 1);
float2 p2 = primIndex == 0 ? float2(1, 0) : float2(1, 1);
uv = p0 * (1.0 - bary.x - bary.y) + p1 * bary.x + p2 * bary.y;
// Compute position and normal from heightmap using shared function.
// Barycentrics give UVMesh-space [0,1]. Cell coordinates are [0, resolution-1].
UnifiedRT::TerrainData terrainData = g_TerrainList[g_TerrainIndex];
float numCells = 1.0 / terrainData.invHeightmapWidthInTexels - 1.0;
float2 heightmapUV = uv * numCells;
float3 localPos, localNormal;
ComputeTerrainLocalPosAndNormal(terrainData, g_TerrainIndex, heightmapUV, localPos, localNormal);
worldPosition = mul(g_ShaderLocalToWorld, float4(localPos, 1)).xyz;
worldNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, localNormal));
worldFaceNormal = worldNormal;
return true;
}
#endif
bool GetExpandedSample(uint dispatchIndex, out uint localSampleOffset, out uint2 instanceTexelPos, inout float3 worldPosition, inout float3 worldNormal, inout float3 worldFaceNormal)
{
localSampleOffset = dispatchIndex % g_ExpandedTexelSampleWidth;
instanceTexelPos = 0;
if (localSampleOffset >= (uint)g_MaxLocalSampleCount)
return false; // no more samples to process
else
{
const uint compactedTexelIndex = dispatchIndex / g_ExpandedTexelSampleWidth;
const uint texelIndex = g_CompactedGBuffer[compactedTexelIndex];
const uint linearChunkOffset = g_ChunkOffsetY * g_InstanceWidth + g_ChunkOffsetX;
const uint linearTexelIndex = texelIndex + linearChunkOffset;
instanceTexelPos = uint2(linearTexelIndex % g_InstanceWidth, linearTexelIndex / g_InstanceWidth);
#ifdef TERRAIN_RAY_MARCHING_ENABLED
if (g_InstanceGeometryIndex == -1)
{
float2 uv;
return GetExpandedSampleTerrain(dispatchIndex, worldPosition, worldNormal, worldFaceNormal, uv);
}
#endif
if (!g_GBuffer[dispatchIndex].IsValid())
return false; // no intersection found, skip this sample
else
{
UnifiedRT::Hit hit;
hit.instanceID = g_GBuffer[dispatchIndex].instanceID;
hit.primitiveIndex = g_GBuffer[dispatchIndex].primitiveIndex;
hit.uvBarycentrics = g_GBuffer[dispatchIndex].barycentrics;
FetchGeomAttributes(hit, g_InstanceGeometryIndex, worldPosition, worldNormal, worldFaceNormal);
worldPosition = mul(g_ShaderLocalToWorld, float4(worldPosition, 1)).xyz;
worldNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldNormal));
worldFaceNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldFaceNormal));
return true;
}
}
}
bool GetExpandedSample(uint dispatchIndex, inout float3 worldPosition, inout float3 worldNormal, inout float3 worldFaceNormal, inout float2 uv1)
{
#ifdef TERRAIN_RAY_MARCHING_ENABLED
if (g_InstanceGeometryIndex == -1)
return GetExpandedSampleTerrain(dispatchIndex, worldPosition, worldNormal, worldFaceNormal, uv1);
#endif
if (!g_GBuffer[dispatchIndex].IsValid())
return false; // no intersection found, skip this sample
else
{
UnifiedRT::Hit hit;
hit.instanceID = g_GBuffer[dispatchIndex].instanceID;
hit.primitiveIndex = g_GBuffer[dispatchIndex].primitiveIndex;
hit.uvBarycentrics = g_GBuffer[dispatchIndex].barycentrics;
FetchGeomAttributes(hit, g_InstanceGeometryIndex, worldPosition, worldNormal, worldFaceNormal, uv1);
worldPosition = mul(g_ShaderLocalToWorld, float4(worldPosition, 1)).xyz;
worldNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldNormal));
worldFaceNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldFaceNormal));
return true;
}
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4d15bddf10493684cbf573c1b43be6c8
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,27 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
int g_SampleOffset;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
float g_AOMaxDistance;
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] = float4(worldNormal, 1.0f);
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1451d029813b192429211be28181beab
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,25 @@
#pragma only_renderers d3d11 vulkan metal glcore
RWTexture2D<float4> g_LightmapInOut;
Texture2D<half2> g_UvFallback;
int g_InstanceOffsetX;
int g_InstanceOffsetY;
float g_InstanceWidthScale;
float g_InstanceHeightScale;
uint g_InstanceWidth;
uint g_InstanceHeight;
#pragma kernel BlitOccupancy
[numthreads(8,8,1)]
void BlitOccupancy(uint3 id : SV_DispatchThreadID)
{
if (id.x < (uint) g_InstanceWidth && id.y < (uint) g_InstanceHeight)
{
uint2 instanceTexelPos = id.xy;
uint2 lightmapTexelPos = uint2(g_InstanceOffsetX, g_InstanceOffsetY) + instanceTexelPos;
float4 output = g_LightmapInOut[lightmapTexelPos];
float fallback = g_UvFallback[instanceTexelPos].x;
g_LightmapInOut[lightmapTexelPos] = fallback >= 0 ? 1.f : output;
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 891acea040e321148ab512076f084f6c
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,79 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
int g_SampleOffset;
uint g_ReceiveShadows;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
RWStructuredBuffer<float4> g_ExpandedSampleCountInW;
void CalculateOcclusionForLight(
UnifiedRT::DispatchInfo dispatchInfo,
PathTracingSampler rngState,
UnifiedRT::RayTracingAccelStruct accelStruct,
PTLight light,
float3 worldPosition,
float3 worldFaceNormal,
inout uint dimsOffset,
inout float visibility[4])
{
if (light.shadowMaskChannel == -1)
return;
if (light.type == EMISSIVE_MESH || light.type == ENVIRONMENT_LIGHT) // Shadowmask only makes sense for punctual lights
return;
float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff);
float3 attenuation = 1.0f;
float isVisible = IsLightVisibleFromPoint(dispatchInfo, accelStruct, g_AccelStructInstanceList, ShadowRayMask(), origin, rngState, dimsOffset, light, g_ReceiveShadows, attenuation) ? dot(float3(1.0f, 1.0f, 1.0f), attenuation)/3.0f : 0.0f;
dimsOffset += 3; // 2 dimensions for sampling the light, 1 dimension for jittering the shadow ray
if (light.shadowMaskChannel < 0 || light.shadowMaskChannel >= 4)
return;
visibility[light.shadowMaskChannel] += isVisible;
}
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
// now sample direct lighting with the gBuffer data
const uint sampleOffset = g_SampleOffset + localSampleOffset;
PathTracingSampler rngState;
rngState.Init(instanceTexelPos, sampleOffset);
// now sample direct lighting with the gBuffer data
uint numLights = g_NumLights;
uint dimsOffset = 1; // first dimension is used for the stochastic gBuffer sampling
float visibility[4] = { 0.f, 0.f, 0.f, 0.f };
for (uint lightIndex = 0; lightIndex < numLights; ++lightIndex)
{
PTLight light = FetchLight(lightIndex);
CalculateOcclusionForLight(dispatchInfo, rngState, accelStruct, light, worldPosition, worldFaceNormal, dimsOffset, visibility);
}
if (g_HasMainDirectionalLight)
{
CalculateOcclusionForLight(dispatchInfo, rngState, accelStruct, g_MainDirectionalLight[0], worldPosition, worldFaceNormal, dimsOffset, visibility);
}
// store new accumulated radiance
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(visibility[0], visibility[1], visibility[2], visibility[3]);
g_ExpandedSampleCountInW[dispatchInfo.dispatchThreadID.x] += float4(0.f, 0.f, 0.f, 1.f);
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d19f4f8a13b6edb48b986a47ec96402e
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,74 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 64
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal
#include "PathTracing.hlsl"
#include "LightmapIntegrationHelpers.hlsl"
int g_SampleOffset;
float g_PushOff;
RWStructuredBuffer<float4> g_ExpandedOutput;
void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo)
{
float3 worldPosition = 0.f;
float3 worldNormal = 0.f;
float3 worldFaceNormal = 0.f;
uint localSampleOffset = 0;
uint2 instanceTexelPos = 0;
const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal);
if (!gotSample)
return;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
// setup rng
const uint sampleOffset = g_SampleOffset + localSampleOffset;
PathTracingSampler rngState;
rngState.Init(instanceTexelPos, sampleOffset);
// now sample validity with the gBuffer data
float invalidity = 0.f;
UnifiedRT::Ray ray;
ray.origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff);
const float3 direction = CosineSample(rngState.GetSample2D(RAND_DIM_SURF_SCATTER), worldNormal);
ray.direction = direction;
ray.tMin = 0;
ray.tMax = Max_float();
rngState.NextBounce();
// trace through potentially several layers of transmissive materials, determining validity at each hit
for (uint i = 0; i < MAX_TRANSMISSION_BOUNCES; i++)
{
UnifiedRT::Hit hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, RayMask(true), ray, 0);
if (!hitResult.IsValid())
break; // Hit nothing, so the ray is valid
// Something was hit
UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID);
PTHitGeom geometry = GetHitGeomInfo(instance, hitResult);
MaterialProperties material = LoadMaterialProperties(instance, geometry.uv0, geometry.uv1);
// Check for transmission
if (ShouldTransmitRay(rngState, material))
{
geometry.FixNormals(direction);
ray.origin = geometry.NextTransmissionRayOrigin();
rngState.NextBounce();
continue;
}
// Check for validity
bool treatAsBackFace = ShouldTreatAsBackface(hitResult, material);
if (!treatAsBackFace)
break; // we have a valid hit
// Hit was invalid and there is no transmission
invalidity += 1.f;
break;
}
g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(invalidity, invalidity, invalidity, 1.0f);
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ace95ac84e213794891b4bd7c9b3c158
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea04c59408e02c948b94f13808d4591c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,91 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma kernel MarkBilinearOverlaps
#define UINT_MAX 4294967295u
#define NO_CHART UINT_MAX
uint _TileSize;
uint _TileX;
uint _TileY;
uint _InstanceIndex;
uint _EdgeCount;
StructuredBuffer<float4> _TriangleEdges; // Per thread. Edge of each triangle (startX, startY, endX, endY)
StructuredBuffer<uint> _ChartIndices; // Per thread chart index.
uint _TextureSize;
RWStructuredBuffer<uint> _PerPixelChart; // Per pixel chart index.
RWStructuredBuffer<uint> _OverlapPixels; // Per pixel, no overlap = 0, overlap = 1
RWStructuredBuffer<uint> _OverlapInstances; // Per instance, no overlap = 0, overlap = 1
[numthreads(128,1,1)]
void MarkBilinearOverlaps(uint3 id : SV_DispatchThreadID)
{
if (id.x >= _EdgeCount)
return;
// Tile the dispatch so we don't do too much work on each thread
uint2 tilePos = uint2(_TileX, _TileY) * _TileSize;
uint2 tileEnd = tilePos + _TileSize;
// Fetch chart and triangle edge for this thread.
uint chartIdx = _ChartIndices[id.x];
float4 edge = _TriangleEdges[id.x];
float2 startPos = edge.xy;
float2 endPos = edge.zw;
uint2 aabbStartPos = max(uint2(floor(min(startPos, endPos) - 0.5f)), 0);
uint2 aabbEndPos = min(uint2(ceil(max(startPos, endPos) + 0.5f)), _TextureSize-1);
aabbStartPos = clamp(aabbStartPos, tilePos, tileEnd);
aabbEndPos = clamp(aabbEndPos, tilePos, tileEnd);
uint2 extent = aabbEndPos - aabbStartPos;
// The edge is described by a line A*x + B*y + C = 0.
float lineA = endPos.y - startPos.y;
float lineB = startPos.x - endPos.x;
float lineC = startPos.y * endPos.x - startPos.x * endPos.y;
// For every pixel in the AABB...
[allow_uav_condition]
for (uint y = 0; y < extent.y; y++)
{
[allow_uav_condition]
for (uint x = 0; x < extent.x; x++)
{
uint2 pixelCoord = uint2(x, y) + aabbStartPos;
float2 bottomLeftCornerPos = pixelCoord - 0.5f;
// For each corner of the bilinear neighborhood...
bool4 cornerMask = false;
[unroll]
for (uint corner = 0; corner < 4; corner++)
{
uint offsetX = 2 * (corner & 1);
uint offsetY = corner & 2;
float2 cornerPos = bottomLeftCornerPos + float2(offsetX, offsetY);
// Check if the corner is below or above the edge.
cornerMask[corner] = lineA * cornerPos.x + lineB * cornerPos.y + lineC > 0;
}
// If some, but not all, corners of the bilinear neighborhood are above the edge,
// the bilinear neighborhood is occupied - mark it.
[branch]
if (any(cornerMask) && !all(cornerMask))
{
uint pixelIndex = pixelCoord.y * _TextureSize + pixelCoord.x;
// Try to write assuming there was no chart already present.
uint prevChart;
InterlockedCompareExchange(_PerPixelChart[pixelIndex], NO_CHART, chartIdx, prevChart);
// If there was already a chart present, and it wasn't this threads chart, we have an overlap, so mark it.
if (prevChart != NO_CHART && prevChart != chartIdx)
{
_OverlapPixels[pixelIndex] = 1;
_OverlapInstances[_InstanceIndex] = 1;
}
}
}
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1a030e4984617e340bf47a116a1d0f9c
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,43 @@
Shader "Hidden/ChartRasterizerHardware"
{
SubShader
{
Pass
{
Cull Off
Conservative On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
nointerpolation uint chartId : TEXCOORD0;
};
float4 g_ScaleAndOffset;
uint g_ChartIndexOffset;
StructuredBuffer<uint> g_VertexToChartID;
v2f vert (float4 vertex : POSITION, uint vertexId : SV_VertexID)
{
v2f o;
o.vertex = float4((vertex.xy * g_ScaleAndOffset.xy + g_ScaleAndOffset.zw) * 2-1, 0, 1);
#if UNITY_UV_STARTS_AT_TOP
o.vertex.y = -o.vertex.y;
#endif
o.chartId = g_VertexToChartID[vertexId];
return o;
}
float frag (v2f i) : SV_Target
{
return g_ChartIndexOffset + i.chartId;
}
ENDCG
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ccc7ffe6cf9c542919823f6486edde9d
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,61 @@
Shader "Hidden/ChartRasterizerSoftware"
{
SubShader
{
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "GeometryUtils.hlsl"
#define PARALLEL_EPS 1e-6f
struct v2f
{
float4 vertex : SV_POSITION;
nointerpolation uint chartId : TEXCOORD0;
nointerpolation float4 aabb : TEXCOORD1;
};
uint g_Width;
uint g_Height;
float4 g_ScaleAndOffset;
uint g_ChartIndexOffset;
StructuredBuffer<float2> g_VertexBuffer;
StructuredBuffer<uint> g_VertexToOriginalVertex;
StructuredBuffer<uint> g_VertexToChartID;
v2f vert (uint vertexId : SV_VertexID)
{
v2f o;
// Expand the triangle.
uint2 resolution = uint2(g_Width, g_Height);
float2 tri[3];
ReadParentTriangle(g_VertexBuffer, vertexId, g_ScaleAndOffset, tri);
ExpandTriangleForConservativeRasterization(resolution, tri, vertexId, o.aabb, o.vertex, PARALLEL_EPS);
// Get the chart index.
uint originalVertexId = g_VertexToOriginalVertex[vertexId];
o.chartId = g_VertexToChartID[originalVertexId];
return o;
}
float frag (v2f i) : SV_Target
{
// Clip overly conservative edges.
float2 pos = i.vertex.xy;
if (pos.x < i.aabb.x || pos.y < i.aabb.y ||
pos.x > i.aabb.z || pos.y > i.aabb.w)
discard;
return g_ChartIndexOffset + i.chartId;
}
ENDCG
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c1e7adc3cf9b84be0b845973cedd828b
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,157 @@
// Given a triangle edge defined by 2 verts, calculate 2 new verts that define the same edge,
// pushed outwards enough to cover centroids of any pixels which may intersect the triangle.
void OffsetEdge(float2 v1, float2 v2, float2 pixelSize, out float2 v1Offset, out float2 v2Offset)
{
// Find the normal of the edge
float2 edge = v2 - v1;
float2 normal = normalize(float2(-edge.y, edge.x));
// Find the amount to offset by. This is the semidiagonal of the pixel box in the same quadrant as the normal.
float2 semidiagonal = pixelSize / sqrt(2.0);
semidiagonal *= float2(normal.x > 0 ? 1 : -1, normal.y > 0 ? 1 : -1);
// Offset the edge
v1Offset = v1 + semidiagonal;
v2Offset = v2 + semidiagonal;
}
// Given 2 lines defined by 2 points each, find the intersection point.
float2 LineIntersect(float2 p1, float2 p2, float2 p3, float2 p4, float eps = 0.0f)
{
// Line p1p2 represented as a1x + b1y = c1
float a1 = p2.y - p1.y;
float b1 = p1.x - p2.x;
float c1 = a1 * p1.x + b1 * p1.y;
// Line p3p4 represented as a2x + b2y = c2
float a2 = p4.y - p3.y;
float b2 = p3.x - p4.x;
float c2 = a2 * p3.x + b2 * p3.y;
float determinant = a1 * b2 - a2 * b1;
if (abs(determinant) <= eps) // Parallel lines - return any valid point.
return p1;
float x = b2 * c1 - b1 * c2;
float y = a1 * c2 - a2 * c1;
return float2(x, y) / determinant;
}
// Check which side the point p is on, relative to the line cp1-cp2.
// Optionally with an epsilon value within which points are considered outside.
bool IsInside(float2 p, float2 cp1, float2 cp2, float eps = 0.0f)
{
return (cp2.x - cp1.x) * (p.y - cp1.y) - (cp2.y - cp1.y) * (p.x - cp1.x) >= eps;
}
// Clip a polygon with a line defined by two points, in place.
// https://en.wikipedia.org/wiki/Sutherland-Hodgman_algorithm
void ClipPolygonWithLine(inout float2 polygon[6], inout uint polygonLength, float2 cp1, float2 cp2, float inside_eps = 0.0f, float parallel_eps = 0.0f)
{
float2 result[6];
uint resultLength = 0;
for (uint i = 0; i < polygonLength; i++)
{
float2 s = polygon[i];
float2 e = polygon[(i + 1) % polygonLength];
if (IsInside(e, cp1, cp2, inside_eps)) // At least one endpoint is inside
{
if (!IsInside(s, cp1, cp2, inside_eps)) // Only the end point is inside, add the intersection
{
result[resultLength] = LineIntersect(cp1, cp2, s, e, parallel_eps);
resultLength++;
}
result[resultLength] = e;
resultLength++;
}
else if (IsInside(s, cp1, cp2, inside_eps)) // Only the start point is inside, add the intersection
{
result[resultLength] = LineIntersect(cp1, cp2, s, e, parallel_eps);
resultLength++;
}
}
polygonLength = resultLength;
for (uint p = 0; p < polygonLength; p++)
polygon[p] = result[p];
}
// Clip a polygon to the bounding box of a texel at the given position
bool ClipPolygonWithTexel(float2 texelPosition, inout float2 polygon[6], inout uint polygonLength, float inside_eps = 0.0f, float parallel_eps = 0.0f)
{
// Get the bounding box of the texel to clip with.
float2 clipPolygon[4] =
{
texelPosition + float2(-0.5, -0.5),
texelPosition + float2(0.5, -0.5),
texelPosition + float2(0.5, 0.5),
texelPosition + float2(-0.5, 0.5),
};
// Clip to each edge of the quad
for (uint edge = 0; edge < 4; edge++)
{
ClipPolygonWithLine(polygon, polygonLength, clipPolygon[edge], clipPolygon[(edge + 1) % 4], inside_eps, parallel_eps);
}
return polygonLength > 0;
}
// Read the parent triangle of a vertex from a vertex buffer, and transform it to the desired space.
void ReadParentTriangle(StructuredBuffer<float2> vertexBuffer, uint vertexId, float4 scaleAndOffset, out float2 tri[3])
{
uint triId = vertexId / 3;
uint baseVertexId = triId * 3;
tri[0] = vertexBuffer[baseVertexId + 0].xy * scaleAndOffset.xy + scaleAndOffset.zw;
tri[1] = vertexBuffer[baseVertexId + 1].xy * scaleAndOffset.xy + scaleAndOffset.zw;
tri[2] = vertexBuffer[baseVertexId + 2].xy * scaleAndOffset.xy + scaleAndOffset.zw;
// Flip the winding order if it doesn't follow Unity's convention
float2 edgeAB = tri[1] - tri[0];
float2 edgeAC = tri[2] - tri[0];
bool flipWinding = ((edgeAB.x * edgeAC.y) - (edgeAB.y * edgeAC.x)) > 0;
if (flipWinding)
{
float2 temp = tri[1];
tri[1] = tri[2];
tri[2] = temp;
}
}
// Given an effective resolution, a triangle, and a specific vertex [0; 2], calculate a new position for the
// vertex, which will result in conservative rasterization when fed to a non-conservative rasterizer.
void ExpandTriangleForConservativeRasterization(
uint2 resolution,
float2 tri[3],
uint vertexId,
out float4 triangleAABB,
out float4 vertex,
float eps = 0.0f)
{
float2 pixelSize = (1.0 / resolution);
// Calculate the AABB to clip off the overly conservative edges
float2 aabbMin = min(min(tri[0], tri[1]), tri[2]);
float2 aabbMax = max(max(tri[0], tri[1]), tri[2]);
triangleAABB = (float4(aabbMin - (pixelSize / 2), aabbMax + (pixelSize / 2))) * resolution.xyxy;
// Get offset lines
float2 v1Off1, v2Off1;
OffsetEdge(tri[0], tri[1], pixelSize, v1Off1, v2Off1);
float2 v2Off2, v3Off2;
OffsetEdge(tri[1], tri[2], pixelSize, v2Off2, v3Off2);
float2 v3Off3, v1Off3;
OffsetEdge(tri[2], tri[0], pixelSize, v3Off3, v1Off3);
// Find their intersections. This is the new triangle
tri[0] = LineIntersect(v1Off1, v2Off1, v3Off3, v1Off3, eps);
tri[1] = LineIntersect(v2Off2, v3Off2, v1Off1, v2Off1, eps);
tri[2] = LineIntersect(v3Off3, v1Off3, v2Off2, v3Off2, eps);
vertex = float4(tri[vertexId % 3]*2-1, 0, 1);
#if UNITY_UV_STARTS_AT_TOP
vertex.y = -vertex.y;
#endif
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: babbf12d148f78a4ebc8b231ebe54046
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,106 @@
Shader "Hidden/UVFallbackBufferGeneration"
{
SubShader
{
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 5.0
#include "GeometryUtils.hlsl"
#define INSIDE_EPS 0.01f // Epsilon chosen to be 1 order of magnitude larger than the maximum
// distance between subsequent floats in range [0; 8192]
#define PARALLEL_EPS 1e-6f
struct v2f
{
float4 vertex : SV_POSITION;
nointerpolation float2 vertices[3] : TEXCOORD0;
};
uint g_Width;
uint g_Height;
float g_WidthScale;
float g_HeightScale;
StructuredBuffer<float2> g_VertexBuffer;
v2f vert (uint vertexId : SV_VertexID)
{
v2f o;
// Expand the triangle.
uint2 resolution = uint2(g_Width, g_Height);
float2 tri[3];
float4 unusedAABB;
const float eps = 0.01f;
ReadParentTriangle(g_VertexBuffer, vertexId, float4(g_WidthScale, g_HeightScale, 0, 0), tri);
ExpandTriangleForConservativeRasterization(resolution, tri, vertexId, unusedAABB, o.vertex, PARALLEL_EPS);
// Scale the triangle to screen coordinates, pass it to fragment shader.
o.vertices[0] = tri[0] * resolution;
o.vertices[1] = tri[1] * resolution;
o.vertices[2] = tri[2] * resolution;
return o;
}
float2 frag (v2f i, out float depth : SV_Depth) : SV_Target
{
float2 a = i.vertices[0];
float2 b = i.vertices[1];
float2 c = i.vertices[2];
// First check the easy case: If the texel center is inside the triangle, no need to clip at all. Just pick it.
// We need to shrink the triangle by a small epsilon to avoid picking a point precisely on the edge of the triangle,
// as our intersector cannot handle such points. Epsilon chosen to be 1 order of magnitude larger than the maximum
// distance between subsequent floats in range [0; 8192]
float2 texelCenter = i.vertex.xy;
if (IsInside(texelCenter, c, b, INSIDE_EPS) && IsInside(texelCenter, b, a, INSIDE_EPS) && IsInside(texelCenter, a, c, INSIDE_EPS))
{
#if UNITY_REVERSED_Z
depth = 1.0;
#else
depth = 0.0;
#endif
return 0.5;
}
// 6 length since clipping a triangle with a quad results in at most a hexagon.
float2 result[6] = { a, b, c, float2(0, 0), float2(0, 0), float2(0, 0) };
uint resultSize = 3;
// Clip to the texel.
ClipPolygonWithTexel(texelCenter, result, resultSize, INSIDE_EPS, PARALLEL_EPS);
// Discard removed triangles.
if (resultSize <= 0)
discard;
// Use the centroid of the clipped triangle as the UV.
float2 center = float2(0, 0);
for (uint i = 0; i < resultSize; i++)
center += result[i];
center /= resultSize;
float2 offset = center - floor(texelCenter);
// If the centroid lies on the boundary of the texel, discard - this is a singularity.
if (offset.x <= 0 || offset.x >= 1.0 || offset.y <= 0 || offset.y >= 1.0)
discard;
// Use the zbuffer to pick the triangle with the smallest distance from centroid to texel center.
float dist = distance(0.5, offset);
depth = (dist / sqrt(0.5));
#if UNITY_REVERSED_Z
depth = 1.0 - depth;
#endif
return offset;
}
ENDCG
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: d28d1bc84c83fd54a85bd554d8b5c9c7
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,59 @@
Shader "Hidden/PassthroughSkybox"
{
Properties
{
[NoScaleOffset] _Tex ("Cubemap", Cube) = "grey" {}
}
SubShader
{
Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" }
Cull Off ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 5.0
#include "UnityCG.cginc"
samplerCUBE _Tex;
half4 _Tex_HDR;
struct appdata_t
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 texcoord : TEXCOORD0;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert (appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = v.vertex.xyz;
return o;
}
float4 frag (v2f i) : SV_Target
{
half4 tex = texCUBE (_Tex, i.texcoord);
half3 c = DecodeHDR (tex, _Tex_HDR);
return half4(c, 1);
}
ENDCG
}
}
Fallback Off
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 5e4da360906ed014eb18ce666f57cc46
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,395 @@
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Sampling/Sampling.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl"
#include "TraceRayPathTracing.hlsl"
#include "PathTracingCommon.hlsl"
#include "PathTracingRandom.hlsl"
int g_BounceCount;
uint g_LightEvaluations;
int g_RenderedInstances;
int g_PathtracerAsGiPreviewMode;
uint g_PathTermination;
#include "LightSampling.hlsl"
UNIFIED_RT_DECLARE_ACCEL_STRUCT(g_SceneAccelStruct);
#define RENDER_ALL 0
#define RENDER_ONLY_STATIC 1
#define RENDER_ALL_IN_CAMERA_RAYS_THEN_ONLY_STATIC 2
#define RAY_TERMINATION 0
#define RAY_SCATTERING 1
int ScatterDiffusely(PTHitGeom hitGeom, float3 V, inout PathTracingSampler rngState, out UnifiedRT::Ray bounceRay, out float brdfPdf)
{
float2 u = rngState.GetSample2D(RAND_DIM_SURF_SCATTER);
bounceRay = (UnifiedRT::Ray)0;
float3 rayDirection;
UNITY_FLATTEN
if (!SampleDiffuseBrdf(u, hitGeom.worldFaceNormal, hitGeom.worldNormal, V, rayDirection, brdfPdf))
return RAY_TERMINATION;
bounceRay.origin = hitGeom.NextRayOrigin();
bounceRay.direction = rayDirection;
bounceRay.tMin = 0;
bounceRay.tMax = K_T_MAX;
return RAY_SCATTERING;
}
uint RayMask(bool isFirstHitSurface)
{
if (g_RenderedInstances == RENDER_ALL)
return DIRECT_RAY_VIS_MASK | INDIRECT_RAY_VIS_MASK;
else if (g_RenderedInstances == RENDER_ALL_IN_CAMERA_RAYS_THEN_ONLY_STATIC)
return isFirstHitSurface ? DIRECT_RAY_VIS_MASK | INDIRECT_RAY_VIS_MASK : INDIRECT_RAY_VIS_MASK;
else // RENDER_ONLY_STATIC
return isFirstHitSurface ? DIRECT_RAY_VIS_MASK : INDIRECT_RAY_VIS_MASK;
}
uint ShadowRayMask()
{
return SHADOW_RAY_VIS_MASK;
}
float ComputeEmissiveTriangleDensity(StructuredBuffer<UnifiedRT::InstanceData> instanceList, PTHitGeom hitGeom, int instanceIndex, float3 rayOrigin)
{
float3 L = hitGeom.worldPosition - rayOrigin;
float d = length(L);
if (d > 0)
L *= rcp(d);
float cosTheta = dot(hitGeom.worldFaceNormal, L);
// pdf to sample this as area light
float weight = (d * d) / (hitGeom.triangleArea * cosTheta);
// adjust pdf based on number of emissive triangles in the submesh that we hit
int geometryIndex = instanceList[instanceIndex].geometryIndex;
int numEmissiveTriangles = g_MeshList[geometryIndex].indexCount / uint(3);
weight /= numEmissiveTriangles;
return weight;
}
bool ShouldTreatAsBackface(UnifiedRT::Hit hitResult, MaterialProperties material)
{
// Have we hit something that is considered a backface when double sided GI is taken into account?
return !hitResult.isFrontFace && !material.doubleSidedGI;
}
bool ShouldTreatAsBackface(bool isFrontFace, bool doubleSidedGI)
{
return !isFrontFace && !doubleSidedGI;
}
bool ShouldTransmitRay(inout PathTracingSampler rngState, MaterialProperties material)
{
bool result = false;
if (material.isTransmissive)
{
// With proper support for IOR, the probability of refraction should be based on the materials fresnel.
// We don't have this information, so we base it on average transmission color, which matches the old baker.
// Additionally, we should divide the contribution of the ray by the probability of choosing either reflection or refraction,
// but we intentionally don't do this, since the old baker didn't do it either.
float transmissionProbability = dot(material.transmission, 1.0f) / 3.0f;
if (rngState.GetSample1D(RAND_DIM_TRANSMISSION) < transmissionProbability)
{
result = true;
}
else
{
result = false;
}
}
return result;
}
#define TRACE_MISS 0
#define TRACE_HIT 1
#define TRACE_TRANSMISSION 2
struct PathIterator
{
UnifiedRT::Ray ray;
UnifiedRT::Hit hitResult;
PTHitGeom hitGeo;
MaterialProperties material;
float lastScatterProbabilityDensity;
float3 radianceSample;
float3 throughput;
};
void AddRadiance(inout float3 radianceAccumulator, float3 throughput, float3 radianceSample)
{
radianceAccumulator += throughput * radianceSample;
}
void InitPathIterator(out PathIterator iter, UnifiedRT::Ray primaryRay)
{
iter = (PathIterator)0;
iter.lastScatterProbabilityDensity = 0;
iter.radianceSample = 0;
iter.throughput = 1;
iter.ray = primaryRay;
}
uint TraceBounceRay(inout PathIterator iterator, uint rayMask, UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, inout PathTracingSampler rngState)
{
uint traceResult = TRACE_MISS;
int rayFlags = UnifiedRT::kRayFlagNone;
iterator.hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, rayMask, iterator.ray, rayFlags);
iterator.hitGeo = (PTHitGeom) 0;
iterator.material = (MaterialProperties) 0;
UnifiedRT::InstanceData instanceInfo = (UnifiedRT::InstanceData) 0;
if (iterator.hitResult.IsValid())
{
instanceInfo = UnifiedRT::GetInstance(iterator.hitResult.instanceID);
iterator.hitGeo = GetHitGeomInfo(instanceInfo, iterator.hitResult);
iterator.hitGeo.FixNormals(iterator.ray.direction);
// Evaluate material properties at hit location
iterator.material = LoadMaterialProperties(instanceInfo, iterator.hitGeo.uv0, iterator.hitGeo.uv1);
traceResult = TRACE_HIT;
}
else
{
traceResult = TRACE_MISS;
}
if (traceResult == TRACE_HIT)
{
// If we hit a transmissive face, we should transmit or reflect.
if (ShouldTransmitRay(rngState, iterator.material))
{
traceResult = TRACE_TRANSMISSION;
}
// We've hit a surface that should be treated as a backface, we should kill the ray instead of bouncing. This matches the old behavior.
else if (ShouldTreatAsBackface(iterator.hitResult, iterator.material))
{
traceResult = TRACE_MISS;
}
}
return traceResult;
}
// Add radiance due to a missed ray which should sample the environment.
void AddEnvironmentRadiance(inout PathIterator iterator, bool applyIndirectScale)
{
float3 envRadiance;
float envPdf;
if (GetEnvironmentLightEmissionAndDensity(iterator.ray.direction, envRadiance, envPdf))
{
envPdf /= GetNumLights(iterator.ray.origin);
if (applyIndirectScale)
envRadiance *= g_IndirectScale;
AddRadiance(iterator.radianceSample, iterator.throughput, envRadiance * EmissiveMISWeightForBrdfRay(envPdf, iterator.lastScatterProbabilityDensity));
}
}
// Add radiance due to a hit emissive surface.
void AddEmissionRadiance(inout PathIterator iterator, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer<UnifiedRT::InstanceData> instanceList, bool applyIndirectScale)
{
float lightDensity = ComputeEmissiveTriangleDensity(instanceList, iterator.hitGeo, iterator.hitResult.instanceID, iterator.ray.origin);
lightDensity /= GetNumLights(iterator.ray.origin);
float3 emission = iterator.material.emission;
if (applyIndirectScale)
emission *= g_IndirectScale;
AddRadiance(iterator.radianceSample, iterator.throughput, emission * EmissiveMISWeightForBrdfRay(lightDensity, iterator.lastScatterProbabilityDensity));
}
// Add radiance due to directly sampled lights.
void AddRadianceFromDirectIllumination(
inout PathIterator iterator,
uint shadowRayMask,
UnifiedRT::DispatchInfo dispatchInfo,
UnifiedRT::RayTracingAccelStruct accelStruct,
StructuredBuffer<UnifiedRT::InstanceData> instanceList,
inout PathTracingSampler rngState,
bool isDirect)
{
const float3 radianceSample = EvalDirectIllumination(
dispatchInfo, accelStruct, instanceList, isDirect, shadowRayMask, iterator.hitGeo, iterator.material, min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS), rngState);
AddRadiance(iterator.radianceSample, iterator.throughput, radianceSample);
}
// Add radiance due to directly sampled lights and randomly hit emissive surfaces.
void AddRadianceFromEmissionAndDirectIllumination(inout PathIterator iterator, int bounceIndex, uint shadowRayMask, UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer<UnifiedRT::InstanceData> instanceList, inout PathTracingSampler rngState)
{
bool isFirstRay = (bounceIndex == 0);
// Emission
if (!g_PathtracerAsGiPreviewMode || !isFirstRay)
{
AddEmissionRadiance(iterator, accelStruct, instanceList, !isFirstRay);
}
// Check if we should do NEE, respecting the max bounce count
if (bounceIndex < g_BounceCount)
{
AddRadianceFromDirectIllumination(iterator, shadowRayMask, dispatchInfo, accelStruct, instanceList, rngState, g_PathtracerAsGiPreviewMode && isFirstRay);
}
}
// Trace the next path segment and accumulate radiance due to directly sampled lights, randomly hit emissive surfaces, and missed rays which sample the environment.
uint TraceBounceRayAndAddRadiance(inout PathIterator iterator, int bounceIndex, uint rayMask, uint shadowRayMask, UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer<UnifiedRT::InstanceData> instanceList, inout PathTracingSampler rngState)
{
uint traceResult = TraceBounceRay(iterator, rayMask, dispatchInfo, accelStruct, rngState);
if (traceResult == TRACE_HIT)
{
AddRadianceFromEmissionAndDirectIllumination(iterator, bounceIndex, shadowRayMask, dispatchInfo, accelStruct, instanceList, rngState);
}
else if (traceResult == TRACE_MISS)
{
AddEnvironmentRadiance(iterator, bounceIndex != 0);
}
float bullet = rngState.GetSample1D(RAND_DIM_RUSSIAN_ROULETTE);
if (bounceIndex >= RUSSIAN_ROULETTE_MIN_BOUNCES)
{
float p = max(iterator.throughput.x, max(iterator.throughput.y, iterator.throughput.z));
if (bullet > p)
traceResult = TRACE_MISS;
else
iterator.throughput /= p;
}
return traceResult;
}
bool Scatter(inout PathIterator iterator, inout PathTracingSampler rngState)
{
float brdfPdf;
int event = ScatterDiffusely(iterator.hitGeo, -iterator.ray.direction, rngState, iterator.ray, brdfPdf);
UNITY_FLATTEN
if (event == RAY_TERMINATION)
return false;
// Here we assumes two things:
// 1) We use cosine distribution for bounce.
// 2) We never multiply the cosine density, cos(θ)/π.
// This cancels out the cosine term of the rendering equation and the division by π
// in the diffuse BRDF. Thus we only need to multiply by albedo below.
iterator.throughput *= iterator.material.baseColor;
iterator.lastScatterProbabilityDensity = brdfPdf;
return true;
}
float3 LoadMaterialTransmission(UnifiedRT::InstanceData instanceInfo, float2 uv0)
{
int materialIndex = instanceInfo.userMaterialID;
PTMaterial matInfo = g_MaterialList[materialIndex];
bool pointSampleTransmission = (matInfo.flags & 4) != 0;
return saturate(SampleAtlas(g_TransmissionTextures, sampler_g_TransmissionTextures, matInfo.transmissionTextureIndex, uv0, matInfo.transmissionScale, matInfo.transmissionOffset, pointSampleTransmission).rgb);
}
#ifdef TERRAIN_RAY_MARCHING_ENABLED
bool IntersectionExecute(UnifiedRT::HitContext hitContext, out float hitT, out float2 uvAttributes, out ProceduralIntersectionAttribs additionalAttribs)
{
bool frontFaceHit = false;
float2 hitUv = 0;
bool hit = RayMarchTerrainTile(hitContext.InstanceID(), hitContext.PrimitiveIndex(),
hitContext.WorldRayOrigin(), hitContext.WorldRayDirection(),
hitContext.RayTCurrent(), hitT, frontFaceHit, hitUv);
if (hit && hitT < hitContext.RayTCurrent() && hitT >= hitContext.RayTMin())
{
uvAttributes = hitUv;
additionalAttribs.isFrontFace = frontFaceHit;
return true;
}
return false;
}
#endif
uint AnyHitExecute(UnifiedRT::HitContext hitContext, inout PathTracingPayload payload)
{
UnifiedRT::Hit hit;
hit.instanceID = hitContext.InstanceID();
hit.primitiveIndex = hitContext.PrimitiveIndex();
hit.uvBarycentrics = hitContext.UvBarycentrics();
hit.hitDistance = hitContext.RayTCurrent();
hit.isFrontFace = hitContext.IsFrontFace();
UnifiedRT::InstanceData instanceInfo = UnifiedRT::GetInstance(hitContext.InstanceID());
UnifiedRT::HitGeomAttributes attributes = UnifiedRT::FetchHitGeomAttributes(hit);
float2 uv0 = attributes.uv0.xy;
float3 transmission = LoadMaterialTransmission(instanceInfo, uv0);
// With proper support for IOR, the probability of refraction should be based on the materials fresnel.
// We don't have this information, so we base it on average transmission color, which matches the old baker.
// Additionally, we should divide the contribution of the ray by the probability of choosing either reflection or refraction,
// but we intentionally don't do this, since the old baker didn't do it either.
if (all(saturate(transmission) < 0.000001f))
{
return UnifiedRT::kAcceptHit;
}
else
{
payload.SetTransmission(payload.GetTransmission() * saturate(transmission));
return UnifiedRT::kIgnoreHit;
}
}
#ifdef TERRAIN_RAY_MARCHING_ENABLED
void ClosestHitExecute(UnifiedRT::HitContext hitContext, inout PathTracingPayload payload, ProceduralIntersectionAttribs proceduralHitAttribs)
{
if (payload.IsShadowRay())
{
payload.MarkHit();
}
else
{
UnifiedRT::Hit hit = (UnifiedRT::Hit)0;
hit.instanceID = hitContext.InstanceID();
hit.primitiveIndex = hitContext.PrimitiveIndex();
hit.uvBarycentrics = hitContext.UvBarycentrics();
hit.isFrontFace = hitContext.PrimitiveType() == UnifiedRT::kCommittedProceduralHit ? proceduralHitAttribs.isFrontFace : hitContext.IsFrontFace();
hit.hitDistance = hitContext.RayTCurrent();
payload.SetHit(hit);
}
}
#else
void ClosestHitExecute(UnifiedRT::HitContext hitContext, inout PathTracingPayload payload)
{
if (payload.IsShadowRay())
{
payload.MarkHit();
}
else
{
UnifiedRT::Hit hit = (UnifiedRT::Hit)0;
hit.instanceID = hitContext.InstanceID();
hit.primitiveIndex = hitContext.PrimitiveIndex();
hit.uvBarycentrics = hitContext.UvBarycentrics();
hit.isFrontFace = hitContext.IsFrontFace();
hit.hitDistance = hitContext.RayTCurrent();
payload.SetHit(hit);
}
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ee19d1386b0b8ef47b4ab104ec460f81
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,243 @@
#ifndef _PATHTRACING_PATHTRACINGCOMMON_HLSL_
#define _PATHTRACING_PATHTRACINGCOMMON_HLSL_
#pragma warning(error: 3206) // Implicit truncation of vector type
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/CommonStructs.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl"
// Include this last so PI doesn't get redefined if it was already defined
#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/Common.hlsl"
// Terrain procedural ray marching resources
#ifdef TERRAIN_RAY_MARCHING_ENABLED
Texture2DArray<float> _TerrainTexture;
SamplerState sampler_TerrainTexture;
float _TerrainTextureInvWidth;
#include "TerrainRayMarching.hlsl"
#endif
// Force uniform sampling of the skybox for debugging / ground truth generation
//#define UNIFORM_ENVSAMPLING
// Emissive mesh sampling: By default we use pure BRDF sampling.
// This can be overriden with a keyword.
#ifndef EMISSIVE_SAMPLING_BRDF_KEYWORD_DECLARED
#define EMISSIVE_SAMPLING_BRDF
#endif
#define SPOT_LIGHT 0
#define DIRECTIONAL_LIGHT 1
#define POINT_LIGHT 2
#define RECTANGULAR_LIGHT 3
#define DISC_LIGHT 4
#define PYRAMID_LIGHT 5
#define BOX_LIGHT 6
#define EMISSIVE_MESH 8 // Must match the variable in UnityEngine.PathTracing.Core.World
#define ENVIRONMENT_LIGHT 9
#define LIGHT_PICKING_METHOD_UNIFORM 0
#define LIGHT_PICKING_METHOD_RESERVOIR_GRID 1
#define LIGHT_PICKING_METHOD_LIGHT_GRID 2
#define DIRECT_RAY_VIS_MASK 1u
#define INDIRECT_RAY_VIS_MASK 2u
#define SHADOW_RAY_VIS_MASK 4u
// warning: bits 8u and 16u are used for lightmap LODs
#define MAX_TRANSMISSION_BOUNCES 6
struct HitEntry
{
uint instanceID;
uint primitiveIndex;
float2 barycentrics;
bool IsValid()
{
return instanceID != -1;
}
};
struct PixelState
{
float3 radiance;
float3 avgLightingDir;
float2 coord;
float2 motionVector;
float3 normal;
uint2 launchIndex;
uint2 launchDim;
float depth;
int bounces;
};
struct PTLight
{
float3 position;
int type;
float3 intensity;
int castsShadows;
float3 forward;
int contributesToDirectLighting;
float4 attenuation;
float3 up;
float width;
float3 right;
float height;
uint layerMask;
float indirectScale;
float2 spotAngle;
float range;
int shadowMaskChannel;
int falloffIndex;
float shadowRadius;
int cookieIndex;
};
int GetMeshLightInstanceID(PTLight light)
{
return light.height;
}
float4 GetColumn(float4x4 mat, int colIndex)
{
return float4(mat[0][3], mat[1][3], mat[2][3], mat[3][3]);
}
// Photometric RGB -> (Perceived) Luminance / digital ITU BT.709 (https://www.itu.int/rec/R-REC-BT.709)
float Luminance(float3 color)
{
const float3 weights = float3(0.2126f, 0.7152f, 0.0722f);
return dot(color, weights);
}
float3 EvalDiffuseBrdf(float3 albedo)
{
return albedo / PI;
}
float ClampedCosine(float3 normal, float3 direction)
{
return saturate(dot(normal, direction));
}
struct PTHitGeom
{
float3 worldPosition;
float3 lastWorldPosition;
float3 worldNormal;
float3 worldFaceNormal;
float2 uv0;
float2 uv1;
float triangleArea;
uint renderingLayerMask;
void FixNormals(float3 rayDirection)
{
worldFaceNormal = dot(rayDirection, worldFaceNormal) >= 0 ? -worldFaceNormal : worldFaceNormal;
worldNormal = dot(worldNormal, worldFaceNormal) < 0 ? -worldNormal : worldNormal;
}
float3 NextRayOrigin()
{
return OffsetRayOrigin(worldPosition, worldFaceNormal);
}
float3 NextTransmissionRayOrigin()
{
return OffsetRayOrigin(worldPosition, -worldFaceNormal);
}
};
#ifdef TERRAIN_RAY_MARCHING_ENABLED
PTHitGeom GetTerrainHitGeomInfo(UnifiedRT::InstanceData instanceInfo, UnifiedRT::Hit hit)
{
UnifiedRT::TerrainData terrainData = UnifiedRT::GetTerrain(instanceInfo.terrainIndex);
// hit.uvBarycentrics is in extent-convention UV space (cellCoord / (resolution-1)).
// Multiply by numCells to recover cell-space coordinates for position/normal.
float numCells = terrainData.heightmapWidthInTexels - 1.0;
float2 heightmapUV = hit.uvBarycentrics * numCells;
float3 localPos, localNormal;
ComputeTerrainLocalPosAndNormal(terrainData, instanceInfo.terrainIndex, heightmapUV, localPos, localNormal);
float3 worldPosition = mul(float4(localPos, 1), instanceInfo.localToWorld).xyz;
float3 worldNormal = normalize(mul((float3x3)instanceInfo.localToWorldNormals, localNormal));
PTHitGeom res = (PTHitGeom)0;
res.worldPosition = worldPosition;
res.lastWorldPosition = worldPosition;
res.worldNormal = worldNormal;
res.worldFaceNormal = worldNormal;
// UVs must match TerrainToMesh convention: vertex.xz / resolution = cellCoord / resolution
res.uv0 = hit.uvBarycentrics;
res.uv1 = hit.uvBarycentrics;
res.renderingLayerMask = instanceInfo.renderingLayerMask;
res.triangleArea = terrainData.terrainScale.x * terrainData.terrainScale.z * 0.5;
return res;
}
#endif
PTHitGeom GetHitGeomInfo(UnifiedRT::InstanceData instanceInfo, UnifiedRT::Hit hit)
{
#ifdef TERRAIN_RAY_MARCHING_ENABLED
if (instanceInfo.terrainIndex >= 0)
return GetTerrainHitGeomInfo(instanceInfo, hit);
#endif
UnifiedRT::HitGeomAttributes attributes = UnifiedRT::FetchHitGeomAttributes(hit);
PTHitGeom res;
res.worldPosition = mul(float4(attributes.position, 1), instanceInfo.localToWorld).xyz;
res.lastWorldPosition = mul(float4(attributes.position, 1), instanceInfo.previousLocalToWorld);
res.worldNormal = normalize(mul((float3x3)instanceInfo.localToWorldNormals, attributes.normal));
res.worldFaceNormal = mul((float3x3)instanceInfo.localToWorldNormals, attributes.faceNormal);
res.uv0 = attributes.uv0.xy;
res.uv1 = attributes.uv1.xy;
res.renderingLayerMask = instanceInfo.renderingLayerMask;
// compute the area of the hit point (used for MIS)
float l = length(res.worldFaceNormal);
res.triangleArea = 0.5 * instanceInfo.localToWorldDeterminant * l;
// normalize the face normal
if (l > 0)
res.worldFaceNormal /= l;
return res;
}
void FetchGeomAttributes(UnifiedRT::Hit hit, int geometryIndex, out float3 position, out float3 normal, out float3 faceNormal)
{
position = normal = faceNormal = 0.0f;
// hit.instanceID is set to be the same as the submeshIndex
// geometryIndex always points to the first submesh, the others submeshes follow in order in the geometry array:
// submeshGeomIndex = geometryIndex + randomUVHit.instanceID
if (!hit.IsValid())
return;
UnifiedRT::HitGeomAttributes hitAttribs = UnifiedRT::FetchHitGeomAttributes(geometryIndex + hit.instanceID, hit.primitiveIndex, hit.uvBarycentrics);
position = hitAttribs.position;
normal = hitAttribs.normal;
faceNormal = hitAttribs.faceNormal;
}
void FetchGeomAttributes(UnifiedRT::Hit hit, int geometryIndex, out float3 position, out float3 normal, out float3 faceNormal, out float2 uv1)
{
position = normal = faceNormal = 0.0f;
uv1 = 0.0f;
// hit.instanceID is set to be the same as the submeshIndex
// geometryIndex always points to the first submesh, the others submeshes follow in order in the geometry array:
// submeshGeomIndex = geometryIndex + randomUVHit.instanceID
if (!hit.IsValid())
return;
UnifiedRT::HitGeomAttributes hitAttribs = UnifiedRT::FetchHitGeomAttributes(geometryIndex + hit.instanceID, hit.primitiveIndex, hit.uvBarycentrics);
position = hitAttribs.position;
normal = hitAttribs.normal;
faceNormal = hitAttribs.faceNormal;
uv1 = hitAttribs.uv1;
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5493fc629e26e32478e42b7007250ec4
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
#ifndef _PATHTRACING_LIGHTGRID_HLSL_
#define _PATHTRACING_LIGHTGRID_HLSL_
// Grid reservoirs per cell
uint g_NumReservoirs;
// Grid dimentions
uint g_GridDimX;
uint g_GridDimY;
uint g_GridDimZ;
// Grid Bounds
float4 g_GridMin;
float4 g_GridSize;
// Cell dimentions
float4 g_CellSize;
float4 g_InvCellSize;
struct ThinReservoir
{
int lightIndex;
float weight;
};
float GetCellSize()
{
return g_CellSize.w;
}
float3 GetCellPosition(uint3 cellIndex)
{
return g_GridMin.xyz + g_CellSize.xyz * (cellIndex + float3(0.5f, 0.5f, 0.5f));
}
uint GetCellIndex(uint3 cellCoord)
{
uint slice = (g_GridDimX * g_GridDimY) * cellCoord.z;
return slice + cellCoord.y * g_GridDimX + cellCoord.x;
}
uint GetCellIndexFromPosition(float3 pos)
{
uint3 cellCoord = pos * g_InvCellSize.xyz - (g_GridMin.xyz * g_InvCellSize.xyz);
cellCoord = clamp(cellCoord, uint3(0, 0, 0), uint3(g_GridDimX, g_GridDimY, g_GridDimZ) - uint3(1, 1, 1));
return GetCellIndex(cellCoord);
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dded1b4d7a77fb940abbbb295be0acff
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
#ifndef _PATHTRACING_PATHTRACINGMATERIALS_HLSL_
#define _PATHTRACING_PATHTRACINGMATERIALS_HLSL_
#include "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialPool/MaterialPool.hlsl"
#define PTMaterial MaterialPool::MaterialEntry
#define MaterialProperties MaterialPool::MaterialProperties
StructuredBuffer<PTMaterial> g_MaterialList;
// Textures
Texture2DArray<float4> g_AlbedoTextures;
Texture2DArray<float4> g_TransmissionTextures;
Texture2DArray<float4> g_EmissionTextures;
float g_AtlasTexelSize; // The size of 1 texel in the atlases above
// Samplers
SamplerState sampler_g_EmissionTextures;
SamplerState sampler_g_AlbedoTextures;
SamplerState sampler_g_TransmissionTextures;
float g_AlbedoBoost;
float4 SampleAtlas(Texture2DArray<float4> atlas, SamplerState atlasSampler, uint index, float2 uv, float2 scale, float2 offset, bool pointFilterMode)
{
return MaterialPool::SampleAtlas(atlas, atlasSampler, g_AtlasTexelSize, index, uv, scale, offset, pointFilterMode);
}
MaterialProperties LoadMaterialProperties(UnifiedRT::InstanceData instanceInfo, float2 uv0, float2 uv1)
{
MaterialProperties output = MaterialPool::LoadMaterialProperties(
g_MaterialList,
g_AlbedoTextures,
sampler_g_AlbedoTextures,
g_TransmissionTextures,
sampler_g_TransmissionTextures,
g_EmissionTextures,
sampler_g_EmissionTextures,
g_AlbedoBoost,
g_AtlasTexelSize,
instanceInfo.userMaterialID,
uv0,
uv1);
return output;
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 584596a7dd3850f41ba28e8ac76d904a
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
// Blue noise sampling dimensions for various effects
#define RAND_DIM_AA 0 // used for jittering within a texel
#define RAND_DIM_SURF_SCATTER 1 // used for determining the scattering direction when we have hit a surface
#define RAND_DIM_TRANSMISSION 2 // used to determine if a ray is transmitted or reflected
#define RAND_DIM_RUSSIAN_ROULETTE 3 // used for Russian roulette
#define RAND_DIM_JITTERED_SHADOW 4 // Used for jittered shadows, ideally should be removed and the jitter handled in the SampleLightShape code instead but that's a refactor for another day
#define RAND_DIM_LIGHT_SELECTION 5 // used for light selection (arbitrarily many dimensions after this point)
#define RUSSIAN_ROULETTE_MIN_BOUNCES 1 // Min bounces for russian roulette. Matches PLM_DEFAULT_MIN_BOUNCES - 1
// Currently we use maximum 5 random numbers per light:
// 1 number to select a (mesh) light,
// 1 to select a triangle index,
// 2 numbers to sample the area/solid angle
// 1 number for RIS
// Each sample consists of 2 floats, so we use 4 samples total, with 3 floats skipped.
#define RAND_SAMPLES_PER_LIGHT 4
// Max number of lights to evaluate per bounce
#define MAX_LIGHT_EVALUATIONS 8
// The number of dimensions used per bounce (depends on the number of light evaluations)
#define QRNG_SAMPLES_PER_BOUNCE (RAND_DIM_LIGHT_SELECTION + RAND_SAMPLES_PER_LIGHT * MAX_LIGHT_EVALUATIONS)
// Which RNG to use for most of our path tracing code.
#define QRNG_METHOD_SOBOL
#define QRNG_SOBOL_02
// Define an alias for the RNG type we have chosen.
#if defined(QRNG_METHOD_SOBOL)
#define QRNG_TYPE QrngSobol2D
#elif defined(QRNG_METHOD_SOBOL_BLUE_NOISE)
#define QRNG_TYPE QrngSobolBlueNoise2D
#elif defined(QRNG_METHOD_GLOBAL_SOBOL_BLUE_NOISE)
#define QRNG_TYPE QrngGlobalSobolBlueNoise2D
#elif defined(QRNG_METHOD_KRONECKER)
#define QRNG_TYPE QrngKronecker2D
#elif defined(QRNG_METHOD_RANDOM_XOR_SHIFT)
#define QRNG_TYPE QrngXorShift
#elif defined(QRNG_METHOD_RANDOM_PCG_4D)
#define QRNG_TYPE QrngPcg4D
#endif
#include "PathTracingSampler.hlsl"
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 648103770f9a45649898dcd9d8aaa640
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,62 @@
#ifndef _SAMPLING_PATHTRACINGSAMPLER_HLSL_
#define _SAMPLING_PATHTRACINGSAMPLER_HLSL_
#if defined(QRNG_METHOD_RANDOM_XOR_SHIFT) || defined(QRNG_METHOD_RANDOM_PCG_4D)
#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/PseudoRandom.hlsl"
#else
#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/QuasiRandom.hlsl"
#endif
// global dimension offset (could be used to alter the noise pattern)
#ifndef QRNG_OFFSET
#define QRNG_OFFSET 0
#endif
#ifndef QRNG_SAMPLES_PER_BOUNCE
#define QRNG_SAMPLES_PER_BOUNCE 32
#endif
struct PathTracingSampler
{
QRNG_TYPE generator;
int pathIndex;
int bounceIndex;
uint2 pixelCoord;
void Init(uint2 startPixelCoord, uint startPathIndex, uint perPixelPathCount = 256)
{
#if defined(QRNG_METHOD_GLOBAL_SOBOL_BLUE_NOISE)
generator.Init(startPixelCoord, startPathIndex, perPixelPathCount);
#else
generator.Init(startPixelCoord, startPathIndex);
#endif
pixelCoord = startPixelCoord;
pathIndex = startPathIndex;
bounceIndex = 0;
}
float GetSample1D(int dimension)
{
uint actualDimension = QRNG_OFFSET + QRNG_SAMPLES_PER_BOUNCE * bounceIndex + dimension;
return generator.GetSample(actualDimension).x;
}
float2 GetSample2D(int dimension)
{
uint actualDimension = QRNG_OFFSET + QRNG_SAMPLES_PER_BOUNCE * bounceIndex + dimension;
return generator.GetSample(actualDimension);
}
void NextBounce()
{
bounceIndex++;
}
void NextPath()
{
generator.NextSample();
bounceIndex = 0;
}
};
#endif // _SAMPLING_PATHTRACINGSAMPLER_HLSL_
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 02eb941b9eeda63448b4714f4522c05a
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,83 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ LIGHT_SAMPLING_UNIFORM LIGHT_SAMPLING_RIS LIGHT_SAMPLING_ROUND_ROBIN
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 128
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC IntegrateDirectRadiance
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl"
#include "PathTracing.hlsl"
#include "SphericalHarmonicsUtils.hlsl"
uint g_LightIndexInCell;
StructuredBuffer<float3> g_Positions;
RWStructuredBuffer<float> g_RadianceShl2;
uint g_PositionsOffset;
uint g_SampleOffset;
uint g_SampleCount;
void IntegrateDirectRadiance(UnifiedRT::DispatchInfo dispatchInfo)
{
const uint threadIdx = dispatchInfo.dispatchThreadID.x;
const uint inProbeIdx = threadIdx / g_SampleCount + g_PositionsOffset;
const uint inProbeSampleIdx = threadIdx % g_SampleCount;
const uint outProbeIdx = threadIdx;
PathTracingSampler rngState;
rngState.Init(inProbeIdx, g_SampleOffset + inProbeSampleIdx);
// Local array to accumulate radiance into, using SoA layout.
float3 accumulatedRadianceSH[SH_COEFFICIENTS_PER_CHANNEL];
for (int i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i)
{
accumulatedRadianceSH[i] = 0.0f;
}
// Set up some stuff we need to sample lights.
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
float3 worldPosition = g_Positions[inProbeIdx];
SampleLightsOptions options;
options.isDirect = true;
options.receiveShadows = true;
options.shadowRayMask = ShadowRayMask();
options.lightsRenderingLayerMask = 0xFFFFFFFF;
options.numLightCandidates = min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS);
// TODO: Specialize sampling codepaths for different light types: https://jira.unity3d.com/browse/GFXLIGHT-2260
// Sample the main directional light
if (g_HasMainDirectionalLight)
{
PTLight directionalLight = g_MainDirectionalLight[0];
LightSample directionalLightSample;
SampleLightRadiance(dispatchInfo, accelStruct, g_AccelStructInstanceList, worldPosition, 0.0f, options, directionalLight, rngState, directionalLightSample);
AccumulateRadianceIntoSH(accumulatedRadianceSH, directionalLightSample.radiance, directionalLightSample.direction);
}
// Sample the environment
if (g_HasEnvironmentLight)
{
PTLight environmentLight = (PTLight)0;
environmentLight.type = ENVIRONMENT_LIGHT;
environmentLight.castsShadows = 1;
environmentLight.contributesToDirectLighting = 1;
environmentLight.layerMask = UINT_MAX;
LightSample environmentLightSample;
SampleLightRadiance(dispatchInfo, accelStruct, g_AccelStructInstanceList, worldPosition, 0.0f, options, environmentLight, rngState, environmentLightSample);
AccumulateRadianceIntoSH(accumulatedRadianceSH, environmentLightSample.radiance, environmentLightSample.direction);
}
const float monteCarloNormalization = 1.0f / (float)g_SampleCount;
for (uint channel = 0; channel < SH_COLOR_CHANNELS; ++channel)
{
for (uint i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i)
{
g_RadianceShl2[SHIndex(outProbeIdx, channel, i)] = accumulatedRadianceSH[i][channel] * monteCarloNormalization;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fe7f1a959d1d3f544a208b7e8805e6cd
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,91 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ LIGHT_SAMPLING_UNIFORM LIGHT_SAMPLING_RIS LIGHT_SAMPLING_ROUND_ROBIN
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 128
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC IntegrateDirectRadiance
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl"
#include "PathTracing.hlsl"
#include "SphericalHarmonicsUtils.hlsl"
uint g_LightIndexInCell;
StructuredBuffer<float3> g_Positions;
RWStructuredBuffer<float> g_RadianceShl2;
uint g_PositionsOffset;
uint g_SampleOffset;
uint g_SampleCount;
void IntegrateDirectRadiance(UnifiedRT::DispatchInfo dispatchInfo)
{
const uint threadIdx = dispatchInfo.dispatchThreadID.x;
const uint inProbeIdx = threadIdx / g_SampleCount + g_PositionsOffset;
const uint inProbeSampleIdx = threadIdx % g_SampleCount;
const uint outProbeIdx = threadIdx;
PathTracingSampler rngState;
rngState.Init(inProbeIdx, g_SampleOffset + inProbeSampleIdx);
// Local array to accumulate radiance into, using SoA layout.
float3 accumulatedRadianceSH[SH_COEFFICIENTS_PER_CHANNEL];
for (int i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i)
{
accumulatedRadianceSH[i] = 0.0f;
}
// Set up some stuff we need to sample lights.
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
float3 worldPosition = g_Positions[inProbeIdx];
SampleLightsOptions options;
options.isDirect = true;
options.receiveShadows = true;
options.shadowRayMask = ShadowRayMask();
options.lightsRenderingLayerMask = 0xFFFFFFFF;
options.numLightCandidates = min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS);
uint numLightsInCell = GetNumLights(worldPosition);
// If there are no lights to sample, continuing is a waste of time.
[branch] if (numLightsInCell == 0)
return;
LightSample lightSample = (LightSample)0;
#ifdef LIGHT_SAMPLING_ROUND_ROBIN
[branch] if (g_LightIndexInCell >= numLightsInCell)
return;
uint lightIndex = LocalToGlobalLightIndex(worldPosition, g_LightIndexInCell);
if (SampleLightRadianceRoundRobin(dispatchInfo, accelStruct, g_AccelStructInstanceList, worldPosition, 0.0f, options, lightIndex, rngState, lightSample))
#else
if (SampleRadianceFromLightGrid(dispatchInfo, accelStruct, g_AccelStructInstanceList, worldPosition, 0.0f, options, rngState, lightSample))
#endif
{
// Project into SH.
AccumulateRadianceIntoSH(accumulatedRadianceSH, lightSample.radiance, lightSample.direction);
}
#ifdef LIGHT_SAMPLING_ROUND_ROBIN
const float monteCarloNormalization = 1.0f / (float)g_SampleCount / (float)numLightsInCell;
#else
const float monteCarloNormalization = 1.0f / (float)g_SampleCount;
#endif
for (uint channel = 0; channel < SH_COLOR_CHANNELS; ++channel)
{
for (uint i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i)
{
#ifdef LIGHT_SAMPLING_ROUND_ROBIN
float result = accumulatedRadianceSH[i][channel] * monteCarloNormalization;
// If we are past the first iteration, we want to accumulate into the buffer rather than overwriting the current value.
if (g_LightIndexInCell > 0)
result += g_RadianceShl2[SHIndex(outProbeIdx, channel, i)];
g_RadianceShl2[SHIndex(outProbeIdx, channel, i)] = result;
#else
g_RadianceShl2[SHIndex(outProbeIdx, channel, i)] = accumulatedRadianceSH[i][channel] * monteCarloNormalization;
#endif
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1f9bfe60b4ad75943a5281839c1a4e5c
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,121 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ LIGHT_SAMPLING_UNIFORM LIGHT_SAMPLING_RIS LIGHT_SAMPLING_ROUND_ROBIN
#pragma multi_compile _ EMISSIVE_SAMPLING_LIGHT EMISSIVE_SAMPLING_BRDF EMISSIVE_SAMPLING_MIS
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 128
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC IntegrateIndirectRadiance
#include "PathTracing.hlsl"
#include "SphericalHarmonicsUtils.hlsl"
StructuredBuffer<float3> g_Positions;
RWStructuredBuffer<float> g_RadianceShl2;
uint g_PositionsOffset;
uint g_SampleOffset;
uint g_SampleCount;
float3 EstimateProbeRadiance(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::Ray ray, inout PathTracingSampler rngState)
{
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
PathIterator pathIter;
InitPathIterator(pathIter, ray);
int transparencyBounce = 0;
for (int bounceIndex = 0; bounceIndex <= g_BounceCount && transparencyBounce < MAX_TRANSMISSION_BOUNCES; bounceIndex++)
{
// The first path segment is special for the indirect pass - we should not add radiance from the
// environment or emission, as these are already explicitly sampled in the direct pass.
bool isFirstPathSegment = bounceIndex == 0;
uint pathRayMask = RayMask(isFirstPathSegment);
uint shadowRayMask = ShadowRayMask();
uint traceResult = TraceBounceRay(pathIter, pathRayMask, dispatchInfo, accelStruct, rngState);
if (traceResult == TRACE_HIT)
{
if (!isFirstPathSegment)
AddEmissionRadiance(pathIter, accelStruct, g_AccelStructInstanceList, false);
// Check if we should do NEE, respecting the max bounce count
if (bounceIndex < g_BounceCount)
AddRadianceFromDirectIllumination(pathIter, shadowRayMask, dispatchInfo, accelStruct, g_AccelStructInstanceList, rngState, false);
}
if (traceResult == TRACE_MISS)
{
if (!isFirstPathSegment)
AddEnvironmentRadiance(pathIter, false);
break;
}
if (traceResult == TRACE_TRANSMISSION)
{
bounceIndex--;
transparencyBounce++;
pathIter.ray.origin = pathIter.hitGeo.NextTransmissionRayOrigin();
pathIter.throughput *= pathIter.material.transmission;
rngState.NextBounce();
continue;
}
if (!Scatter(pathIter, rngState))
break;
if (bounceIndex >= RUSSIAN_ROULETTE_MIN_BOUNCES)
{
float p = max(pathIter.throughput.x, max(pathIter.throughput.y, pathIter.throughput.z));
if (rngState.GetSample1D(RAND_DIM_RUSSIAN_ROULETTE) > p)
break;
else
pathIter.throughput /= p;
}
rngState.NextBounce();
}
return pathIter.radianceSample;
}
void IntegrateIndirectRadiance(UnifiedRT::DispatchInfo dispatchInfo)
{
const uint threadIdx = dispatchInfo.dispatchThreadID.x;
const uint inProbeIdx = threadIdx / g_SampleCount + g_PositionsOffset;
const uint inProbeSampleIdx = threadIdx % g_SampleCount;
const uint outProbeIdx = threadIdx;
PathTracingSampler rngState;
rngState.Init(inProbeIdx, g_SampleOffset + inProbeSampleIdx);
// TODO(pema.malling): This works but that is sort of by accident. Avoid coupling to AA (which is unrelated to probe integration). https://jira.unity3d.com/browse/LIGHT-1687
const float3 uniformSphereDir = MapSquareToSphere(rngState.GetSample2D(RAND_DIM_AA));
UnifiedRT::Ray ray;
ray.origin = g_Positions[inProbeIdx];
ray.direction = uniformSphereDir;
ray.tMin = 0;
ray.tMax = K_T_MAX;
float3 radiance = EstimateProbeRadiance(dispatchInfo, ray, rngState);
// Local array to accumulate radiance into, using SoA layout.
float3 accumulatedRadianceSH[SH_COEFFICIENTS_PER_CHANNEL];
for (int i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i)
accumulatedRadianceSH[i] = 0.0f;
AccumulateRadianceIntoSH(accumulatedRadianceSH, radiance, uniformSphereDir);
const float reciprocalSampleCount = 1.0f / (float) g_SampleCount;
const float reciprocalUniformSphereDensity = 4.0f * PI;
const float monteCarloNormalization = reciprocalSampleCount * reciprocalUniformSphereDensity;
for (uint channel = 0; channel < SH_COLOR_CHANNELS; ++channel)
{
for (uint i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i)
{
g_RadianceShl2[SHIndex(outProbeIdx, channel, i)] = accumulatedRadianceSH[i][channel] * monteCarloNormalization;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5ecdabd800f05984696f22ba95284516
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,81 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 128
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC IntegrateOcclusion
#include "PathTracing.hlsl"
#include "LightSampling.hlsl"
StructuredBuffer<float3> g_Positions;
StructuredBuffer<int> g_PerProbeLightIndices;
RWStructuredBuffer<float> g_Occlusion;
uint g_ExpansionOffset;
uint g_PositionsOffset;
uint g_PerProbeLightIndicesOffset;
uint g_MaxLightsPerProbe;
uint g_SampleCount;
uint g_SampleOffset;
void IntegrateOcclusion(UnifiedRT::DispatchInfo dispatchInfo)
{
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
const uint threadIdx = dispatchInfo.dispatchThreadID.x;
const uint inProbeIdx = threadIdx / g_SampleCount;
const uint inProbeIdxWithOffset = inProbeIdx + g_PositionsOffset;
const uint inProbeSampleIdx = threadIdx % g_SampleCount;
float3 worldPosition = g_Positions[inProbeIdxWithOffset];
// Which probe in the expanded g_Occlusion buffer are we in?
const uint outProbeIdx = inProbeIdx * g_SampleCount + inProbeSampleIdx;
uint dimsOffset = 0;
for (uint indirectLightIndex = 0; indirectLightIndex < g_MaxLightsPerProbe; indirectLightIndex++)
{
// Which light in the expanded g_Occlusion buffer are we in?
const uint outOcclusionValueIdx = outProbeIdx * g_MaxLightsPerProbe + indirectLightIndex;
// Which light in the non-expanded g_PerProbeLightIndices buffer are we in?
const uint perProbeLightIndicesIdx = (inProbeIdx + g_ExpansionOffset) * g_MaxLightsPerProbe + indirectLightIndex + g_PerProbeLightIndicesOffset;
PathTracingSampler rngState;
rngState.Init(inProbeIdxWithOffset, g_SampleOffset + inProbeSampleIdx);
// NOTE: Do NOT attempt to refactor these nested if-statements to use 'continue'.
// FXC chokes on 'continue' instructions, resulting in completely incorrect output.
int lightIndex = g_PerProbeLightIndices[perProbeLightIndicesIdx];
if (lightIndex >= 0)
{
PTLight light;
if (lightIndex >= int(g_NumLights))
light = g_MainDirectionalLight[0];
else
light = FetchLight(lightIndex);
if (light.type == SPOT_LIGHT || light.type == POINT_LIGHT || light.type == DIRECTIONAL_LIGHT)
{
float3 attenuation = 1.0f;
bool isVisible = IsLightVisibleFromPoint(dispatchInfo, accelStruct, g_AccelStructInstanceList, SHADOW_RAY_VIS_MASK, worldPosition, rngState, dimsOffset, light, true, attenuation);
dimsOffset += 3; // 2 dimensions for sampling the light, 1 dimension for jittering the shadow ray
if (isVisible)
{
g_Occlusion[outOcclusionValueIdx] = 1.0f / g_SampleCount;
}
else
{
g_Occlusion[outOcclusionValueIdx] = 0.0f;
}
}
else
{
g_Occlusion[outOcclusionValueIdx] = 0.0f;
}
}
else
{
g_Occlusion[outOcclusionValueIdx] = 0.0f;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: de3013e16d99f2746ab1735ec06a660b
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,59 @@
#pragma only_renderers d3d11 vulkan metal glcore
#pragma multi_compile _ TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_GROUP_SIZE_X 128
#define UNIFIED_RT_GROUP_SIZE_Y 1
#define UNIFIED_RT_RAYGEN_FUNC IntegrateValidity
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/TraceRayAndQueryHit.hlsl"
#include "PathTracingCommon.hlsl"
#include "PathTracingMaterials.hlsl"
#include "PathTracingRandom.hlsl"
UNIFIED_RT_DECLARE_ACCEL_STRUCT(g_SceneAccelStruct);
StructuredBuffer<float3> g_Positions;
RWStructuredBuffer<float> g_Validity;
uint g_PositionsOffset;
uint g_SampleCount;
uint g_SampleOffset;
void IntegrateValidity(UnifiedRT::DispatchInfo dispatchInfo)
{
const uint threadIdx = dispatchInfo.dispatchThreadID.x;
const uint inProbeIdx = threadIdx / g_SampleCount + g_PositionsOffset;
const uint inProbeSampleIdx = threadIdx % g_SampleCount;
const uint outProbeIdx = threadIdx;
PathTracingSampler rngState;
rngState.Init(inProbeIdx, g_SampleOffset + inProbeSampleIdx);
// TODO(pema.malling): This works but that is sort of by accident. Avoid coupling to AA (which is unrelated to probe integration). https://jira.unity3d.com/browse/LIGHT-1687
const float3 uniformSphereDir = MapSquareToSphere(rngState.GetSample2D(RAND_DIM_AA));
UnifiedRT::Ray ray;
ray.origin = g_Positions[inProbeIdx];
ray.direction = uniformSphereDir;
ray.tMin = 0;
ray.tMax = K_T_MAX;
int rayFlags = UnifiedRT::kRayFlagNone;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
UnifiedRT::Hit hitResult = UnifiedRT::TraceRayClosestHit(dispatchInfo, accelStruct, UINT_MAX, ray, rayFlags);
bool invalidHit = false;
if (hitResult.IsValid())
{
UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID);
PTHitGeom geometry = GetHitGeomInfo(instance, hitResult);
MaterialProperties material = LoadMaterialProperties(instance, geometry.uv0, geometry.uv1);
if (!hitResult.isFrontFace && !material.doubleSidedGI && !material.isTransmissive)
invalidHit = true;
}
g_Validity[outProbeIdx] = invalidHit ? 1.0f / g_SampleCount : 0.0f;
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2c32233d92a2d0745930664c087c5c9a
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,27 @@
#pragma only_renderers d3d11 vulkan metal glcore
// See UnityComputeProbeIntegrator.IntegrateOcclusion()
#pragma kernel MapIndices
RWStructuredBuffer<int> g_PerProbeLightIndicesOutput;
StructuredBuffer<int> g_PerProbeLightIndicesInput;
StructuredBuffer<int> g_MappingTable;
uint g_PerProbeLightIndicesInputOffset;
uint g_MaxLightsPerProbe;
uint g_ProbeCount;
[numthreads(64,1,1)]
void MapIndices(uint3 id : SV_DispatchThreadID)
{
if (id.x >= g_MaxLightsPerProbe * g_ProbeCount)
return;
for (uint i = 0; i < g_MaxLightsPerProbe; i++)
{
uint probeIdx = id.x * g_MaxLightsPerProbe + i;
int inLightIdx = g_PerProbeLightIndicesInput[probeIdx + g_PerProbeLightIndicesInputOffset];
if (inLightIdx < 0)
g_PerProbeLightIndicesOutput[probeIdx] = -1;
else
g_PerProbeLightIndicesOutput[probeIdx] = g_MappingTable[inLightIdx];
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8b69d394493d90648bb31414187ae4db
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,167 @@
#pragma only_renderers d3d11 vulkan metal glcore
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#define UNIFIED_RT_BACKEND_COMPUTE
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/CommonStructs.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl"
#include "PathTracingCommon.hlsl"
#include "SphericalHarmonicsUtils.hlsl"
#pragma kernel ConvolveRadianceToIrradiance
#pragma kernel ConvertToUnityFormat
#pragma kernel AddSphericalHarmonicsL2
#pragma kernel ScaleSphericalHarmonicsL2
#pragma kernel WindowSphericalHarmonicsL2
StructuredBuffer<float> g_PrimaryInputShl2;
StructuredBuffer<float> g_SecondaryInputShl2;
RWStructuredBuffer<float> g_OutputShl2;
uint g_PrimaryInputOffset;
uint g_SecondaryInputOffset;
uint g_OutputOffset;
uint g_ProbeCount;
float g_Scale;
[numthreads(64, 1, 1)]
void ConvolveRadianceToIrradiance(in uint3 gidx: SV_DispatchThreadID)
{
if (gidx.x >= g_ProbeCount)
return;
uint inProbeIdx = gidx.x + g_PrimaryInputOffset;
uint outProbeIdx = gidx.x + g_OutputOffset;
// aHat is from https://cseweb.ucsd.edu/~ravir/papers/envmap/envmap.pdf and is used
// to convert spherical radiance to irradiance.
float aHat0 = 3.1415926535897932384626433832795028841971693993751058209749445923f; // π
float aHat1 = 2.0943951023931954923084289221863352561314462662500705473166297282f; // 2π/3
float aHat2 = 0.7853981633974483096156608458198757210492923498437764552437361480f; // π/4
for (uint channel = 0; channel < SH_COLOR_CHANNELS; channel++)
{
g_OutputShl2[SHIndex(outProbeIdx, channel, 0)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, channel, 0)] * aHat0;
uint i = 0;
for (i = 1; i < 4; ++i)
{
g_OutputShl2[SHIndex(outProbeIdx, channel, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, channel, i)] * aHat1;
}
for (i = 4; i < 9; ++i)
{
g_OutputShl2[SHIndex(outProbeIdx, channel, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, channel, i)] * aHat2;
}
}
}
[numthreads(64, 1, 1)]
void ConvertToUnityFormat(in uint3 gidx: SV_DispatchThreadID)
{
if (gidx.x >= g_ProbeCount)
return;
uint inProbeIdx = gidx.x + g_PrimaryInputOffset;
uint outProbeIdx = gidx.x + g_OutputOffset;
for (int rgb = 0; rgb < SH_COLOR_CHANNELS; ++rgb)
{
// Copy input SH so we don't overwrite the inputs while we're reading them,
// in case the input and output buffers are aliased.
const uint baseInputIdx = SHIndex(inProbeIdx, rgb, 0);
float l00 = g_PrimaryInputShl2[baseInputIdx];
float l1_1 = g_PrimaryInputShl2[baseInputIdx + 1];
float l10 = g_PrimaryInputShl2[baseInputIdx + 2];
float l11 = g_PrimaryInputShl2[baseInputIdx + 3];
float l2_2 = g_PrimaryInputShl2[baseInputIdx + 4];
float l2_1 = g_PrimaryInputShl2[baseInputIdx + 5];
float l20 = g_PrimaryInputShl2[baseInputIdx + 6];
float l21 = g_PrimaryInputShl2[baseInputIdx + 7];
float l22 = g_PrimaryInputShl2[baseInputIdx + 8];
// Calculate output indices
const uint l00Index = SHIndex(outProbeIdx, rgb, 0);
const uint l1_1Index = l00Index + 1;
const uint l10Index = l00Index + 2;
const uint l11Index = l00Index + 3;
const uint l2_2Index = l00Index + 4;
const uint l2_1Index = l00Index + 5;
const uint l20Index = l00Index + 6;
const uint l21Index = l00Index + 7;
const uint l22Index = l00Index + 8;
// L0
g_OutputShl2[l00Index] = (l00 * SH_L0_NORMALIZATION) / PI;
// L1
g_OutputShl2[l1_1Index] = (l10 * SH_L1_NORMALIZATION) / PI;
g_OutputShl2[l10Index] = (l11 * SH_L1_NORMALIZATION) / PI;
g_OutputShl2[l11Index] = (l1_1 * SH_L1_NORMALIZATION) / PI;
// L2
g_OutputShl2[l2_2Index] = (l2_2 * SH_L2_2_NORMALIZATION) / PI;
g_OutputShl2[l2_1Index] = (l2_1 * SH_L2_1_NORMALIZATION) / PI;
g_OutputShl2[l20Index] = (l20 * SH_L20_NORMALIZATION) / PI;
g_OutputShl2[l21Index] = (l21 * SH_L21_NORMALIZATION) / PI;
g_OutputShl2[l22Index] = (l22 * SH_L22_NORMALIZATION) / PI;
}
}
[numthreads(64, 1, 1)]
void AddSphericalHarmonicsL2(in uint3 gidx: SV_DispatchThreadID)
{
if (gidx.x >= g_ProbeCount)
return;
uint inProbeIdx = gidx.x + g_PrimaryInputOffset;
uint inSecondaryProbeIdx = gidx.x + g_SecondaryInputOffset;
uint outProbeIdx = gidx.x + g_OutputOffset;
for (uint i = 0; i < SH_TOTAL_COEFFICIENTS; i++)
{
g_OutputShl2[SHIndex(outProbeIdx, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, i)] + g_SecondaryInputShl2[SHIndex(inSecondaryProbeIdx, i)];
}
}
[numthreads(64, 1, 1)]
void ScaleSphericalHarmonicsL2(in uint3 gidx: SV_DispatchThreadID)
{
if (gidx.x >= g_ProbeCount)
return;
uint inProbeIdx = gidx.x + g_PrimaryInputOffset;
uint outProbeIdx = gidx.x + g_OutputOffset;
for (uint i = 0; i < SH_TOTAL_COEFFICIENTS; i++)
{
g_OutputShl2[SHIndex(outProbeIdx, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, i)] * g_Scale;
}
}
[numthreads(64, 1, 1)]
void WindowSphericalHarmonicsL2(in uint3 gidx: SV_DispatchThreadID)
{
if (gidx.x >= g_ProbeCount)
return;
uint inProbeIdx = gidx.x + g_PrimaryInputOffset;
uint outProbeIdx = gidx.x + g_OutputOffset;
// Windowing constants from WindowDirectSH in SHDering.cpp.
const float extraWindow[3] = { 1.0f, 0.922066f, 0.731864f };
// Apply windowing: Essentially SHConv3 times the window constants.
for (uint coefficient = 0; coefficient < SH_COEFFICIENTS_PER_CHANNEL; coefficient++)
{
float window;
if (coefficient == 0) // DC
window = extraWindow[0];
else if (coefficient < 4) // L1
window = extraWindow[1];
else
window = extraWindow[2]; // L2
g_OutputShl2[SHIndex(outProbeIdx, SH_CHANNEL_RED, coefficient)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, SH_CHANNEL_RED, coefficient)] * window;
g_OutputShl2[SHIndex(outProbeIdx, SH_CHANNEL_GREEN, coefficient)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, SH_CHANNEL_GREEN, coefficient)] * window;
g_OutputShl2[SHIndex(outProbeIdx, SH_CHANNEL_BLUE, coefficient)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, SH_CHANNEL_BLUE, coefficient)] * window;
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 592b8faa1d5c42841bcb3308316baf9e
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,97 @@
#pragma only_renderers d3d11 vulkan metal glcore
RWTexture2D<float4> g_LightmapInOut;
int g_TextureWidth;
int g_TextureHeight;
RWTexture2D<float4> g_DirectionalInOut;
Texture2D<float4> g_InputSampleCountInW;
Texture2D<float4> g_Normals;
#pragma kernel NormalizeValidity
[numthreads(8, 8, 1)]
void NormalizeValidity(
in uint3 gidx : SV_DispatchThreadID,
in uint lidx : SV_GroupIndex)
{
if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1)
return;
const float4 validity = g_LightmapInOut[gidx.xy];
const float sampleCount = max(validity.w, 1.0f);
const float4 normalizedValidity = float4(validity.xyz * (1.f / sampleCount), 1.f);
g_LightmapInOut[gidx.xy] = validity.w > 0.0f ? normalizedValidity : 0.0f;
}
#pragma kernel NormalizeAO
[numthreads(8, 8, 1)]
void NormalizeAO(
in uint3 gidx : SV_DispatchThreadID,
in uint lidx : SV_GroupIndex)
{
if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1)
return;
const float4 occlusion = g_LightmapInOut[gidx.xy];
const float4 vZero = 0.0f;
const float sampleCount = max(occlusion.w, 1.0f);
const float4 normalizedOcclusion = float4(1.f - (occlusion.xyz * (1.f / sampleCount)), 1.f);
g_LightmapInOut[gidx.xy] = occlusion.w > 0.0f ? normalizedOcclusion : vZero;
}
#pragma kernel NormalizeRadiance
[numthreads(8, 8, 1)]
void NormalizeRadiance(
in uint3 gidx : SV_DispatchThreadID,
in uint lidx : SV_GroupIndex)
{
if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1)
return;
const float4 radiance = g_LightmapInOut[gidx.xy];
const float4 vZero = 0.0f;
const float sampleCount = max(radiance.w, 1.0f);
const float4 normalizedRadiance = float4(radiance.xyz * (1.f / sampleCount), 1.f);
g_LightmapInOut[gidx.xy] = radiance.w > 0.0f ? normalizedRadiance : vZero;
}
#pragma kernel NormalizeDirection
[numthreads(8, 8, 1)]
void NormalizeDirection(
in uint3 gidx : SV_DispatchThreadID,
in uint lidx : SV_GroupIndex)
{
if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1)
return;
const float4 vHalf = 0.5f;
const bool valid = g_InputSampleCountInW[gidx.xy].w > 0.0f;
if (!valid)
{
g_DirectionalInOut[gidx.xy] = vHalf;
return;
}
const float4 epsilon = 0.001f;
const float3 normal = g_Normals[gidx.xy].xyz;
float4 directionality = g_DirectionalInOut[gidx.xy];
directionality *= 1.0f / max(epsilon, directionality.wwww);
directionality.w = dot(normal, directionality.xyz);
g_DirectionalInOut[gidx.xy] = directionality * vHalf + vHalf;
}
#pragma kernel NormalizeShadowMask
[numthreads(8, 8, 1)]
void NormalizeShadowMask(
in uint3 gidx : SV_DispatchThreadID,
in uint lidx : SV_GroupIndex)
{
if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1)
return;
const float4 shadow = g_LightmapInOut[gidx.xy];
const float4 vZero = 0.0f;
const float sampleCount = max(g_InputSampleCountInW[gidx.xy].w, 1.0f);
const float4 normalizedShadow = float4(shadow * (1.f / sampleCount));
g_LightmapInOut[gidx.xy] = g_InputSampleCountInW[gidx.xy].w > 0.0f ? normalizedShadow : vZero;
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d9225443dcd40974c9a13cb6e166ca5f
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,88 @@
#pragma only_renderers d3d11 vulkan metal glcore
// This shader computes a strided segmented reduction - essentially N reductions in parallel, in a single pass.
// A very simple approach is used, where each thread just computes the i'th reduction sequentially.
// Each segment is assumed to be the same length. The shader adds to its output, so zero-initialize it if needed.
// Example with width = 2, stride = 3:
//
// Input buffer layout:
// |val 0|val 1|val 2|val 3|val 4|val 5|val 6|val 7|val 8|val 9|val 10|val 11|...
// | element 0 | element 1 | element 2 | element 3 |...
// | segment 0 | segment 1 |...
//
// Resulting output buffer:
// |val 0 + val 3|val 1 + val 4|val 2 + val 5|val 6 + val 9|val 7 + val 10|val 8 + val 11|...
// | element 0 + element 1 | element 2 + element 3 |...
#pragma kernel SegmentedReductionFloat
#define MAX_SEGMENT_STRIDE 32
uint g_SegmentWidth; // How many elements in each segment
uint g_SegmentStride; // How many values in each element
uint g_SegmentCount; // How many segments
uint g_InputOffset; // Offset into input buffer, specified in elements
uint g_OutputOffset; // Offset into output buffer, specified in elements
uint g_OverwriteOutput; // If 1, overwrite the output buffer. Otherwise, add to it.
StructuredBuffer<float> g_InputFloatBuffer; // Length = g_SegmentWidth * g_SegmentStride * g_NumSegments
RWStructuredBuffer<float> g_OutputFloatBuffer; // Length = g_SegmentStride * g_NumSegments
// These uniforms can be used for multi-pass reductions, handling cases where the input segments can
// not be cleanly divided into sub-segments. If g_TruncateInterval > 0 && g_TruncatedSegmentWidth > 0,
// every g_TruncateInterval'th segment will be truncated to a width given by g_TruncatedSegmentWidth.
uint g_TruncateInterval;
uint g_TruncatedSegmentWidth;
[numthreads(64,1,1)]
void SegmentedReductionFloat(uint3 id : SV_DispatchThreadID)
{
if (id.x >= g_SegmentCount)
return;
uint currentSegmentWidth = g_SegmentWidth; // How large is this threads segment?
uint truncatedElementsThusFar = 0; // How many elements have been truncated thus far?
// Handle truncation
bool truncationEnabled = g_TruncateInterval > 0 && g_TruncatedSegmentWidth > 0;
if (truncationEnabled)
{
truncatedElementsThusFar = (id.x / g_TruncateInterval) * (g_SegmentWidth - g_TruncatedSegmentWidth);
bool shouldTruncate = (id.x + 1) % g_TruncateInterval == 0;
if (shouldTruncate)
{
currentSegmentWidth = g_TruncatedSegmentWidth;
}
}
// Step 1: Zero initialize an accumulator in local memory
float accumulator[MAX_SEGMENT_STRIDE];
uint valueIdx;
for (valueIdx = 0; valueIdx < g_SegmentStride; valueIdx++)
{
accumulator[valueIdx] = 0.0f;
}
// Step 2: Accumulate values in the segment into local memory
const uint baseElementIndex = id.x * g_SegmentWidth - truncatedElementsThusFar + g_InputOffset;
for (uint elementIdx = 0; elementIdx < currentSegmentWidth; elementIdx++)
{
for (valueIdx = 0; valueIdx < g_SegmentStride; valueIdx++)
{
accumulator[valueIdx] += g_InputFloatBuffer[(baseElementIndex + elementIdx) * g_SegmentStride + valueIdx];
}
}
// Step 3: Write accumulated values to global memory
for (valueIdx = 0; valueIdx < g_SegmentStride; valueIdx++)
{
if (g_OverwriteOutput)
{
g_OutputFloatBuffer[(id.x + g_OutputOffset) * g_SegmentStride + valueIdx] = accumulator[valueIdx];
}
else
{
g_OutputFloatBuffer[(id.x + g_OutputOffset) * g_SegmentStride + valueIdx] += accumulator[valueIdx];
}
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 065a0fb9495518147aaf6f482d53ff42
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
#pragma only_renderers d3d11 vulkan metal glcore ps5 switch2
#define GROUP_SIZE_X 8
#define GROUP_SIZE_Y 8
int g_TextureWidth;
int g_TextureHeight;
int g_TargetSlice;
int g_TargetOffsetX;
int g_TargetOffsetY;
Texture2D g_OpacityTexture;
SamplerState sampler_g_OpacityTexture;
float g_Alpha;
uint g_UseAlphaCutoff;
float g_AlphaCutoff;
float4 g_OpacityTextureUVTransform;
RWTexture2DArray<float4> g_AlbedoTextures;
#pragma kernel SetAlphaChannel
[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)]
void SetAlphaChannel(in uint2 gidx: SV_DispatchThreadID)
{
if (gidx.x >= uint(g_TextureWidth) || gidx.y >= uint(g_TextureHeight))
return;
uint2 targetOffset = uint2(g_TargetOffsetX, g_TargetOffsetY);
float2 uv = (float2(gidx)+0.5) / float2(g_TextureWidth, g_TextureHeight);
uv = g_OpacityTextureUVTransform.xy * uv + g_OpacityTextureUVTransform.zw;
float2 dUvdx = g_OpacityTextureUVTransform.x / float(g_TextureWidth);
float2 dUvdy = g_OpacityTextureUVTransform.y / float(g_TextureHeight);
float4 texelValue = g_AlbedoTextures[uint3(gidx + targetOffset, g_TargetSlice)];
texelValue.a = g_OpacityTexture.SampleGrad(sampler_g_OpacityTexture, uv, dUvdx, dUvdy).a * g_Alpha;
// Alpha clipping
if (g_UseAlphaCutoff)
{
texelValue.a = (texelValue.a > g_AlphaCutoff) ? 1.0f : 0.0f;
}
g_AlbedoTextures[uint3(gidx + targetOffset, g_TargetSlice)] = texelValue;
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5efaea0e81c66334aa9d062d6573e6fd
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,101 @@
#define SH_COLOR_CHANNELS 3
#define SH_COEFFICIENTS_PER_CHANNEL 9
#define SH_TOTAL_COEFFICIENTS (SH_COLOR_CHANNELS * SH_COEFFICIENTS_PER_CHANNEL)
#define SH_CHANNEL_RED 0
#define SH_CHANNEL_GREEN 1
#define SH_CHANNEL_BLUE 2
// 1/2 * sqrt(1/π)
#define SH_L0_NORMALIZATION 0.2820947917738781434740397257803862929220253146644994284220428608f
// 1/2 * sqrt(3/π)
#define SH_L1_NORMALIZATION 0.4886025119029199215863846228383470045758856081942277021382431574f
// sqrt(15/π)/2
#define SH_L2_2_NORMALIZATION 1.0925484305920790705433857058026884026904329595042589753478516999f
// sqrt(15/π)/2
#define SH_L2_1_NORMALIZATION SH_L2_2_NORMALIZATION
// sqrt(5/π)/4
#define SH_L20_NORMALIZATION 0.3153915652525200060308936902957104933242475070484115878434078878f
// sqrt(15/π)/2
#define SH_L21_NORMALIZATION SH_L2_2_NORMALIZATION
// sqrt(15/π)/4
#define SH_L22_NORMALIZATION 0.5462742152960395352716928529013442013452164797521294876739258499f
// Index into a buffer of floats representing contiguous L2 Spherical Harmonics coefficients, with separate channel and level.
uint SHIndex(uint probeIdx, uint channel, uint level)
{
return probeIdx * SH_TOTAL_COEFFICIENTS + channel * SH_COEFFICIENTS_PER_CHANNEL + level;
}
// Index into a buffer of floats representing contiguous L2 Spherical Harmonics coefficients, with the coefficient index.
uint SHIndex(uint probeIdx, uint coefficient)
{
return probeIdx * SH_TOTAL_COEFFICIENTS + coefficient;
}
// Basis function Y_0.
float SHL0()
{
return SH_L0_NORMALIZATION;
}
// Basis function Y_1,-1.
float SHL1_1(float3 direction)
{
return SH_L1_NORMALIZATION * direction.x;
}
// Basis function Y_1,0.
float SHL10(float3 direction)
{
return SH_L1_NORMALIZATION * direction.y;
}
// Basis function Y_1,1.
float SHL11(float3 direction)
{
return SH_L1_NORMALIZATION * direction.z;
}
// Basis function Y_2,-2.
float SHL2_2(float3 direction)
{
return SH_L2_2_NORMALIZATION * direction.x * direction.y;
}
// Basis function Y_2,-1.
float SHL2_1(float3 direction)
{
return SH_L2_1_NORMALIZATION * direction.y * direction.z;
}
// Basis function Y_2,0.
float SHL20(float3 direction)
{
return SH_L20_NORMALIZATION * (3.0f * direction.z * direction.z - 1.0f);
}
// Basis function Y_2,1.
float SHL21(float3 direction)
{
return SH_L21_NORMALIZATION * direction.x * direction.z;
}
// Basis function Y_2,2.
float SHL22(float3 direction)
{
return SH_L22_NORMALIZATION * (direction.x * direction.x - direction.y * direction.y);
}
void AccumulateRadianceIntoSH(inout float3 sh[SH_COEFFICIENTS_PER_CHANNEL], float3 radiance, float3 direction)
{
sh[0] += radiance * SHL0();
sh[1] += radiance * SHL1_1(direction);
sh[2] += radiance * SHL10(direction);
sh[3] += radiance * SHL11(direction);
sh[4] += radiance * SHL2_2(direction);
sh[5] += radiance * SHL2_1(direction);
sh[6] += radiance * SHL20(direction);
sh[7] += radiance * SHL21(direction);
sh[8] += radiance * SHL22(direction);
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 53b08de14f6b2bb48be2613f00bbc94d
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
#ifndef _PATHTRACING_STOCHASTICLIGHTMAPSAMPLING_HLSL_
#define _PATHTRACING_STOCHASTICLIGHTMAPSAMPLING_HLSL_
// Silence FXC's annoying "potentially uninitialized variable" warnings
#pragma warning (disable : 4000)
#include "PathTracingCommon.hlsl"
UnifiedRT::Ray MakeUVRay(float2 origin)
{
UnifiedRT::Ray uvRay;
uvRay.direction = float3(0.0, 0.0, 1.0);
uvRay.tMax = 0.0002;
uvRay.tMin = 0.0;
uvRay.origin = float3(origin, -0.0001);
return uvRay;
};
UnifiedRT::Hit LightmapSampleTexelOffset(
float2 texelPos,
float2 texelOffset,
float2 resolution,
float2 instanceScale,
UnifiedRT::DispatchInfo dispatchInfo,
UnifiedRT::RayTracingAccelStruct uvAccelStruct,
half2 uvFallback)
{
// No valid uv data for this texel?
if (uvFallback.x < 0)
return UnifiedRT::Hit::Invalid();
// select a random point in the texel
const float2 scaledResolution = resolution * instanceScale;
const float2 texelSample = (texelPos + texelOffset) / scaledResolution;
// shoot a random ray towards the texel
const UnifiedRT::Ray uvRay = MakeUVRay(texelSample);
UnifiedRT::Hit randomUVHit = UnifiedRT::TraceRayClosestHit(dispatchInfo, uvAccelStruct, 0xFFFFFFFF, uvRay, 0);
if (randomUVHit.IsValid())
{
return randomUVHit;
}
else
{
// use fallback uv
const float2 fallbackUvSample = (texelPos + uvFallback) / scaledResolution;
const UnifiedRT::Ray uvRay = MakeUVRay(fallbackUvSample);
return UnifiedRT::TraceRayClosestHit(dispatchInfo, uvAccelStruct, 0xFFFFFFFF, uvRay, 0);
}
return UnifiedRT::Hit::Invalid();
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e713aa3169115ab43a1b2383e0e200e5
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,255 @@
#ifndef _PATHTRACING_TERRAINRAYMARCHING_HLSL_
#define _PATHTRACING_TERRAINRAYMARCHING_HLSL_
// DDA heightmap ray marching for terrain procedural intersection.
//// The terrain is divided in tiles      A tile is divided in cells         A cell corresponds to 4 heightmap texels
// +---+---+---+---+---+---+              +----+----+----+----+              +----+ and is divided into 2 triangles
// |   |   |   |   |   |   |              |   /|   /|   /|   /|              |   /|
// |   |   |   |   |   |   |              |  / |  / |  / |  / |              |  / |
// +---+---+---+---+---+---+              | /  | /  | /  | /  |              | /  |
// |   |   |   |   |   |   |              |/   |/   |/   |/   |              |/   |
// |   |   |   |   |   |   |              +----+----+----+----+              +----+
// +---+---+---+---+---+---+              |   /|   /|   /|   /|
// |   |   |   |   |   |   |              |  / |  / |  / |  / |
// |   |   |   |   |   |   |              | /  | /  | /  | /  |
// +---+---+---+---+---+---+              |/   |/   |/   |/   |
// |   |   |   |   |   |   |              +----+----+----+----+
// |   |   |   |   |   |   |              |   /|   /|   /|   /|
// +---+---+---+---+---+---+              |  / |  / |  / |  / |
// |   |   |   |   |   |   |              | /  | /  | /  | /  |
// |   |   |   |   |   |   |              |/   |/   |/   |/   |
// +---+---+---+---+---+---+              +----+----+----+----+
// |   |   |   |   |   |   |                tileWidthInCells
// |   |   |   |   |   |   |              <------------------->
// +---+---+---+---+---+---+
//        tileCountX
// <----------------------->
float _terrainMax4(float4 v)
{
return max(max(v.x, v.y), max(v.z, v.w));
}
float2 TerrainBboxIntersect(float3 rayOrigin, float3 rayInvDir, float3 boxMin, float3 boxMax, float tMin, float tMax)
{
float3 f = (boxMax - rayOrigin) * rayInvDir;
float3 n = (boxMin - rayOrigin) * rayInvDir;
float3 tmax = max(f, n);
float3 tmin = min(f, n);
float maxT = min(min(min(tmax.x, tmax.y), tmax.z), tMax);
float minT = max(max(max(tmin.x, tmin.y), tmin.z), tMin);
return float2(minT, maxT);
}
// Ordering of a cell's corners as returned by textureGather:
//
// 0 +----+ 1
//     |   /|
//     |  / |
//     | /  |
//     |/   |
//  3 +----+ 2
// ---> X axis
static const int kCellCorner01 = 0;
static const int kCellCorner11 = 1;
static const int kCellCorner10 = 2;
static const int kCellCorner00 = 3;
void GetCellCornerHeights(int2 coord, int terrainIndex, out float4 cellHeights, out bool cellIsHole)
{
float2 uv = (float2(coord) + 1.0) * _TerrainTextureInvWidth;
cellHeights = _TerrainTexture.Gather(sampler_TerrainTexture, float3(uv, terrainIndex));
// Hole encoding (AccelStructAdapter.CreateTerrainTexture) negates the texel at the cell's
// (cx, cy) corner -- corner00 of that cell -- so check that specific corner.
cellIsHole = cellHeights[kCellCorner00] < 0;
// abs() before subtracting the bias so a negated hole-corner texel decodes
// symmetrically (otherwise it would become (H+2)/32767 instead of H/32767).
cellHeights = abs(cellHeights) - 1.0f / 32767.f;
}
void IntersectRayWithTrianglePlane(float C, float A, float B, float3 rayOrigin, float3 rayDirection, float2 currentCell, out float hitT, out float2 hitPos, out bool frontFaceHit)
{
float denom = A * rayDirection.x + B * rayDirection.y - rayDirection.z;
frontFaceHit = (denom > 0);
hitT = (A * (currentCell.x - rayOrigin.x) + B * (currentCell.y - rayOrigin.y) - C + rayOrigin.z) * rcp(denom);
hitPos = rayOrigin.xy - currentCell + hitT * rayDirection.xy;
}
bool RayMarchTerrainTile(uint instanceID, int tileIndex, float3 rayOrigin, float3 rayDirection, float rayTmax, out float hitT, out bool frontFaceHit, out float2 hitUv)
{
UnifiedRT::InstanceData instanceData = UnifiedRT::GetInstance(instanceID);
UnifiedRT::TerrainData terrainData = UnifiedRT::GetTerrain(instanceData.terrainIndex);
// Transform world-space ray to terrain-local cell coordinates
float3 terrainOrigin = mul(float4(0, 0, 0, 1), instanceData.localToWorld);
rayOrigin = (rayOrigin - terrainOrigin) * terrainData.invTerrainScale;
rayDirection *= terrainData.invTerrainScale;
rayDirection.yz = rayDirection.zy;
rayOrigin.yz = rayOrigin.zy;
// Avoid division by zero in DDA setup
if (rayDirection.x == 0.0)
rayDirection.x = 1.e-37;
if (rayDirection.y == 0.0)
rayDirection.y = 1.e-37;
// Tile bounding box in cell coordinates
int2 tileCoords = int2(tileIndex & terrainData.pow2ModuloTileCountX, tileIndex >> terrainData.pow2DivideTileCountX);
int2 tileBboxMin = tileCoords * terrainData.tileWidthInCells;
int2 tileBboxMax = (tileCoords + 1) * terrainData.tileWidthInCells;
float3 boxMin = float3(tileBboxMin.x, tileBboxMin.y, 0.0);
float3 boxMax = float3(tileBboxMax.x, tileBboxMax.y, 1.0);
// Intersect ray with tile AABB
float2 tRange = TerrainBboxIntersect(rayOrigin, 1.0 / rayDirection, boxMin, boxMax, 0.0f, rayTmax);
float tTileEntry = tRange.x;
float tTileExit = tRange.y;
hitT = 0;
hitUv = 0;
frontFaceHit = false;
// No intersection with tile AABB
if (tTileEntry > tTileExit)
return false;
// DDA grid traversal setup
float3 startPos = rayOrigin + tTileEntry * rayDirection;
int2 startCell = clamp(int2(startPos.xy), tileBboxMin, tileBboxMax - 1);
int2 step = int2(sign(rayDirection.xy));
float2 nextCellEdge = float2(startCell) + float2(step.x > 0 ? 1.0f : 0.0f, step.y > 0 ? 1.0f : 0.0f);
float2 tMax = (nextCellEdge - rayOrigin.xy) / rayDirection.xy;
float2 tDelta = abs(1.0 / rayDirection.xy);
float tCellEntry = tTileEntry;
int2 iterCell = startCell;
for (int i = 0; i < 2 * terrainData.tileWidthInCells; i++)
{
if (iterCell.x < tileBboxMin.x || iterCell.y < tileBboxMin.y ||
iterCell.x >= tileBboxMax.x || iterCell.y >= tileBboxMax.y ||
tCellEntry > tTileExit)
break;
float4 cellHeights;
bool cellIsHole;
GetCellCornerHeights(iterCell, instanceData.terrainIndex, cellHeights, cellIsHole);
float tCellExit = min(tMax.x, tMax.y);
float hRayAtExit = rayOrigin.z + tCellExit * rayDirection.z;
float hRayAtEntry = rayOrigin.z + tCellEntry * rayDirection.z;
// Check if ray's height range overlaps the cell's height range. A small absolute
// tolerance avoids FP rounding silently dropping hits where the ray grazes the surface
// exactly (e.g. a perfectly flat zero-height terrain hit by an axis-aligned ray --
// hRayAtEntry can round to either side of zero).
const float kHeightOverlapTolerance = 1e-7f;
if (min(hRayAtExit, hRayAtEntry) <= _terrainMax4(cellHeights) + kHeightOverlapTolerance && !cellIsHole)
{
bool hit = false;
// Triangle 1: C00, C10, C11 (upper-right triangle)
float t1;
float2 p1;
bool frontFaceHit1;
IntersectRayWithTrianglePlane(cellHeights[kCellCorner00], cellHeights[kCellCorner10] - cellHeights[kCellCorner00], cellHeights[kCellCorner11] - cellHeights[kCellCorner10], rayOrigin, rayDirection, iterCell, t1, p1, frontFaceHit1);
if (saturate(p1.x) == p1.x && saturate(p1.y) == p1.y && p1.x >= p1.y && t1 >= tCellEntry)
{
hitT = t1;
hitUv = (iterCell + p1) * terrainData.invTerrainWidthInCells;
frontFaceHit = frontFaceHit1;
hit = true;
}
// Triangle 2: C00, C11, C01 (lower-left triangle)
float t2;
float2 p2;
bool frontFaceHit2;
IntersectRayWithTrianglePlane(cellHeights[kCellCorner00], cellHeights[kCellCorner11] - cellHeights[kCellCorner01], cellHeights[kCellCorner01] - cellHeights[kCellCorner00], rayOrigin, rayDirection, iterCell, t2, p2, frontFaceHit2);
if (saturate(p2.x) == p2.x && saturate(p2.y) == p2.y && p2.x <= p2.y && t2 >= tCellEntry && (!hit || t2 < t1))
{
hitT = t2;
hitUv = (iterCell + p2) * terrainData.invTerrainWidthInCells;
frontFaceHit = frontFaceHit2;
hit = true;
}
if (hit)
return true;
}
// Advance to next cell
tCellEntry = tCellExit;
if (tMax.x < tMax.y)
{
tMax.x += tDelta.x;
iterCell.x += step.x;
}
else
{
tMax.y += tDelta.y;
iterCell.y += step.y;
}
}
return false;
}
// Compute terrain local-space position and normal from cell coordinates.
// Shared between the ray hit path (GetTerrainHitGeomInfo) and the GBuffer path (GetExpandedSampleTerrain).
// Position uses triangle-matched interpolation (matching DDA ray marching exactly).
// Normal uses central differences for smooth shading (matching vertex-interpolated normals).
void ComputeTerrainLocalPosAndNormal(UnifiedRT::TerrainData terrainData, int terrainIndex, float2 heightmapUV, out float3 localPos, out float3 localNormal)
{
float3 heightmapScale = terrainData.terrainScale;
float3 invHeightmapScale = terrainData.invTerrainScale;
float numCells = terrainData.heightmapWidthInTexels - 1.0;
int2 cellCoord = clamp(int2(heightmapUV), 0, int(numCells) - 1);
float2 cellFrac = heightmapUV - float2(cellCoord);
float4 cellHeights;
bool cellIsHole;
GetCellCornerHeights(cellCoord, terrainIndex, cellHeights, cellIsHole);
// Interpolate height using the correct triangle (matching DDA split).
float h;
if (cellFrac.x >= cellFrac.y)
h = cellHeights[kCellCorner00] + (cellHeights[kCellCorner10] - cellHeights[kCellCorner00]) * cellFrac.x + (cellHeights[kCellCorner11] - cellHeights[kCellCorner10]) * cellFrac.y;
else
h = cellHeights[kCellCorner00] + (cellHeights[kCellCorner11] - cellHeights[kCellCorner01]) * cellFrac.x + (cellHeights[kCellCorner01] - cellHeights[kCellCorner00]) * cellFrac.y;
localPos = float3(heightmapUV.x * heightmapScale.x, h * heightmapScale.y, heightmapUV.y * heightmapScale.z);
// Sobel 3x3 normal filter matching TerrainToMesh.CalculateTerrainNormal.
// Kernel weights: [-1,-2,-1, 0,0,0, 1,2,1] for each axis.
// UV math uses atlas width because the heightmap may live in a padded slot.
float2 sampleUV = (heightmapUV + 0.5) * _TerrainTextureInvWidth;
float eps = _TerrainTextureInvWidth;
// Clamp each tap to the terrain's own texel range so Sobel taps near the edges
// of a padded slot don't fall in the zero-padded region (or wrap into it).
float minUV = 0.5 * _TerrainTextureInvWidth;
float maxUV = (terrainData.heightmapWidthInTexels - 0.5) * _TerrainTextureInvWidth;
#define _TERRAIN_SAMPLE(off) abs(_TerrainTexture.SampleLevel(sampler_TerrainTexture, float3(clamp(sampleUV + (off), minUV, maxUV), terrainIndex), 0)) - 1.0 / 32767.0
float hTL = _TERRAIN_SAMPLE(float2(-eps, -eps));
float hML = _TERRAIN_SAMPLE(float2(-eps, 0));
float hBL = _TERRAIN_SAMPLE(float2(-eps, eps));
float hTR = _TERRAIN_SAMPLE(float2( eps, -eps));
float hMR = _TERRAIN_SAMPLE(float2( eps, 0));
float hBR = _TERRAIN_SAMPLE(float2( eps, eps));
float hTM = _TERRAIN_SAMPLE(float2( 0, -eps));
float hBM = _TERRAIN_SAMPLE(float2( 0, eps));
#undef _TERRAIN_SAMPLE
float dX = (-hTL - 2.0 * hML - hBL + hTR + 2.0 * hMR + hBR) * heightmapScale.y * invHeightmapScale.x;
float dZ = (-hTL - 2.0 * hTM - hTR + hBL + 2.0 * hBM + hBR) * heightmapScale.y * invHeightmapScale.z;
localNormal = normalize(float3(-dX, 8.0, -dZ));
}
#endif // _PATHTRACING_TERRAINRAYMARCHING_HLSL_
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d92584f8beb85b84ca606313cc276c8b
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,92 @@
#ifndef _UNIFIEDRAYTRACING_TRACERAYANDQUERYHIT_HLSL_
#define _UNIFIEDRAYTRACING_TRACERAYANDQUERYHIT_HLSL_
// float3 transmission and Hit aliased to minimize payload size
struct PathTracingPayload
{
UnifiedRT::Hit _hit;
void Init(bool isShadowRay)
{
_hit = (UnifiedRT::Hit)0;
_hit.instanceID = -1;
_hit.isFrontFace = isShadowRay;
}
bool IsShadowRay()
{
return _hit.isFrontFace; // before invoking ClosestHitExecute, isFrontFace holds isShadowRay
}
UnifiedRT::Hit GetHit()
{
return _hit;
}
void SetHit(UnifiedRT::Hit hit)
{
_hit = hit;
}
void SetTransmission(float3 transmission)
{
_hit.uvBarycentrics = transmission.rg;
_hit.hitDistance = transmission.b;
}
float3 GetTransmission()
{
return float3(_hit.uvBarycentrics, _hit.hitDistance);
}
bool HasHit()
{
return _hit.IsValid();
}
void MarkHit()
{
_hit.instanceID = 1;
}
};
struct ProceduralIntersectionAttribs
{
bool isFrontFace;
};
#ifndef UNIFIED_RT_RAYGEN_FUNC
#define UNIFIED_RT_RAYGEN_FUNC RayGenExecute
#endif
#define UNIFIED_RT_ANYHIT_FUNC AnyHitExecute
#define UNIFIED_RT_CLOSESTHIT_FUNC ClosestHitExecute
#ifdef TERRAIN_RAY_MARCHING_ENABLED
#define UNIFIED_RT_INTERSECTION_FUNC IntersectionExecute
#define UNIFIED_RT_ADDITIONAL_INTERSECTION_ATTRIBS ProceduralIntersectionAttribs
#endif
#define UNIFIED_RT_PAYLOAD PathTracingPayload
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/TraceRay.hlsl"
UnifiedRT::Hit TraceRayClosestHit(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, uint instanceMask, UnifiedRT::Ray ray, uint rayFlags)
{
PathTracingPayload payload;
payload.Init(false);
UnifiedRT::TraceRay(dispatchInfo, accelStruct, instanceMask, ray, rayFlags | UnifiedRT::kRayFlagForceOpaque, payload);
return payload.GetHit();
}
bool TraceShadowRay(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, uint instanceMask, UnifiedRT::Ray ray, uint rayFlags, out float3 transmission)
{
PathTracingPayload payLoadShadow= (PathTracingPayload)0;
payLoadShadow.Init(true);
payLoadShadow.SetTransmission(1.0f);
UnifiedRT::TraceRay(dispatchInfo, accelStruct, instanceMask, ray, rayFlags | UnifiedRT::kRayFlagAcceptFirstHitAndEndSearch, payLoadShadow);
transmission = payLoadShadow.GetTransmission();
return payLoadShadow.HasHit();
}
#endif // _UNIFIEDRAYTRACING_TRACERAYANDQUERYHIT_HLSL_
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3135a2ca4096d0d4197185f22349d08e
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: