467 lines
17 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|