您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
257 行
11 KiB
257 行
11 KiB
using System;
|
|
using UnityEngine.Rendering;
|
|
|
|
// TEMPORARY, minimalist post-processing stack until the fully-featured framework is ready
|
|
|
|
namespace UnityEngine.Experimental.Rendering.HDPipeline
|
|
{
|
|
using GradingType = PostProcessing.ColorGradingSettings.GradingType;
|
|
using EyeAdaptationType = PostProcessing.EyeAdaptationSettings.EyeAdaptationType;
|
|
|
|
[ExecuteInEditMode]
|
|
public sealed class PostProcessing : MonoBehaviour
|
|
{
|
|
[Serializable]
|
|
public sealed class ColorGradingSettings
|
|
{
|
|
public enum GradingType
|
|
{
|
|
None,
|
|
Neutral,
|
|
Custom
|
|
}
|
|
|
|
public GradingType type = GradingType.None;
|
|
public float exposure = 0f;
|
|
|
|
public Texture logLut = null;
|
|
|
|
[Range(-0.10f, 0.1f)] public float neutralBlackIn = 0.02f;
|
|
[Range( 1.00f, 20.0f)] public float neutralWhiteIn = 10f;
|
|
[Range(-0.09f, 0.1f)] public float neutralBlackOut = 0f;
|
|
[Range( 1.00f, 19.0f)] public float neutralWhiteOut = 10f;
|
|
[Range( 0.10f, 20.0f)] public float neutralWhiteLevel = 5.3f;
|
|
[Range( 1.00f, 10.0f)] public float neutralWhiteClip = 10f;
|
|
}
|
|
|
|
[Serializable]
|
|
public sealed class EyeAdaptationSettings
|
|
{
|
|
public enum EyeAdaptationType
|
|
{
|
|
Progressive,
|
|
Fixed
|
|
}
|
|
|
|
public bool enabled = false;
|
|
public bool showDebugHistogramInGameView = true;
|
|
|
|
[Range(1f, 99f)] public float lowPercent = 65f;
|
|
[Range(1f, 99f)] public float highPercent = 95f;
|
|
|
|
public float minLuminance = 0.03f;
|
|
public float maxLuminance = 2f;
|
|
public float exposureCompensation = 0.5f;
|
|
|
|
public EyeAdaptationType adaptationType = EyeAdaptationType.Progressive;
|
|
|
|
public float speedUp = 2f;
|
|
public float speedDown = 1f;
|
|
|
|
[Range(-16, -1)] public int logMin = -8;
|
|
[Range( 1, 16)] public int logMax = 4;
|
|
}
|
|
|
|
public EyeAdaptationSettings eyeAdaptation = new EyeAdaptationSettings();
|
|
public ColorGradingSettings colorGrading = new ColorGradingSettings();
|
|
|
|
Material m_EyeAdaptationMaterial;
|
|
Material m_FinalPassMaterial;
|
|
|
|
ComputeShader m_EyeCompute;
|
|
ComputeBuffer m_HistogramBuffer;
|
|
|
|
readonly RenderTexture[] m_AutoExposurePool = new RenderTexture[2];
|
|
int m_AutoExposurePingPing;
|
|
RenderTexture m_CurrentAutoExposure;
|
|
RenderTexture m_DebugHistogram = null;
|
|
|
|
static uint[] s_EmptyHistogramBuffer = new uint[k_HistogramBins];
|
|
|
|
bool m_FirstFrame = true;
|
|
|
|
// Don't forget to update 'EyeAdaptation.cginc' if you change these values !
|
|
const int k_HistogramBins = 64;
|
|
const int k_HistogramThreadX = 16;
|
|
const int k_HistogramThreadY = 16;
|
|
|
|
void OnEnable()
|
|
{
|
|
m_FinalPassMaterial = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/FinalPass");
|
|
m_EyeAdaptationMaterial = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/EyeAdaptation");
|
|
|
|
m_EyeCompute = Resources.Load<ComputeShader>("EyeHistogram");
|
|
m_HistogramBuffer = new ComputeBuffer(k_HistogramBins, sizeof(uint));
|
|
|
|
m_AutoExposurePool[0] = new RenderTexture(1, 1, 0, RenderTextureFormat.RFloat);
|
|
m_AutoExposurePool[1] = new RenderTexture(1, 1, 0, RenderTextureFormat.RFloat);
|
|
|
|
m_FirstFrame = true;
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
Utilities.Destroy(m_EyeAdaptationMaterial);
|
|
Utilities.Destroy(m_FinalPassMaterial);
|
|
|
|
foreach (var rt in m_AutoExposurePool)
|
|
Utilities.Destroy(rt);
|
|
|
|
Utilities.Destroy(m_DebugHistogram);
|
|
Utilities.SafeRelease(m_HistogramBuffer);
|
|
}
|
|
|
|
public void Render(Camera camera, ScriptableRenderContext context, RenderTargetIdentifier source, RenderTargetIdentifier destination)
|
|
{
|
|
m_FinalPassMaterial.shaderKeywords = null;
|
|
|
|
var cmd = new CommandBuffer { name = "Final Pass" };
|
|
|
|
if (eyeAdaptation.enabled)
|
|
{
|
|
int tempRt = Shader.PropertyToID("_Source");
|
|
|
|
// Downscale the framebuffer, we don't need an absolute precision for auto exposure
|
|
// and it helps making it more stable - should be using a previously downscaled pass
|
|
var scaleOffsetRes = GetHistogramScaleOffsetRes(camera);
|
|
|
|
cmd.GetTemporaryRT(tempRt, (int)scaleOffsetRes.z, (int)scaleOffsetRes.w, 0, FilterMode.Bilinear, RenderTextureFormat.ARGBHalf);
|
|
cmd.Blit(source, tempRt);
|
|
|
|
// Clears the buffer on every frame as we use it to accumulate luminance values on each frame
|
|
m_HistogramBuffer.SetData(s_EmptyHistogramBuffer);
|
|
|
|
// Gets a log histogram
|
|
int kernel = m_EyeCompute.FindKernel("KEyeHistogram");
|
|
|
|
cmd.SetComputeBufferParam(m_EyeCompute, kernel, "_Histogram", m_HistogramBuffer);
|
|
cmd.SetComputeTextureParam(m_EyeCompute, kernel, "_Source", tempRt);
|
|
cmd.SetComputeVectorParam(m_EyeCompute, "_ScaleOffsetRes", scaleOffsetRes);
|
|
cmd.DispatchCompute(m_EyeCompute, kernel, Mathf.CeilToInt(scaleOffsetRes.z / (float)k_HistogramThreadX), Mathf.CeilToInt(scaleOffsetRes.w / (float)k_HistogramThreadY), 1);
|
|
|
|
// Cleanup
|
|
cmd.ReleaseTemporaryRT(tempRt);
|
|
|
|
// Make sure filtering values are correct to avoid apocalyptic consequences
|
|
const float kMinDelta = 1e-2f;
|
|
eyeAdaptation.highPercent = Mathf.Clamp(eyeAdaptation.highPercent, 1f + kMinDelta, 99f);
|
|
eyeAdaptation.lowPercent = Mathf.Clamp(eyeAdaptation.lowPercent, 1f, eyeAdaptation.highPercent - kMinDelta);
|
|
|
|
// Compute auto exposure
|
|
m_EyeAdaptationMaterial.SetBuffer("_Histogram", m_HistogramBuffer);
|
|
m_EyeAdaptationMaterial.SetVector("_Params", new Vector4(eyeAdaptation.lowPercent * 0.01f, eyeAdaptation.highPercent * 0.01f, eyeAdaptation.minLuminance, eyeAdaptation.maxLuminance));
|
|
m_EyeAdaptationMaterial.SetVector("_Speed", new Vector2(eyeAdaptation.speedDown, eyeAdaptation.speedUp));
|
|
m_EyeAdaptationMaterial.SetVector("_ScaleOffsetRes", scaleOffsetRes);
|
|
m_EyeAdaptationMaterial.SetFloat("_ExposureCompensation", eyeAdaptation.exposureCompensation);
|
|
|
|
if (m_FirstFrame || !Application.isPlaying)
|
|
{
|
|
// We don't want eye adaptation when not in play mode because the GameView isn't
|
|
// animated, thus making it harder to tweak. Just use the final audo exposure value.
|
|
m_CurrentAutoExposure = m_AutoExposurePool[0];
|
|
cmd.Blit(null, m_CurrentAutoExposure, m_EyeAdaptationMaterial, (int)EyeAdaptationType.Fixed);
|
|
|
|
// Copy current exposure to the other pingpong target on first frame to avoid adapting from black
|
|
cmd.Blit(m_AutoExposurePool[0], m_AutoExposurePool[1]);
|
|
} else
|
|
{
|
|
int pp = m_AutoExposurePingPing;
|
|
var src = m_AutoExposurePool[++pp % 2];
|
|
var dst = m_AutoExposurePool[++pp % 2];
|
|
cmd.Blit(src, dst, m_EyeAdaptationMaterial, (int)eyeAdaptation.adaptationType);
|
|
m_AutoExposurePingPing = ++pp % 2;
|
|
m_CurrentAutoExposure = dst;
|
|
}
|
|
|
|
m_FinalPassMaterial.EnableKeyword("EYE_ADAPTATION");
|
|
m_FinalPassMaterial.SetTexture("_AutoExposure", m_CurrentAutoExposure);
|
|
|
|
// Debug histogram visualization
|
|
if (eyeAdaptation.showDebugHistogramInGameView)
|
|
{
|
|
if (m_DebugHistogram == null || !m_DebugHistogram.IsCreated())
|
|
{
|
|
m_DebugHistogram = new RenderTexture(256, 128, 0, RenderTextureFormat.ARGB32)
|
|
{
|
|
filterMode = FilterMode.Point,
|
|
wrapMode = TextureWrapMode.Clamp
|
|
};
|
|
}
|
|
|
|
m_EyeAdaptationMaterial.SetFloat("_DebugWidth", m_DebugHistogram.width);
|
|
cmd.Blit(null, m_DebugHistogram, m_EyeAdaptationMaterial, 2);
|
|
}
|
|
|
|
m_FirstFrame = false;
|
|
}
|
|
|
|
float ev = Mathf.Exp(colorGrading.exposure * 0.6931471805599453f);
|
|
m_FinalPassMaterial.SetFloat("_Exposure", ev);
|
|
|
|
if (colorGrading.type == GradingType.Neutral)
|
|
{
|
|
const float kScaleFactor = 20f;
|
|
const float kScaleFactorHalf = kScaleFactor * 0.5f;
|
|
|
|
float inBlack = colorGrading.neutralBlackIn * kScaleFactor + 1f;
|
|
float outBlack = colorGrading.neutralBlackOut * kScaleFactorHalf + 1f;
|
|
float inWhite = colorGrading.neutralWhiteIn / kScaleFactor;
|
|
float outWhite = 1f - colorGrading.neutralWhiteOut / kScaleFactor;
|
|
float blackRatio = inBlack / outBlack;
|
|
float whiteRatio = inWhite / outWhite;
|
|
|
|
const float a = 0.2f;
|
|
float b = Mathf.Max(0f, Mathf.LerpUnclamped(0.57f, 0.37f, blackRatio));
|
|
float c = Mathf.LerpUnclamped(0.01f, 0.24f, whiteRatio);
|
|
float d = Mathf.Max(0f, Mathf.LerpUnclamped(0.02f, 0.20f, blackRatio));
|
|
const float e = 0.02f;
|
|
const float f = 0.30f;
|
|
|
|
m_FinalPassMaterial.SetVector("_NeutralTonemapperParams1", new Vector4(a, b, c, d));
|
|
m_FinalPassMaterial.SetVector("_NeutralTonemapperParams2", new Vector4(e, f, colorGrading.neutralWhiteLevel, colorGrading.neutralWhiteClip / kScaleFactorHalf));
|
|
m_FinalPassMaterial.EnableKeyword("NEUTRAL_GRADING");
|
|
}
|
|
else if (colorGrading.type == GradingType.Custom)
|
|
{
|
|
if (colorGrading.logLut != null)
|
|
{
|
|
var lut = colorGrading.logLut;
|
|
m_FinalPassMaterial.SetTexture("_LogLut", lut);
|
|
m_FinalPassMaterial.SetVector("_LogLut_Params", new Vector3(1f / lut.width, 1f / lut.height, lut.height - 1f));
|
|
m_FinalPassMaterial.EnableKeyword("CUSTOM_GRADING");
|
|
}
|
|
}
|
|
|
|
cmd.Blit(source, destination, m_FinalPassMaterial, 0);
|
|
context.ExecuteCommandBuffer(cmd);
|
|
cmd.Dispose();
|
|
}
|
|
|
|
Vector4 GetHistogramScaleOffsetRes(Camera camera)
|
|
{
|
|
float diff = eyeAdaptation.logMax - eyeAdaptation.logMin;
|
|
float scale = 1f / diff;
|
|
float offset = -eyeAdaptation.logMin * scale;
|
|
return new Vector4(scale, offset, Mathf.Floor(camera.pixelWidth / 2f), Mathf.Floor(camera.pixelHeight / 2f));
|
|
}
|
|
|
|
void OnGUI()
|
|
{
|
|
if (!eyeAdaptation.enabled || !eyeAdaptation.showDebugHistogramInGameView || m_DebugHistogram == null || !m_DebugHistogram.IsCreated())
|
|
return;
|
|
|
|
var rect = new Rect(8f, 8f, m_DebugHistogram.width, m_DebugHistogram.height);
|
|
GUI.DrawTexture(rect, m_DebugHistogram);
|
|
}
|
|
}
|
|
}
|