// HDRP generic includes #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl" // Raytracing includes (should probably be in generic files) #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/ShaderVariablesRaytracing.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/RaytracingSampling.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/RayTracingCommon.hlsl" // #pragma enable_d3d11_debug_symbols #pragma kernel TraceGlobalIllumination TRACE_GLOBAL_ILLUMINATION=TraceGlobalIllumination GI_TRACE #pragma kernel TraceGlobalIlluminationHalf TRACE_GLOBAL_ILLUMINATION=TraceGlobalIlluminationHalf GI_TRACE HALF_RES #pragma kernel ReprojectGlobalIllumination REPROJECT_GLOBAL_ILLUMINATION=ReprojectGlobalIllumination GI_REPROJECT #pragma kernel ReprojectGlobalIlluminationHalf REPROJECT_GLOBAL_ILLUMINATION=ReprojectGlobalIlluminationHalf GI_REPROJECT HALF_RES #pragma kernel ConvertYCoCgToRGB CONVERT_YCOCG_TO_RGB=ConvertYCoCgToRGB #pragma kernel ConvertYCoCgToRGBHalf CONVERT_YCOCG_TO_RGB=ConvertYCoCgToRGBHalf HALF_RES // The dispatch tile resolution #define INDIRECT_DIFFUSE_TILE_SIZE 8 // Epslon value used for the computation #define GI_TRACE_EPS 0.00024414 #define PERCEPTUAL_SPACE // Input depth pyramid texture TEXTURE2D_X(_DepthTexture); // Input texture that holds the offset for every level of the depth pyramid StructuredBuffer _DepthPyramidMipLevelOffsets; // Flag value that defines if a given pixel recieves reflections or not TEXTURE2D_X_UINT2(_StencilTexture); // Constant buffer that holds all scalar that we need CBUFFER_START(UnityScreenSpaceGlobalIllumination) int _IndirectDiffuseSteps; float _IndirectDiffuseThicknessScale; float _IndirectDiffuseThicknessBias; int _IndirectDiffuseProbeFallbackFlag; int _IndirectDiffuseProbeFallbackBias; float4 _ColorPyramidUvScaleAndLimitPrevFrame; int _SsrStencilBit; int _IndirectDiffuseFrameIndex; CBUFFER_END // Output texture that holds the hit point NDC coordinates RW_TEXTURE2D_X(float2, _IndirectDiffuseHitPointTextureRW); // TODO: It is not really possible to share all of this with SSR for couple reason, but it probably possible share a part of it bool RayMarch(float3 positionWS, float3 sampleDir, float3 normalWS, float2 positionSS, float deviceDepth, bool killRay, out float3 rayPos) { // Initialize ray pos to invalid value rayPos = float3(-1.0, -1.0, -1.0); // Due to a warning on Vulkan and Metal if returning early, this is the only way we found to avoid it. bool status = false; // If the point is behind the camera or the ray is invalid, this ray should not be cast if (!killRay) { // We start tracing from the center of the current pixel, and do so up to the far plane. float3 rayOrigin = float3(positionSS + 0.5, deviceDepth); float3 sampledPosWS = positionWS + sampleDir * 0.001; float3 sampledPosNDC = ComputeNormalizedDeviceCoordinatesWithZ(sampledPosWS, UNITY_MATRIX_VP); // Jittered float3 sampledPosSS = float3(sampledPosNDC.xy * _ScreenSize.xy, sampledPosNDC.z); float3 rayDir = (sampledPosSS - rayOrigin); float2 rayDirSS = normalize(rayDir.xy); float tx = (rayDirSS.x > 0 ? (_ScreenSize.x - rayOrigin.x) : rayOrigin.x) / rayDirSS.x; float ty = (rayDirSS.y > 0 ? (_ScreenSize.y - rayOrigin.y) : rayOrigin.y) / rayDirSS.y; // CompĂ ute the actual ray direction rayDir = min(abs(tx), abs(ty)) * normalize(rayDir); // Compute the reciprocal of the direction (not sure why tho ftm) float3 rcpRayDir = rcp(rayDir); // Compute a ray step (added an abs here looks better, maybe its wrong need to check mmore) int2 rayStep = int2((rcpRayDir.x) >= 0 ? 1 : 0, (rcpRayDir.y) >= 0 ? 1 : 0); float3 raySign = float3(rcpRayDir.x >= 0 ? 1 : -1, rcpRayDir.y >= 0 ? 1 : -1, rcpRayDir.z >= 0 ? 1 : -1); bool rayTowardsEye = rcpRayDir.z >= 0; // Build the bounds that start at the center of the pixel and travel to the edge of the screen float tMax; { // Shrink the frustum by half a texel for efficiency reasons. const float halfTexel = 0.5; float3 bounds; bounds.x = clamp(rayOrigin.x + rayDir.x, halfTexel, _ScreenSize.x - halfTexel); bounds.y = clamp(rayOrigin.y + rayDir.y, halfTexel, _ScreenSize.y - halfTexel); // If we do not want to intersect the skybox, it is more efficient to not trace too far. float maxDepth = -0.00000024; // 2^-22 bounds.z = (rcpRayDir.z >= 0) ? 1 : maxDepth; float3 dist = bounds * rcpRayDir - (rayOrigin * rcpRayDir); tMax = Min3(dist.x, dist.y, dist.z); } // Start ray marching from the next texel to avoid self-intersections. float t; { // 'rayOrigin' is the exact texel center. float2 dist = abs(0.5 * rcpRayDir.xy); t = min(dist.x, dist.y); } int mipLevel = 0; int2 mipOffset = _DepthPyramidMipLevelOffsets[mipLevel]; int iterCount = 0; bool hit = false; bool miss = false; bool belowMip0 = false; // This value is set prior to entering the cell while (!(hit || miss) && (t <= tMax) && (iterCount < _IndirectDiffuseSteps)) { rayPos = rayOrigin + t * rayDir; // Ray position often ends up on the edge. To determine (and look up) the right cell, // we need to bias the position by a small epsilon in the direction of the ray. float2 sgnEdgeDist = round(rayPos.xy) - rayPos.xy; float2 satEdgeDist = clamp(raySign.xy * sgnEdgeDist + GI_TRACE_EPS, 0, GI_TRACE_EPS); rayPos.xy += raySign.xy * satEdgeDist; int2 mipCoord = (int2)rayPos.xy >> mipLevel; // Bounds define 4 faces of a cube: // 2 walls in front of the ray, and a floor and a base below it. float4 bounds; bounds.z = LOAD_TEXTURE2D_X(_CameraDepthTexture, mipOffset + mipCoord).r; bounds.xy = (mipCoord + rayStep) << mipLevel; // We define the depth of the base as the depth value as: // b = DeviceDepth((1 + thickness) * LinearDepth(d)) // b = ((f - n) * d + n * (1 - (1 + thickness))) / ((f - n) * (1 + thickness)) // b = ((f - n) * d - n * thickness) / ((f - n) * (1 + thickness)) // b = d / (1 + thickness) - n / (f - n) * (thickness / (1 + thickness)) // b = d * k_s + k_b bounds.w = bounds.z * _IndirectDiffuseThicknessScale + _IndirectDiffuseThicknessBias; float4 dist = bounds * rcpRayDir.xyzz - (rayOrigin.xyzz * rcpRayDir.xyzz); float distWall = min(dist.x, dist.y); float distFloor = dist.z; float distBase = dist.w; // Note: 'rayPos' given by 't' can correspond to one of several depth values: // - above or exactly on the floor // - inside the floor (between the floor and the base) // - below the base bool belowFloor = rayPos.z < bounds.z; bool aboveBase = rayPos.z >= bounds.w; bool insideFloor = belowFloor && aboveBase; bool hitFloor = (t <= distFloor) && (distFloor <= distWall); // Game rules: // * if the closest intersection is with the wall of the cell, switch to the coarser MIP, and advance the ray. // * if the closest intersection is with the heightmap below, switch to the finer MIP, and advance the ray. // * if the closest intersection is with the heightmap above, switch to the finer MIP, and do NOT advance the ray. // Victory conditions: // * See below. Do NOT reorder the statements! miss = belowMip0 && insideFloor; hit = (mipLevel == 0) && (hitFloor || insideFloor); belowMip0 = (mipLevel == 0) && belowFloor; // 'distFloor' can be smaller than the current distance 't'. // We can also safely ignore 'distBase'. // If we hit the floor, it's always safe to jump there. // If we are at (mipLevel != 0) and we are below the floor, we should not move. t = hitFloor ? distFloor : (((mipLevel != 0) && belowFloor) ? t : distWall); rayPos.z = bounds.z; // Retain the depth of the potential intersection // Warning: both rays towards the eye, and tracing behind objects has linear // rather than logarithmic complexity! This is due to the fact that we only store // the maximum value of depth, and not the min-max. mipLevel += (hitFloor || belowFloor || rayTowardsEye) ? -1 : 1; mipLevel = clamp(mipLevel, 0, 6); mipOffset = _DepthPyramidMipLevelOffsets[mipLevel]; // mipLevel = 0; iterCount++; } // Treat intersections with the sky as misses. miss = miss || ((rayPos.z == 0)); status = hit && !miss; } return status; } [numthreads(INDIRECT_DIFFUSE_TILE_SIZE, INDIRECT_DIFFUSE_TILE_SIZE, 1)] void TRACE_GLOBAL_ILLUMINATION(uint3 dispatchThreadId : SV_DispatchThreadID, uint2 groupThreadId : SV_GroupThreadID, uint2 groupId : SV_GroupID) { UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z); // Compute the pixel position to process uint2 currentCoord = dispatchThreadId.xy; #if HALF_RES // Compute the full resolution pixel for the inputs that do not have a pyramid currentCoord = currentCoord * 2; #endif // Read the depth value as early as possible float deviceDepth = LOAD_TEXTURE2D_X(_DepthTexture, currentCoord).x; // Initialize the hitpoint texture to a miss _IndirectDiffuseHitPointTextureRW[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = float2(99.0, 0.0); // Read the pixel normal NormalData normalData; DecodeFromNormalBuffer(currentCoord.xy, normalData); // Generete a new direction to follow float2 newSample; newSample.x = GetBNDSequenceSample(currentCoord.xy, _RaytracingFrameIndex, 0); newSample.y = GetBNDSequenceSample(currentCoord.xy, _RaytracingFrameIndex, 1); // Importance sample with a cosine lobe (direction that will be used for ray casting) float3 sampleDir = SampleHemisphereCosine(newSample.x, newSample.y, normalData.normalWS); // Compute the camera position float3 camPosWS = GetCurrentViewPosition(); // If this is a background pixel, we flag the ray as a dead ray (we are also trying to keep the usage of the depth buffer the latest possible) bool killRay = deviceDepth == UNITY_RAW_FAR_CLIP_VALUE; // Convert this to a world space position (camera relative) PositionInputs posInput = GetPositionInput(currentCoord, _ScreenSize.zw, deviceDepth, UNITY_MATRIX_I_VP, GetWorldToViewMatrix(), 0); // Compute the view direction (world space) float3 viewWS = GetWorldSpaceNormalizeViewDir(posInput.positionWS); // Apply normal bias with the magnitude dependent on the distance from the camera. // Unfortunately, we only have access to the shading normal, which is less than ideal... posInput.positionWS = camPosWS + (posInput.positionWS - camPosWS) * (1 - 0.001 * rcp(max(dot(normalData.normalWS, viewWS), FLT_EPS))); deviceDepth = ComputeNormalizedDeviceCoordinatesWithZ(posInput.positionWS, UNITY_MATRIX_VP).z; // Ray March along our ray float3 rayPos; bool hit = RayMarch(posInput.positionWS, sampleDir, normalData.normalWS, posInput.positionSS, deviceDepth, killRay, rayPos); // If we had a hit, store the NDC position of the intersection point if (hit) { // Note that we are using 'rayPos' from the penultimate iteration, rather than // recompute it using the last value of 't', which would result in an overshoot. // It also needs to be precisely at the center of the pixel to avoid artifacts. float2 hitPositionNDC = floor(rayPos.xy) * _ScreenSize.zw + (0.5 * _ScreenSize.zw); // Should we precompute the half-texel bias? We seem to use it a lot. _IndirectDiffuseHitPointTextureRW[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = hitPositionNDC; } } // Input hit point texture that holds the NDC position if an intersection was found TEXTURE2D_X(_IndirectDiffuseHitPointTexture); // Depth buffer of the previous frame (full resolution) TEXTURE2D_X(_HistoryDepthTexture); // Output indirect diffuse texture RW_TEXTURE2D_X(float4, _IndirectDiffuseTexture0RW); RW_TEXTURE2D_X(float4, _IndirectDiffuseTexture1RW); // The maximal difference in depth that is considered acceptable to read from the color pyramid #define DEPTH_DIFFERENCE_THRESHOLD 0.1 void RGBToYCoCgUtil(float3 inColor, float3 inDirection, out float4 outYSH, out float2 outCoCg) { // Convert the color to ycocg space float3 yCoCg = RGBToYCoCg(inColor); // Compute the coeffs required for the projection float Y00 = 0.282095; float Y11 = 0.488603 * inDirection.x; float Y10 = 0.488603 * inDirection.z; float Y1_1 = 0.488603 * inDirection.y; // Output the values outYSH.x = yCoCg.x * Y00; outYSH.y = yCoCg.x * Y11; outYSH.z = yCoCg.x * Y10; outYSH.w = yCoCg.x * Y1_1; outCoCg.x = yCoCg.y; outCoCg.y = yCoCg.z; } [numthreads(INDIRECT_DIFFUSE_TILE_SIZE, INDIRECT_DIFFUSE_TILE_SIZE, 1)] void REPROJECT_GLOBAL_ILLUMINATION(uint3 dispatchThreadId : SV_DispatchThreadID, uint2 groupThreadId : SV_GroupThreadID, uint2 groupId : SV_GroupID) { UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z); // Compute the pixel position to process uint2 currentCoord = groupId * INDIRECT_DIFFUSE_TILE_SIZE + groupThreadId; #if HALF_RES // Compute the full resolution pixel for the inputs that do not have a pyramid currentCoord = currentCoord * 2; #endif float deviceDepth = LOAD_TEXTURE2D_X(_DepthTexture, currentCoord).x; // Read the hit point ndc position to fetch float2 hitPositionNDC = LOAD_TEXTURE2D_X(_IndirectDiffuseHitPointTexture, dispatchThreadId.xy).xy; // Grab the depth of the hit point float hitPointDepth = LOAD_TEXTURE2D_X(_DepthTexture, hitPositionNDC * _ScreenSize.xy).x; // Flag that tracks if this ray lead to a valid result bool invalid = false; // If this missed, we need to find something else to fallback on if (hitPositionNDC.x > 1.0) invalid = true; // Fetch the motion vector of the current target pixel float2 motionVectorNDC; DecodeMotionVector(SAMPLE_TEXTURE2D_X_LOD(_CameraMotionVectorsTexture, s_linear_clamp_sampler, hitPositionNDC, 0), motionVectorNDC); float2 prevFrameNDC = hitPositionNDC - motionVectorNDC; float2 prevFrameUV = prevFrameNDC * _ColorPyramidUvScaleAndLimitPrevFrame.xy; // If the previous value to read was out of screen, this is invalid, needs a fallback if ((prevFrameUV.x < 0) || (prevFrameUV.x > _ColorPyramidUvScaleAndLimitPrevFrame.z) || (prevFrameUV.y < 0) || (prevFrameUV.y > _ColorPyramidUvScaleAndLimitPrevFrame.w)) invalid = true; // Grab the depth of the hit point and reject the history buffer if the depth is too different // TODO: Find a better metric float hitPointHistoryDepth = LOAD_TEXTURE2D_X(_HistoryDepthTexture, prevFrameNDC * _ScreenSize.xy).x; if (abs(hitPointHistoryDepth - hitPointDepth) > DEPTH_DIFFERENCE_THRESHOLD) invalid = true; // Based on if the intersection was valid (or not, pick a source for the lighting) float3 color = 0.0; if (!invalid) // The intersection was considered valid, we can read from the color pyramid color = SAMPLE_TEXTURE2D_X_LOD(_ColorPyramidTexture, s_linear_clamp_sampler, prevFrameUV, 0).rgb; // TODO: Remove me when you can find where the nans come from if (AnyIsNaN(color)) color = 0.0f; // We need to recreate the direction that was generated float2 newSample; newSample.x = GetBNDSequenceSample(currentCoord.xy, _IndirectDiffuseFrameIndex, 0); newSample.y = GetBNDSequenceSample(currentCoord.xy, _IndirectDiffuseFrameIndex, 1); // Read the pixel normal NormalData normalData; DecodeFromNormalBuffer(currentCoord.xy, normalData); #ifdef PERCEPTUAL_SPACE // We tone map the signal. Due to the very small budget for denoising, we need to compress the range of the signal color = color / (1.0 + color); #endif // Re-compute the direction that was used to do the generation float3 sampleDir = SampleHemisphereCosine(newSample.x, newSample.y, normalData.normalWS); // Convert the color to our target format float4 outYSH; float2 outCoCg; RGBToYCoCgUtil(color, sampleDir, outYSH, outCoCg); // We are simply interested to know if the intersected pixel was moving, so we multiply it by a big number // TODO: make this process not binary // Write the output to the target pixel _IndirectDiffuseTexture0RW[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = float4(outYSH); _IndirectDiffuseTexture1RW[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = float4(outCoCg, invalid ? 0.0 : 1.0, length(motionVectorNDC * 10000.0f)); } void ConvertYCoCgToRGBUtil(float4 inYSH, float2 inCoCg, float3 inNormal, out float3 outColor) { // Compute the coeffs required for the projection float Y00 = 0.282095; float Y11 = 0.488603 * inNormal.x; float Y10 = 0.488603 * inNormal.z; float Y1_1 = 0.488603 * inNormal.y; // Compute the Y value float y = 3.141593 * Y00 * inYSH.x + 2.094395 * Y1_1 * inYSH.w + 2.094395 * Y10 * inYSH.z + 2.094395 * Y11 * inYSH.y; // Compute the output color outColor = y != 0.0 ? YCoCgToRGB(float3(y, inCoCg.xy)) : 0.0; outColor = max(outColor, 0.0); } TEXTURE2D_X(_IndirectDiffuseTexture1); [numthreads(INDIRECT_DIFFUSE_TILE_SIZE, INDIRECT_DIFFUSE_TILE_SIZE, 1)] void CONVERT_YCOCG_TO_RGB(uint3 dispatchThreadId : SV_DispatchThreadID, uint2 groupThreadId : SV_GroupThreadID, uint2 groupId : SV_GroupID) { UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z); // Fetch the current pixel coordinate uint2 currentCoord = dispatchThreadId.xy; // If the depth of this pixel is the depth of the background, we can end the process right away #if HALF_RES currentCoord = currentCoord * 2; #endif // Fetch the depth of the current pixel float deviceDepth = LOAD_TEXTURE2D_X(_DepthTexture, currentCoord).x; if (deviceDepth == UNITY_RAW_FAR_CLIP_VALUE) { _IndirectDiffuseTexture0RW[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = float4(0.0, 0.0, 0.0, 0.0); return; } // Fetch the normal NormalData normalData; DecodeFromNormalBuffer(currentCoord.xy, normalData); // Convert the signal back to a color float3 color; float4 ySH = _IndirectDiffuseTexture0RW[COORD_TEXTURE2D_X(dispatchThreadId.xy)]; float3 cocgB = LOAD_TEXTURE2D_X(_IndirectDiffuseTexture1, dispatchThreadId.xy).xyz; ConvertYCoCgToRGBUtil(ySH, cocgB.xy, normalData.normalWS, color); #ifdef PERCEPTUAL_SPACE // We invert the tonemap color = color / (1.0 - color); // The mulitplication is wrong, but with all the approximations that we need to compensate a bit // the fact that the signal was significantly attenuated (due to blurring in tonemapped space to reduce the blobbyness). // This has been experimentally tested. However, it needs more testing and potetially reverted if found more harmful than useful color *= (lerp(5.0, 1.0, cocgB.z)); #endif // Does this pixel recieve SSGI? uint stencilValue = GetStencilValue(LOAD_TEXTURE2D_X(_StencilTexture, currentCoord)); if ((stencilValue & _SsrStencilBit) == 0) cocgB.z = 0.0; // Output the color as well as the blend factor _IndirectDiffuseTexture0RW[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = float4(color, cocgB.z); }