Files
2026-03-04 10:03:45 +08:00

467 lines
17 KiB
C#

using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace Artngame.SKYMASTER.SSRT
{
[RequireComponent(typeof(Camera))]
public class SSRT : MonoBehaviour
{
public enum DebugMode
{
AO = 4,
BentNormal = 5,
GI = 6,
Combined = 7
}
public enum FallbackMethod
{
Off = 0,
StaticIrradianceCubemap = 1,
DynamicCubemap = 2
}
public enum ResolutionDownscale
{
Full = 1,
Half = 2,
Quarter = 4,
Eight = 8
}
public enum RenderPass
{
SSRT = 0,
Upsample = 1,
SampleReuse = 2,
TemporalReproj = 3,
DebugModeAO = 4,
DebugModeBentNormal = 5,
DebugModeGI = 6,
DebugModeCombined = 7,
GetDepth = 8,
GetNormal = 9,
GetLightmask = 10,
CopyLightmask = 11
}
public readonly string version = "1.0.0";
[Header("Sampling")]
[Tooltip("Number of directionnal rotations applied during sampling.")]
[Range(1f, 4f)]
public int rotationCount = 4;
[Tooltip("Number of samples taken along one edge of the current conic slice.")]
[Range(1f, 16f)]
public int stepCount = 8;
[Tooltip("Effective sampling radius in world space. AO and GI can only have influence within that radius.")]
[Range(1f, 25f)]
public float radius = 3.5f;
[Tooltip("Controls samples distribution. Exp Start is an initial multiplier on the step size, and Exp Factor is an exponent applied at each step. By using a start value < 1, and an exponent > 1, it's possible to get exponential step size.")]
[Range(0.1f, 1f)]
public float expStart = 1f;
[Tooltip("Controls samples distribution. Exp Start is an initial multiplier on the step size, and Exp Factor is an exponent applied at each step. By using a start value < 1, and an exponent > 1, it's possible to get exponential step size.")]
[Range(1f, 2f)]
public float expFactor = 1f;
[Tooltip("Applies some noise on sample positions to hide the banding artifacts that can occur when there is undersampling.")]
public bool jitterSamples = true;
[Header("GI")]
[Tooltip("Intensity of the indirect diffuse light.")]
[Range(0f, 75f)]
public float GIBoost = 20f;
[Tooltip("Using an HDR light buffer gives more accurate lighting but have an impact on performances.")]
public bool lightBufferHDR;
[Tooltip("Using lower resolution light buffer can help performances but can accentuate aliasing.")]
public ResolutionDownscale lightBufferResolution = ResolutionDownscale.Half;
[Tooltip("Bypass the dot(lightNormal, lightDirection) weighting.")]
[Range(0f, 1f)]
public float LnDlOffset;
[Tooltip("Bypass the dot(normal, lightDirection) weighting.")]
[Range(0f, 1f)]
public float nDlOffset;
[Header("Occlusion")]
[Tooltip("Power function applied to AO to make it appear darker/lighter.")]
[Range(1f, 8f)]
public float power = 1.5f;
[Tooltip("Constant thickness value of objects on the screen in world space. Is used to ignore occlusion past that thickness level, as if light can travel behind the object.")]
[Range(0.1f, 10f)]
public float thickness = 10f;
[Tooltip("Occlusion falloff relative to distance.")]
[Range(1f, 50f)]
public float falloff = 1f;
[Tooltip("Multi-Bounce analytic approximation from GTAO.")]
public bool multiBounceAO;
[Tooltip("Composite AO also on direct lighting.")]
public bool directLightingAO;
[Header("Offscreen Fallback")]
[Tooltip("Ambient lighting to use. Off uses the Unity ambient lighting, but it's possible to use instead a static irradiance cubemap (pre-convolved), or render a cubemap around camera every frame (expensive).")]
public FallbackMethod fallbackMethod;
[Tooltip("Static irradiance cubemap to use if it's the chosen fallback.")]
public Cubemap cubemapFallback;
[Header("Filters")]
[Tooltip("The resolution at which SSRT is computed. If lower than fullscreen the effect will be upscaled to fullscreen afterwards. Lower resolution can help performances but can also introduce more flickering/aliasing.")]
public ResolutionDownscale resolutionDownscale = ResolutionDownscale.Half;
[Tooltip("Number of neighbor pixel to reuse (helps reduce noise).")]
[Range(1f, 8f)]
public int reuseCount = 5;
[Tooltip("Enable/Disable temporal reprojection")]
public bool temporalEnabled = true;
[Tooltip("Controls the speed of the accumulation, slower accumulation is more effective at removing noise but can introduce ghosting.")]
[Range(0f, 1f)]
public float temporalResponse = 0.35f;
[Header("Debug Mode")]
[Tooltip("View of the different SSRT buffers for debug purposes.")]
public DebugMode debugMode = DebugMode.Combined;
[Tooltip("If enabled will show only the radiance that affects the surface, if unchecked radiance will be multiplied by surface albedo.")]
public bool lightOnly;
private Camera cam;
private Camera cubemapCamera;
private Material ssrtMaterial;
private CommandBuffer ssrtBuffer;
private CommandBuffer storeAmbientBuffer;
private CommandBuffer clearBuffer;
private Mesh mesh;
private Matrix4x4 lastFrameViewProjectionMatrix;
private Matrix4x4 viewProjectionMatrix;
private Matrix4x4 lastFrameInverseViewProjectionMatrix;
private Vector2 cameraSize;
private Vector2 renderResolution;
private RenderTexture ambientTexture;
private RenderTexture previousFrameTexture;
private RenderTexture previousDepthTexture;
private RenderTexture[] ssrtMrt = new RenderTexture[2];
private static readonly float[] temporalRotations = new float[6] { 60f, 300f, 180f, 240f, 120f, 0f };
private static readonly float[] spatialOffsets = new float[4] { 0f, 0.5f, 0.25f, 0.75f };
private void GenerateCommandBuffers()
{
ssrtBuffer.Clear();
storeAmbientBuffer.Clear();
clearBuffer.Clear();
clearBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget);
clearBuffer.ClearRenderTarget(clearDepth: false, clearColor: true, Color.black);
storeAmbientBuffer.Blit(BuiltinRenderTextureType.CameraTarget, ambientTexture);
storeAmbientBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget);
storeAmbientBuffer.ClearRenderTarget(clearDepth: false, clearColor: true, Color.black);
int num = Shader.PropertyToID("_CameraTexture");
ssrtBuffer.GetTemporaryRT(num, (int)renderResolution.x, (int)renderResolution.y, 0, FilterMode.Point, RenderTextureFormat.DefaultHDR);
ssrtBuffer.Blit(BuiltinRenderTextureType.CameraTarget, num);
int num2 = Shader.PropertyToID("_LightmaskTexture");
ssrtBuffer.GetTemporaryRT(num2, (int)renderResolution.x / (int)lightBufferResolution, (int)renderResolution.y / (int)lightBufferResolution, 0, FilterMode.Point, lightBufferHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.ARGB32);
ssrtBuffer.SetRenderTarget(num2);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 10);
RenderTargetIdentifier[] colors = new RenderTargetIdentifier[2]
{
ssrtMrt[0],
ssrtMrt[1]
};
ssrtBuffer.SetRenderTarget(colors, ssrtMrt[1]);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 0);
int num3 = Shader.PropertyToID("_FilterTexture1");
ssrtBuffer.GetTemporaryRT(num3, (int)renderResolution.x, (int)renderResolution.y, 0, FilterMode.Point, RenderTextureFormat.DefaultHDR);
int num4 = Shader.PropertyToID("_FilterTexture2");
ssrtBuffer.GetTemporaryRT(num4, (int)renderResolution.x, (int)renderResolution.y, 0, FilterMode.Point, RenderTextureFormat.DefaultHDR);
if (resolutionDownscale != ResolutionDownscale.Full)
{
int num5 = Shader.PropertyToID("_CurrentDepth");
ssrtBuffer.GetTemporaryRT(num5, (int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, FilterMode.Point, RenderTextureFormat.RFloat, RenderTextureReadWrite.Linear);
int num6 = Shader.PropertyToID("_CurrentNormal");
ssrtBuffer.GetTemporaryRT(num6, (int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, FilterMode.Point, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
ssrtBuffer.SetRenderTarget(num5);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 8);
ssrtBuffer.SetRenderTarget(num6);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 9);
ssrtBuffer.SetRenderTarget(num3);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 1);
}
else
{
ssrtBuffer.Blit(ssrtMrt[1], num3);
}
if (reuseCount > 1)
{
ssrtBuffer.SetRenderTarget(num4);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 2);
ssrtBuffer.CopyTexture(num4, num3);
}
if (temporalEnabled)
{
ssrtBuffer.SetRenderTarget(num4);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 3);
ssrtBuffer.Blit(num4, previousFrameTexture);
ssrtBuffer.SetRenderTarget(previousDepthTexture);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 8);
}
else
{
ssrtBuffer.Blit(num3, num4);
}
ssrtBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget);
ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, (int)debugMode);
lastFrameViewProjectionMatrix = viewProjectionMatrix;
lastFrameInverseViewProjectionMatrix = viewProjectionMatrix.inverse;
}
private void UpdateVariables()
{
Matrix4x4 worldToCameraMatrix = cam.worldToCameraMatrix;
ssrtMaterial.SetMatrix("_CameraToWorldMatrix", worldToCameraMatrix.inverse);
Matrix4x4 gPUProjectionMatrix = GL.GetGPUProjectionMatrix(cam.projectionMatrix, renderIntoTexture: false);
ssrtMaterial.SetMatrix("_InverseProjectionMatrix", gPUProjectionMatrix.inverse);
viewProjectionMatrix = gPUProjectionMatrix * worldToCameraMatrix;
ssrtMaterial.SetMatrix("_InverseViewProjectionMatrix", viewProjectionMatrix.inverse);
ssrtMaterial.SetMatrix("_LastFrameViewProjectionMatrix", lastFrameViewProjectionMatrix);
ssrtMaterial.SetMatrix("_LastFrameInverseViewProjectionMatrix", lastFrameInverseViewProjectionMatrix);
ssrtMaterial.SetInt("_RotationCount", rotationCount);
ssrtMaterial.SetInt("_StepCount", stepCount);
ssrtMaterial.SetFloat("_GIBoost", GIBoost);
ssrtMaterial.SetFloat("_LnDlOffset", LnDlOffset);
ssrtMaterial.SetFloat("_NDlOffset", nDlOffset);
ssrtMaterial.SetFloat("_Radius", radius);
ssrtMaterial.SetFloat("_ExpStart", expStart);
ssrtMaterial.SetFloat("_ExpFactor", expFactor);
ssrtMaterial.SetFloat("_Thickness", thickness);
ssrtMaterial.SetFloat("_Falloff", falloff);
ssrtMaterial.SetFloat("_Power", power);
ssrtMaterial.SetFloat("_TemporalResponse", temporalResponse);
ssrtMaterial.SetInt("_MultiBounceAO", multiBounceAO ? 1 : 0);
ssrtMaterial.SetFloat("_DirectLightingAO", directLightingAO ? 1 : 0);
ssrtMaterial.SetTexture("_CubemapFallback", cubemapFallback);
ssrtMaterial.SetInt("_FallbackMethod", (int)fallbackMethod);
ssrtMaterial.SetInt("_LightOnly", lightOnly ? 1 : 0);
ssrtMaterial.SetInt("_ReuseCount", reuseCount);
ssrtMaterial.SetInt("_JitterSamples", jitterSamples ? 1 : 0);
float value = renderResolution.y / (Mathf.Tan(cam.fieldOfView * (MathF.PI / 180f) * 0.5f) * 2f) * 0.5f;
ssrtMaterial.SetFloat("_HalfProjScale", value);
ssrtMaterial.SetInt("_ResolutionDownscale", (int)resolutionDownscale);
float num = temporalRotations[Time.frameCount % 6];
float value2 = spatialOffsets[Time.frameCount % ((resolutionDownscale == ResolutionDownscale.Full) ? 4 : 2)];
ssrtMaterial.SetFloat("_TemporalDirections", num / 360f);
ssrtMaterial.SetFloat("_TemporalOffsets", value2);
if (cameraSize != renderResolution / (float)resolutionDownscale)
{
cameraSize = renderResolution / (float)resolutionDownscale;
if (ssrtMrt[0] != null)
{
ssrtMrt[0].Release();
}
ssrtMrt[0] = new RenderTexture((int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, RenderTextureFormat.ARGBHalf);
ssrtMrt[0].filterMode = FilterMode.Point;
ssrtMrt[0].Create();
if (ssrtMrt[1] != null)
{
ssrtMrt[1].Release();
}
ssrtMrt[1] = new RenderTexture((int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, RenderTextureFormat.DefaultHDR);
ssrtMrt[1].filterMode = FilterMode.Point;
ssrtMrt[1].Create();
if (ambientTexture != null)
{
ambientTexture.Release();
}
ambientTexture = new RenderTexture((int)renderResolution.x, (int)renderResolution.y, 0, RenderTextureFormat.DefaultHDR);
if (previousFrameTexture != null)
{
previousFrameTexture.Release();
}
previousFrameTexture = new RenderTexture((int)renderResolution.x, (int)renderResolution.y, 0, RenderTextureFormat.DefaultHDR);
previousFrameTexture.filterMode = FilterMode.Point;
previousFrameTexture.Create();
if (previousDepthTexture != null)
{
previousDepthTexture.Release();
}
previousDepthTexture = new RenderTexture((int)renderResolution.x, (int)renderResolution.y, 0, RenderTextureFormat.RFloat);
previousDepthTexture.filterMode = FilterMode.Point;
previousDepthTexture.Create();
}
ssrtMaterial.SetTexture("_BentNormalTexture", ssrtMrt[0]);
ssrtMaterial.SetTexture("_GIOcclusionTexture", ssrtMrt[1]);
ssrtMaterial.SetTexture("_AmbientTexture", ambientTexture);
ssrtMaterial.SetTexture("_PreviousColor", previousFrameTexture);
ssrtMaterial.SetTexture("_PreviousDepth", previousDepthTexture);
}
private void RenderCubemap()
{
if (cubemapCamera == null)
{
GameObject gameObject = new GameObject("CubemapCamera", typeof(Camera));
gameObject.transform.SetParent(cam.transform);
cubemapCamera = gameObject.GetComponent<Camera>();
cubemapCamera.CopyFrom(cam);
cubemapCamera.enabled = false;
cubemapCamera.renderingPath = RenderingPath.Forward;
}
if ((bool)cubemapFallback)
{
cubemapCamera.RenderToCubemap(cubemapFallback, 1 << Time.frameCount % 6);
}
}
private void Awake()
{
if ((renderResolution.x % 2f == 1f || renderResolution.y % 2f == 1f) && resolutionDownscale != ResolutionDownscale.Full)
{
Debug.LogWarning("SSRT: Using uneven camera resolution (" + renderResolution.x + ", " + renderResolution.y + ") with downscaling can introduce artifacts! Use a fixed resolution instead of free aspect.");
}
cam = base.gameObject.GetComponent<Camera>();
cam.depthTextureMode |= DepthTextureMode.Depth | DepthTextureMode.MotionVectors;
ssrtMaterial = new Material(Shader.Find("Hidden/SSRT"));
mesh = new Mesh();
mesh.vertices = new Vector3[4]
{
new Vector3(-1f, -1f, 1f),
new Vector3(-1f, 1f, 1f),
new Vector3(1f, 1f, 1f),
new Vector3(1f, -1f, 1f)
};
mesh.uv = new Vector2[4]
{
new Vector2(0f, 1f),
new Vector2(0f, 0f),
new Vector2(1f, 0f),
new Vector2(1f, 1f)
};
mesh.SetIndices(new int[4] { 0, 1, 2, 3 }, MeshTopology.Quads, 0);
if (fallbackMethod == FallbackMethod.DynamicCubemap)
{
cubemapFallback = new Cubemap(32, TextureFormat.RGB24, mipChain: true);
cubemapFallback.Apply(updateMipmaps: true);
}
}
private void OnPreRender()
{
renderResolution = new Vector2(cam.pixelWidth, cam.pixelHeight);
if (ssrtBuffer != null)
{
if (fallbackMethod == FallbackMethod.DynamicCubemap && Application.isPlaying)
{
RenderCubemap();
}
UpdateVariables();
GenerateCommandBuffers();
}
}
private void OnEnable()
{
ssrtBuffer = new CommandBuffer();
ssrtBuffer.name = "SSRT";
storeAmbientBuffer = new CommandBuffer();
storeAmbientBuffer.name = "StoreAmbient";
clearBuffer = new CommandBuffer();
clearBuffer.name = "ClearBuffer";
cam.AddCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, ssrtBuffer);
cam.AddCommandBuffer(CameraEvent.BeforeLighting, storeAmbientBuffer);
cam.AddCommandBuffer(CameraEvent.BeforeGBuffer, clearBuffer);
}
private void OnDisable()
{
if (ssrtBuffer != null)
{
cam.RemoveCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, ssrtBuffer);
ssrtBuffer = null;
}
if (storeAmbientBuffer != null)
{
cam.RemoveCommandBuffer(CameraEvent.BeforeLighting, storeAmbientBuffer);
storeAmbientBuffer = null;
}
if (clearBuffer != null)
{
cam.RemoveCommandBuffer(CameraEvent.BeforeGBuffer, clearBuffer);
clearBuffer = null;
}
}
private void OnDestroy()
{
if (ssrtMrt[0] != null)
{
ssrtMrt[0].Release();
ssrtMrt[0] = null;
}
if (ssrtMrt[1] != null)
{
ssrtMrt[1].Release();
ssrtMrt[1] = null;
}
if (ambientTexture != null)
{
ambientTexture.Release();
ambientTexture = null;
}
if (previousFrameTexture != null)
{
previousFrameTexture.Release();
previousFrameTexture = null;
}
if (previousDepthTexture != null)
{
previousDepthTexture.Release();
previousDepthTexture = null;
}
if (ssrtBuffer != null)
{
ssrtBuffer.Dispose();
ssrtBuffer = null;
}
if (storeAmbientBuffer != null)
{
storeAmbientBuffer.Dispose();
storeAmbientBuffer = null;
}
if (clearBuffer != null)
{
clearBuffer.Dispose();
clearBuffer = null;
}
}
}
}