Shader "Hidden/Post FX/Eye Adaptation" { Properties { _MainTex("Texture", 2D) = "white" {} } CGINCLUDE #pragma target 4.5 #pragma multi_compile __ AUTO_KEY_VALUE #include "UnityCG.cginc" #include "Common.cginc" #include "EyeAdaptation.cginc" // Eye adaptation pass float4 _Params; // x: lowPercent, y: highPercent, z: minBrightness, w: maxBrightness float2 _Speed; // x: down, y: up float4 _ScaleOffsetRes; // x: scale, y: offset, w: histogram pass width, h: histogram pass height float _ExposureCompensation; StructuredBuffer _Histogram; float GetBinValue(uint index, float maxHistogramValue) { return float(_Histogram[index]) * maxHistogramValue; } // Done in the vertex shader float FindMaxHistogramValue() { uint maxValue = 0u; for (uint i = 0; i < HISTOGRAM_BINS; i++) { uint h = _Histogram[i]; maxValue = max(maxValue, h); } return float(maxValue); } void FilterLuminance(uint i, float maxHistogramValue, inout float4 filter) { float binValue = GetBinValue(i, maxHistogramValue); // Filter dark areas float offset = min(filter.z, binValue); binValue -= offset; filter.zw -= offset.xx; // Filter highlights binValue = min(filter.w, binValue); filter.w -= binValue; // Luminance at the bin float luminance = GetLuminanceFromHistogramBin(float(i) / float(HISTOGRAM_BINS), _ScaleOffsetRes.xy); filter.xy += float2(luminance * binValue, binValue); } float GetAverageLuminance(float maxHistogramValue) { // Sum of all bins uint i; float totalSum = 0.0; UNITY_LOOP for (i = 0; i < HISTOGRAM_BINS; i++) totalSum += GetBinValue(i, maxHistogramValue); // Skip darker and lighter parts of the histogram to stabilize the auto exposure // x: filtered sum // y: accumulator // zw: fractions float4 filter = float4(0.0, 0.0, totalSum * _Params.xy); UNITY_LOOP for (i = 0; i < HISTOGRAM_BINS; i++) FilterLuminance(i, maxHistogramValue, filter); // Clamp to user brightness range return clamp(filter.x / max(filter.y, EPSILON), _Params.z, _Params.w); } float GetExposureMultiplier(float avgLuminance) { avgLuminance = max(EPSILON, avgLuminance); #if AUTO_KEY_VALUE half keyValue = 1.03 - (2.0 / (2.0 + log2(avgLuminance + 1.0))); #else half keyValue = _ExposureCompensation; #endif half exposure = keyValue / avgLuminance; return exposure; } float InterpolateExposure(float newExposure, float oldExposure) { float delta = newExposure - oldExposure; float speed = delta > 0.0 ? _Speed.x : _Speed.y; float exposure = oldExposure + delta * (1.0 - exp2(-unity_DeltaTime.x * speed)); //float exposure = oldExposure + delta * (unity_DeltaTime.x * speed); return exposure; } float4 FragAdaptProgressive(VaryingsDefault i) : SV_Target { float maxValue = 1.0 / FindMaxHistogramValue(); float avgLuminance = GetAverageLuminance(maxValue); float exposure = GetExposureMultiplier(avgLuminance); float prevExposure = tex2D(_MainTex, (0.5).xx); exposure = InterpolateExposure(exposure, prevExposure); return exposure.xxxx; } float4 FragAdaptFixed(VaryingsDefault i) : SV_Target { float maxValue = 1.0 / FindMaxHistogramValue(); float avgLuminance = GetAverageLuminance(maxValue); float exposure = GetExposureMultiplier(avgLuminance); return exposure.xxxx; } // ---- Editor stuff int _DebugWidth; struct VaryingsEditorHisto { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float maxValue : TEXCOORD1; float avgLuminance : TEXCOORD2; }; VaryingsEditorHisto VertEditorHisto(AttributesDefault v) { VaryingsEditorHisto o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy; o.maxValue = 1.0 / FindMaxHistogramValue(); o.avgLuminance = GetAverageLuminance(o.maxValue); return o; } float4 FragEditorHisto(VaryingsEditorHisto i) : SV_Target { const float3 kRangeColor = float3(0.05, 0.4, 0.6); const float3 kAvgColor = float3(0.8, 0.3, 0.05); float4 color = float4(0.0, 0.0, 0.0, 0.7); uint ix = (uint)(round(i.uv.x * HISTOGRAM_BINS)); float bin = saturate(float(_Histogram[ix]) * i.maxValue); float fill = step(i.uv.y, bin); // Min / max brightness markers float luminanceMin = GetHistogramBinFromLuminance(_Params.z, _ScaleOffsetRes.xy); float luminanceMax = GetHistogramBinFromLuminance(_Params.w, _ScaleOffsetRes.xy); color.rgb += fill.rrr; if (i.uv.x > luminanceMin && i.uv.x < luminanceMax) { color.rgb = fill.rrr * kRangeColor; color.rgb += kRangeColor; } // Current average luminance marker float luminanceAvg = GetHistogramBinFromLuminance(i.avgLuminance, _ScaleOffsetRes.xy); float avgPx = luminanceAvg * _DebugWidth; if (abs(i.pos.x - avgPx) < 2) color.rgb = kAvgColor; return color; } ENDCG SubShader { Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex VertDefault #pragma fragment FragAdaptProgressive ENDCG } Pass { CGPROGRAM #pragma vertex VertDefault #pragma fragment FragAdaptFixed ENDCG } Pass { CGPROGRAM #pragma vertex VertEditorHisto #pragma fragment FragEditorHisto ENDCG } } }