282 lines
9.1 KiB
C#

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);
}
}