You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

386 lines
19 KiB

using UnityEngine.Experimental.Rendering;
using System.Collections.Generic;
using UnityEngine.Rendering.RenderGraphModule;
namespace UnityEngine.Rendering.HighDefinition
{
class HDRaytracingLightCluster
{
// External data
HDRenderPipeline m_RenderPipeline = null;
// Culling result
GraphicsBuffer m_LightCullResult = null;
// Output cluster data
GraphicsBuffer m_LightCluster = null;
// World light subset used for ray tracing
WorldLightSubSet m_WorldLightSubSet = new WorldLightSubSet();
// Light cluster debug material
Material m_DebugMaterial = null;
// String values
const string m_LightClusterKernelName = "RaytracingLightCluster";
const string m_LightCullKernelName = "RaytracingLightCull";
public static readonly int _ClusterCellSize = Shader.PropertyToID("_ClusterCellSize");
public static readonly int _LightVolumes = Shader.PropertyToID("_LightVolumes");
public static readonly int _LightSubSet = Shader.PropertyToID("_LightSubSet");
public static readonly int _LightVolumeCount = Shader.PropertyToID("_LightVolumeCount");
public static readonly int _LightSubSetCount = Shader.PropertyToID("_LightSubSetCount");
public static readonly int _DebugColorGradientTexture = Shader.PropertyToID("_DebugColorGradientTexture");
public static readonly int _DebutLightClusterTexture = Shader.PropertyToID("_DebutLightClusterTexture");
public static readonly int _RaytracingLightCullResult = Shader.PropertyToID("_RaytracingLightCullResult");
public static readonly int _ClusterCenterPosition = Shader.PropertyToID("_ClusterCenterPosition");
public static readonly int _ClusterDimension = Shader.PropertyToID("_ClusterDimension");
public static readonly int _ClusterLightCategoryDebug = Shader.PropertyToID("_ClusterLightCategoryDebug");
// Temporary variables
// This value is now fixed for every HDRP asset
int m_NumLightsPerCell = 0;
// These values are overriden for every light cluster that is built
Vector3 minClusterPos = new Vector3(0.0f, 0.0f, 0.0f);
Vector3 maxClusterPos = new Vector3(0.0f, 0.0f, 0.0f);
Vector3 clusterCellSize = new Vector3(0.0f, 0.0f, 0.0f);
Vector3 clusterCenter = new Vector3(0.0f, 0.0f, 0.0f);
Vector3 clusterDimension = new Vector3(0.0f, 0.0f, 0.0f);
public void Initialize(HDRenderPipeline renderPipeline)
{
// Keep track of the render pipeline
m_RenderPipeline = renderPipeline;
// Allocate the light cluster buffer at the right size
m_NumLightsPerCell = renderPipeline.asset.currentPlatformRenderPipelineSettings.lightLoopSettings.maxLightsPerClusterCell;
int bufferSize = HDLightClusterDefinitions.s_ClusterCellCount * (m_NumLightsPerCell + HDLightClusterDefinitions.s_CellMetaDataSize);
ResizeClusterBuffer(bufferSize);
// Create the material required for debug
m_DebugMaterial = CoreUtils.CreateEngineMaterial(renderPipeline.rayTracingResources.lightClusterDebugS);
}
public void ReleaseResources()
{
CoreUtils.SafeRelease(m_LightCluster);
m_LightCluster = null;
CoreUtils.SafeRelease(m_LightCullResult);
m_LightCullResult = null;
CoreUtils.Destroy(m_DebugMaterial);
m_DebugMaterial = null;
m_WorldLightSubSet.Release();
}
void ResizeClusterBuffer(int bufferSize)
{
// Release the previous buffer
if (m_LightCluster != null)
{
// If it is not null and it has already the right size, we are pretty much done
if (m_LightCluster.count == bufferSize)
return;
CoreUtils.SafeRelease(m_LightCluster);
m_LightCluster = null;
}
// Allocate the next buffer buffer
if (bufferSize > 0)
{
m_LightCluster = new GraphicsBuffer(GraphicsBuffer.Target.Structured, bufferSize, sizeof(uint));
}
}
void ResizeCullResultBuffer(int numLights)
{
// Release the previous buffer
if (m_LightCullResult != null)
{
// If it is not null and it has already the right size, we are pretty much done
if (m_LightCullResult.count == numLights)
return;
CoreUtils.SafeRelease(m_LightCullResult);
m_LightCullResult = null;
}
// Allocate the next buffer buffer
if (numLights > 0)
{
m_LightCullResult = new GraphicsBuffer(GraphicsBuffer.Target.Structured, numLights, sizeof(uint));
}
}
static internal Bounds GetLightClusterBounds(HDCamera hdCamera)
{
var settings = hdCamera.volumeStack.GetComponent<LightCluster>();
Vector3 camPosWS = Vector3.zero;
if (ShaderConfig.s_CameraRelativeRendering == 0)
camPosWS = hdCamera.mainViewConstants.worldSpaceCameraPos;
float range = settings.cameraClusterRange.value;
if (hdCamera.IsPathTracingEnabled())
{
// For path tracing we use the max extent of the extended culling frustum as the light cluster size
Vector3 extendedFrustumExtent = (hdCamera.camera.transform.up + hdCamera.camera.transform.right + hdCamera.camera.transform.forward) * hdCamera.camera.farClipPlane;
range = Mathf.Max(Mathf.Max(Mathf.Abs(extendedFrustumExtent.x), Mathf.Abs(extendedFrustumExtent.y)), Mathf.Abs(extendedFrustumExtent.z));
}
return new Bounds(camPosWS, 2.0f * new Vector3(range, range, range));
}
void EvaluateClusterVolume(HDCamera hdCamera, in WorldLightSubSet subset)
{
var cluster = GetLightClusterBounds(hdCamera);
minClusterPos = Vector3.Max(subset.bounds.min, cluster.min);
maxClusterPos = Vector3.Min(subset.bounds.max, cluster.max);
// Compute the cell size per dimension
clusterCellSize = (maxClusterPos - minClusterPos);
clusterCellSize.x /= HDLightClusterDefinitions.s_ClusterSize.x;
clusterCellSize.y /= HDLightClusterDefinitions.s_ClusterSize.y;
clusterCellSize.z /= HDLightClusterDefinitions.s_ClusterSize.z;
// Compute the bounds of the cluster volume3
clusterCenter = (maxClusterPos + minClusterPos) / 2.0f;
clusterDimension = (maxClusterPos - minClusterPos);
}
void CullLights(CommandBuffer cmd, WorldLightsVolumes worldLightsVolumes)
{
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RaytracingCullLights)))
{
int totalLightCount = worldLightsVolumes.GetCount();
// Make sure the culling buffer has the right size
if (m_LightCullResult == null || m_LightCullResult.count != totalLightCount)
{
ResizeCullResultBuffer(totalLightCount);
}
ComputeShader lightClusterCS = m_RenderPipeline.rayTracingResources.lightClusterBuildCS;
// Grab the kernel
int lightClusterCullKernel = lightClusterCS.FindKernel(m_LightCullKernelName);
// Inject all the parameters
cmd.SetComputeVectorParam(lightClusterCS, _ClusterCenterPosition, clusterCenter);
cmd.SetComputeVectorParam(lightClusterCS, _ClusterDimension, clusterDimension);
cmd.SetComputeIntParam(lightClusterCS, _LightVolumeCount, totalLightCount);
cmd.SetComputeBufferParam(lightClusterCS, lightClusterCullKernel, _LightVolumes, worldLightsVolumes.GetBuffer());
cmd.SetComputeBufferParam(lightClusterCS, lightClusterCullKernel, _RaytracingLightCullResult, m_LightCullResult);
// Dispatch a compute
int numLightGroups = (totalLightCount / 16 + 1);
cmd.DispatchCompute(lightClusterCS, lightClusterCullKernel, numLightGroups, 1, 1);
}
}
void BuildLightCluster(HDCamera hdCamera, CommandBuffer cmd, WorldLightsVolumes worldLightsVolumes, WorldLightSubSet worldLightSubSet)
{
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RaytracingBuildCluster)))
{
// Grab the kernel
ComputeShader lightClusterCS = m_RenderPipeline.rayTracingResources.lightClusterBuildCS;
int lightClusterKernel = lightClusterCS.FindKernel(m_LightClusterKernelName);
// Inject all the parameters
cmd.SetComputeBufferParam(lightClusterCS, lightClusterKernel, HDShaderIDs._RaytracingLightClusterRW, m_LightCluster);
cmd.SetComputeVectorParam(lightClusterCS, _ClusterCellSize, clusterCellSize);
cmd.SetComputeBufferParam(lightClusterCS, lightClusterKernel, _LightVolumes, worldLightsVolumes.GetBuffer());
cmd.SetComputeBufferParam(lightClusterCS, lightClusterKernel, _LightSubSet, worldLightSubSet.GetBuffer());
cmd.SetComputeIntParam(lightClusterCS, _LightVolumeCount, worldLightsVolumes.GetCount());
cmd.SetComputeIntParam(lightClusterCS, _LightSubSetCount, worldLightSubSet.GetCount());
cmd.SetComputeBufferParam(lightClusterCS, lightClusterKernel, _RaytracingLightCullResult, m_LightCullResult);
// Dispatch a compute
int numGroupsX = CoreUtils.DivRoundUp(HDLightClusterDefinitions.s_ClusterSize.x, 8);
int numGroupsY = CoreUtils.DivRoundUp(HDLightClusterDefinitions.s_ClusterSize.y, 8);
int numGroupsZ = CoreUtils.DivRoundUp(HDLightClusterDefinitions.s_ClusterSize.z, 8);
cmd.DispatchCompute(lightClusterCS, lightClusterKernel, numGroupsX, numGroupsY, numGroupsZ);
}
}
class LightClusterDebugPassData
{
public int texWidth;
public int texHeight;
public int lightClusterDebugKernel;
public int clusterLightCategory;
public Vector3 clusterCellSize;
public Material debugMaterial;
public BufferHandle lightCluster;
public ComputeShader lightClusterDebugCS;
public TextureHandle depthStencilBuffer;
public TextureHandle depthPyramid;
public TextureHandle outputBuffer;
}
public void EvaluateClusterDebugView(RenderGraph renderGraph, HDCamera hdCamera, TextureHandle depthStencilBuffer, TextureHandle depthPyramid)
{
// TODO: Investigate why this behavior causes a leak in player mode only.
if (FullScreenDebugMode.LightCluster != m_RenderPipeline.m_CurrentDebugDisplaySettings.data.fullScreenDebugMode)
return;
TextureHandle debugTexture;
using (var builder = renderGraph.AddUnsafePass<LightClusterDebugPassData>("Debug Texture for the Light Cluster", out var passData, ProfilingSampler.Get(HDProfileId.RaytracingDebugCluster)))
{
passData.texWidth = hdCamera.actualWidth;
passData.texHeight = hdCamera.actualHeight;
passData.clusterCellSize = clusterCellSize;
passData.lightCluster = renderGraph.ImportBuffer(m_LightCluster);
builder.UseBuffer(passData.lightCluster, AccessFlags.Read);
passData.lightClusterDebugCS = m_RenderPipeline.rayTracingResources.lightClusterDebugCS;
passData.lightClusterDebugKernel = passData.lightClusterDebugCS.FindKernel("DebugLightCluster");
passData.debugMaterial = m_DebugMaterial;
passData.depthStencilBuffer = depthStencilBuffer;
passData.depthPyramid = depthStencilBuffer;
builder.UseTexture(passData.depthPyramid, AccessFlags.Read);
passData.outputBuffer = renderGraph.CreateTexture(new TextureDesc(Vector2.one, true, true)
{ format = GraphicsFormat.R16G16B16A16_SFloat, enableRandomWrite = true, name = "Light Cluster Debug Texture" });
builder.UseTexture(passData.outputBuffer, AccessFlags.Write);
passData.clusterLightCategory = m_RenderPipeline.m_CurrentDebugDisplaySettings.data.lightClusterCategoryDebug;
builder.SetRenderFunc(
(LightClusterDebugPassData data, UnsafeGraphContext ctx) =>
{
var debugMaterialProperties = ctx.renderGraphPool.GetTempMaterialPropertyBlock();
// Bind the output texture
CoreUtils.SetRenderTarget(CommandBufferHelpers.GetNativeCommandBuffer(ctx.cmd), data.outputBuffer, data.depthStencilBuffer, clearFlag: ClearFlag.Color, clearColor: Color.black);
// Inject all the parameters to the debug compute
ctx.cmd.SetComputeBufferParam(data.lightClusterDebugCS, data.lightClusterDebugKernel, HDShaderIDs._RaytracingLightCluster, data.lightCluster);
ctx.cmd.SetComputeVectorParam(data.lightClusterDebugCS, _ClusterCellSize, data.clusterCellSize);
ctx.cmd.SetComputeIntParam(data.lightClusterDebugCS, _ClusterLightCategoryDebug, data.clusterLightCategory);
ctx.cmd.SetComputeTextureParam(data.lightClusterDebugCS, data.lightClusterDebugKernel, HDShaderIDs._CameraDepthTexture, data.depthStencilBuffer);
// Target output texture
ctx.cmd.SetComputeTextureParam(data.lightClusterDebugCS, data.lightClusterDebugKernel, _DebutLightClusterTexture, data.outputBuffer);
// Dispatch the compute
int lightVolumesTileSize = 8;
int numTilesX = (data.texWidth + (lightVolumesTileSize - 1)) / lightVolumesTileSize;
int numTilesY = (data.texHeight + (lightVolumesTileSize - 1)) / lightVolumesTileSize;
ctx.cmd.DispatchCompute(data.lightClusterDebugCS, data.lightClusterDebugKernel, numTilesX, numTilesY, 1);
// Bind the parameters
debugMaterialProperties.SetBuffer(HDShaderIDs._RaytracingLightCluster, data.lightCluster);
debugMaterialProperties.SetVector(_ClusterCellSize, data.clusterCellSize);
debugMaterialProperties.SetTexture(HDShaderIDs._CameraDepthTexture, data.depthPyramid);
// Draw the faces
ctx.cmd.DrawProcedural(Matrix4x4.identity, data.debugMaterial, 1, MeshTopology.Lines, 48, HDLightClusterDefinitions.s_ClusterCellCount, debugMaterialProperties);
ctx.cmd.DrawProcedural(Matrix4x4.identity, data.debugMaterial, 0, MeshTopology.Triangles, 36, HDLightClusterDefinitions.s_ClusterCellCount, debugMaterialProperties);
});
debugTexture = passData.outputBuffer;
}
m_RenderPipeline.PushFullScreenDebugTexture(renderGraph, debugTexture, FullScreenDebugMode.LightCluster);
}
public GraphicsBuffer GetCluster()
{
return m_LightCluster;
}
public Vector3 GetMinClusterPos()
{
return minClusterPos;
}
public Vector3 GetMaxClusterPos()
{
return maxClusterPos;
}
public Vector3 GetClusterCellSize()
{
return clusterCellSize;
}
public int GetLightPerCellCount()
{
return m_NumLightsPerCell;
}
void InvalidateCluster()
{
// Invalidate the cluster's bounds so that we never access the buffer (the buffer's access in hlsl is surrounded by position testing)
minClusterPos.Set(float.MaxValue, float.MaxValue, float.MaxValue);
maxClusterPos.Set(-float.MaxValue, -float.MaxValue, -float.MaxValue);
}
public void CullForRayTracing(HDCamera hdCamera, WorldLights worldLights, WorldLightsVolumes worldLightsVolumes)
{
uint filter = hdCamera.IsPathTracingEnabled() ? (uint)WorldLightFlags.ActivePathtracing : (uint)WorldLightFlags.ActiveRaytracing;
// Filter the world lights
WorldLightCulling.GetLightSubSetUsingFlags(worldLightsVolumes, filter, m_WorldLightSubSet);
// If there is no lights to process or no environment not the shader is missing
if (worldLights.totalLightCount == 0 || !m_RenderPipeline.GetRayTracingState())
{
InvalidateCluster();
return;
}
// If no valid light were found, invalidate the cluster and leave
if (m_WorldLightSubSet.GetCount() == 0)
{
InvalidateCluster();
return;
}
// Evaluate the volume of the cluster
EvaluateClusterVolume(hdCamera, m_WorldLightSubSet);
}
public void BuildLightClusterBuffer(CommandBuffer cmd, HDCamera hdCamera, WorldLightsVolumes worldLightsVolumes)
{
// If there is no lights to process or no environment not the shader is missing
if (m_WorldLightSubSet.GetCount() == 0 || !m_RenderPipeline.GetRayTracingState())
return;
// Cull the lights within the evaluated cluster range
CullLights(cmd, worldLightsVolumes);
// Build the light Cluster
BuildLightCluster(hdCamera, cmd, worldLightsVolumes, m_WorldLightSubSet);
}
public void ReserveCookieAtlasSlots(WorldLights rayTracingLights)
{
HDLightRenderDatabase lightEntities = HDLightRenderDatabase.instance;
for (int lightIdx = 0; lightIdx < rayTracingLights.hdLightEntityArray.Length; ++lightIdx)
{
int dataIndex = lightEntities.GetEntityDataIndex(rayTracingLights.hdLightEntityArray[lightIdx].light);
HDAdditionalLightData additionalLightData = lightEntities.hdAdditionalLightData[dataIndex];
// Grab the additional light data to process
// Fetch the light component for this light
additionalLightData.gameObject.TryGetComponent(out Light lightComponent);
// Reserve the cookie resolution in the 2D atlas
m_RenderPipeline.ReserveCookieAtlasTexture(additionalLightData, lightComponent, additionalLightData.legacyLight.type);
}
}
public void BindLightClusterData(CommandBuffer cmd)
{
cmd.SetGlobalBuffer(HDShaderIDs._RaytracingLightCluster, GetCluster());
}
}
}