830 lines
40 KiB
C#
830 lines
40 KiB
C#
using UnityEngine.Experimental.Rendering;
|
|
using System.Collections.Generic;
|
|
using UnityEngine.Experimental.Rendering.RenderGraphModule;
|
|
|
|
namespace UnityEngine.Rendering.HighDefinition
|
|
{
|
|
[GenerateHLSL(PackingRules.Exact, false)]
|
|
struct LightVolume
|
|
{
|
|
public int active;
|
|
public int shape;
|
|
public Vector3 position;
|
|
public Vector3 range;
|
|
public uint lightType;
|
|
public uint lightIndex;
|
|
}
|
|
|
|
class HDRaytracingLightCluster
|
|
{
|
|
// External data
|
|
RenderPipelineResources m_RenderPipelineResources = null;
|
|
HDRenderPipelineRayTracingResources m_RenderPipelineRayTracingResources = null;
|
|
HDRenderPipeline m_RenderPipeline = null;
|
|
|
|
// Light Culling data
|
|
LightVolume[] m_LightVolumesCPUArray = null;
|
|
ComputeBuffer m_LightVolumeGPUArray = null;
|
|
|
|
// Culling result
|
|
ComputeBuffer m_LightCullResult = null;
|
|
|
|
// Output cluster data
|
|
ComputeBuffer m_LightCluster = null;
|
|
|
|
// Light runtime data
|
|
List<LightData> m_LightDataCPUArray = new List<LightData>();
|
|
ComputeBuffer m_LightDataGPUArray = null;
|
|
|
|
// Env Light data
|
|
List<EnvLightData> m_EnvLightDataCPUArray = new List<EnvLightData>();
|
|
ComputeBuffer m_EnvLightDataGPUArray = null;
|
|
|
|
// 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 _LightVolumeCount = Shader.PropertyToID("_LightVolumeCount");
|
|
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");
|
|
|
|
// 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);
|
|
int punctualLightCount = 0;
|
|
int areaLightCount = 0;
|
|
int envLightCount = 0;
|
|
int totalLightCount = 0;
|
|
Bounds bounds = new Bounds();
|
|
Vector3 minBounds = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
|
Vector3 maxBounds = new Vector3(-float.MaxValue, -float.MaxValue, -float.MaxValue);
|
|
Matrix4x4 localToWorldMatrix = new Matrix4x4();
|
|
VisibleLight visibleLight = new VisibleLight();
|
|
Light lightComponent;
|
|
|
|
public void Initialize(HDRenderPipeline renderPipeline)
|
|
{
|
|
// Keep track of the external buffers
|
|
m_RenderPipelineResources = renderPipeline.asset.renderPipelineResources;
|
|
m_RenderPipelineRayTracingResources = renderPipeline.asset.renderPipelineRayTracingResources;
|
|
|
|
// Keep track of the render pipeline
|
|
m_RenderPipeline = renderPipeline;
|
|
|
|
// Pre allocate the cluster with a dummy size
|
|
m_LightDataGPUArray = new ComputeBuffer(1, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightData)));
|
|
m_EnvLightDataGPUArray = new ComputeBuffer(1, System.Runtime.InteropServices.Marshal.SizeOf(typeof(EnvLightData)));
|
|
|
|
// Allocate the light cluster buffer at the right size
|
|
m_NumLightsPerCell = renderPipeline.asset.currentPlatformRenderPipelineSettings.lightLoopSettings.maxLightsPerClusterCell;
|
|
int bufferSize = 64 * 64 * 32 * (renderPipeline.asset.currentPlatformRenderPipelineSettings.lightLoopSettings.maxLightsPerClusterCell + 4);
|
|
ResizeClusterBuffer(bufferSize);
|
|
|
|
// Create the material required for debug
|
|
m_DebugMaterial = CoreUtils.CreateEngineMaterial(m_RenderPipelineRayTracingResources.lightClusterDebugS);
|
|
}
|
|
|
|
public void ReleaseResources()
|
|
{
|
|
CoreUtils.SafeRelease(m_LightVolumeGPUArray);
|
|
m_LightVolumeGPUArray = null;
|
|
|
|
CoreUtils.SafeRelease(m_LightCluster);
|
|
m_LightCluster = null;
|
|
|
|
CoreUtils.SafeRelease(m_LightCullResult);
|
|
m_LightCullResult = null;
|
|
|
|
CoreUtils.SafeRelease(m_LightDataGPUArray);
|
|
m_LightDataGPUArray = null;
|
|
|
|
CoreUtils.SafeRelease(m_EnvLightDataGPUArray);
|
|
m_EnvLightDataGPUArray = null;
|
|
|
|
CoreUtils.Destroy(m_DebugMaterial);
|
|
m_DebugMaterial = null;
|
|
}
|
|
|
|
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 ComputeBuffer(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 ComputeBuffer(numLights, sizeof(uint));
|
|
}
|
|
}
|
|
|
|
void ResizeVolumeBuffer(int numLights)
|
|
{
|
|
// Release the previous buffer
|
|
if (m_LightVolumeGPUArray != null)
|
|
{
|
|
// If it is not null and it has already the right size, we are pretty much done
|
|
if (m_LightVolumeGPUArray.count == numLights)
|
|
return;
|
|
|
|
CoreUtils.SafeRelease(m_LightVolumeGPUArray);
|
|
m_LightVolumeGPUArray = null;
|
|
}
|
|
|
|
// Allocate the next buffer buffer
|
|
if (numLights > 0)
|
|
{
|
|
m_LightVolumesCPUArray = new LightVolume[numLights];
|
|
m_LightVolumeGPUArray = new ComputeBuffer(numLights, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightVolume)));
|
|
}
|
|
}
|
|
|
|
void ResizeLightDataBuffer(int numLights)
|
|
{
|
|
// Release the previous buffer
|
|
if (m_LightDataGPUArray != null)
|
|
{
|
|
// If it is not null and it has already the right size, we are pretty much done
|
|
if (m_LightDataGPUArray.count == numLights)
|
|
return;
|
|
|
|
// It is not the right size, free it to be reallocated
|
|
CoreUtils.SafeRelease(m_LightDataGPUArray);
|
|
m_LightDataGPUArray = null;
|
|
}
|
|
|
|
// Allocate the next buffer buffer
|
|
if (numLights > 0)
|
|
{
|
|
m_LightDataGPUArray = new ComputeBuffer(numLights, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightData)));
|
|
}
|
|
}
|
|
|
|
void ResizeEnvLightDataBuffer(int numEnvLights)
|
|
{
|
|
// Release the previous buffer
|
|
if (m_EnvLightDataGPUArray != null)
|
|
{
|
|
// If it is not null and it has already the right size, we are pretty much done
|
|
if (m_EnvLightDataGPUArray.count == numEnvLights)
|
|
return;
|
|
|
|
CoreUtils.SafeRelease(m_EnvLightDataGPUArray);
|
|
m_EnvLightDataGPUArray = null;
|
|
}
|
|
|
|
// Allocate the next buffer buffer
|
|
if (numEnvLights > 0)
|
|
{
|
|
m_EnvLightDataGPUArray = new ComputeBuffer(numEnvLights, System.Runtime.InteropServices.Marshal.SizeOf(typeof(EnvLightData)));
|
|
}
|
|
}
|
|
|
|
void OOBBToAABBBounds(Vector3 centerWS, Vector3 extents, Vector3 up, Vector3 right, Vector3 forward, ref Bounds outBounds)
|
|
{
|
|
// Reset the bounds of the AABB
|
|
bounds.min = minBounds;
|
|
bounds.max = maxBounds;
|
|
|
|
// Push the 8 corners of the oobb into the AABB
|
|
bounds.Encapsulate(centerWS + right * extents.x + up * extents.y + forward * extents.z);
|
|
bounds.Encapsulate(centerWS + right * extents.x + up * extents.y - forward * extents.z);
|
|
bounds.Encapsulate(centerWS + right * extents.x - up * extents.y + forward * extents.z);
|
|
bounds.Encapsulate(centerWS + right * extents.x - up * extents.y - forward * extents.z);
|
|
bounds.Encapsulate(centerWS - right * extents.x + up * extents.y + forward * extents.z);
|
|
bounds.Encapsulate(centerWS - right * extents.x + up * extents.y - forward * extents.z);
|
|
bounds.Encapsulate(centerWS - right * extents.x - up * extents.y + forward * extents.z);
|
|
bounds.Encapsulate(centerWS - right * extents.x - up * extents.y - forward * extents.z);
|
|
}
|
|
|
|
void BuildGPULightVolumes(HDCamera hdCamera, HDRayTracingLights rayTracingLights)
|
|
{
|
|
int totalNumLights = rayTracingLights.lightCount;
|
|
|
|
// Make sure the light volume buffer has the right size
|
|
if (m_LightVolumesCPUArray == null || totalNumLights != m_LightVolumesCPUArray.Length)
|
|
{
|
|
ResizeVolumeBuffer(totalNumLights);
|
|
}
|
|
|
|
// Set Light volume data to the CPU buffer
|
|
punctualLightCount = 0;
|
|
areaLightCount = 0;
|
|
envLightCount = 0;
|
|
totalLightCount = 0;
|
|
|
|
int realIndex = 0;
|
|
for (int lightIdx = 0; lightIdx < rayTracingLights.hdLightArray.Count; ++lightIdx)
|
|
{
|
|
HDAdditionalLightData currentLight = rayTracingLights.hdLightArray[lightIdx];
|
|
|
|
// When the user deletes a light source in the editor, there is a single frame where the light is null before the collection of light in the scene is triggered
|
|
// the workaround for this is simply to not add it if it is null for that invalid frame
|
|
if (currentLight != null)
|
|
{
|
|
Light light = currentLight.gameObject.GetComponent<Light>();
|
|
if (light == null || !light.enabled)
|
|
continue;
|
|
|
|
// If the light is flagged as baked and has been effectively been baked, we need to skip it and not add it to the light cluster
|
|
if (light.bakingOutput.lightmapBakeType == LightmapBakeType.Baked && light.bakingOutput.isBaked)
|
|
continue;
|
|
|
|
// If this light should not be included when ray tracing is active on the camera, skip it
|
|
if (hdCamera.frameSettings.IsEnabled(FrameSettingsField.RayTracing) && !currentLight.includeForRayTracing)
|
|
continue;
|
|
|
|
// Reserve space in the cookie atlas
|
|
m_RenderPipeline.ReserveCookieAtlasTexture(currentLight, light, currentLight.type);
|
|
|
|
// Compute the camera relative position
|
|
Vector3 lightPositionRWS = currentLight.gameObject.transform.position;
|
|
if (ShaderConfig.s_CameraRelativeRendering != 0)
|
|
{
|
|
lightPositionRWS -= hdCamera.camera.transform.position;
|
|
}
|
|
|
|
// Grab the light range
|
|
float lightRange = light.range;
|
|
|
|
if (currentLight.type != HDLightType.Area)
|
|
{
|
|
m_LightVolumesCPUArray[realIndex].range = new Vector3(lightRange, lightRange, lightRange);
|
|
m_LightVolumesCPUArray[realIndex].position = lightPositionRWS;
|
|
m_LightVolumesCPUArray[realIndex].active = (currentLight.gameObject.activeInHierarchy ? 1 : 0);
|
|
m_LightVolumesCPUArray[realIndex].lightIndex = (uint)lightIdx;
|
|
m_LightVolumesCPUArray[realIndex].shape = 0;
|
|
m_LightVolumesCPUArray[realIndex].lightType = 0;
|
|
punctualLightCount++;
|
|
}
|
|
else
|
|
{
|
|
// let's compute the oobb of the light influence volume first
|
|
Vector3 oobbDimensions = new Vector3(currentLight.shapeWidth + 2 * lightRange, currentLight.shapeHeight + 2 * lightRange, lightRange); // One-sided
|
|
Vector3 extents = 0.5f * oobbDimensions;
|
|
Vector3 oobbCenter = lightPositionRWS + extents.z * currentLight.gameObject.transform.forward;
|
|
|
|
// Let's now compute an AABB that matches the previously defined OOBB
|
|
OOBBToAABBBounds(oobbCenter, extents, currentLight.gameObject.transform.up, currentLight.gameObject.transform.right, currentLight.gameObject.transform.forward, ref bounds);
|
|
|
|
// Fill the volume data
|
|
m_LightVolumesCPUArray[realIndex].range = bounds.extents;
|
|
m_LightVolumesCPUArray[realIndex].position = bounds.center;
|
|
m_LightVolumesCPUArray[realIndex].active = (currentLight.gameObject.activeInHierarchy ? 1 : 0);
|
|
m_LightVolumesCPUArray[realIndex].lightIndex = (uint)lightIdx;
|
|
m_LightVolumesCPUArray[realIndex].shape = 1;
|
|
m_LightVolumesCPUArray[realIndex].lightType = 1;
|
|
areaLightCount++;
|
|
}
|
|
realIndex++;
|
|
}
|
|
}
|
|
|
|
int indexOffset = realIndex;
|
|
|
|
// Set Env Light volume data to the CPU buffer
|
|
for (int lightIdx = 0; lightIdx < rayTracingLights.reflectionProbeArray.Count; ++lightIdx)
|
|
{
|
|
HDProbe currentEnvLight = rayTracingLights.reflectionProbeArray[lightIdx];
|
|
|
|
|
|
if (currentEnvLight != null)
|
|
{
|
|
// If the reflection probe is disabled, we should not be adding it
|
|
if (!currentEnvLight.enabled) continue;
|
|
|
|
// Compute the camera relative position
|
|
Vector3 probePositionRWS = currentEnvLight.influenceToWorld.GetColumn(3);
|
|
if (ShaderConfig.s_CameraRelativeRendering != 0)
|
|
{
|
|
probePositionRWS -= hdCamera.camera.transform.position;
|
|
}
|
|
|
|
if (currentEnvLight.influenceVolume.shape == InfluenceShape.Sphere)
|
|
{
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].shape = 0;
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].range = new Vector3(currentEnvLight.influenceVolume.sphereRadius, currentEnvLight.influenceVolume.sphereRadius, currentEnvLight.influenceVolume.sphereRadius);
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].position = probePositionRWS;
|
|
}
|
|
else
|
|
{
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].shape = 1;
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].range = new Vector3(currentEnvLight.influenceVolume.boxSize.x / 2.0f, currentEnvLight.influenceVolume.boxSize.y / 2.0f, currentEnvLight.influenceVolume.boxSize.z / 2.0f);
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].position = probePositionRWS;
|
|
}
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].active = (currentEnvLight.gameObject.activeInHierarchy ? 1 : 0);
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].lightIndex = (uint)lightIdx;
|
|
m_LightVolumesCPUArray[lightIdx + indexOffset].lightType = 2;
|
|
envLightCount++;
|
|
}
|
|
}
|
|
|
|
totalLightCount = punctualLightCount + areaLightCount + envLightCount;
|
|
|
|
// Push the light volumes to the GPU
|
|
m_LightVolumeGPUArray.SetData(m_LightVolumesCPUArray);
|
|
}
|
|
|
|
void EvaluateClusterVolume(HDCamera hdCamera)
|
|
{
|
|
var settings = hdCamera.volumeStack.GetComponent<LightCluster>();
|
|
|
|
if (ShaderConfig.s_CameraRelativeRendering != 0)
|
|
clusterCenter.Set(0, 0, 0);
|
|
else
|
|
clusterCenter = hdCamera.camera.gameObject.transform.position;
|
|
|
|
minClusterPos.Set(float.MaxValue, float.MaxValue, float.MaxValue);
|
|
maxClusterPos.Set(-float.MaxValue, -float.MaxValue, -float.MaxValue);
|
|
|
|
for (int lightIdx = 0; lightIdx < totalLightCount; ++lightIdx)
|
|
{
|
|
minClusterPos.x = Mathf.Min(m_LightVolumesCPUArray[lightIdx].position.x - m_LightVolumesCPUArray[lightIdx].range.x, minClusterPos.x);
|
|
minClusterPos.y = Mathf.Min(m_LightVolumesCPUArray[lightIdx].position.y - m_LightVolumesCPUArray[lightIdx].range.y, minClusterPos.y);
|
|
minClusterPos.z = Mathf.Min(m_LightVolumesCPUArray[lightIdx].position.z - m_LightVolumesCPUArray[lightIdx].range.z, minClusterPos.z);
|
|
|
|
maxClusterPos.x = Mathf.Max(m_LightVolumesCPUArray[lightIdx].position.x + m_LightVolumesCPUArray[lightIdx].range.x, maxClusterPos.x);
|
|
maxClusterPos.y = Mathf.Max(m_LightVolumesCPUArray[lightIdx].position.y + m_LightVolumesCPUArray[lightIdx].range.y, maxClusterPos.y);
|
|
maxClusterPos.z = Mathf.Max(m_LightVolumesCPUArray[lightIdx].position.z + m_LightVolumesCPUArray[lightIdx].range.z, maxClusterPos.z);
|
|
}
|
|
|
|
minClusterPos.x = minClusterPos.x < clusterCenter.x - settings.cameraClusterRange.value ? clusterCenter.x - settings.cameraClusterRange.value : minClusterPos.x;
|
|
minClusterPos.y = minClusterPos.y < clusterCenter.y - settings.cameraClusterRange.value ? clusterCenter.y - settings.cameraClusterRange.value : minClusterPos.y;
|
|
minClusterPos.z = minClusterPos.z < clusterCenter.z - settings.cameraClusterRange.value ? clusterCenter.z - settings.cameraClusterRange.value : minClusterPos.z;
|
|
|
|
maxClusterPos.x = maxClusterPos.x > clusterCenter.x + settings.cameraClusterRange.value ? clusterCenter.x + settings.cameraClusterRange.value : maxClusterPos.x;
|
|
maxClusterPos.y = maxClusterPos.y > clusterCenter.y + settings.cameraClusterRange.value ? clusterCenter.y + settings.cameraClusterRange.value : maxClusterPos.y;
|
|
maxClusterPos.z = maxClusterPos.z > clusterCenter.z + settings.cameraClusterRange.value ? clusterCenter.z + settings.cameraClusterRange.value : maxClusterPos.z;
|
|
|
|
// Compute the cell size per dimension
|
|
clusterCellSize = (maxClusterPos - minClusterPos);
|
|
clusterCellSize.x /= 64.0f;
|
|
clusterCellSize.y /= 64.0f;
|
|
clusterCellSize.z /= 32.0f;
|
|
|
|
// Compute the bounds of the cluster volume3
|
|
clusterCenter = (maxClusterPos + minClusterPos) / 2.0f;
|
|
clusterDimension = (maxClusterPos - minClusterPos);
|
|
}
|
|
|
|
void CullLights(CommandBuffer cmd)
|
|
{
|
|
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RaytracingCullLights)))
|
|
{
|
|
// Make sure the culling buffer has the right size
|
|
if (m_LightCullResult == null || m_LightCullResult.count != totalLightCount)
|
|
{
|
|
ResizeCullResultBuffer(totalLightCount);
|
|
}
|
|
|
|
ComputeShader lightClusterCS = m_RenderPipelineRayTracingResources.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.SetComputeFloatParam(lightClusterCS, _LightVolumeCount, HDShadowUtils.Asfloat(totalLightCount));
|
|
|
|
cmd.SetComputeBufferParam(lightClusterCS, lightClusterCullKernel, _LightVolumes, m_LightVolumeGPUArray);
|
|
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)
|
|
{
|
|
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RaytracingBuildCluster)))
|
|
{
|
|
// Grab the kernel
|
|
ComputeShader lightClusterCS = m_RenderPipelineRayTracingResources.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, m_LightVolumeGPUArray);
|
|
cmd.SetComputeFloatParam(lightClusterCS, _LightVolumeCount, HDShadowUtils.Asfloat(totalLightCount));
|
|
cmd.SetComputeBufferParam(lightClusterCS, lightClusterKernel, _RaytracingLightCullResult, m_LightCullResult);
|
|
|
|
// Dispatch a compute
|
|
int numGroupsX = 8;
|
|
int numGroupsY = 8;
|
|
int numGroupsZ = 4;
|
|
cmd.DispatchCompute(lightClusterCS, lightClusterKernel, numGroupsX, numGroupsY, numGroupsZ);
|
|
}
|
|
}
|
|
|
|
void BuildLightData(CommandBuffer cmd, HDCamera hdCamera, HDRayTracingLights rayTracingLights, DebugDisplaySettings debugDisplaySettings)
|
|
{
|
|
// If no lights, exit
|
|
if (rayTracingLights.lightCount == 0)
|
|
{
|
|
ResizeLightDataBuffer(1);
|
|
return;
|
|
}
|
|
|
|
// Also we need to build the light list data
|
|
if (m_LightDataGPUArray == null || m_LightDataGPUArray.count != rayTracingLights.lightCount)
|
|
{
|
|
ResizeLightDataBuffer(rayTracingLights.lightCount);
|
|
}
|
|
|
|
m_LightDataCPUArray.Clear();
|
|
|
|
// Grab the shadow settings
|
|
var hdShadowSettings = hdCamera.volumeStack.GetComponent<HDShadowSettings>();
|
|
BoolScalableSetting contactShadowScalableSetting = HDAdditionalLightData.ScalableSettings.UseContactShadow(m_RenderPipeline.asset);
|
|
|
|
// Build the data for every light
|
|
for (int lightIdx = 0; lightIdx < rayTracingLights.hdLightArray.Count; ++lightIdx)
|
|
{
|
|
// Grab the additinal light data to process
|
|
HDAdditionalLightData additionalLightData = rayTracingLights.hdLightArray[lightIdx];
|
|
|
|
LightData lightData = new LightData();
|
|
// When the user deletes a light source in the editor, there is a single frame where the light is null before the collection of light in the scene is triggered
|
|
// the workaround for this is simply to add an invalid light for that frame
|
|
if (additionalLightData == null)
|
|
{
|
|
m_LightDataCPUArray.Add(lightData);
|
|
continue;
|
|
}
|
|
|
|
// Evaluate all the light type data that we need
|
|
LightCategory lightCategory = LightCategory.Count;
|
|
GPULightType gpuLightType = GPULightType.Point;
|
|
LightVolumeType lightVolumeType = LightVolumeType.Count;
|
|
HDLightType lightType = additionalLightData.type;
|
|
HDRenderPipeline.EvaluateGPULightType(lightType, additionalLightData.spotLightShape, additionalLightData.areaLightShape, ref lightCategory, ref gpuLightType, ref lightVolumeType);
|
|
|
|
// Fetch the light component for this light
|
|
additionalLightData.gameObject.TryGetComponent(out lightComponent);
|
|
|
|
// Build the processed light data that we need
|
|
ProcessedLightData processedData = new ProcessedLightData();
|
|
processedData.additionalLightData = additionalLightData;
|
|
processedData.lightType = additionalLightData.type;
|
|
processedData.lightCategory = lightCategory;
|
|
processedData.gpuLightType = gpuLightType;
|
|
processedData.lightVolumeType = lightVolumeType;
|
|
// Both of these positions are non-camera-relative.
|
|
processedData.distanceToCamera = (additionalLightData.gameObject.transform.position - hdCamera.camera.transform.position).magnitude;
|
|
processedData.lightDistanceFade = HDUtils.ComputeLinearDistanceFade(processedData.distanceToCamera, additionalLightData.fadeDistance);
|
|
processedData.volumetricDistanceFade = HDUtils.ComputeLinearDistanceFade(processedData.distanceToCamera, additionalLightData.volumetricFadeDistance);
|
|
processedData.isBakedShadowMask = HDRenderPipeline.IsBakedShadowMaskLight(lightComponent);
|
|
|
|
// Build a visible light
|
|
Color finalColor = lightComponent.color.linear * lightComponent.intensity;
|
|
if (additionalLightData.useColorTemperature)
|
|
finalColor *= Mathf.CorrelatedColorTemperatureToRGB(lightComponent.colorTemperature);
|
|
visibleLight.finalColor = finalColor;
|
|
visibleLight.range = lightComponent.range;
|
|
// This should be done explicitely, localtoworld matrix doesn't work here
|
|
localToWorldMatrix.SetColumn(3, lightComponent.gameObject.transform.position);
|
|
localToWorldMatrix.SetColumn(2, lightComponent.transform.forward);
|
|
localToWorldMatrix.SetColumn(1, lightComponent.transform.up);
|
|
localToWorldMatrix.SetColumn(0, lightComponent.transform.right);
|
|
visibleLight.localToWorldMatrix = localToWorldMatrix;
|
|
visibleLight.spotAngle = lightComponent.spotAngle;
|
|
|
|
int shadowIndex = additionalLightData.shadowIndex;
|
|
int screenSpaceShadowIndex = -1;
|
|
int screenSpaceChannelSlot = -1;
|
|
Vector3 lightDimensions = new Vector3(0.0f, 0.0f, 0.0f);
|
|
|
|
// Use the shared code to build the light data
|
|
m_RenderPipeline.GetLightData(cmd, hdCamera, hdShadowSettings, visibleLight, lightComponent, in processedData,
|
|
shadowIndex, contactShadowScalableSetting, isRasterization: false, ref lightDimensions, ref screenSpaceShadowIndex, ref screenSpaceChannelSlot, ref lightData);
|
|
|
|
// We make the light position camera-relative as late as possible in order
|
|
// to allow the preceding code to work with the absolute world space coordinates.
|
|
Vector3 camPosWS = hdCamera.mainViewConstants.worldSpaceCameraPos;
|
|
HDRenderPipeline.UpdateLightCameraRelativetData(ref lightData, camPosWS);
|
|
|
|
// Set the data for this light
|
|
m_LightDataCPUArray.Add(lightData);
|
|
}
|
|
|
|
// Push the data to the GPU
|
|
m_LightDataGPUArray.SetData(m_LightDataCPUArray);
|
|
}
|
|
|
|
void BuildEnvLightData(CommandBuffer cmd, HDCamera hdCamera, HDRayTracingLights lights)
|
|
{
|
|
int totalReflectionProbes = lights.reflectionProbeArray.Count;
|
|
if (totalReflectionProbes == 0)
|
|
{
|
|
ResizeEnvLightDataBuffer(1);
|
|
return;
|
|
}
|
|
|
|
// Also we need to build the light list data
|
|
if (m_EnvLightDataCPUArray == null || m_EnvLightDataGPUArray == null || m_EnvLightDataGPUArray.count != totalReflectionProbes)
|
|
{
|
|
ResizeEnvLightDataBuffer(totalReflectionProbes);
|
|
}
|
|
|
|
// Make sure the Cpu list is empty
|
|
m_EnvLightDataCPUArray.Clear();
|
|
ProcessedProbeData processedProbe = new ProcessedProbeData();
|
|
|
|
// Build the data for every light
|
|
for (int lightIdx = 0; lightIdx < lights.reflectionProbeArray.Count; ++lightIdx)
|
|
{
|
|
HDProbe probeData = lights.reflectionProbeArray[lightIdx];
|
|
|
|
// Skip the probe if the probe has never rendered (in realtime cases) or if texture is null
|
|
if (!probeData.HasValidRenderedData()) continue;
|
|
|
|
HDRenderPipeline.PreprocessProbeData(ref processedProbe, probeData, hdCamera);
|
|
|
|
var envLightData = new EnvLightData();
|
|
m_RenderPipeline.GetEnvLightData(cmd, hdCamera, processedProbe, ref envLightData);
|
|
|
|
// We make the light position camera-relative as late as possible in order
|
|
// to allow the preceding code to work with the absolute world space coordinates.
|
|
Vector3 camPosWS = hdCamera.mainViewConstants.worldSpaceCameraPos;
|
|
HDRenderPipeline.UpdateEnvLighCameraRelativetData(ref envLightData, camPosWS);
|
|
|
|
m_EnvLightDataCPUArray.Add(envLightData);
|
|
}
|
|
|
|
// Push the data to the GPU
|
|
m_EnvLightDataGPUArray.SetData(m_EnvLightDataCPUArray);
|
|
}
|
|
|
|
public struct LightClusterDebugParameters
|
|
{
|
|
public int texWidth;
|
|
public int texHeight;
|
|
public int lightClusterDebugKernel;
|
|
public Vector3 clusterCellSize;
|
|
public Material debugMaterial;
|
|
public ComputeBuffer lightCluster;
|
|
public ComputeShader lightClusterDebugCS;
|
|
}
|
|
|
|
LightClusterDebugParameters PrepareLightClusterDebugParameters(HDCamera hdCamera)
|
|
{
|
|
LightClusterDebugParameters parameters = new LightClusterDebugParameters();
|
|
parameters.texWidth = hdCamera.actualWidth;
|
|
parameters.texHeight = hdCamera.actualHeight;
|
|
parameters.clusterCellSize = clusterCellSize;
|
|
parameters.lightCluster = m_LightCluster;
|
|
parameters.lightClusterDebugCS = m_RenderPipelineRayTracingResources.lightClusterDebugCS;
|
|
parameters.lightClusterDebugKernel = parameters.lightClusterDebugCS.FindKernel("DebugLightCluster");
|
|
parameters.debugMaterial = m_DebugMaterial;
|
|
return parameters;
|
|
}
|
|
|
|
public struct LightClusterDebugResources
|
|
{
|
|
public RTHandle depthStencilBuffer;
|
|
public RTHandle depthTexture;
|
|
public RTHandle debugLightClusterTexture;
|
|
}
|
|
|
|
static public void ExecuteLightClusterDebug(CommandBuffer cmd, LightClusterDebugParameters parameters, LightClusterDebugResources resources, MaterialPropertyBlock debugMaterialProperties)
|
|
{
|
|
// Bind the output texture
|
|
CoreUtils.SetRenderTarget(cmd, resources.debugLightClusterTexture, resources.depthStencilBuffer, clearFlag: ClearFlag.Color, clearColor: Color.black);
|
|
|
|
// Inject all the parameters to the debug compute
|
|
cmd.SetComputeBufferParam(parameters.lightClusterDebugCS, parameters.lightClusterDebugKernel, HDShaderIDs._RaytracingLightCluster, parameters.lightCluster);
|
|
cmd.SetComputeVectorParam(parameters.lightClusterDebugCS, _ClusterCellSize, parameters.clusterCellSize);
|
|
cmd.SetComputeTextureParam(parameters.lightClusterDebugCS, parameters.lightClusterDebugKernel, HDShaderIDs._CameraDepthTexture, resources.depthStencilBuffer);
|
|
|
|
// Target output texture
|
|
cmd.SetComputeTextureParam(parameters.lightClusterDebugCS, parameters.lightClusterDebugKernel, _DebutLightClusterTexture, resources.debugLightClusterTexture);
|
|
|
|
// Dispatch the compute
|
|
int lightVolumesTileSize = 8;
|
|
int numTilesX = (parameters.texWidth + (lightVolumesTileSize - 1)) / lightVolumesTileSize;
|
|
int numTilesY = (parameters.texHeight + (lightVolumesTileSize - 1)) / lightVolumesTileSize;
|
|
|
|
cmd.DispatchCompute(parameters.lightClusterDebugCS, parameters.lightClusterDebugKernel, numTilesX, numTilesY, 1);
|
|
|
|
// Bind the parameters
|
|
debugMaterialProperties.SetBuffer(HDShaderIDs._RaytracingLightCluster, parameters.lightCluster);
|
|
debugMaterialProperties.SetVector(_ClusterCellSize, parameters.clusterCellSize);
|
|
debugMaterialProperties.SetTexture(HDShaderIDs._CameraDepthTexture, resources.depthTexture);
|
|
|
|
// Draw the faces
|
|
cmd.DrawProcedural(Matrix4x4.identity, parameters.debugMaterial, 1, MeshTopology.Lines, 48, 64 * 64 * 32, debugMaterialProperties);
|
|
cmd.DrawProcedural(Matrix4x4.identity, parameters.debugMaterial, 0, MeshTopology.Triangles, 36, 64 * 64 * 32, debugMaterialProperties);
|
|
}
|
|
|
|
class LightClusterDebugPassData
|
|
{
|
|
public LightClusterDebugParameters parameters;
|
|
public TextureHandle depthStencilBuffer;
|
|
public TextureHandle depthPyramid;
|
|
public TextureHandle outputBuffer;
|
|
}
|
|
|
|
public void EvaluateClusterDebugView(RenderGraph renderGraph, HDCamera hdCamera, TextureHandle depthStencilBuffer, TextureHandle depthPyramid)
|
|
{
|
|
TextureHandle debugTexture;
|
|
using (var builder = renderGraph.AddRenderPass<LightClusterDebugPassData>("Debug Texture for the Light Cluster", out var passData, ProfilingSampler.Get(HDProfileId.RaytracingDebugCluster)))
|
|
{
|
|
builder.EnableAsyncCompute(false);
|
|
|
|
passData.parameters = PrepareLightClusterDebugParameters(hdCamera);
|
|
passData.depthStencilBuffer = builder.UseDepthBuffer(depthStencilBuffer, DepthAccess.Read);
|
|
passData.depthPyramid = builder.ReadTexture(depthStencilBuffer);
|
|
passData.outputBuffer = builder.WriteTexture(renderGraph.CreateTexture(new TextureDesc(Vector2.one, true, true)
|
|
{ colorFormat = GraphicsFormat.R16G16B16A16_SFloat, enableRandomWrite = true, name = "Light Cluster Debug Texture" }));
|
|
|
|
builder.SetRenderFunc(
|
|
(LightClusterDebugPassData data, RenderGraphContext ctx) =>
|
|
{
|
|
// We need to fill the structure that holds the various resources
|
|
LightClusterDebugResources resources = new LightClusterDebugResources();
|
|
resources.depthStencilBuffer = data.depthStencilBuffer;
|
|
resources.depthTexture = data.depthPyramid;
|
|
resources.debugLightClusterTexture = data.outputBuffer;
|
|
ExecuteLightClusterDebug(ctx.cmd, data.parameters, resources, ctx.renderGraphPool.GetTempMaterialPropertyBlock());
|
|
});
|
|
|
|
debugTexture = passData.outputBuffer;
|
|
}
|
|
|
|
m_RenderPipeline.PushFullScreenDebugTexture(renderGraph, debugTexture, FullScreenDebugMode.LightCluster);
|
|
}
|
|
|
|
public ComputeBuffer GetCluster()
|
|
{
|
|
return m_LightCluster;
|
|
}
|
|
|
|
public ComputeBuffer GetLightDatas()
|
|
{
|
|
return m_LightDataGPUArray;
|
|
}
|
|
|
|
public ComputeBuffer GetEnvLightDatas()
|
|
{
|
|
return m_EnvLightDataGPUArray;
|
|
}
|
|
|
|
public Vector3 GetMinClusterPos()
|
|
{
|
|
return minClusterPos;
|
|
}
|
|
|
|
public Vector3 GetMaxClusterPos()
|
|
{
|
|
return maxClusterPos;
|
|
}
|
|
|
|
public Vector3 GetClusterCellSize()
|
|
{
|
|
return clusterCellSize;
|
|
}
|
|
|
|
public int GetPunctualLightCount()
|
|
{
|
|
return punctualLightCount;
|
|
}
|
|
|
|
public int GetAreaLightCount()
|
|
{
|
|
return areaLightCount;
|
|
}
|
|
|
|
public int GetEnvLightCount()
|
|
{
|
|
return envLightCount;
|
|
}
|
|
|
|
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);
|
|
punctualLightCount = 0;
|
|
areaLightCount = 0;
|
|
}
|
|
|
|
public void CullForRayTracing(HDCamera hdCamera, HDRayTracingLights rayTracingLights)
|
|
{
|
|
// If there is no lights to process or no environment not the shader is missing
|
|
if (rayTracingLights.lightCount == 0 || !m_RenderPipeline.GetRayTracingState())
|
|
{
|
|
InvalidateCluster();
|
|
return;
|
|
}
|
|
|
|
// Build the Light volumes
|
|
BuildGPULightVolumes(hdCamera, rayTracingLights);
|
|
|
|
// If no valid light were found, invalidate the cluster and leave
|
|
if (totalLightCount == 0)
|
|
{
|
|
InvalidateCluster();
|
|
return;
|
|
}
|
|
|
|
// Evaluate the volume of the cluster
|
|
EvaluateClusterVolume(hdCamera);
|
|
}
|
|
|
|
public void BuildLightClusterBuffer(CommandBuffer cmd, HDCamera hdCamera, HDRayTracingLights rayTracingLights)
|
|
{
|
|
// If there is no lights to process or no environment not the shader is missing
|
|
if (totalLightCount == 0 || rayTracingLights.lightCount == 0 || !m_RenderPipeline.GetRayTracingState())
|
|
return;
|
|
|
|
// Cull the lights within the evaluated cluster range
|
|
CullLights(cmd);
|
|
|
|
// Build the light Cluster
|
|
BuildLightCluster(hdCamera, cmd);
|
|
}
|
|
|
|
public void ReserveCookieAtlasSlots(HDRayTracingLights rayTracingLights)
|
|
{
|
|
for (int lightIdx = 0; lightIdx < rayTracingLights.hdLightArray.Count; ++lightIdx)
|
|
{
|
|
// Grab the additional light data to process
|
|
HDAdditionalLightData additionalLightData = rayTracingLights.hdLightArray[lightIdx];
|
|
|
|
// Fetch the light component for this light
|
|
additionalLightData.gameObject.TryGetComponent(out lightComponent);
|
|
|
|
// Reserve the cookie resolution in the 2D atlas
|
|
m_RenderPipeline.ReserveCookieAtlasTexture(additionalLightData, lightComponent, additionalLightData.type);
|
|
}
|
|
}
|
|
|
|
public void BuildRayTracingLightData(CommandBuffer cmd, HDCamera hdCamera, HDRayTracingLights rayTracingLights, DebugDisplaySettings debugDisplaySettings)
|
|
{
|
|
// Build the light data
|
|
BuildLightData(cmd, hdCamera, rayTracingLights, debugDisplaySettings);
|
|
|
|
// Build the light data
|
|
BuildEnvLightData(cmd, hdCamera, rayTracingLights);
|
|
}
|
|
|
|
public void BindLightClusterData(CommandBuffer cmd)
|
|
{
|
|
cmd.SetGlobalBuffer(HDShaderIDs._RaytracingLightCluster, GetCluster());
|
|
cmd.SetGlobalBuffer(HDShaderIDs._LightDatasRT, GetLightDatas());
|
|
cmd.SetGlobalBuffer(HDShaderIDs._EnvLightDatasRT, GetEnvLightDatas());
|
|
}
|
|
}
|
|
}
|