1862 lines
94 KiB
C#

using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_2023_3_OR_NEWER
using UnityEngine.Rendering.RenderGraphModule;
#endif
using UnityEngine.Rendering.Universal;
using DebugView = RadiantGI.Universal.RadiantGlobalIllumination.DebugView;
using DeferredLights = UnityEngine.Rendering.Universal.Internal.DeferredLights;
namespace RadiantGI.Universal {
[HelpURL("https://kronnect.com/docs/radiant-gi-urp/")]
public partial class RadiantRenderFeature : ScriptableRendererFeature {
public static bool IsAmbientProbeValid(SphericalHarmonicsL2 probe) {
if (RenderSettings.ambientIntensity < 0.001f) return true;
if (RenderSettings.ambientMode == AmbientMode.Flat && RenderSettings.ambientLight.maxColorComponent < 0.001f) return true;
if (RenderSettings.ambientMode == AmbientMode.Trilight && RenderSettings.ambientSkyColor.maxColorComponent < 0.001f && RenderSettings.ambientEquatorColor.maxColorComponent < 0.001f && RenderSettings.ambientGroundColor.maxColorComponent < 0.001f) return true;
float sumSq = 0f;
for (int rgb = 0; rgb < 3; rgb++) {
for (int i = 0; i < 9; i++) {
float v = probe[rgb, i];
sumSq += v * v;
}
}
return sumSq > 1e-6f;
}
public enum RenderingPath {
[InspectorName("Forward | Forward+")]
Forward,
[InspectorName("Deferred | Deferred+")]
Deferred,
Both
}
enum Pass {
CopyExact,
Raycast,
TemporalAccum,
Albedo,
Normals,
Compose,
Compare,
FinalGIDebug,
Copy,
WideFilter,
Depth,
CopyDepth,
RSM,
NFO,
NFOBlur,
RayAccum,
MotionVectors
}
static readonly List<RadiantVirtualEmitter> emitters = new List<RadiantVirtualEmitter>();
static bool emittersForceRefresh;
static readonly List<RadiantTransparentSupport> transparentSupport = new List<RadiantTransparentSupport>();
static class ShaderParams {
// targets
public static int MainTex = Shader.PropertyToID("_MainTex");
public static int ResolveRT = Shader.PropertyToID("_ResolveRT");
public static int SourceSize = Shader.PropertyToID("_SourceSize");
public static int NoiseTex = Shader.PropertyToID("_NoiseTex");
public static int Downscaled1RT = Shader.PropertyToID("_Downscaled1RT");
public static int Downscaled2RT = Shader.PropertyToID("_Downscaled2RT");
public static int InputRT = Shader.PropertyToID("_InputRTGI");
public static int CompareTex = Shader.PropertyToID("_CompareTexGI");
public static int PrevResolve = Shader.PropertyToID("_RadiantPrevResolve");
public static int RayBufferRT = Shader.PropertyToID("_RadiantRayBufferRT");
public static int RayConfidenceRT = Shader.PropertyToID("_RadiantRayConfidenceRT");
public static int DownscaledDepthRT = Shader.PropertyToID("_DownscaledDepthRT");
public static int TransparentDepthTexture = Shader.PropertyToID("_RadiantTransparentDepthTexture");
public static int RadiantGITexture = Shader.PropertyToID("_RadiantGITexture");
public static int Probe1Cube = Shader.PropertyToID("_Probe1Cube");
public static int Probe2Cube = Shader.PropertyToID("_Probe2Cube");
public static int NFO_RT = Shader.PropertyToID("_NFO_RT");
public static int NFOBlurRT = Shader.PropertyToID("_NFOBlurRT");
public static int RSMBuffer = Shader.PropertyToID("_RadiantRSMBuffer");
// RG supplemental ids
public static int CameraDepthTexture = Shader.PropertyToID("_CameraDepthTexture");
public static int CameraNormalsTexture = Shader.PropertyToID("_CameraNormalsTexture");
public static int MotionVectorTexture = Shader.PropertyToID("_MotionVectorTexture");
public static int CameraGBuffer0 = Shader.PropertyToID("_GBuffer0");
public static int CameraGBuffer1 = Shader.PropertyToID("_GBuffer1");
public static int CameraGBuffer2 = Shader.PropertyToID("_GBuffer2");
// uniforms
public static int IndirectData = Shader.PropertyToID("_IndirectData");
public static int RayData = Shader.PropertyToID("_RayData");
public static int TemporalData = Shader.PropertyToID("_TemporalData");
public static int TemporalData2 = Shader.PropertyToID("_TemporalData2");
public static int WorldToViewDir = Shader.PropertyToID("_WorldToViewDir");
public static int PrevInvViewProj = Shader.PropertyToID("_PrevInvViewProj");
public static int CompareParams = Shader.PropertyToID("_CompareParams");
public static int ExtraData = Shader.PropertyToID("_ExtraData");
public static int ExtraData2 = Shader.PropertyToID("_ExtraData2");
public static int ExtraData3 = Shader.PropertyToID("_ExtraData3");
public static int ExtraData4 = Shader.PropertyToID("_ExtraData4");
public static int ExtraData5 = Shader.PropertyToID("_ExtraData5");
public static int EmittersPositions = Shader.PropertyToID("_EmittersPositions");
public static int EmittersBoxMin = Shader.PropertyToID("_EmittersBoxMin");
public static int EmittersBoxMax = Shader.PropertyToID("_EmittersBoxMax");
public static int EmittersColors = Shader.PropertyToID("_EmittersColors");
public static int EmittersCount = Shader.PropertyToID("_EmittersCount");
public static int RSMIntensity = Shader.PropertyToID("_RadiantShadowMapIntensity");
public static int StencilValue = Shader.PropertyToID("_StencilValue");
public static int StencilCompareFunction = Shader.PropertyToID("_StencilCompareFunction");
public static int ProbeData = Shader.PropertyToID("_ProbeData");
public static int Probe1HDR = Shader.PropertyToID("_Probe1HDR");
public static int Probe2HDR = Shader.PropertyToID("_Probe2HDR");
public static int Probe1BoxMin = Shader.PropertyToID("_Probe1BoxMin");
public static int Probe1BoxMax = Shader.PropertyToID("_Probe1BoxMax");
public static int Probe1ProbePosition = Shader.PropertyToID("_Probe1ProbePosition");
public static int Probe2BoxMin = Shader.PropertyToID("_Probe2BoxMin");
public static int Probe2BoxMax = Shader.PropertyToID("_Probe2BoxMax");
public static int Probe2ProbePosition = Shader.PropertyToID("_Probe2ProbePosition");
public static int BoundsXZ = Shader.PropertyToID("_BoundsXZ");
public static int DebugDepthMultiplier = Shader.PropertyToID("_DebugDepthMultiplier");
public static int DebugMotionVectorMultiplier = Shader.PropertyToID("_DebugMotionVectorMultiplier");
public static int NFOTint = Shader.PropertyToID("_NFOTint");
public static int FallbackDefaultAmbient = Shader.PropertyToID("_FallbackDefaultAmbient");
public static int OrganicLightData = Shader.PropertyToID("_OrganicLightData");
public static int OrganicLightTint = Shader.PropertyToID("_OrganicLightTint");
public static int OrganicLightOffset = Shader.PropertyToID("_OrganicLightOffset");
// keywords
public const string SKW_FORWARD = "_FORWARD";
public const string SKW_FORWARD_AND_DEFERRED = "_FORWARD_AND_DEFERRED";
public const string SKW_USES_MULTIPLE_RAYS = "_USES_MULTIPLE_RAYS";
public const string SKW_REUSE_RAYS = "_REUSE_RAYS";
public const string SKW_ONE_EXTRA_BOUNCE = "_ONE_EXTRA_BOUNCE";
public const string SKW_FALLBACK_1_PROBE = "_FALLBACK_1_PROBE";
public const string SKW_FALLBACK_2_PROBES = "_FALLBACK_2_PROBES";
public const string SKW_FALLBACK_PROBE_ATLAS = "_FALLBACK_PROBE_ATLAS";
public const string SKW_VIRTUAL_EMITTERS = "_VIRTUAL_EMITTERS";
public const string SKW_USES_NEAR_FIELD_OBSCURANCE = "_USES_NEAR_FIELD_OBSCURANCE";
public const string SKW_ORTHO_SUPPORT = "_ORTHO_SUPPORT";
public const string SKW_DISTANCE_BLENDING = "_DISTANCE_BLENDING";
public const string SKW_FALLBACK_RSM = "_FALLBACK_RSM";
public const string SKW_TRANSPARENT_DEPTH_PREPASS = "_TRANSPARENT_DEPTH_PREPASS";
}
static Mesh _fullScreenMesh;
static Mesh fullscreenMesh {
get {
if (_fullScreenMesh != null) {
return _fullScreenMesh;
}
float num = 1f;
float num2 = 0f;
Mesh val = new Mesh();
_fullScreenMesh = val;
_fullScreenMesh.SetVertices(new List<Vector3> {
new Vector3 (-1f, -1f, 0f),
new Vector3 (-1f, 1f, 0f),
new Vector3 (1f, -1f, 0f),
new Vector3 (1f, 1f, 0f)
});
_fullScreenMesh.SetUVs(0, new List<Vector2> {
new Vector2 (0f, num2),
new Vector2 (0f, num),
new Vector2 (1f, num2),
new Vector2 (1f, num)
});
_fullScreenMesh.SetIndices(new int[6] { 0, 1, 2, 2, 1, 3 }, (MeshTopology)0, 0, false);
_fullScreenMesh.UploadMeshData(true);
return _fullScreenMesh;
}
}
partial class RadiantPass : ScriptableRenderPass {
const string RGI_CBUF_NAME = "RadiantGI";
const float GOLDEN_RATIO = 0.618033989f;
const int MAX_EMITTERS = 32;
const float TEMPORAL_CAMERA_TRANSLATION_RESPONSE = 200f;
class PerCameraData {
public Vector3 prevCameraRefPos;
public Matrix4x4 prevInvViewProj;
public bool prevInvViewProjValid;
// Guard against duplicate execution for identical camera state within the same frame.
public int lastExecutedFrame = -1;
public Matrix4x4 lastExecutionViewProj;
public bool lastExecutionViewProjValid;
// Double-buffered accumulation RTs
public RenderTexture[] rtAcum = new RenderTexture[2];
public int rtAcumIndex;
public RenderTexture rtBounce;
// Double-buffered ray temporal RTs
public RenderTexture[] rtRayAcum = new RenderTexture[2];
public RenderTexture[] rtRaySamples = new RenderTexture[2];
public int rtRayIndex;
public int bufferCreationFrame;
// emitters
public float emittersSortTime = float.MinValue;
public Vector3 emittersLastCameraPosition;
public readonly List<RadiantVirtualEmitter> emittersSorted = new List<RadiantVirtualEmitter>();
}
UniversalRenderer renderer;
static RadiantRenderFeature settings;
static RenderTextureDescriptor sourceDesc, cameraTargetDesc;
static readonly Dictionary<Camera, PerCameraData> prevs = new Dictionary<Camera, PerCameraData>();
static RadiantGlobalIllumination radiant;
static float goldenRatioAcum;
static bool usesCompareMode;
static readonly Func<RadiantVirtualEmitter, RadiantVirtualEmitter, int> emittersSortFunction = EmittersDistanceComparer;
static Vector3 camPos;
static Volume[] volumes;
static Material mat;
static readonly Vector4 unlimitedBounds = new Vector4(-1e8f, -1e8f, 1e8f, 1e8f);
static Vector4[] emittersBoxMin, emittersBoxMax, emittersColors, emittersPositions;
static readonly Plane[] cameraPlanes = new Plane[6];
static public RenderTargetIdentifier computedGIRT;
static public bool computedGIRTValid;
// SH coefficients for ambient lighting
static readonly List<SphericalHarmonicsL2> probes = new List<SphericalHarmonicsL2>();
public static MaterialPropertyBlock probesProps;
// Cached gradient ambient probe
static SphericalHarmonicsL2 cachedGradientProbe;
static bool cachedGradientProbeValid;
static bool cachedAmbientSettingsValid;
static AmbientMode cachedAmbientMode;
static Color cachedAmbientSkyColor;
static Color cachedAmbientEquatorColor;
static Color cachedAmbientGroundColor;
static Color cachedAmbientLightColor;
static float cachedAmbientIntensity;
static readonly RenderTargetIdentifier[] rayTemporalMRT = new RenderTargetIdentifier[2];
public bool Setup (RadiantGlobalIllumination radiant, ScriptableRenderer renderer, RadiantRenderFeature settings, bool isSceneView) {
if (!radiant.IsActive()) return false;
RadiantPass.radiant = radiant;
#if UNITY_EDITOR
if (isSceneView && !radiant.showInSceneView.value) return false;
if (!Application.isPlaying && !radiant.showInEditMode.value) return false;
#endif
usesCompareMode = radiant.compareMode.value && !isSceneView;
renderPassEvent = RenderPassEvent.AfterRenderingSkybox + 2; // just after depth texture and motion vectors
#if UNITY_2022_1_OR_NEWER && !UNITY_2022_3_OR_NEWER
// when using motion vectors in RG, it needs to execute after motion vector pass which happens BeforeRenderingPostProcessing so we force Radiant to run just before post processing
// in Unity 2022.3 it was fixed, so motion vectors just follow the depth texture mode pass so we can keep Radiant run before transparent
int minRenderPassEvent = (int)RenderPassEvent.BeforeRenderingPostProcessing;
if ((int)renderPassEvent < minRenderPassEvent) {
renderPassEvent = (RenderPassEvent)minRenderPassEvent;
}
#endif
this.renderer = renderer as UniversalRenderer;
RadiantPass.settings = settings;
if (mat == null) {
mat = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Kronnect/RadiantGI_URP"));
mat.SetTexture(ShaderParams.NoiseTex, Resources.Load<Texture>("RadiantGI/blueNoiseGI128RGB"));
}
mat.SetInt(ShaderParams.StencilValue, radiant.stencilValue.value);
mat.SetInt(ShaderParams.StencilCompareFunction, radiant.stencilCheck.value ? (int)radiant.stencilCompareFunction.value : (int)CompareFunction.Always);
return true;
}
ScriptableRenderPassInput GetRequiredInputs () {
ScriptableRenderPassInput input = ScriptableRenderPassInput.Depth | ScriptableRenderPassInput.Motion;
// Only request Normal input in forward mode - in deferred, use G-buffer normals instead
if (settings != null && settings.renderingPath != RenderingPath.Deferred) {
input |= ScriptableRenderPassInput.Normal;
}
return input;
}
static bool AreMatricesSimilar(Matrix4x4 a, Matrix4x4 b) {
const float epsilon = 1e-6f;
for (int i = 0; i < 16; i++) {
if (Mathf.Abs(a[i] - b[i]) > epsilon) {
return false;
}
}
return true;
}
readonly PassData passData = new PassData();
class PassData {
public CommandBuffer cmd;
public Camera cam;
public NativeArray<VisibleReflectionProbe> reflectionProbes;
public bool usesProbeAtlas;
#if UNITY_2022_2_OR_NEWER
public RTHandle source;
#else
public RenderTargetIdentifier source;
#endif
#if UNITY_2023_3_OR_NEWER
public TextureHandle colorTexture, depthTexture, cameraNormalsTexture, motionVectorTexture, gBuffer0, gBuffer1, gBuffer2;
#endif
}
#if UNITY_2023_3_OR_NEWER
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) {
using (var builder = renderGraph.AddUnsafePass<PassData>("Radiant GI RG Pass", out var passData)) {
builder.AllowPassCulling(false);
ConfigureInput(GetRequiredInputs());
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
UniversalRenderingData renderingData = frameData.Get<UniversalRenderingData>();
passData.cam = cameraData.camera;
passData.reflectionProbes = renderingData.cullResults.visibleReflectionProbes;
sourceDesc = cameraData.cameraTargetDescriptor;
sourceDesc.colorFormat = RenderTextureFormat.ARGBHalf;
sourceDesc.useMipMap = false;
sourceDesc.msaaSamples = 1;
sourceDesc.depthBufferBits = 0;
cameraTargetDesc = sourceDesc;
float downsampling = radiant.downsampling.value;
sourceDesc.width = (int)(sourceDesc.width / downsampling);
sourceDesc.height = (int)(sourceDesc.height / downsampling);
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
passData.colorTexture = resourceData.activeColorTexture;
builder.UseTexture(resourceData.activeColorTexture, AccessFlags.ReadWrite);
if (settings.renderingPath == RenderingPath.Forward) {
builder.UseTexture(resourceData.cameraDepthTexture);
passData.depthTexture = resourceData.cameraDepthTexture;
builder.UseTexture(resourceData.cameraNormalsTexture);
passData.cameraNormalsTexture = resourceData.cameraNormalsTexture;
} else if (settings.renderingPath == RenderingPath.Deferred) {
if (resourceData.gBuffer[0].IsValid()) {
builder.UseTexture(resourceData.gBuffer[0]);
passData.gBuffer0 = resourceData.gBuffer[0];
}
if (resourceData.gBuffer[1].IsValid()) {
builder.UseTexture(resourceData.gBuffer[1]);
passData.gBuffer1 = resourceData.gBuffer[1];
}
if (resourceData.gBuffer[2].IsValid()) {
builder.UseTexture(resourceData.gBuffer[2]);
passData.gBuffer2 = resourceData.gBuffer[2];
}
} else {
// Both mode - use normals texture for forward compatibility
builder.UseTexture(resourceData.cameraNormalsTexture);
passData.cameraNormalsTexture = resourceData.cameraNormalsTexture;
}
builder.UseTexture(resourceData.motionVectorColor);
passData.motionVectorTexture = resourceData.motionVectorColor;
#if UNITY_6000_1_OR_NEWER
bool usesProbeAtlas = false;
int renderingMode = (int)renderer.renderingModeActual;
bool isClusterLighting = (renderingMode == 2 || renderingMode == 3);
if (isClusterLighting) {
UniversalLightData lightData = frameData.Get<UniversalLightData>();
usesProbeAtlas = lightData.reflectionProbeAtlas;
}
passData.usesProbeAtlas = usesProbeAtlas;
#else
passData.usesProbeAtlas = false;
#endif
builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => {
CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
if (passData.depthTexture.IsValid()) {
cmd.SetGlobalTexture(ShaderParams.CameraDepthTexture, passData.depthTexture);
}
if (passData.motionVectorTexture.IsValid()) {
cmd.SetGlobalTexture(ShaderParams.MotionVectorTexture, passData.motionVectorTexture);
}
if (settings.renderingPath == RenderingPath.Deferred) {
if (passData.gBuffer0.IsValid()) {
cmd.SetGlobalTexture(ShaderParams.CameraGBuffer0, passData.gBuffer0);
}
if (passData.gBuffer1.IsValid()) {
cmd.SetGlobalTexture(ShaderParams.CameraGBuffer1, passData.gBuffer1);
}
if (passData.gBuffer2.IsValid()) {
cmd.SetGlobalTexture(ShaderParams.CameraGBuffer2, passData.gBuffer2);
// Bind GBuffer2 to _CameraNormalsTexture so LoadSceneNormals can use it
cmd.SetGlobalTexture(ShaderParams.CameraNormalsTexture, passData.gBuffer2);
}
} else if (passData.cameraNormalsTexture.IsValid()) {
cmd.SetGlobalTexture(ShaderParams.CameraNormalsTexture, passData.cameraNormalsTexture);
}
passData.source = passData.colorTexture;
passData.cmd = cmd;
RenderGI(passData);
});
}
}
#endif
static SphericalHarmonicsL2 CreateAmbientProbeFromGradient (Color skyColor, Color equatorColor, Color groundColor, float intensity) {
SphericalHarmonicsL2 probe = new SphericalHarmonicsL2();
Color linearSky = CoreUtils.ConvertSRGBToActiveColorSpace(skyColor) * intensity;
Color linearEquator = CoreUtils.ConvertSRGBToActiveColorSpace(equatorColor) * intensity;
Color linearGround = CoreUtils.ConvertSRGBToActiveColorSpace(groundColor) * intensity;
probe[0, 0] = (linearSky.r + linearEquator.r + linearGround.r) / 3.0f;
probe[1, 0] = (linearSky.g + linearEquator.g + linearGround.g) / 3.0f;
probe[2, 0] = (linearSky.b + linearEquator.b + linearGround.b) / 3.0f;
probe[0, 2] = (linearSky.r - linearGround.r) * 0.5f;
probe[1, 2] = (linearSky.g - linearGround.g) * 0.5f;
probe[2, 2] = (linearSky.b - linearGround.b) * 0.5f;
return probe;
}
static SphericalHarmonicsL2 CreateAmbientProbeFromColor(Color ambientColor, float intensity) {
Color linearAmbient = CoreUtils.ConvertSRGBToActiveColorSpace(ambientColor) * intensity;
SphericalHarmonicsL2 probe = new SphericalHarmonicsL2();
probe[0, 0] = linearAmbient.r;
probe[1, 0] = linearAmbient.g;
probe[2, 0] = linearAmbient.b;
return probe;
}
static void RenderGI (PassData passData) {
computedGIRTValid = false;
#if UNITY_2022_2_OR_NEWER
RTHandle source = passData.source;
#else
RenderTargetIdentifier source = passData.source;
#endif
CommandBuffer cmd = passData.cmd;
Camera cam = passData.cam;
camPos = cam.transform.position;
// Setup SH coefficients for ambient lighting (needed by SampleSH in shader)
SphericalHarmonicsL2 ambientProbe = RenderSettings.ambientProbe;
// Detect if ambient probe is available/baked
bool isAmbientProbeAvailable = IsAmbientProbeValid(ambientProbe);
bool realtimeAmbientProbe = false;
// Fallback: If ambientProbe is empty, use cached gradient probe or create it once
if (!isAmbientProbeAvailable) {
bool ambientSettingsChanged = !cachedAmbientSettingsValid ||
cachedAmbientMode != RenderSettings.ambientMode ||
cachedAmbientIntensity != RenderSettings.ambientIntensity ||
cachedAmbientSkyColor != RenderSettings.ambientSkyColor ||
cachedAmbientEquatorColor != RenderSettings.ambientEquatorColor ||
cachedAmbientGroundColor != RenderSettings.ambientGroundColor ||
cachedAmbientLightColor != RenderSettings.ambientLight;
if (!cachedGradientProbeValid || ambientSettingsChanged) {
if (RenderSettings.ambientMode == AmbientMode.Flat) {
cachedGradientProbe = CreateAmbientProbeFromColor(
RenderSettings.ambientLight,
RenderSettings.ambientIntensity
);
} else {
cachedGradientProbe = CreateAmbientProbeFromGradient(
RenderSettings.ambientSkyColor,
RenderSettings.ambientEquatorColor,
RenderSettings.ambientGroundColor,
RenderSettings.ambientIntensity
);
}
cachedGradientProbeValid = true;
cachedAmbientSettingsValid = true;
cachedAmbientMode = RenderSettings.ambientMode;
cachedAmbientIntensity = RenderSettings.ambientIntensity;
cachedAmbientSkyColor = RenderSettings.ambientSkyColor;
cachedAmbientEquatorColor = RenderSettings.ambientEquatorColor;
cachedAmbientGroundColor = RenderSettings.ambientGroundColor;
cachedAmbientLightColor = RenderSettings.ambientLight;
}
ambientProbe = cachedGradientProbe;
realtimeAmbientProbe = true;
}
if (probesProps == null) probesProps = new MaterialPropertyBlock();
probes.Clear();
probes.Add(ambientProbe);
probesProps.CopySHCoefficientArraysFrom(probes);
DebugView debugView = radiant.debugView.value;
bool usesBounce = radiant.rayBounce.value;
bool usesForward = settings.renderingPath != RenderingPath.Deferred;
float normalMapInfluence = radiant.normalMapInfluence.value;
float downsampling = radiant.downsampling.value;
int currentFrame = GetRenderFrameId();
bool usesRSM = RadiantShadowMap.installed && radiant.fallbackReflectiveShadowMap.value && radiant.reflectiveShadowMapIntensity.value > 0;
bool usesEmitters = radiant.virtualEmitters.value;
float fallbackAmbient = radiant.fallbackAmbient.value;
float unityAmbientIntensity = radiant.unityAmbientLighting.value;
bool transparencySupportEnabled = radiant.transparencySupport.value;
float occlusionIntensity = radiant.occlusionIntensity.value;
// pass radiant settings to shader
mat.SetVector(ShaderParams.IndirectData, new Vector4(radiant.indirectIntensity.value, radiant.indirectMaxSourceBrightness.value, radiant.indirectDistanceAttenuation.value, radiant.rayReuse.value));
mat.SetVector(ShaderParams.RayData, new Vector4(radiant.rayCount.value, radiant.rayMaxLength.value, radiant.rayMaxSamples.value, radiant.thickness.value));
// some uniforms required by compare render feature so declared as global vectors instead of material properties
cmd.SetGlobalVector(ShaderParams.ExtraData2, new Vector4(radiant.brightnessThreshold.value, radiant.brightnessMax.value, radiant.saturation.value, radiant.reflectiveShadowMapIntensity.value)); // global because these params are needed by the compare pass
mat.DisableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED);
mat.DisableKeyword(ShaderParams.SKW_FORWARD);
if (usesForward) {
if (settings.renderingPath == RenderingPath.Both) {
mat.EnableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED);
}
else {
mat.EnableKeyword(ShaderParams.SKW_FORWARD);
}
}
if (radiant.rayCount.value > 1) {
mat.EnableKeyword(ShaderParams.SKW_USES_MULTIPLE_RAYS);
}
else {
mat.DisableKeyword(ShaderParams.SKW_USES_MULTIPLE_RAYS);
}
float nearFieldObscurance = radiant.nearFieldObscurance.value;
bool useNFO = nearFieldObscurance > 0;
cmd.SetGlobalVector(ShaderParams.ExtraData4, new Vector4(
useNFO ? radiant.nearFieldObscuranceMaxCameraDistance.value : 0f,
useNFO ? (1f - radiant.nearFieldObscuranceOccluderDistance.value) * 10f : 0f,
1f - unityAmbientIntensity,
realtimeAmbientProbe ? 1f : 0f));
if (useNFO) {
cmd.SetGlobalColor(ShaderParams.NFOTint, radiant.nearFieldObscuranceTintColor.value);
mat.EnableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE);
}
else {
mat.DisableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE);
}
if (cam.orthographic) {
mat.EnableKeyword(ShaderParams.SKW_ORTHO_SUPPORT);
}
else {
mat.DisableKeyword(ShaderParams.SKW_ORTHO_SUPPORT);
}
cmd.SetGlobalVector(ShaderParams.ExtraData3, new Vector4(radiant.aoInfluence.value, radiant.nearFieldObscuranceSpread.value * 0.5f, 1f / (radiant.nearCameraAttenuation.value + 0.0001f), nearFieldObscurance)); // global because these params are needed by the compare pass
// restricts to volume bounds?
SetupVolumeBounds(cmd);
// pass reprojection & other raymarch data
goldenRatioAcum += GOLDEN_RATIO * radiant.rayCount.value;
goldenRatioAcum %= 5000;
cmd.SetGlobalVector(ShaderParams.SourceSize, new Vector4(cameraTargetDesc.width, cameraTargetDesc.height, goldenRatioAcum, currentFrame));
float baseBlurSpread = Mathf.Max(radiant.blurSpread.value, sourceDesc.width / 1920f);
cmd.SetGlobalVector(ShaderParams.ExtraData, new Vector4(radiant.rayJitter.value, baseBlurSpread, normalMapInfluence, occlusionIntensity));
Vector4 extraData5 = new Vector4(downsampling, radiant.sourceBrightness.value, radiant.giWeight.value, fallbackAmbient);
cmd.SetGlobalVector(ShaderParams.ExtraData5, extraData5);
// pass UNITY_MATRIX_V
cmd.SetGlobalMatrix(ShaderParams.WorldToViewDir, cam.worldToCameraMatrix);
// create downscaled depth
RenderTextureDescriptor downDesc = cameraTargetDesc;
downDesc.width = Mathf.Min(sourceDesc.width, downDesc.width / 2);
downDesc.height = Mathf.Min(sourceDesc.height, downDesc.height / 2);
int downHalfDescWidth = downDesc.width;
int downHalfDescHeight = downDesc.height;
// depth buffer handling
if (transparencySupportEnabled) {
bool hasLayerMask = radiant.transparentLayerMask.value != 0;
bool hasScriptObjects = transparentSupport.Count > 0;
if (hasLayerMask || hasScriptObjects) {
mat.EnableKeyword(ShaderParams.SKW_TRANSPARENT_DEPTH_PREPASS);
}
else {
mat.DisableKeyword(ShaderParams.SKW_TRANSPARENT_DEPTH_PREPASS);
}
}
int downsamplingDepth = 9 - radiant.raytracerAccuracy.value;
RenderTextureDescriptor rtDownDepth = sourceDesc;
rtDownDepth.width = Mathf.CeilToInt((float)rtDownDepth.width / downsamplingDepth);
rtDownDepth.height = Mathf.CeilToInt((float)rtDownDepth.height / downsamplingDepth);
#if UNITY_WEBGL
rtDownDepth.colorFormat = RenderTextureFormat.RFloat;
#else
rtDownDepth.colorFormat = cam.orthographic
? RenderTextureFormat.RFloat
: RenderTextureFormat.RHalf;
#endif
rtDownDepth.sRGB = false;
GetTemporaryRT(cmd, ShaderParams.DownscaledDepthRT, ref rtDownDepth, FilterMode.Point);
FullScreenBlit(cmd, ShaderParams.DownscaledDepthRT, Pass.CopyDepth);
// are we reusing rays?
if (!prevs.TryGetValue(cam, out PerCameraData frameAcumData)) {
prevs[cam] = frameAcumData = new PerCameraData();
frameAcumData.bufferCreationFrame = currentFrame;
}
Matrix4x4 proj = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true);
Matrix4x4 view = cam.worldToCameraMatrix;
Matrix4x4 currViewProj = proj * view;
// Only treat as duplicate when frame index and camera matrices match.
// This avoids false positives during editor repaints/camera pivoting where
// frame counters may not advance consistently.
bool isSameFrame = frameAcumData.lastExecutedFrame == currentFrame;
bool isSameViewProj = frameAcumData.lastExecutionViewProjValid && AreMatricesSimilar(frameAcumData.lastExecutionViewProj, currViewProj);
bool isDoubleExecution = isSameFrame && isSameViewProj;
frameAcumData.lastExecutedFrame = currentFrame;
frameAcumData.lastExecutionViewProj = currViewProj;
frameAcumData.lastExecutionViewProjValid = true;
Matrix4x4 currInvViewProj = currViewProj.inverse;
if (!frameAcumData.prevInvViewProjValid) {
frameAcumData.prevInvViewProj = currInvViewProj;
frameAcumData.prevInvViewProjValid = true;
}
cmd.SetGlobalMatrix(ShaderParams.PrevInvViewProj, frameAcumData.prevInvViewProj);
frameAcumData.prevInvViewProj = currInvViewProj;
// early debug views
switch (debugView) {
case DebugView.Albedo:
FullScreenBlit(cmd, source, Pass.Albedo);
return;
case DebugView.Normals:
FullScreenBlit(cmd, source, Pass.Normals);
return;
case DebugView.Depth:
mat.SetFloat(ShaderParams.DebugDepthMultiplier, radiant.debugDepthMultiplier.value);
FullScreenBlit(cmd, source, Pass.Depth);
return;
case DebugView.MotionVectors:
mat.SetFloat(ShaderParams.DebugMotionVectorMultiplier, radiant.debugMotionVectorMultiplier.value);
FullScreenBlit(cmd, source, Pass.MotionVectors);
return;
}
RenderTexture bounceRT = frameAcumData.rtBounce;
RenderTargetIdentifier raycastInput = source;
if (usesBounce) {
if (bounceRT != null && (bounceRT.width != cameraTargetDesc.width || bounceRT.height != cameraTargetDesc.height)) {
bounceRT.Release();
bounceRT = null;
}
if (bounceRT == null) {
bounceRT = new RenderTexture(cameraTargetDesc);
bounceRT.Create();
frameAcumData.rtBounce = bounceRT;
frameAcumData.bufferCreationFrame = currentFrame;
}
else {
if (currentFrame - frameAcumData.bufferCreationFrame > 2) {
raycastInput = bounceRT; // only uses bounce rt a few frames after it's created
}
}
}
else if (bounceRT != null) {
bounceRT.Release();
DestroyImmediate(bounceRT);
}
// virtual emitters
if (usesEmitters) {
float now = Time.time;
if (emittersForceRefresh) {
emittersForceRefresh = false;
foreach (PerCameraData cameraData in prevs.Values) {
cameraData.emittersSortTime = float.MinValue;
}
}
if (now - frameAcumData.emittersSortTime > 5 || (frameAcumData.emittersLastCameraPosition - camPos).sqrMagnitude > 25) {
frameAcumData.emittersSortTime = now;
frameAcumData.emittersLastCameraPosition = camPos;
SortEmitters();
frameAcumData.emittersSorted.Clear();
frameAcumData.emittersSorted.AddRange(emitters);
}
usesEmitters = SetupEmitters(cam, frameAcumData.emittersSorted);
}
if (usesEmitters) {
mat.EnableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS);
}
else {
mat.DisableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS);
}
// set the fallback mode
mat.DisableKeyword(ShaderParams.SKW_REUSE_RAYS);
mat.DisableKeyword(ShaderParams.SKW_ONE_EXTRA_BOUNCE);
mat.DisableKeyword(ShaderParams.SKW_FALLBACK_1_PROBE);
mat.DisableKeyword(ShaderParams.SKW_FALLBACK_2_PROBES);
mat.DisableKeyword(ShaderParams.SKW_FALLBACK_RSM);
#if UNITY_6000_1_OR_NEWER
mat.DisableKeyword(ShaderParams.SKW_FALLBACK_PROBE_ATLAS);
#endif
// Enable extra bounce keyword when bounce is active (for motion vector compensation)
if (usesBounce && currentFrame - frameAcumData.bufferCreationFrame > 2) {
mat.EnableKeyword(ShaderParams.SKW_ONE_EXTRA_BOUNCE);
}
if (radiant.fallbackReflectionProbes.value) {
#if UNITY_6000_1_OR_NEWER
if (passData.usesProbeAtlas)
{
// Forward+/Deferred+ with probe atlas - use cluster-based sampling
// Skip calling SetupProbes() - no manual probe selection needed
mat.EnableKeyword(ShaderParams.SKW_FALLBACK_PROBE_ATLAS);
cmd.SetGlobalVector(ShaderParams.ProbeData, new Vector4(0, 0, radiant.probesIntensity.value, 0));
}
else
#endif
{
// Traditional Forward/Deferred path or Unity < 6.1
if (SetupProbes(cmd, passData.reflectionProbes, out int numProbes)) {
mat.EnableKeyword(numProbes == 1 ? ShaderParams.SKW_FALLBACK_1_PROBE : ShaderParams.SKW_FALLBACK_2_PROBES);
}
}
}
if (radiant.fallbackReuseRays.value && currentFrame - frameAcumData.bufferCreationFrame > 2 && radiant.rayReuse.value > 0 && frameAcumData.rtAcum[frameAcumData.rtAcumIndex] != null) {
RenderTargetIdentifier reuseRaysPrevRT = new RenderTargetIdentifier(frameAcumData.rtAcum[frameAcumData.rtAcumIndex], 0, CubemapFace.Unknown, -1);
cmd.SetGlobalTexture(ShaderParams.PrevResolve, reuseRaysPrevRT);
mat.EnableKeyword(ShaderParams.SKW_REUSE_RAYS);
}
if (usesRSM) {
GetTemporaryRT(cmd, ShaderParams.RSMBuffer, ref downDesc, FilterMode.Bilinear);
FullScreenBlit(cmd, ShaderParams.RSMBuffer, Pass.RSM);
mat.EnableKeyword(ShaderParams.SKW_FALLBACK_RSM);
}
float invIndirectIntensity = radiant.indirectIntensity.value > 0f ? 1f / radiant.indirectIntensity.value : 0f;
cmd.SetGlobalColor(ShaderParams.FallbackDefaultAmbient, radiant.fallbackDefaultAmbient.value * invIndirectIntensity);
// raycast & resolve
GetTemporaryRT(cmd, ShaderParams.ResolveRT, ref sourceDesc, FilterMode.Bilinear);
FullScreenBlit(cmd, raycastInput, ShaderParams.ResolveRT, Pass.Raycast, probesProps);
// Double-buffer indices: read from current, write to next
// On double execution, undo the swap from the first execution to read/write the same buffers
int rayReadIndex = isDoubleExecution ? (1 - frameAcumData.rtRayIndex) : frameAcumData.rtRayIndex;
int rayWriteIndex = 1 - rayReadIndex;
RenderTexture rayRead = frameAcumData.rtRayAcum[rayReadIndex];
RenderTexture rayWrite = frameAcumData.rtRayAcum[rayWriteIndex];
RenderTexture raySamplesRead = frameAcumData.rtRaySamples[rayReadIndex];
RenderTexture raySamplesWrite = frameAcumData.rtRaySamples[rayWriteIndex];
// Check if buffers need resizing
bool needsRayResize = (rayRead != null && (rayRead.width != sourceDesc.width || rayRead.height != sourceDesc.height)) ||
(rayWrite != null && (rayWrite.width != sourceDesc.width || rayWrite.height != sourceDesc.height));
RenderTextureDescriptor raySamplesDesc = sourceDesc;
#if UNITY_WEBGL
raySamplesDesc.colorFormat = RenderTextureFormat.RFloat;
#else
raySamplesDesc.colorFormat = RenderTextureFormat.RHalf;
#endif
raySamplesDesc.sRGB = false;
bool needsSamplesResize = (raySamplesRead != null && (raySamplesRead.width != sourceDesc.width || raySamplesRead.height != sourceDesc.height)) ||
(raySamplesWrite != null && (raySamplesWrite.width != sourceDesc.width || raySamplesWrite.height != sourceDesc.height));
// Release and recreate if size changed
if (needsRayResize) {
for (int i = 0; i < 2; i++) {
if (frameAcumData.rtRayAcum[i] != null) {
frameAcumData.rtRayAcum[i].Release();
DestroyImmediate(frameAcumData.rtRayAcum[i]);
frameAcumData.rtRayAcum[i] = null;
}
}
frameAcumData.bufferCreationFrame = currentFrame;
rayRead = rayWrite = null;
}
if (needsSamplesResize) {
for (int i = 0; i < 2; i++) {
if (frameAcumData.rtRaySamples[i] != null) {
frameAcumData.rtRaySamples[i].Release();
DestroyImmediate(frameAcumData.rtRaySamples[i]);
frameAcumData.rtRaySamples[i] = null;
}
}
raySamplesRead = raySamplesWrite = null;
}
// Create double buffers if needed
for (int i = 0; i < 2; i++) {
if (frameAcumData.rtRayAcum[i] == null) {
frameAcumData.rtRayAcum[i] = new RenderTexture(sourceDesc);
frameAcumData.rtRayAcum[i].Create();
frameAcumData.bufferCreationFrame = currentFrame;
// Clear new ray accumulation buffer
RenderTargetIdentifier initRT = new RenderTargetIdentifier(frameAcumData.rtRayAcum[i], 0, CubemapFace.Unknown, -1);
cmd.SetRenderTarget(initRT, 0, CubemapFace.Unknown, -1);
cmd.ClearRenderTarget(false, true, Color.clear);
}
if (frameAcumData.rtRaySamples[i] == null) {
frameAcumData.rtRaySamples[i] = new RenderTexture(raySamplesDesc) {
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp
};
frameAcumData.rtRaySamples[i].Create();
// Clear new sample buffer
RenderTargetIdentifier initRT = new RenderTargetIdentifier(frameAcumData.rtRaySamples[i], 0, CubemapFace.Unknown, -1);
cmd.SetRenderTarget(initRT, 0, CubemapFace.Unknown, -1);
cmd.ClearRenderTarget(false, true, Color.clear);
}
}
// Get current read/write buffers after potential creation
rayRead = frameAcumData.rtRayAcum[rayReadIndex];
rayWrite = frameAcumData.rtRayAcum[rayWriteIndex];
raySamplesRead = frameAcumData.rtRaySamples[rayReadIndex];
raySamplesWrite = frameAcumData.rtRaySamples[rayWriteIndex];
RenderTargetIdentifier rayReadRT = new RenderTargetIdentifier(rayRead, 0, CubemapFace.Unknown, -1);
RenderTargetIdentifier rayWriteRT = new RenderTargetIdentifier(rayWrite, 0, CubemapFace.Unknown, -1);
RenderTargetIdentifier raySamplesReadRT = new RenderTargetIdentifier(raySamplesRead, 0, CubemapFace.Unknown, -1);
RenderTargetIdentifier raySamplesWriteRT = new RenderTargetIdentifier(raySamplesWrite, 0, CubemapFace.Unknown, -1);
cmd.SetGlobalTexture(ShaderParams.RayBufferRT, rayReadRT);
cmd.SetGlobalTexture(ShaderParams.RayConfidenceRT, raySamplesReadRT);
// On double execution, skip temporal draws - the persistent RTs already have
// the correct result from the first execution
if (!isDoubleExecution) {
float temporalBlend = 0.95f;
float motionScale = radiant.temporalResponseSpeed.value / 1080f;
float historyLength = radiant.temporalStabilization.value;
Vector3 refPos = camPos + cam.transform.forward;
float camTranslationDelta = 0f;
if (frameAcumData.rtAcum[0] != null) {
camTranslationDelta = Vector3.Distance(refPos, frameAcumData.prevCameraRefPos) * TEMPORAL_CAMERA_TRANSLATION_RESPONSE;
camTranslationDelta = Mathf.Clamp01(camTranslationDelta);
}
frameAcumData.prevCameraRefPos = refPos;
mat.SetVector(ShaderParams.TemporalData, new Vector4(temporalBlend, motionScale, historyLength, camTranslationDelta));
mat.SetVector(ShaderParams.TemporalData2, new Vector4(radiant.darkThreshold.value, radiant.darkThresholdMultiplier.value, 0, 0));
// Double buffering: render directly to write buffers (no copy needed)
rayTemporalMRT[0] = rayWriteRT;
rayTemporalMRT[1] = raySamplesWriteRT;
cmd.SetRenderTarget(rayTemporalMRT, BuiltinRenderTextureType.None);
cmd.SetGlobalTexture(ShaderParams.MainTex, ShaderParams.ResolveRT);
cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)Pass.RayAccum);
frameAcumData.rtRayIndex = rayWriteIndex;
}
// Prepare NFO
if (useNFO) {
RenderTextureDescriptor nfoDesc = downDesc;
nfoDesc.colorFormat = RenderTextureFormat.RHalf;
GetTemporaryRT(cmd, ShaderParams.NFO_RT, ref nfoDesc, FilterMode.Bilinear);
GetTemporaryRT(cmd, ShaderParams.NFOBlurRT, ref nfoDesc, FilterMode.Bilinear);
FullScreenBlit(cmd, ShaderParams.NFOBlurRT, Pass.NFO);
FullScreenBlit(cmd, ShaderParams.NFOBlurRT, ShaderParams.NFO_RT, Pass.NFOBlur);
cmd.ReleaseTemporaryRT(ShaderParams.NFOBlurRT);
}
// downscale & blur
GetTemporaryRT(cmd, ShaderParams.Downscaled2RT, ref downDesc, FilterMode.Bilinear);
int lastBlurRT = ShaderParams.Downscaled2RT;
RenderTargetIdentifier raySource = rayWriteRT;
if (downsampling <= 1f) {
GetTemporaryRT(cmd, ShaderParams.Downscaled1RT, ref downDesc, FilterMode.Bilinear);
FullScreenBlit(cmd, raySource, ShaderParams.Downscaled1RT, Pass.Copy);
FullScreenBlit(cmd, ShaderParams.Downscaled1RT, ShaderParams.Downscaled2RT, Pass.WideFilter);
}
else {
cmd.SetGlobalVector(ShaderParams.ExtraData, new Vector4(radiant.rayJitter.value, baseBlurSpread * 1.5f, normalMapInfluence, occlusionIntensity));
FullScreenBlit(cmd, raySource, ShaderParams.Downscaled2RT, Pass.WideFilter);
}
computedGIRT = lastBlurRT;
// Double-buffer indices for final accumulation
// On double execution, undo the swap from the first execution
int acumReadIndex = isDoubleExecution ? (1 - frameAcumData.rtAcumIndex) : frameAcumData.rtAcumIndex;
int acumWriteIndex = 1 - acumReadIndex;
RenderTextureDescriptor acumDesc = sourceDesc;
acumDesc.width = downHalfDescWidth;
acumDesc.height = downHalfDescHeight;
// Check if buffers need resizing
bool needsAcumResize = false;
for (int i = 0; i < 2; i++) {
if (frameAcumData.rtAcum[i] != null &&
(frameAcumData.rtAcum[i].width != downHalfDescWidth || frameAcumData.rtAcum[i].height != downHalfDescHeight)) {
needsAcumResize = true;
break;
}
}
if (needsAcumResize) {
for (int i = 0; i < 2; i++) {
if (frameAcumData.rtAcum[i] != null) {
frameAcumData.rtAcum[i].Release();
DestroyImmediate(frameAcumData.rtAcum[i]);
frameAcumData.rtAcum[i] = null;
}
}
frameAcumData.bufferCreationFrame = currentFrame;
}
Pass acumPass = Pass.TemporalAccum;
bool isFirstFrame = false;
// Create double buffers if needed
for (int i = 0; i < 2; i++) {
if (frameAcumData.rtAcum[i] == null) {
frameAcumData.rtAcum[i] = new RenderTexture(acumDesc);
frameAcumData.rtAcum[i].Create();
// Clear new accumulation buffer
RenderTargetIdentifier initRT = new RenderTargetIdentifier(frameAcumData.rtAcum[i], 0, CubemapFace.Unknown, -1);
cmd.SetRenderTarget(initRT, 0, CubemapFace.Unknown, -1);
cmd.ClearRenderTarget(false, true, Color.clear);
if (i == 0) {
frameAcumData.prevCameraRefPos = camPos + cam.transform.forward;
frameAcumData.bufferCreationFrame = currentFrame;
isFirstFrame = true;
}
}
}
if (isFirstFrame) {
acumPass = Pass.Copy;
}
RenderTexture acumRead = frameAcumData.rtAcum[acumReadIndex];
RenderTexture acumWrite = frameAcumData.rtAcum[acumWriteIndex];
RenderTargetIdentifier acumReadRT = new RenderTargetIdentifier(acumRead, 0, CubemapFace.Unknown, -1);
RenderTargetIdentifier acumWriteRT = new RenderTargetIdentifier(acumWrite, 0, CubemapFace.Unknown, -1);
// Double buffering: read from previous, write directly to next (no copy needed)
// On double execution, skip temporal draw - persistent RT has correct result
if (!isDoubleExecution) {
cmd.SetGlobalTexture(ShaderParams.PrevResolve, acumReadRT);
FullScreenBlit(cmd, computedGIRT, acumWriteRT, acumPass);
// Swap buffer index for next frame
frameAcumData.rtAcumIndex = acumWriteIndex;
}
computedGIRT = acumWriteRT;
computedGIRTValid = true;
// Expose GI texture globally for transparent shaders if transparency support is enabled
if (transparencySupportEnabled) {
cmd.SetGlobalTexture(ShaderParams.RadiantGITexture, computedGIRT);
}
// Prepare output blending
GetTemporaryRT(cmd, ShaderParams.InputRT, ref cameraTargetDesc, FilterMode.Point);
FullScreenBlit(cmd, source, ShaderParams.InputRT, Pass.CopyExact);
if (usesCompareMode) {
GetTemporaryRT(cmd, ShaderParams.CompareTex, ref cameraTargetDesc, FilterMode.Point); // needed by the compare pass
if (usesBounce) {
FullScreenBlit(cmd, computedGIRT, bounceRT, Pass.Compose, probesProps);
FullScreenBlit(cmd, bounceRT, ShaderParams.CompareTex, Pass.CopyExact);
}
}
else if (usesBounce) {
FullScreenBlit(cmd, computedGIRT, bounceRT, Pass.Compose, probesProps);
FullScreenBlit(cmd, bounceRT, source, Pass.CopyExact);
}
else {
FullScreenBlit(cmd, computedGIRT, source, Pass.Compose, probesProps);
}
// Debug views that require all buffers to be generated
switch (debugView) {
case DebugView.Downscaled:
FullScreenBlit(cmd, ShaderParams.Downscaled2RT, source, Pass.CopyExact);
return;
case DebugView.Raycast:
FullScreenBlit(cmd, ShaderParams.ResolveRT, source, Pass.CopyExact);
return;
case DebugView.RaycastAccumulated:
FullScreenBlit(cmd, rayWriteRT, source, Pass.CopyExact);
return;
case DebugView.ReflectiveShadowMap:
if (usesRSM) {
FullScreenBlit(cmd, source, Pass.RSM);
}
return;
case DebugView.TemporalAccumulationBuffer:
FullScreenBlit(cmd, computedGIRT, source, Pass.CopyExact);
return;
case DebugView.FinalGI:
FullScreenBlit(cmd, computedGIRT, source, Pass.FinalGIDebug);
return;
}
}
static void GetTemporaryRT (CommandBuffer cmd, int nameID, ref RenderTextureDescriptor desc, FilterMode filterMode) {
if (desc.width < 0) desc.width = 1;
if (desc.height < 0) desc.height = 1;
cmd.GetTemporaryRT(nameID, desc, filterMode);
cmd.SetGlobalTexture(nameID, nameID);
}
static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier destination, Pass pass) {
cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1);
cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass);
}
static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass) {
cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1);
cmd.SetGlobalTexture(ShaderParams.MainTex, source);
cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass);
}
static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass, MaterialPropertyBlock props) {
cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1);
cmd.SetGlobalTexture(ShaderParams.MainTex, source);
cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass, props);
}
static float CalculateProbeWeight (Vector3 wpos, Vector3 probeBoxMin, Vector3 probeBoxMax, float blendDistance) {
Vector3 weightDir = Vector3.Min(wpos - probeBoxMin, probeBoxMax - wpos) / blendDistance;
return Mathf.Clamp01(Mathf.Min(weightDir.x, Mathf.Min(weightDir.y, weightDir.z)));
}
static bool SetupProbes (CommandBuffer cmd, NativeArray<VisibleReflectionProbe> visibleProbes, out int numProbes) {
numProbes = PickNearProbes(visibleProbes, out ReflectionProbe probe1, out ReflectionProbe probe2);
if (numProbes == 0) return false;
if (!probe1.bounds.Contains(camPos)) return false;
if (numProbes >= 2 && !probe2.bounds.Contains(camPos)) numProbes = 1;
float probe1Weight = 0, probe2Weight = 0;
if (numProbes >= 1) {
cmd.SetGlobalTexture(ShaderParams.Probe1Cube, probe1.texture);
cmd.SetGlobalVector(ShaderParams.Probe1HDR, probe1.textureHDRDecodeValues);
Bounds probe1Bounds = probe1.bounds;
probe1Weight = CalculateProbeWeight(camPos, probe1Bounds.min, probe1Bounds.max, probe1.blendDistance);
if (probe1.boxProjection) {
Vector3 probe1Position = probe1.transform.position;
cmd.SetGlobalVector(ShaderParams.Probe1BoxMin, probe1Bounds.min);
cmd.SetGlobalVector(ShaderParams.Probe1BoxMax, probe1Bounds.max);
cmd.SetGlobalVector(ShaderParams.Probe1ProbePosition, new Vector4(probe1Position.x, probe1Position.y, probe1Position.z, 1));
}
else {
cmd.SetGlobalVector(ShaderParams.Probe1ProbePosition, Vector4.zero);
}
}
if (numProbes >= 2) {
cmd.SetGlobalTexture(ShaderParams.Probe2Cube, probe2.texture);
cmd.SetGlobalVector(ShaderParams.Probe2HDR, probe2.textureHDRDecodeValues);
Bounds probe2Bounds = probe2.bounds;
probe2Weight = CalculateProbeWeight(camPos, probe2Bounds.min, probe2Bounds.max, probe2.blendDistance);
if (probe2.boxProjection) {
Vector3 probe2Position = probe2.transform.position;
cmd.SetGlobalVector(ShaderParams.Probe2BoxMin, probe2Bounds.min);
cmd.SetGlobalVector(ShaderParams.Probe2BoxMax, probe2Bounds.max);
cmd.SetGlobalVector(ShaderParams.Probe2ProbePosition, new Vector4(probe2Position.x, probe2Position.y, probe2Position.z, 1));
}
else {
cmd.SetGlobalVector(ShaderParams.Probe2ProbePosition, Vector4.zero);
}
}
float probesIntensity = radiant.probesIntensity.value;
cmd.SetGlobalVector(ShaderParams.ProbeData, new Vector4(probe1Weight * probesIntensity, probe2Weight * probesIntensity, probesIntensity, 0));
return true;
}
static int PickNearProbes (NativeArray<VisibleReflectionProbe> visibleProbes, out ReflectionProbe probe1, out ReflectionProbe probe2) {
probe1 = probe2 = null;
int probesCount = visibleProbes.Length;
if (probesCount == 0) return 0;
float probe1Value = float.MaxValue;
float probe2Value = float.MaxValue;
for (int k = 0; k < probesCount; k++) {
ReflectionProbe probe = visibleProbes[k].reflectionProbe;
if (probe == null || probe.texture == null) continue;
float probeValue = ComputeProbeValue(camPos, probe);
if (probeValue < probe2Value) {
probe2 = probe;
probe2Value = probeValue;
if (probe2Value < probe1Value) {
// swap probe1 & probe2
float tempValue = probe1Value;
ReflectionProbe tempProbe = probe1;
probe1 = probe2;
probe1Value = probe2Value;
probe2 = tempProbe;
probe2Value = tempValue;
}
}
}
if (probe1 == null) return 0;
if (probe2 == null) return 1;
return 2;
}
static float ComputeProbeValue (Vector3 camPos, ReflectionProbe probe) {
Vector3 probePos = probe.transform.position;
float d = (probePos - camPos).sqrMagnitude * (probe.importance + 1) * 1000;
if (!probe.bounds.Contains(camPos)) d += 100000;
return d;
}
static void SetupVolumeBounds (CommandBuffer cmd) {
if (!radiant.limitToVolumeBounds.value) {
cmd.SetGlobalVector(ShaderParams.BoundsXZ, unlimitedBounds);
return;
}
if (volumes == null) {
volumes = VolumeManager.instance.GetVolumes(-1);
}
int volumeCount = volumes.Length;
for (int k = 0; k < volumeCount; k++) {
Volume volume = volumes[k];
if (volume == null) continue;
List<Collider> colliders = volume.colliders;
int colliderCount = colliders.Count;
for (int j = 0; j < colliderCount; j++) {
Collider collider = colliders[j];
if (collider == null) continue;
Bounds bounds = collider.bounds;
if (collider.bounds.Contains(camPos) && volume.sharedProfile.Has<RadiantGlobalIllumination>()) {
Vector4 effectBounds = new Vector4(bounds.min.x, bounds.min.z, bounds.max.x, bounds.max.z);
cmd.SetGlobalVector(ShaderParams.BoundsXZ, effectBounds);
return;
}
}
}
}
static bool SetupEmitters (Camera cam, List<RadiantVirtualEmitter> emitters) {
// copy emitters data
if (emittersBoxMax == null || emittersBoxMax.Length != MAX_EMITTERS) {
emittersBoxMax = new Vector4[MAX_EMITTERS];
emittersBoxMin = new Vector4[MAX_EMITTERS];
emittersColors = new Vector4[MAX_EMITTERS];
emittersPositions = new Vector4[MAX_EMITTERS];
}
int emittersCount = 0;
const int EMITTERS_BUDGET = 150; // max number of emitters to be processed per frame
int emittersMax = Mathf.Min(EMITTERS_BUDGET, emitters.Count);
GeometryUtility.CalculateFrustumPlanes(cam, cameraPlanes);
for (int k = 0; k < emittersMax; k++) {
RadiantVirtualEmitter emitter = emitters[k];
// Cull emitters
// disabled emitter?
if (emitter == null || !emitter.isActiveAndEnabled) continue;
// emitter with no intensity or range?
if (emitter.intensity <= 0 || emitter.range <= 0) continue;
// emitter bounds out of camera frustum?
Bounds emitterBounds = emitter.GetBounds();
if (!GeometryUtility.TestPlanesAABB(cameraPlanes, emitterBounds)) continue;
// emitter with black color (nothing to inject)?
Vector4 colorAndRange = emitter.GetGIColorAndRange();
if (emitter.fadeDistance > 0) {
float fade = ComputeVolumeFade(emitterBounds, emitter.fadeDistance);
colorAndRange.x *= fade;
colorAndRange.y *= fade;
colorAndRange.z *= fade;
}
if (colorAndRange.x == 0 && colorAndRange.y == 0 && colorAndRange.z == 0) continue;
// add emitter
Vector3 emitterPosition = emitter.transform.position;
emittersPositions[emittersCount] = emitterPosition;
emittersColors[emittersCount] = colorAndRange;
Vector3 boxMin = emitterBounds.min;
Vector3 boxMax = emitterBounds.max;
float lightRangeSqr = colorAndRange.w * colorAndRange.w;
// Commented out for future versions if needed
//float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;
//float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);
//float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr;
//float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;
float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightRangeSqr);
float pointAttenX = oneOverLightRangeSqr;
//float pointAttenY = lightRangeSqrOverFadeRangeSqr;
emittersBoxMin[emittersCount] = new Vector4(boxMin.x, boxMin.y, boxMin.z, pointAttenX);
emittersBoxMax[emittersCount] = new Vector4(boxMax.x, boxMax.y, boxMax.z, 0); // pointAttenY
emittersCount++;
if (emittersCount >= MAX_EMITTERS) break;
}
if (emittersCount == 0) return false;
Shader.SetGlobalVectorArray(ShaderParams.EmittersPositions, emittersPositions);
Shader.SetGlobalVectorArray(ShaderParams.EmittersBoxMin, emittersBoxMin);
Shader.SetGlobalVectorArray(ShaderParams.EmittersBoxMax, emittersBoxMax);
Shader.SetGlobalVectorArray(ShaderParams.EmittersColors, emittersColors);
Shader.SetGlobalInt(ShaderParams.EmittersCount, emittersCount);
return true;
}
static void SortEmitters () {
emitters.Sort((p1, p2) => emittersSortFunction(p1, p2));
}
static int EmittersDistanceComparer (RadiantVirtualEmitter p1, RadiantVirtualEmitter p2) {
Vector3 p1Pos = p1.transform.position;
Vector3 p2Pos = p2.transform.position;
float d1 = (p1Pos - camPos).sqrMagnitude;
float d2 = (p2Pos - camPos).sqrMagnitude;
Bounds p1bounds = p1.GetBounds();
Bounds p2bounds = p2.GetBounds();
if (!p1bounds.Contains(camPos)) d1 += 100000;
if (!p2bounds.Contains(camPos)) d2 += 100000;
if (d1 < d2) return -1; else if (d1 > d2) return 1;
return 0;
}
static float ComputeVolumeFade (Bounds emitterBounds, float fadeDistance) {
Vector3 diff = emitterBounds.center - camPos;
diff.x = diff.x < 0 ? -diff.x : diff.x;
diff.y = diff.y < 0 ? -diff.y : diff.y;
diff.z = diff.z < 0 ? -diff.z : diff.z;
Vector3 extents = emitterBounds.extents;
Vector3 gap = diff - extents;
float maxDiff = gap.x > gap.y ? gap.x : gap.y;
maxDiff = maxDiff > gap.z ? maxDiff : gap.z;
fadeDistance += 0.0001f;
float t = 1f - Mathf.Clamp01(maxDiff / fadeDistance);
return t;
}
public void Cleanup () {
CoreUtils.Destroy(mat);
if (prevs != null) {
foreach (PerCameraData fad in prevs.Values) {
// Dispose double-buffered accumulation RTs
for (int i = 0; i < 2; i++) {
if (fad.rtAcum[i] != null) {
fad.rtAcum[i].Release();
DestroyImmediate(fad.rtAcum[i]);
fad.rtAcum[i] = null;
}
if (fad.rtRayAcum[i] != null) {
fad.rtRayAcum[i].Release();
DestroyImmediate(fad.rtRayAcum[i]);
fad.rtRayAcum[i] = null;
}
if (fad.rtRaySamples[i] != null) {
fad.rtRaySamples[i].Release();
DestroyImmediate(fad.rtRaySamples[i]);
fad.rtRaySamples[i] = null;
}
}
if (fad.rtBounce != null) {
fad.rtBounce.Release();
DestroyImmediate(fad.rtBounce);
}
}
prevs.Clear();
}
goldenRatioAcum = 0f;
volumes = null;
radiant = null;
settings = null;
cachedGradientProbeValid = false;
cachedAmbientSettingsValid = false;
}
}
partial class RadiantComparePass : ScriptableRenderPass {
const string RGI_CBUF_NAME = "RadiantGICompare";
static Material mat;
static RadiantGlobalIllumination radiant;
static RadiantRenderFeature settings;
class PassData {
public CommandBuffer cmd;
#if UNITY_2022_2_OR_NEWER
public RTHandle source, sourceDepth;
#else
public RenderTargetIdentifier source, sourceDepth;
#endif
#if UNITY_2023_3_OR_NEWER
public TextureHandle colorTexture, depthTexture;
#endif
public RenderTextureDescriptor cameraTargetDesc;
}
readonly PassData passData = new PassData();
public bool Setup (ScriptableRenderer renderer, RadiantRenderFeature settings) {
radiant = VolumeManager.instance.stack.GetComponent<RadiantGlobalIllumination>();
if (radiant == null || !radiant.IsActive() || radiant.debugView.value != DebugView.None) return false;
#if UNITY_EDITOR
if (!Application.isPlaying && !radiant.showInEditMode.value) return false;
#endif
if (!radiant.compareMode.value) return false;
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing + 1;
RadiantComparePass.settings = settings;
if (mat == null) {
mat = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Kronnect/RadiantGI_URP"));
}
return true;
}
#if UNITY_2023_3_OR_NEWER
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) {
using (var builder = renderGraph.AddUnsafePass<PassData>("Radiant GI Compare RG Pass", out var passData)) {
builder.AllowPassCulling(false);
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
passData.colorTexture = resourceData.activeColorTexture;
passData.depthTexture = resourceData.activeDepthTexture;
RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
desc.useMipMap = false;
desc.msaaSamples = 1;
desc.depthBufferBits = 0;
passData.cameraTargetDesc = desc;
builder.UseTexture(resourceData.activeColorTexture, AccessFlags.ReadWrite);
builder.UseTexture(resourceData.activeDepthTexture, AccessFlags.Read);
builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => {
CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
passData.cmd = cmd;
passData.source = passData.colorTexture;
passData.sourceDepth = passData.depthTexture;
ExecutePass(passData);
});
}
}
#endif
static void ExecutePass (PassData passData) {
if (!RadiantPass.computedGIRTValid) return;
mat.DisableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED);
mat.DisableKeyword(ShaderParams.SKW_FORWARD);
if (settings.renderingPath == RenderingPath.Both) {
mat.EnableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED);
}
else if (settings.renderingPath == RenderingPath.Forward) {
mat.EnableKeyword(ShaderParams.SKW_FORWARD);
}
if (radiant.virtualEmitters.value) {
mat.EnableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS);
}
else {
mat.DisableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS);
}
float nearFieldObscurance = radiant.nearFieldObscurance.value;
if (nearFieldObscurance > 0) {
mat.EnableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE);
}
else {
mat.DisableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE);
}
mat.SetVector(ShaderParams.IndirectData, new Vector4(radiant.indirectIntensity.value, radiant.indirectMaxSourceBrightness.value, radiant.indirectDistanceAttenuation.value, radiant.rayReuse.value));
float angle = radiant.compareSameSide.value ? Mathf.PI * 0.5f : radiant.compareLineAngle.value;
mat.SetVector(ShaderParams.CompareParams, new Vector4(Mathf.Cos(angle), Mathf.Sin(angle), radiant.compareSameSide.value ? radiant.comparePanning.value : -10, radiant.compareLineWidth.value));
mat.SetInt(ShaderParams.StencilValue, radiant.stencilValue.value);
mat.SetInt(ShaderParams.StencilCompareFunction, radiant.stencilCheck.value ? (int)radiant.stencilCompareFunction.value : (int)CompareFunction.Always);
CommandBuffer cmd = passData.cmd;
RenderTextureDescriptor desc = passData.cameraTargetDesc;
if (desc.width < 1) desc.width = 1;
if (desc.height < 1) desc.height = 1;
cmd.GetTemporaryRT(ShaderParams.InputRT, desc, FilterMode.Point);
cmd.SetGlobalTexture(ShaderParams.InputRT, ShaderParams.InputRT);
cmd.GetTemporaryRT(ShaderParams.CompareTex, desc, FilterMode.Point);
cmd.SetGlobalTexture(ShaderParams.CompareTex, ShaderParams.CompareTex);
FullScreenBlit(cmd, passData.source, ShaderParams.InputRT, Pass.CopyExact); // include transparent objects in the original compare texture
FullScreenBlit(cmd, RadiantPass.computedGIRT, ShaderParams.CompareTex, Pass.Compose, RadiantPass.probesProps); // add gi
FullScreenBlit(cmd, ShaderParams.InputRT, passData.source, Pass.Compare); // render the split
cmd.ReleaseTemporaryRT(ShaderParams.InputRT);
cmd.ReleaseTemporaryRT(ShaderParams.CompareTex);
}
static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass) {
cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1);
cmd.SetGlobalTexture(ShaderParams.MainTex, source);
cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass);
}
static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass, MaterialPropertyBlock props) {
cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1);
cmd.SetGlobalTexture(ShaderParams.MainTex, source);
cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass, props);
}
public void Cleanup () {
CoreUtils.Destroy(mat);
}
}
partial class RadiantOrganicLightPass : ScriptableRenderPass {
enum Pass {
OrganicLight = 0
}
const string m_strProfilerTag = "Radiant GI Organic Light";
static Material mat;
DeferredLights m_DeferredLights;
Texture2D noiseTex;
Vector3 offset;
public bool Setup (RadiantGlobalIllumination radiant, ScriptableRenderer renderer, bool isSceneView) {
if (radiant.organicLight.value <= 0) return false;
#if UNITY_EDITOR
if (isSceneView && !radiant.showInSceneView.value) return false;
if (!Application.isPlaying && !radiant.showInEditMode.value) return false;
#endif
DeferredLights deferredLights = ((UniversalRenderer)renderer).deferredLights;
if (deferredLights == null) return false;
renderPassEvent = RenderPassEvent.AfterRenderingGbuffer;
m_DeferredLights = deferredLights;
if (mat == null) {
mat = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Kronnect/RadiantGIOrganicLight"));
}
if (noiseTex == null) {
noiseTex = Resources.Load<Texture2D>("RadiantGI/NoiseTex");
}
mat.SetTexture(ShaderParams.NoiseTex, noiseTex);
mat.SetVector(ShaderParams.OrganicLightData, new Vector4(1.001f - radiant.organicLightSpread.value, radiant.organicLight.value, radiant.organicLightThreshold.value, radiant.organicLightNormalsInfluence.value));
mat.SetColor(ShaderParams.OrganicLightTint, radiant.organicLightTintColor.value);
offset += radiant.organicLightAnimationSpeed.value * Time.deltaTime;
offset.x %= 10000f;
offset.y %= 10000f;
offset.z %= 10000f;
mat.SetVector(ShaderParams.OrganicLightOffset, offset);
if (radiant.organicLightDistanceScaling.value) {
mat.EnableKeyword(ShaderParams.SKW_DISTANCE_BLENDING);
}
else {
mat.DisableKeyword(ShaderParams.SKW_DISTANCE_BLENDING);
}
return true;
}
#if UNITY_2022_1_OR_NEWER
RTHandle GetAlbedoFromGbuffer () {
return m_DeferredLights.GbufferAttachments[m_DeferredLights.GBufferAlbedoIndex];
}
#else
RenderTargetIdentifier GetAlbedoFromGbuffer () {
return m_DeferredLights.GbufferAttachmentIdentifiers[m_DeferredLights.GBufferAlbedoIndex];
}
#endif
#if UNITY_2023_3_OR_NEWER
class PassData {
public TextureHandle depthTexture;
public TextureHandle normalsTexture;
public TextureHandle gBuffer0;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) {
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
if (!resourceData.gBuffer[0].IsValid()) return;
using (var builder = renderGraph.AddUnsafePass<PassData>(m_strProfilerTag, out var passData)) {
passData.depthTexture = resourceData.activeDepthTexture;
passData.normalsTexture = resourceData.cameraNormalsTexture;
passData.gBuffer0 = resourceData.gBuffer[0];
builder.UseTexture(resourceData.gBuffer[0], AccessFlags.ReadWrite);
builder.UseTexture(resourceData.activeDepthTexture, AccessFlags.Read);
if (resourceData.cameraNormalsTexture.IsValid()) {
builder.UseTexture(resourceData.cameraNormalsTexture, AccessFlags.Read);
}
builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => {
CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
cmd.SetGlobalTexture(ShaderParams.CameraDepthTexture, passData.depthTexture);
if (passData.normalsTexture.IsValid()) {
cmd.SetGlobalTexture(ShaderParams.CameraNormalsTexture, passData.normalsTexture);
}
cmd.SetRenderTarget(passData.gBuffer0);
cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)Pass.OrganicLight);
});
}
}
#endif
public void Cleanup () {
CoreUtils.Destroy(mat);
}
}
partial class TransparentDepthRenderPass : ScriptableRenderPass {
const string m_ProfilerTag = "Radiant GI Transparent Depth PrePass";
const string m_DepthOnlyShader = "Hidden/Kronnect/RadiantGI/DepthOnly";
public static int transparentLayerMask;
static FilteringSettings filterSettings;
static readonly List<ShaderTagId> shaderTagIdList = new List<ShaderTagId>();
#if UNITY_2022_2_OR_NEWER
RTHandle m_Depth;
#else
RenderTargetIdentifier m_Depth;
#endif
static Material depthOnlyMaterial;
const bool useOptimizedDepthOnlyShader = true;
public TransparentDepthRenderPass () {
#if UNITY_2022_2_OR_NEWER
RenderTargetIdentifier rti = new RenderTargetIdentifier(ShaderParams.TransparentDepthTexture, 0, CubemapFace.Unknown, -1);
m_Depth = RTHandles.Alloc(rti, name: "_RadiantTransparentDepthTexture");
#else
m_Depth = new RenderTargetIdentifier(ShaderParams.TransparentDepthTexture, 0, CubemapFace.Unknown, -1);
#endif
shaderTagIdList.Clear();
shaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
shaderTagIdList.Add(new ShaderTagId("UniversalForward"));
shaderTagIdList.Add(new ShaderTagId("LightweightForward"));
filterSettings = new FilteringSettings(RenderQueueRange.transparent, 0);
}
public void Setup (int transparentLayerMask) {
TransparentDepthRenderPass.transparentLayerMask = transparentLayerMask;
}
#if UNITY_2023_3_OR_NEWER
class PassData {
public RendererListHandle rendererListHandle;
public UniversalCameraData cameraData;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) {
using (var builder = renderGraph.AddUnsafePass<PassData>(m_ProfilerTag, out var passData)) {
builder.AllowPassCulling(false);
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
UniversalRenderingData renderingData = frameData.Get<UniversalRenderingData>();
UniversalLightData lightData = frameData.Get<UniversalLightData>();
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
passData.cameraData = cameraData;
SortingCriteria sortingCriteria = SortingCriteria.CommonTransparent;
var drawingSettings = CreateDrawingSettings(shaderTagIdList, renderingData, cameraData, lightData, sortingCriteria);
drawingSettings.perObjectData = PerObjectData.None;
if (useOptimizedDepthOnlyShader) {
if (depthOnlyMaterial == null) {
Shader depthOnly = Shader.Find(m_DepthOnlyShader);
if (depthOnly != null) {
depthOnlyMaterial = new Material(depthOnly);
}
}
if (depthOnlyMaterial != null) {
drawingSettings.overrideMaterial = depthOnlyMaterial;
}
}
if (transparentLayerMask != 0) {
if (transparentLayerMask != filterSettings.layerMask) {
filterSettings = new FilteringSettings(RenderQueueRange.transparent, transparentLayerMask);
}
RendererListParams listParams = new RendererListParams(renderingData.cullResults, drawingSettings, filterSettings);
passData.rendererListHandle = renderGraph.CreateRendererList(listParams);
builder.UseRendererList(passData.rendererListHandle);
}
builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => {
CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
RenderTextureDescriptor depthDesc = passData.cameraData.cameraTargetDescriptor;
depthDesc.colorFormat = RenderTextureFormat.Depth;
depthDesc.depthBufferBits = 24;
depthDesc.msaaSamples = 1;
cmd.GetTemporaryRT(ShaderParams.TransparentDepthTexture, depthDesc, FilterMode.Point);
RenderTargetIdentifier rti = new RenderTargetIdentifier(ShaderParams.TransparentDepthTexture, 0, CubemapFace.Unknown, -1);
cmd.SetGlobalTexture(ShaderParams.TransparentDepthTexture, rti);
cmd.SetRenderTarget(rti);
cmd.ClearRenderTarget(true, true, Color.black);
if (transparentLayerMask != 0 && passData.rendererListHandle.IsValid()) {
context.cmd.DrawRendererList(passData.rendererListHandle);
}
if (depthOnlyMaterial != null) {
int transparentSupportCount = RadiantRenderFeature.transparentSupport.Count;
for (int i = 0; i < transparentSupportCount; i++) {
Renderer renderer = RadiantRenderFeature.transparentSupport[i].theRenderer;
if (renderer != null) {
cmd.DrawRenderer(renderer, depthOnlyMaterial);
}
}
}
});
}
}
#endif
public void Cleanup () {
CoreUtils.Destroy(depthOnlyMaterial);
#if UNITY_2022_2_OR_NEWER
if (m_Depth != null) {
m_Depth.Release();
}
#endif
}
}
[Tooltip("Select the rendering mode according to the URP asset")]
public RenderingPath renderingPath = RenderingPath.Deferred;
[Tooltip("Allows Radiant to be executed even if camera has Post Processing option disabled")]
public bool ignorePostProcessingOption = true;
[Tooltip("Enable this option to skip rendering GI on overlay cameras")]
public bool ignoreOverlayCameras = true;
[Tooltip("Which cameras can use Radiant Global Illumination")]
public LayerMask camerasLayerMask = -1;
RadiantPass radiantPass;
RadiantComparePass comparePass;
RadiantOrganicLightPass organicLightPass;
TransparentDepthRenderPass transparentDepthPass;
void OnDisable () {
if (radiantPass != null) {
radiantPass.Cleanup();
}
if (comparePass != null) {
comparePass.Cleanup();
}
if (organicLightPass != null) {
organicLightPass.Cleanup();
}
if (transparentDepthPass != null) {
transparentDepthPass.Cleanup();
}
installed = false;
}
public override void Create () {
radiantPass = new RadiantPass();
comparePass = new RadiantComparePass();
organicLightPass = new RadiantOrganicLightPass();
transparentDepthPass = new TransparentDepthRenderPass();
emittersForceRefresh = true;
}
public static bool needRTRefresh;
public static bool isRenderingInDeferred;
public static bool installed;
static int GetRenderFrameId() {
int frameId = Application.isPlaying ? Time.frameCount : Time.renderedFrameCount;
if (frameId <= 0) {
frameId = Time.frameCount;
}
return frameId;
}
public override void AddRenderPasses (ScriptableRenderer renderer, ref RenderingData renderingData) {
installed = true;
// Quality of life check
#if UNITY_EDITOR
UniversalRenderer universalRenderer = renderer as UniversalRenderer;
if (universalRenderer == null) return;
bool isForward = (int)universalRenderer.renderingModeActual == 0 || (int)universalRenderer.renderingModeActual == 2;
if (isForward && renderingPath == RenderingPath.Deferred) {
renderingPath = RenderingPath.Forward;
} else if (!isForward && renderingPath == RenderingPath.Forward) {
renderingPath = RenderingPath.Deferred;
}
isRenderingInDeferred = !isForward;
#endif
if (!renderingData.cameraData.postProcessEnabled && !ignorePostProcessingOption) return;
Camera cam = renderingData.cameraData.camera;
if (cam.cameraType == CameraType.Reflection) return;
bool isSceneView = cam.cameraType == CameraType.SceneView;
if (cam.cameraType != CameraType.Game && !isSceneView) return;
#if UNITY_EDITOR
if (isSceneView && !cam.TryGetComponent<UniversalAdditionalCameraData>(out _)) {
cam.gameObject.AddComponent<UniversalAdditionalCameraData>();
}
#endif
CameraRenderType renderType = renderingData.cameraData.renderType;
if (ignoreOverlayCameras && renderType == CameraRenderType.Overlay) return;
if ((camerasLayerMask & (1 << cam.gameObject.layer)) == 0) return;
#if UNITY_EDITOR
if (UnityEditor.ShaderUtil.anythingCompiling) {
needRTRefresh = true;
}
if (needRTRefresh) {
needRTRefresh = false;
radiantPass.Cleanup();
comparePass.Cleanup();
organicLightPass.Cleanup();
}
#endif
RadiantGlobalIllumination radiant = VolumeManager.instance.stack.GetComponent<RadiantGlobalIllumination>();
if (radiant == null) return;
if (organicLightPass.Setup(radiant, renderer, isSceneView)) {
renderer.EnqueuePass(organicLightPass);
}
if (radiantPass.Setup(radiant, renderer, this, isSceneView)) {
if (radiant.transparencySupport.value) {
bool hasLayerMask = radiant.transparentLayerMask.value != 0;
bool hasScriptObjects = transparentSupport.Count > 0;
if (hasLayerMask || hasScriptObjects) {
transparentDepthPass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox + 1;
transparentDepthPass.Setup(radiant.transparentLayerMask.value);
renderer.EnqueuePass(transparentDepthPass);
}
}
renderer.EnqueuePass(radiantPass);
if (!isSceneView && comparePass.Setup(renderer, this)) {
renderer.EnqueuePass(comparePass);
}
}
}
public static void RegisterVirtualEmitter (RadiantVirtualEmitter emitter) {
if (emitter == null) return;
if (!emitters.Contains(emitter)) {
emitters.Add(emitter);
emittersForceRefresh = true;
}
}
public static void UnregisterVirtualEmitter (RadiantVirtualEmitter emitter) {
if (emitter == null) return;
if (emitters.Contains(emitter)) {
emitters.Remove(emitter);
emittersForceRefresh = true;
}
}
public static void RegisterTransparentSupport (RadiantTransparentSupport o) {
if (o == null) return;
if (!transparentSupport.Contains(o)) {
transparentSupport.Add(o);
}
}
public static void UnregisterTransparentSupport (RadiantTransparentSupport o) {
if (o == null) return;
if (transparentSupport.Contains(o)) {
transparentSupport.Remove(o);
}
}
}
}