using UnityEngine; // Glow uses the alpha channel as a source of "extra brightness". // All builtin Unity shaders output baseTexture.alpha * color.alpha, plus // specularHighlight * specColor.alpha into that. // Usually you'd want either to make base textures to have zero alpha; or // set the color to have zero alpha (by default alpha is 0.5). [ExecuteInEditMode] [RequireComponent(typeof(Camera))] [AddComponentMenu("Image Effects/Glow")] public class GlowEffect : MonoBehaviour { /// The brightness of the glow. Values larger than one give extra "boost". public float glowIntensity = 1.5f; /// Blur iterations - larger number means more blur. public int blurIterations = 3; /// Blur spread for each iteration. Lower values /// give better looking blur, but require more iterations to /// get large blurs. Value is usually between 0.5 and 1.0. public float blurSpread = 0.7f; /// Tint glow with this color. Alpha adds additional glow everywhere. public Color glowTint = Color.white; // -------------------------------------------------------- // The final composition shader: // adds (glow color * glow alpha * amount) to the original image. // In the combiner glow amount can be only in 0..1 range; we apply extra // amount during the blurring phase. private static string compositeMatString = @"Shader ""GlowCompose"" { Properties { _Color (""Glow Amount"", Color) = (1,1,1,1) _MainTex ("""", RECT) = ""white"" {} } SubShader { Pass { ZTest Always Cull Off ZWrite Off Fog { Mode Off } Blend One One SetTexture [_MainTex] {constantColor [_Color] combine constant * texture DOUBLE} } } Fallback off }"; static Material m_CompositeMaterial = null; protected static Material compositeMaterial { get { if (m_CompositeMaterial == null) { m_CompositeMaterial = new Material(compositeMatString); m_CompositeMaterial.hideFlags = HideFlags.HideAndDontSave; m_CompositeMaterial.shader.hideFlags = HideFlags.HideAndDontSave; } return m_CompositeMaterial; } } // -------------------------------------------------------- // The blur iteration shader. // Basically it just takes 4 texture samples and averages them. // By applying it repeatedly and spreading out sample locations // we get a Gaussian blur approximation. // The alpha value in _Color would normally be 0.25 (to average 4 samples), // however if we have glow amount larger than 1 then we increase this. private static string blurMatString = @"Shader ""GlowConeTap"" { Properties { _Color (""Blur Boost"", Color) = (0,0,0,0.25) _MainTex ("""", RECT) = ""white"" {} } SubShader { Pass { ZTest Always Cull Off ZWrite Off Fog { Mode Off } SetTexture [_MainTex] {constantColor [_Color] combine texture * constant alpha} SetTexture [_MainTex] {constantColor [_Color] combine texture * constant + previous} SetTexture [_MainTex] {constantColor [_Color] combine texture * constant + previous} SetTexture [_MainTex] {constantColor [_Color] combine texture * constant + previous} } } Fallback off }"; static Material m_BlurMaterial = null; protected static Material blurMaterial { get { if (m_BlurMaterial == null) { m_BlurMaterial = new Material(blurMatString); m_BlurMaterial.hideFlags = HideFlags.HideAndDontSave; m_BlurMaterial.shader.hideFlags = HideFlags.HideAndDontSave; } return m_BlurMaterial; } } // -------------------------------------------------------- // The image downsample shaders for each brightness mode. // It is in external assets as it's quite complex and uses Cg. public Shader downsampleShader; Material m_DownsampleMaterial = null; protected Material downsampleMaterial { get { if (m_DownsampleMaterial == null) { m_DownsampleMaterial = new Material(downsampleShader); m_DownsampleMaterial.hideFlags = HideFlags.HideAndDontSave; } return m_DownsampleMaterial; } } // -------------------------------------------------------- // finally, the actual code protected void OnDisable() { if (m_CompositeMaterial) { DestroyImmediate(m_CompositeMaterial.shader); DestroyImmediate(m_CompositeMaterial); } if (m_BlurMaterial) { DestroyImmediate(m_BlurMaterial.shader); DestroyImmediate(m_BlurMaterial); } if (m_DownsampleMaterial) DestroyImmediate(m_DownsampleMaterial); } protected void Start() { // Disable if we don't support image effects if (!SystemInfo.supportsImageEffects) { enabled = false; return; } // Disable the effect if no downsample shader is setup if (downsampleShader == null) { Debug.Log("No downsample shader assigned! Disabling glow."); enabled = false; } // Disable if any of the shaders can't run on the users graphics card else { if (!blurMaterial.shader.isSupported) enabled = false; if (!compositeMaterial.shader.isSupported) enabled = false; if (!downsampleMaterial.shader.isSupported) enabled = false; } } // Performs one blur iteration. public void FourTapCone(RenderTexture source, RenderTexture dest, int iteration) { RenderTexture.active = dest; blurMaterial.SetTexture("_MainTex", source); float offsetX = (.5F + iteration * blurSpread) / (float)source.width; float offsetY = (.5F + iteration * blurSpread) / (float)source.height; GL.PushMatrix(); GL.LoadOrtho(); for (int i = 0; i < blurMaterial.passCount; i++) { blurMaterial.SetPass(i); Render4TapQuad(dest, offsetX, offsetY); } GL.PopMatrix(); } // Downsamples the texture to a quarter resolution. private void DownSample4x(RenderTexture source, RenderTexture dest) { downsampleMaterial.color = new Color(glowTint.r, glowTint.g, glowTint.b, glowTint.a / 4.0f); ImageEffects.BlitWithMaterial(downsampleMaterial, source, dest); } // Called by the camera to apply the image effect void OnRenderImage(RenderTexture source, RenderTexture destination) { if (!source) { //Sometimes occurs when entering or exiting water... return; } // Clamp parameters to sane values glowIntensity = Mathf.Clamp(glowIntensity, 0.0f, 10.0f); blurIterations = Mathf.Clamp(blurIterations, 0, 30); blurSpread = Mathf.Clamp(blurSpread, 0.5f, 1.0f); RenderTexture buffer = RenderTexture.GetTemporary(source.width / 4, source.height / 4, 0); RenderTexture buffer2 = RenderTexture.GetTemporary(source.width / 4, source.height / 4, 0); // Copy source to the 4x4 smaller texture. DownSample4x(source, buffer); // Blur the small texture float extraBlurBoost = Mathf.Clamp01((glowIntensity - 1.0f) / 4.0f); blurMaterial.color = new Color(1F, 1F, 1F, 0.25f + extraBlurBoost); bool oddEven = true; for (int i = 0; i < blurIterations; i++) { if (oddEven) FourTapCone(buffer, buffer2, i); else FourTapCone(buffer2, buffer, i); oddEven = !oddEven; } ImageEffects.Blit(source, destination); if (oddEven) BlitGlow(buffer, destination); else BlitGlow(buffer2, destination); RenderTexture.ReleaseTemporary(buffer); RenderTexture.ReleaseTemporary(buffer2); } public void BlitGlow(RenderTexture source, RenderTexture dest) { compositeMaterial.color = new Color(1F, 1F, 1F, Mathf.Clamp01(glowIntensity)); ImageEffects.BlitWithMaterial(compositeMaterial, source, dest); } private static void Render4TapQuad(RenderTexture dest, float offsetX, float offsetY) { GL.Begin(GL.QUADS); // Direct3D needs interesting texel offsets! Vector2 off = Vector2.zero; if (dest != null) off = dest.GetTexelOffset() * 0.75f; Set4TexCoords(off.x, off.y, offsetX, offsetY); GL.Vertex3(0, 0, .1f); Set4TexCoords(1.0f + off.x, off.y, offsetX, offsetY); GL.Vertex3(1, 0, .1f); Set4TexCoords(1.0f + off.x, 1.0f + off.y, offsetX, offsetY); GL.Vertex3(1, 1, .1f); Set4TexCoords(off.x, 1.0f + off.y, offsetX, offsetY); GL.Vertex3(0, 1, .1f); GL.End(); } private static void Set4TexCoords(float x, float y, float offsetX, float offsetY) { GL.MultiTexCoord2(0, x - offsetX, y - offsetY); GL.MultiTexCoord2(1, x + offsetX, y - offsetY); GL.MultiTexCoord2(2, x + offsetX, y + offsetY); GL.MultiTexCoord2(3, x - offsetX, y + offsetY); } }