// HDR color pipeline is rendered to a 2D strip lut (works on HDR platforms without compute // and 3D texture support). Precision is sliiiiiiightly lower than when using a 3D texture // LUT (33^3 -> 32^3) but most of the time it's imperceptible. void RenderHDRPipeline2D(PostProcessRenderContext context) { // For the same reasons as in RenderHDRPipeline3D, regen LUT on evey frame { CheckInternalStripLut(); // Lut setup var lutSheet = context.propertySheets.Get(context.resources.shaders.lut2DBaker); lutSheet.ClearKeywords(); lutSheet.properties.SetVector(ShaderIDs.Lut2D_Params, new Vector4(k_Lut2DSize, 0.5f / (k_Lut2DSize * k_Lut2DSize), 0.5f / k_Lut2DSize, k_Lut2DSize / (k_Lut2DSize - 1f))); var colorBalance = ColorUtilities.ComputeColorBalance(settings.temperature.value, settings.tint.value); lutSheet.properties.SetVector(ShaderIDs.ColorBalance, colorBalance); lutSheet.properties.SetVector(ShaderIDs.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] lutSheet.properties.SetVector(ShaderIDs.HueSatCon, new Vector3(hue, sat, con)); var channelMixerR = new Vector3(settings.mixerRedOutRedIn, settings.mixerRedOutGreenIn, settings.mixerRedOutBlueIn); var channelMixerG = new Vector3(settings.mixerGreenOutRedIn, settings.mixerGreenOutGreenIn, settings.mixerGreenOutBlueIn); var channelMixerB = new Vector3(settings.mixerBlueOutRedIn, settings.mixerBlueOutGreenIn, settings.mixerBlueOutBlueIn); lutSheet.properties.SetVector(ShaderIDs.ChannelMixerRed, channelMixerR / 100f); // Remap to [-2;2] lutSheet.properties.SetVector(ShaderIDs.ChannelMixerGreen, channelMixerG / 100f); lutSheet.properties.SetVector(ShaderIDs.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); lutSheet.properties.SetVector(ShaderIDs.Lift, lift); lutSheet.properties.SetVector(ShaderIDs.InvGamma, invgamma); lutSheet.properties.SetVector(ShaderIDs.Gain, gain); lutSheet.properties.SetTexture(ShaderIDs.Curves, GetCurveTexture(false)); var tonemapper = settings.tonemapper.value; if (tonemapper == Tonemapper.Custom) { lutSheet.EnableKeyword("TONEMAPPING_CUSTOM"); m_HableCurve.Init( settings.toneCurveToeStrength.value, settings.toneCurveToeLength.value, settings.toneCurveShoulderStrength.value, settings.toneCurveShoulderLength.value, settings.toneCurveShoulderAngle.value, settings.toneCurveGamma.value ); lutSheet.properties.SetVector(ShaderIDs.CustomToneCurve, m_HableCurve.uniforms.curve); lutSheet.properties.SetVector(ShaderIDs.ToeSegmentA, m_HableCurve.uniforms.toeSegmentA); lutSheet.properties.SetVector(ShaderIDs.ToeSegmentB, m_HableCurve.uniforms.toeSegmentB); lutSheet.properties.SetVector(ShaderIDs.MidSegmentA, m_HableCurve.uniforms.midSegmentA); lutSheet.properties.SetVector(ShaderIDs.MidSegmentB, m_HableCurve.uniforms.midSegmentB); lutSheet.properties.SetVector(ShaderIDs.ShoSegmentA, m_HableCurve.uniforms.shoSegmentA); lutSheet.properties.SetVector(ShaderIDs.ShoSegmentB, m_HableCurve.uniforms.shoSegmentB); } else if (tonemapper == Tonemapper.ACES) { lutSheet.EnableKeyword("TONEMAPPING_ACES"); } else if (tonemapper == Tonemapper.Neutral) { lutSheet.EnableKeyword("TONEMAPPING_NEUTRAL"); } // Generate the lut context.command.BeginSample("HdrColorGradingLut2D"); context.command.BlitFullscreenTriangle(BuiltinRenderTextureType.None, m_InternalLdrLut, lutSheet, (int)Pass.LutGenHDR2D); context.command.EndSample("HdrColorGradingLut2D"); } var lut = m_InternalLdrLut; var uberSheet = context.uberSheet; uberSheet.EnableKeyword("COLOR_GRADING_HDR_2D"); uberSheet.properties.SetVector(ShaderIDs.Lut2D_Params, new Vector3(1f / lut.width, 1f / lut.height, lut.height - 1f)); uberSheet.properties.SetTexture(ShaderIDs.Lut2D, lut); uberSheet.properties.SetFloat(ShaderIDs.PostExposure, RuntimeUtilities.Exp2(settings.postExposure.value)); }
// LDR color pipeline is rendered to a 2D strip lut (works on every platform) void RenderLDRPipeline2D(PostProcessRenderContext context) { // For the same reasons as in RenderHDRPipeline3D, regen LUT on every frame { CheckInternalStripLut(); // Lut setup var lutSheet = context.propertySheets.Get(context.resources.shaders.lut2DBaker); lutSheet.ClearKeywords(); lutSheet.properties.SetVector(ShaderIDs.Lut2D_Params, new Vector4(k_Lut2DSize, 0.5f / (k_Lut2DSize * k_Lut2DSize), 0.5f / k_Lut2DSize, k_Lut2DSize / (k_Lut2DSize - 1f))); var colorBalance = ColorUtilities.ComputeColorBalance(settings.temperature.value, settings.tint.value); lutSheet.properties.SetVector(ShaderIDs.ColorBalance, colorBalance); lutSheet.properties.SetVector(ShaderIDs.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] lutSheet.properties.SetVector(ShaderIDs.HueSatCon, new Vector3(hue, sat, con)); var channelMixerR = new Vector3(settings.mixerRedOutRedIn, settings.mixerRedOutGreenIn, settings.mixerRedOutBlueIn); var channelMixerG = new Vector3(settings.mixerGreenOutRedIn, settings.mixerGreenOutGreenIn, settings.mixerGreenOutBlueIn); var channelMixerB = new Vector3(settings.mixerBlueOutRedIn, settings.mixerBlueOutGreenIn, settings.mixerBlueOutBlueIn); lutSheet.properties.SetVector(ShaderIDs.ChannelMixerRed, channelMixerR / 100f); // Remap to [-2;2] lutSheet.properties.SetVector(ShaderIDs.ChannelMixerGreen, channelMixerG / 100f); lutSheet.properties.SetVector(ShaderIDs.ChannelMixerBlue, channelMixerB / 100f); var lift = ColorUtilities.ColorToLift(settings.lift.value); var gain = ColorUtilities.ColorToGain(settings.gain.value); var invgamma = ColorUtilities.ColorToInverseGamma(settings.gamma.value); lutSheet.properties.SetVector(ShaderIDs.Lift, lift); lutSheet.properties.SetVector(ShaderIDs.InvGamma, invgamma); lutSheet.properties.SetVector(ShaderIDs.Gain, gain); lutSheet.properties.SetFloat(ShaderIDs.Brightness, (settings.brightness.value + 100f) / 100f); lutSheet.properties.SetTexture(ShaderIDs.Curves, GetCurveTexture(false)); // Generate the lut context.command.BeginSample("LdrColorGradingLut2D"); var userLut = settings.ldrLut.value; if (userLut == null || userLut.width != userLut.height * userLut.height) { context.command.BlitFullscreenTriangle(BuiltinRenderTextureType.None, m_InternalLdrLut, lutSheet, (int)Pass.LutGenLDRFromScratch); } else { lutSheet.properties.SetVector(ShaderIDs.UserLut2D_Params, new Vector4(1f / userLut.width, 1f / userLut.height, userLut.height - 1f, settings.ldrLutContribution)); context.command.BlitFullscreenTriangle(userLut, m_InternalLdrLut, lutSheet, (int)Pass.LutGenLDR); } context.command.EndSample("LdrColorGradingLut2D"); } var lut = m_InternalLdrLut; var uberSheet = context.uberSheet; uberSheet.EnableKeyword("COLOR_GRADING_LDR_2D"); uberSheet.properties.SetVector(ShaderIDs.Lut2D_Params, new Vector3(1f / lut.width, 1f / lut.height, lut.height - 1f)); uberSheet.properties.SetTexture(ShaderIDs.Lut2D, lut); }
// HDR color pipeline is rendered to a 3D lut; it requires Texture3D & compute shaders // support - Desktop / Consoles / Some high-end mobiles void RenderHDRPipeline3D(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 groupSizeXY = Mathf.CeilToInt(k_Lut3DSize / 8f); int groupSizeZ = Mathf.CeilToInt(k_Lut3DSize / (RuntimeUtilities.isAndroidOpenGL ? 2f : 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 ); cmd.SetComputeVectorParam(compute, "_CustomToneCurve", m_HableCurve.uniforms.curve); cmd.SetComputeVectorParam(compute, "_ToeSegmentA", m_HableCurve.uniforms.toeSegmentA); cmd.SetComputeVectorParam(compute, "_ToeSegmentB", m_HableCurve.uniforms.toeSegmentB); cmd.SetComputeVectorParam(compute, "_MidSegmentA", m_HableCurve.uniforms.midSegmentA); cmd.SetComputeVectorParam(compute, "_MidSegmentB", m_HableCurve.uniforms.midSegmentB); cmd.SetComputeVectorParam(compute, "_ShoSegmentA", m_HableCurve.uniforms.shoSegmentA); cmd.SetComputeVectorParam(compute, "_ShoSegmentB", m_HableCurve.uniforms.shoSegmentB); } // Generate the lut context.command.BeginSample("HdrColorGradingLut3D"); cmd.DispatchCompute(compute, kernel, groupSizeXY, groupSizeXY, groupSizeZ); context.command.EndSample("HdrColorGradingLut3D"); } var lut = m_InternalLogLut; var uberSheet = context.uberSheet; uberSheet.EnableKeyword("COLOR_GRADING_HDR_3D"); uberSheet.properties.SetTexture(ShaderIDs.Lut3D, lut); uberSheet.properties.SetVector(ShaderIDs.Lut3D_Params, new Vector2(1f / lut.width, lut.width - 1f)); uberSheet.properties.SetFloat(ShaderIDs.PostExposure, RuntimeUtilities.Exp2(settings.postExposure.value)); context.logLut = lut; }
/* * void Update() * { * // Unfortunately we need to track the current layer to update the volume manager in * // real-time as the user could change it at any time in the editor or at runtime. * // Because no event is raised when the layer changes, we have to track it on every * // frame :/ * int layer = gameObject.layer; * if (layer != m_PreviousLayer) * { * PostProcessManager.instance.UpdateVolumeLayer(this, m_PreviousLayer, layer); * m_PreviousLayer = layer; * } * * // Same for `priority`. We could use a property instead, but it doesn't play nice with * // the serialization system. Using a custom Attribute/PropertyDrawer for a property is * // possible but it doesn't work with Undo/Redo in the editor, which makes it useless. * if (priority != m_PreviousPriority) * { * PostProcessManager.instance.SetLayerDirty(layer); * m_PreviousPriority = priority; * } * } */ // TODO: Look into a better volume previsualization system void OnDrawGizmos() { var colliders = m_TempColliders; GetComponents(colliders); if (isGlobal || colliders == null) { return; } #if UNITY_EDITOR // Can't access the UnityEditor.Rendering.PostProcessing namespace from here, so // we'll get the preferred color manually unchecked { int value = UnityEditor.EditorPrefs.GetInt("PostProcessing.Volume.GizmoColor", (int)0x8033cc1a); Gizmos.color = ColorUtilities.ToRGBA((uint)value); } #endif var scale = transform.lossyScale; var invScale = new Vector3(1f / scale.x, 1f / scale.y, 1f / scale.z); Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, scale); // Draw a separate gizmo for each collider foreach (var collider in colliders) { if (!collider.enabled) { continue; } // We'll just use scaling as an approximation for volume skin. It's far from being // correct (and is completely wrong in some cases). Ultimately we'd use a distance // field or at least a tesselate + push modifier on the collider's mesh to get a // better approximation, but the current Gizmo system is a bit limited and because // everything is dynamic in Unity and can be changed at anytime, it's hard to keep // track of changes in an elegant way (which we'd need to implement a nice cache // system for generated volume meshes). var type = collider.GetType(); if (type == typeof(BoxCollider)) { var c = (BoxCollider)collider; Gizmos.DrawCube(c.center, c.size); Gizmos.DrawWireCube(c.center, c.size + invScale * blendDistance * 4f); } else if (type == typeof(SphereCollider)) { var c = (SphereCollider)collider; Gizmos.DrawSphere(c.center, c.radius); Gizmos.DrawWireSphere(c.center, c.radius + invScale.x * blendDistance * 2f); } else if (type == typeof(MeshCollider)) { var c = (MeshCollider)collider; // Only convex mesh colliders are allowed if (!c.convex) { c.convex = true; } // Mesh pivot should be centered or this won't work Gizmos.DrawMesh(c.sharedMesh); Gizmos.DrawWireMesh(c.sharedMesh, Vector3.zero, Quaternion.identity, Vector3.one + invScale * blendDistance * 4f); } // Nothing for capsule (DrawCapsule isn't exposed in Gizmo), terrain, wheel and // other colliders... } colliders.Clear(); }