// HDR color pipeline is rendered to a 3D lut; it requires Texture3D & compute shaders // support - Desktop / Consoles / Some high-end mobiles void RenderHDRPipeline(PostProcessRenderContext context) { // Unfortunately because AnimationCurve doesn't implement GetHashCode and we don't have // any reliable way to figure out if a curve data is different from another one we can't // skip regenerating the Lut if nothing has changed. So it has to be done on every // frame... // It's not a very expensive operation anyway (we're talking about filling a 33x33x33 // Lut on the GPU) but every little thing helps, especially on mobile. { CheckInternalLogLut(); // Lut setup var compute = context.resources.computeShaders.lut3DBaker; int kernel = 0; switch (settings.tonemapper.value) { case Tonemapper.None: kernel = compute.FindKernel("KGenLut3D_NoTonemap"); break; case Tonemapper.Neutral: kernel = compute.FindKernel("KGenLut3D_NeutralTonemap"); break; case Tonemapper.ACES: kernel = compute.FindKernel("KGenLut3D_AcesTonemap"); break; case Tonemapper.Custom: kernel = compute.FindKernel("KGenLut3D_CustomTonemap"); break; } int groupSize = Mathf.CeilToInt(k_Lut3DSize / 8f); var cmd = context.command; cmd.SetComputeTextureParam(compute, kernel, "_Output", m_InternalLogLut); cmd.SetComputeVectorParam(compute, "_Size", new Vector4(k_Lut3DSize, 1f / (k_Lut3DSize - 1f), 0f, 0f)); var colorBalance = ColorUtilities.ComputeColorBalance(settings.temperature.value, settings.tint.value); cmd.SetComputeVectorParam(compute, "_ColorBalance", colorBalance); cmd.SetComputeVectorParam(compute, "_ColorFilter", settings.colorFilter.value); float hue = settings.hueShift.value / 360f; // Remap to [-0.5;0.5] float sat = settings.saturation.value / 100f + 1f; // Remap to [0;2] float con = settings.contrast.value / 100f + 1f; // Remap to [0;2] cmd.SetComputeVectorParam(compute, "_HueSatCon", new Vector4(hue, sat, con, 0f)); var channelMixerR = new Vector4(settings.mixerRedOutRedIn, settings.mixerRedOutGreenIn, settings.mixerRedOutBlueIn, 0f); var channelMixerG = new Vector4(settings.mixerGreenOutRedIn, settings.mixerGreenOutGreenIn, settings.mixerGreenOutBlueIn, 0f); var channelMixerB = new Vector4(settings.mixerBlueOutRedIn, settings.mixerBlueOutGreenIn, settings.mixerBlueOutBlueIn, 0f); cmd.SetComputeVectorParam(compute, "_ChannelMixerRed", channelMixerR / 100f); // Remap to [-2;2] cmd.SetComputeVectorParam(compute, "_ChannelMixerGreen", channelMixerG / 100f); cmd.SetComputeVectorParam(compute, "_ChannelMixerBlue", channelMixerB / 100f); var lift = ColorUtilities.ColorToLift(settings.lift.value * 0.2f); var gain = ColorUtilities.ColorToGain(settings.gain.value * 0.8f); var invgamma = ColorUtilities.ColorToInverseGamma(settings.gamma.value * 0.8f); cmd.SetComputeVectorParam(compute, "_Lift", new Vector4(lift.x, lift.y, lift.z, 0f)); cmd.SetComputeVectorParam(compute, "_InvGamma", new Vector4(invgamma.x, invgamma.y, invgamma.z, 0f)); cmd.SetComputeVectorParam(compute, "_Gain", new Vector4(gain.x, gain.y, gain.z, 0f)); cmd.SetComputeTextureParam(compute, kernel, "_Curves", GetCurveTexture(true)); if (settings.tonemapper.value == Tonemapper.Custom) { m_HableCurve.Init( settings.toneCurveToeStrength.value, settings.toneCurveToeLength.value, settings.toneCurveShoulderStrength.value, settings.toneCurveShoulderLength.value, settings.toneCurveShoulderAngle.value, settings.toneCurveGamma.value ); var curve = new Vector4(m_HableCurve.inverseWhitePoint, m_HableCurve.x0, m_HableCurve.x1, 0f); cmd.SetComputeVectorParam(compute, "_CustomToneCurve", curve); var toe = m_HableCurve.segments[0]; var mid = m_HableCurve.segments[1]; var sho = m_HableCurve.segments[2]; var toeSegmentA = new Vector4(toe.offsetX, toe.offsetY, toe.scaleX, toe.scaleY); var toeSegmentB = new Vector4(toe.lnA, toe.B, 0f, 0f); var midSegmentA = new Vector4(mid.offsetX, mid.offsetY, mid.scaleX, mid.scaleY); var midSegmentB = new Vector4(mid.lnA, mid.B, 0f, 0f); var shoSegmentA = new Vector4(sho.offsetX, sho.offsetY, sho.scaleX, sho.scaleY); var shoSegmentB = new Vector4(sho.lnA, sho.B, 0f, 0f); cmd.SetComputeVectorParam(compute, "_ToeSegmentA", toeSegmentA); cmd.SetComputeVectorParam(compute, "_ToeSegmentB", toeSegmentB); cmd.SetComputeVectorParam(compute, "_MidSegmentA", midSegmentA); cmd.SetComputeVectorParam(compute, "_MidSegmentB", midSegmentB); cmd.SetComputeVectorParam(compute, "_ShoSegmentA", shoSegmentA); cmd.SetComputeVectorParam(compute, "_ShoSegmentB", shoSegmentB); } // Generate the lut context.command.BeginSample("HdrColorGradingLut"); cmd.DispatchCompute(compute, kernel, groupSize, groupSize, groupSize); context.command.EndSample("HdrColorGradingLut"); } var lut = m_InternalLogLut; var uberSheet = context.uberSheet; uberSheet.EnableKeyword("COLOR_GRADING_HDR"); uberSheet.properties.SetTexture(Uniforms._Lut3D, lut); uberSheet.properties.SetVector(Uniforms._Lut3D_Params, new Vector2(1f / lut.width, lut.width - 1f)); uberSheet.properties.SetFloat(Uniforms._PostExposure, RuntimeUtilities.Exp2(settings.postExposure.value)); context.logLut = lut; }
internal void BeginFrame(PostProcessRenderContext context) { m_Command = context.command; m_PropertySheets = context.propertySheets; m_Resources = context.resources; }
public override void Render(PostProcessRenderContext context) { var cmd = context.command; cmd.BeginSample("BloomPyramid"); var sheet = context.propertySheets.Get(context.resources.shaders.bloom); // Apply auto exposure adjustment in the prefiltering pass sheet.properties.SetTexture(Uniforms._AutoExposureTex, context.autoExposureTexture); // Determine the iteration count float logh = Mathf.Log(context.height, 2f) + settings.diffusion.value - 10f; int logh_i = Mathf.FloorToInt(logh); int iterations = Mathf.Clamp(logh_i, 1, k_MaxPyramidSize); float sampleScale = 0.5f + logh - logh_i; sheet.properties.SetFloat(Uniforms._SampleScale, sampleScale); // Do bloom on a half-res buffer, full-res doesn't bring much and kills performances on // fillrate limited platforms int tw = context.width / 2; int th = context.height / 2; // Prefiltering parameters float lthresh = Mathf.GammaToLinearSpace(settings.threshold.value); float knee = lthresh * settings.softKnee.value + 1e-5f; var threshold = new Vector4(lthresh, lthresh - knee, knee * 2f, 0.25f / knee); sheet.properties.SetVector(Uniforms._Threshold, threshold); int qualityOffset = settings.mobileOptimized ? 1 : 0; // Downsample var last = context.source; for (int i = 0; i < iterations; i++) { int mipDown = m_Pyramid[i].down; int mipUp = m_Pyramid[i].up; int pass = i == 0 ? (int)Pass.Prefilter13 + qualityOffset : (int)Pass.Downsample13 + qualityOffset; cmd.GetTemporaryRT(mipDown, tw, th, 0, FilterMode.Bilinear, context.sourceFormat); cmd.GetTemporaryRT(mipUp, tw, th, 0, FilterMode.Bilinear, context.sourceFormat); cmd.BlitFullscreenTriangle(last, mipDown, sheet, pass); last = mipDown; tw /= 2; th /= 2; } // Upsample last = m_Pyramid[iterations - 1].down; for (int i = iterations - 2; i >= 0; i--) { int mipDown = m_Pyramid[i].down; int mipUp = m_Pyramid[i].up; cmd.SetGlobalTexture(Uniforms._BloomTex, mipDown); cmd.BlitFullscreenTriangle(last, mipUp, sheet, (int)Pass.UpsampleTent + qualityOffset); last = mipUp; } var shaderSettings = new Vector4( sampleScale, RuntimeUtilities.Exp2(settings.intensity.value / 10f) - 1f, settings.lensIntensity.value, iterations ); var dirtTexture = settings.lensTexture.value == null ? RuntimeUtilities.blackTexture : settings.lensTexture.value; var uberSheet = context.uberSheet; uberSheet.EnableKeyword("BLOOM"); uberSheet.properties.SetVector(Uniforms._Bloom_Settings, shaderSettings); uberSheet.properties.SetColor(Uniforms._Bloom_Color, settings.color.value.linear); uberSheet.properties.SetTexture(Uniforms._Bloom_DirtTex, dirtTexture); cmd.SetGlobalTexture(Uniforms._BloomTex, m_Pyramid[0].up); // Cleanup for (int i = 0; i < iterations; i++) { cmd.ReleaseTemporaryRT(m_Pyramid[i].down); cmd.ReleaseTemporaryRT(m_Pyramid[i].up); } cmd.EndSample("BloomPyramid"); }
void RenderList(List <PostProcessBundle> list, PostProcessRenderContext context, string marker) { var cmd = context.command; cmd.BeginSample(marker); // First gather active effects - we need this to manage render targets more efficiently m_ActivePool.Clear(); for (int i = 0; i < list.Count; i++) { var effect = list[i]; if (effect.settings.IsEnabledAndSupported()) { if (!m_IsRenderingInSceneView || (m_IsRenderingInSceneView && effect.attribute.allowInSceneView)) { m_ActivePool.Add(effect.renderer); } } } int count = m_ActivePool.Count; // If there's only one active effect, we can simply execute it and skip the rest if (count == 1) { m_ActivePool[0].Render(context); } else { // Else create the target chain m_TargetPool.Clear(); m_TargetPool.Add(context.source); // First target is always source for (int i = 0; i < count - 1; i++) { m_TargetPool.Add(Uniforms._TempTargetPool[i % 2]); } m_TargetPool.Add(context.destination); // Last target is always destination // Render cmd.GetTemporaryRT(Uniforms._TempTargetPool[0], context.width, context.height, 24, FilterMode.Bilinear, context.sourceFormat); if (count > 2) { cmd.GetTemporaryRT(Uniforms._TempTargetPool[1], context.width, context.height, 24, FilterMode.Bilinear, context.sourceFormat); } for (int i = 0; i < count; i++) { context.source = m_TargetPool[i]; context.destination = m_TargetPool[i + 1]; m_ActivePool[i].Render(context); } cmd.ReleaseTemporaryRT(Uniforms._TempTargetPool[0]); if (count > 2) { cmd.ReleaseTemporaryRT(Uniforms._TempTargetPool[1]); } } cmd.EndSample(marker); }
// Renders before-stack, builtin stack and after-stack effects // TODO: Refactor this, it's a mess and it's hard to maintain public void Render(PostProcessRenderContext context) { // Update & override layer settings first (volume blending) if the opaque only pass // hasn't been called this frame. UpdateSettingsIfNeeded(context); SetupContext(context); var finalDestination = context.destination; var cmd = context.command; // Do temporal anti-aliasing first if (context.IsTemporalAntialiasingActive() && !m_IsRenderingInSceneView) { temporalAntialiasing.SetProjectionMatrix(context.camera); cmd.GetTemporaryRT(Uniforms._AATemp, context.width, context.height, 24, FilterMode.Bilinear, context.sourceFormat); context.destination = Uniforms._AATemp; temporalAntialiasing.Render(context); context.source = Uniforms._AATemp; context.destination = Uniforms._TempTargetPool[4]; } bool hasBeforeStack = HasActiveEffects(m_SortedBundles[PostProcessEvent.BeforeStack]); bool hasAfterStack = HasActiveEffects(m_SortedBundles[PostProcessEvent.AfterStack]); // Render builtin stack and user effects if (hasBeforeStack) { cmd.GetTemporaryRT(Uniforms._TempTargetPool[2], context.width, context.height, 24, FilterMode.Bilinear, context.sourceFormat); context.destination = Uniforms._TempTargetPool[2]; RenderList(m_SortedBundles[PostProcessEvent.BeforeStack], context, "BeforeStack"); context.source = Uniforms._TempTargetPool[2]; context.destination = Uniforms._TempTargetPool[4]; } if (hasAfterStack) { cmd.GetTemporaryRT(Uniforms._TempTargetPool[3], context.width, context.height, 24, FilterMode.Bilinear, context.sourceFormat); context.destination = Uniforms._TempTargetPool[3]; RenderBuiltins(context); if (hasBeforeStack) { cmd.ReleaseTemporaryRT(Uniforms._TempTargetPool[2]); } context.source = Uniforms._TempTargetPool[3]; context.destination = Uniforms._TempTargetPool[4]; cmd.GetTemporaryRT(Uniforms._TempTargetPool[4], context.width, context.height, 24, FilterMode.Bilinear, context.sourceFormat); RenderList(m_SortedBundles[PostProcessEvent.AfterStack], context, "AfterStack"); cmd.ReleaseTemporaryRT(Uniforms._TempTargetPool[3]); } else // Should be skippable if nothing's enabled in builtins { context.destination = Uniforms._TempTargetPool[4]; cmd.GetTemporaryRT(Uniforms._TempTargetPool[4], context.width, context.height, 24, FilterMode.Bilinear, context.sourceFormat); RenderBuiltins(context); if (hasBeforeStack) { cmd.ReleaseTemporaryRT(Uniforms._TempTargetPool[2]); } } context.source = Uniforms._TempTargetPool[4]; context.destination = finalDestination; RenderFinalPass(context); cmd.ReleaseTemporaryRT(Uniforms._TempTargetPool[4]); m_SettingsUpdateNeeded = true; }
public override void Render(PostProcessRenderContext context) { var cmd = context.command; if (m_ResetHistory) { cmd.BlitFullscreenTriangle(context.source, context.destination); m_ResetHistory = false; return; } const float kMaxBlurRadius = 5f; var vectorRTFormat = RenderTextureFormat.RGHalf; var packedRTFormat = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.ARGB2101010) ? RenderTextureFormat.ARGB2101010 : RenderTextureFormat.ARGB32; var sheet = context.propertySheets.Get(context.resources.shaders.motionBlur); cmd.BeginSample("MotionBlur"); // Calculate the maximum blur radius in pixels. int maxBlurPixels = (int)(kMaxBlurRadius * context.height / 100); // Calculate the TileMax size. // It should be a multiple of 8 and larger than maxBlur. int tileSize = ((maxBlurPixels - 1) / 8 + 1) * 8; // Pass 1 - Velocity/depth packing var velocityScale = settings.shutterAngle / 360f; sheet.properties.SetFloat(Uniforms._VelocityScale, velocityScale); sheet.properties.SetFloat(Uniforms._MaxBlurRadius, maxBlurPixels); sheet.properties.SetFloat(Uniforms._RcpMaxBlurRadius, 1f / maxBlurPixels); int vbuffer = Uniforms._VelocityTex; cmd.GetTemporaryRT(vbuffer, context.width, context.height, 0, FilterMode.Point, packedRTFormat, RenderTextureReadWrite.Linear); cmd.BlitFullscreenTriangle(BuiltinRenderTextureType.None, vbuffer, sheet, (int)Pass.VelocitySetup); // Pass 2 - First TileMax filter (1/2 downsize) int tile2 = Uniforms._Tile2RT; cmd.GetTemporaryRT(tile2, context.width / 2, context.height / 2, 0, FilterMode.Point, vectorRTFormat, RenderTextureReadWrite.Linear); cmd.BlitFullscreenTriangle(vbuffer, tile2, sheet, (int)Pass.TileMax1); // Pass 3 - Second TileMax filter (1/2 downsize) int tile4 = Uniforms._Tile4RT; cmd.GetTemporaryRT(tile4, context.width / 4, context.height / 4, 0, FilterMode.Point, vectorRTFormat, RenderTextureReadWrite.Linear); cmd.BlitFullscreenTriangle(tile2, tile4, sheet, (int)Pass.TileMax2); cmd.ReleaseTemporaryRT(tile2); // Pass 4 - Third TileMax filter (1/2 downsize) int tile8 = Uniforms._Tile8RT; cmd.GetTemporaryRT(tile8, context.width / 8, context.height / 8, 0, FilterMode.Point, vectorRTFormat, RenderTextureReadWrite.Linear); cmd.BlitFullscreenTriangle(tile4, tile8, sheet, (int)Pass.TileMax2); cmd.ReleaseTemporaryRT(tile4); // Pass 5 - Fourth TileMax filter (reduce to tileSize) var tileMaxOffs = Vector2.one * (tileSize / 8f - 1f) * -0.5f; sheet.properties.SetVector(Uniforms._TileMaxOffs, tileMaxOffs); sheet.properties.SetFloat(Uniforms._TileMaxLoop, (int)(tileSize / 8f)); int tile = Uniforms._TileVRT; cmd.GetTemporaryRT(tile, context.width / tileSize, context.height / tileSize, 0, FilterMode.Point, vectorRTFormat, RenderTextureReadWrite.Linear); cmd.BlitFullscreenTriangle(tile8, tile, sheet, (int)Pass.TileMaxV); cmd.ReleaseTemporaryRT(tile8); // Pass 6 - NeighborMax filter int neighborMax = Uniforms._NeighborMaxTex; int neighborMaxWidth = context.width / tileSize; int neighborMaxHeight = context.height / tileSize; cmd.GetTemporaryRT(neighborMax, neighborMaxWidth, neighborMaxHeight, 0, FilterMode.Point, vectorRTFormat, RenderTextureReadWrite.Linear); cmd.BlitFullscreenTriangle(tile, neighborMax, sheet, (int)Pass.NeighborMax); cmd.ReleaseTemporaryRT(tile); // Pass 7 - Reconstruction pass sheet.properties.SetFloat(Uniforms._LoopCount, Mathf.Clamp(settings.sampleCount / 2, 1, 64)); cmd.BlitFullscreenTriangle(context.source, context.destination, sheet, (int)Pass.Reconstruction); cmd.ReleaseTemporaryRT(vbuffer); cmd.ReleaseTemporaryRT(neighborMax); cmd.EndSample("MotionBlur"); }
public override void Render(PostProcessRenderContext context) { var colorFormat = RenderTextureFormat.ARGBHalf; var cocFormat = SelectFormat(RenderTextureFormat.R8, RenderTextureFormat.RHalf); // Avoid using R8 on OSX with Metal. #896121, https://goo.gl/MgKqu6 #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Metal) { cocFormat = SelectFormat(RenderTextureFormat.RHalf, RenderTextureFormat.Default); } #endif // Material setup var f = settings.focalLength.value / 1000f; var s1 = Mathf.Max(settings.focusDistance.value, f); var aspect = (float)context.width / (float)context.height; var coeff = f * f / (settings.aperture.value * (s1 - f) * k_FilmHeight * 2); var maxCoC = CalculateMaxCoCRadius(context.height); var sheet = context.propertySheets.Get(context.resources.shaders.depthOfField); sheet.properties.Clear(); sheet.properties.SetFloat(Uniforms._Distance, s1); sheet.properties.SetFloat(Uniforms._LensCoeff, coeff); sheet.properties.SetFloat(Uniforms._MaxCoC, maxCoC); sheet.properties.SetFloat(Uniforms._RcpMaxCoC, 1f / maxCoC); sheet.properties.SetFloat(Uniforms._RcpAspect, 1f / aspect); var cmd = context.command; cmd.BeginSample("DepthOfField"); // CoC calculation pass cmd.GetTemporaryRT(Uniforms._CoCTex, context.width, context.height, 0, FilterMode.Bilinear, cocFormat); cmd.BlitFullscreenTriangle(BuiltinRenderTextureType.None, Uniforms._CoCTex, sheet, (int)Pass.CoCCalculation); // CoC temporal filter pass when TAA is enabled if (context.IsTemporalAntialiasingActive()) { float motionBlending = context.temporalAntialiasing.motionBlending; float blend = m_ResetHistory ? 0f : motionBlending; // Handles first frame blending var jitter = context.temporalAntialiasing.jitter; sheet.properties.SetVector(Uniforms._TaaParams, new Vector3(jitter.x, jitter.y, blend)); int pp = m_HistoryPingPong; var historyRead = CheckHistory(++pp % 2, context.width, context.height, cocFormat); var historyWrite = CheckHistory(++pp % 2, context.width, context.height, cocFormat); m_HistoryPingPong = ++pp % 2; cmd.BlitFullscreenTriangle(historyRead, historyWrite, sheet, (int)Pass.CoCTemporalFilter); cmd.ReleaseTemporaryRT(Uniforms._CoCTex); cmd.SetGlobalTexture(Uniforms._CoCTex, historyWrite); } // Downsampling and prefiltering pass cmd.GetTemporaryRT(Uniforms._DepthOfFieldTex, context.width / 2, context.height / 2, 0, FilterMode.Bilinear, colorFormat); cmd.BlitFullscreenTriangle(context.source, Uniforms._DepthOfFieldTex, sheet, (int)Pass.DownsampleAndPrefilter); // Bokeh simulation pass cmd.GetTemporaryRT(Uniforms._DepthOfFieldTemp, context.width / 2, context.height / 2, 0, FilterMode.Bilinear, colorFormat); cmd.BlitFullscreenTriangle(Uniforms._DepthOfFieldTex, Uniforms._DepthOfFieldTemp, sheet, (int)Pass.BokehSmallKernel + (int)settings.kernelSize.value); // Postfilter pass cmd.BlitFullscreenTriangle(Uniforms._DepthOfFieldTemp, Uniforms._DepthOfFieldTex, sheet, (int)Pass.PostFilter); cmd.ReleaseTemporaryRT(Uniforms._DepthOfFieldTemp); // Combine pass cmd.BlitFullscreenTriangle(context.source, context.destination, sheet, (int)Pass.Combine); cmd.ReleaseTemporaryRT(Uniforms._DepthOfFieldTex); if (!context.IsTemporalAntialiasingActive()) { cmd.ReleaseTemporaryRT(Uniforms._CoCTex); } cmd.EndSample("DepthOfField"); m_ResetHistory = false; }
void OnLightMeterGUI(PostProcessRenderContext context) { if (m_TickLabelStyle == null) { m_TickLabelStyle = new GUIStyle("Label") { fontStyle = FontStyle.Bold, fontSize = 10, alignment = TextAnchor.MiddleCenter }; } var histogram = context.logHistogram; int kMargin = 8; int x = kMargin; int w = (int)(context.width * (3 / 5f) - kMargin * 2); int h = context.height / 4; int y = context.height - h - kMargin - 30; var rect = new Rect(x, y, w, h); if (Event.current.type == EventType.Repaint) { CheckTexture(ref m_LightMeterRT, (int)rect.width, (int)rect.height); var material = context.propertySheets.Get(context.resources.shaders.lightMeter).material; material.shaderKeywords = null; material.SetBuffer(Uniforms._HistogramBuffer, histogram.data); var scaleOffsetRes = histogram.GetHistogramScaleOffsetRes(context); scaleOffsetRes.z = 1f / rect.width; scaleOffsetRes.w = 1f / rect.height; material.SetVector(Uniforms._ScaleOffsetRes, scaleOffsetRes); if (context.logLut != null) { material.EnableKeyword("COLOR_GRADING_HDR"); material.SetTexture(Uniforms._Lut3D, context.logLut); } if (context.autoExposure != null) { var settings = context.autoExposure; // Make sure filtering values are correct to avoid apocalyptic consequences float lowPercent = settings.filtering.value.x; float highPercent = settings.filtering.value.y; const float kMinDelta = 1e-2f; highPercent = Mathf.Clamp(highPercent, 1f + kMinDelta, 99f); lowPercent = Mathf.Clamp(lowPercent, 1f, highPercent - kMinDelta); material.EnableKeyword("AUTO_EXPOSURE"); material.SetVector(Uniforms._Params, new Vector4(lowPercent * 0.01f, highPercent * 0.01f, RuntimeUtilities.Exp2(settings.minLuminance.value), RuntimeUtilities.Exp2(settings.maxLuminance.value))); } RuntimeUtilities.BlitFullscreenTriangle(null, m_LightMeterRT, material, 0); GUI.DrawTexture(rect, m_LightMeterRT); } // Labels rect.y += rect.height; rect.height = 30; int maxSize = Mathf.FloorToInt(rect.width / (LogHistogram.rangeMax - LogHistogram.rangeMin + 2)); GUI.DrawTexture(rect, RuntimeUtilities.blackTexture); GUILayout.BeginArea(rect); { GUILayout.BeginHorizontal(); { GUILayout.Space(4); for (int i = LogHistogram.rangeMin; i < LogHistogram.rangeMax; i++) { GUILayout.Label(i + "\n" + RuntimeUtilities.Exp2(i).ToString("0.###"), m_TickLabelStyle, GUILayout.Width(maxSize)); GUILayout.FlexibleSpace(); } GUILayout.Label(LogHistogram.rangeMax + "\n" + RuntimeUtilities.Exp2(LogHistogram.rangeMax).ToString("0.###"), m_TickLabelStyle, GUILayout.Width(maxSize)); GUILayout.Space(4); } GUILayout.EndHorizontal(); } GUILayout.EndArea(); }