Doom glow - fake volumetric light glow effect in Unity by Tili_us
// This file is in the public domain. Where | |
// a public domain declaration is not recognized, you are granted | |
// a license to freely use, modify, and redistribute this file in | |
// any way you choose. | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
// Unity remake of a fake volumetric light glow effect | |
// DoomGlow code by tim.tili.sabo@gmail.com -- http://yzergame.com/doomGlare.html | |
// Ideas by Simon https://simonschreibt.de/gat/doom-3-volumetric-glow/ | |
public class DoomGlow : MonoBehaviour | |
{ | |
public Color color = new Color(0.0f, 1.0f, 1.0f, 0.95f); | |
public float pushDistance = 0.3f; | |
public bool showBottom = false; | |
const int edgeCount = 4; | |
const int triCount = 18 * 3; | |
Vector3[] quadPoints = new Vector3[edgeCount]; | |
Vector3 centerPoint; | |
Vector4[] screenQuadPoints = new Vector4[edgeCount]; | |
MeshFilter meshFilter; | |
Mesh mesh; | |
int[] triBuffer = new int[triCount]; | |
Vector3[] vertBuffer = new Vector3[triCount]; | |
Color[] colorBuffer = new Color[triCount]; | |
// helper struct | |
private class EdgePush { | |
public Vector3 test1; | |
public Vector3 test2; | |
public Vector3 w1; | |
public Vector3 w2; | |
public Vector3 sharedPoint; | |
} | |
EdgePush[] pushedEdges = new EdgePush[4]; | |
void Start() | |
{ | |
quadPoints[0] = new Vector3(0, 0, 0); | |
quadPoints[1] = new Vector3(1, 0, 0); | |
quadPoints[2] = new Vector3(1, 0, 1); | |
quadPoints[3] = new Vector3(0, 0, 1); | |
centerPoint = quadPoints[0] + quadPoints[1] + quadPoints[2] + quadPoints[3]; | |
centerPoint *= 0.25f; | |
meshFilter = GetComponent<MeshFilter>(); | |
mesh = new Mesh(); | |
meshFilter.mesh = mesh; | |
pushedEdges[0] = new EdgePush(); | |
pushedEdges[1] = new EdgePush(); | |
pushedEdges[2] = new EdgePush(); | |
pushedEdges[3] = new EdgePush(); | |
// because I can't be arsed to re-use verts | |
for (int i = 0; i < triCount; ++i) { | |
triBuffer[i] = i; | |
} | |
} | |
// Optimised to allocate zero bytes per frame | |
void Update() | |
{ | |
//lets project the points to screen space | |
for (int i = 0; i < quadPoints.Length; ++i) | |
{ | |
screenQuadPoints[i] = GetScreenFromWorld(quadPoints[i]); | |
} | |
// get normal of quad | |
Vector3 u = quadPoints[1] - quadPoints[0]; | |
Vector3 v = quadPoints[2] - quadPoints[0]; | |
Vector3 normal = Vector3.Cross(u, v); | |
normal.Normalize(); | |
float dot = Vector3.Dot((centerPoint - Camera.main.transform.position).normalized, normal); | |
float alpha = LinearMap(dot, 0.001f, 0.1f, 0.0f, 1.0f); | |
if (showBottom) { | |
alpha = LinearMap(Mathf.Abs(dot), 0.001f, 0.1f, 0.0f, 1.0f); | |
} | |
// now calculate the directions for each line of the quad | |
for (int i = 0; i < edgeCount; ++i) { | |
Vector3 diff = screenQuadPoints[(i + 1)%edgeCount] - screenQuadPoints[i]; | |
Vector2 edge = new Vector2(diff.x, diff.y); | |
// Find the axis perpendicular to the current edge | |
Vector2 axis = new Vector2(edge.y, -edge.x); | |
axis.Normalize(); | |
// flip the axis when looking at the bottom | |
if (showBottom && dot < 0) { | |
axis *= -1.0f; | |
} | |
// push the edge away using that axis and store those points | |
Vector2 p1 = ToVec2(screenQuadPoints[i]) + axis * pushDistance; | |
Vector2 p2 = ToVec2(screenQuadPoints[(i + 1)%edgeCount]) + axis * pushDistance; | |
Vector3 pushedPointInWS1 = GetWorldFromScreen(new Vector4(p1.x, p1.y, screenQuadPoints[i].z, 1.0f)); | |
Vector3 pushedPointInWS2 = GetWorldFromScreen(new Vector4(p2.x, p2.y, screenQuadPoints[(i + 1)%edgeCount].z, 1.0f)); | |
pushedEdges[i].test1 = pushedPointInWS1; | |
pushedEdges[i].test2 = pushedPointInWS2; | |
Vector3 pushedPoint1 = pushedPointInWS1 - quadPoints[i]; | |
pushedPoint1.Normalize(); // find the world space direction of the point and normalize it | |
pushedEdges[i].w1 = quadPoints[i] + pushedPoint1 * pushDistance; // now we push in world space! | |
Vector3 pushedPoint = pushedPointInWS2 - quadPoints[(i + 1)%edgeCount]; | |
pushedPoint.Normalize(); // find the world space direction of the point and normalize it | |
pushedEdges[i].w2 = quadPoints[(i + 1)%edgeCount] + pushedPoint * pushDistance; // now we push in world space! | |
} | |
// this needs data from other edges so it is in another loop | |
for (int i = 0; i < edgeCount; ++i) { | |
// calc averaged point between the 2 edges | |
Vector3 p = pushedEdges[i].w1 + (pushedEdges[(i+edgeCount-1)%edgeCount].w2 - pushedEdges[i].w1) * 0.5f; // this finds the world space point halfway between 2 points | |
Vector3 dir = p - quadPoints[i]; // get the direction from the point itself | |
dir.Normalize(); | |
pushedEdges[i].sharedPoint = quadPoints[i] + dir * pushDistance; // now we push in world space! | |
} | |
// fill buffers to store vertex info | |
int currentIndex = 0; | |
// setup some colors | |
Color colorFilled = new Color(1.0f, 1.0f, 1.0f, alpha * color.a); | |
Color colorEdge = new Color(color.r, color.g, color.b, 0.0f); | |
// fill the original quad | |
FillBuffer(quadPoints[0], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[1], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[2], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[0], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[2], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[3], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
for (int i = 0; i < edgeCount; ++i) { | |
// render pushed quads | |
FillBuffer(quadPoints[i], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(pushedEdges[i].w1, ref colorEdge, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[(i + 1)%edgeCount], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(pushedEdges[i].w1, ref colorEdge, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(pushedEdges[i].w2, ref colorEdge, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[(i + 1)%edgeCount], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
// render connections between the newly generated quads | |
FillBuffer(pushedEdges[i].sharedPoint, ref colorEdge, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(pushedEdges[i].w1, ref colorEdge, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[i], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(pushedEdges[i].sharedPoint, ref colorEdge, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(quadPoints[i], ref colorFilled, ref currentIndex, vertBuffer, colorBuffer); | |
FillBuffer(pushedEdges[(i+edgeCount-1)%edgeCount].w2, ref colorEdge, ref currentIndex, vertBuffer, colorBuffer); | |
} | |
// fill mesh | |
mesh.vertices = vertBuffer; | |
mesh.colors = colorBuffer; | |
mesh.triangles = triBuffer; | |
mesh.RecalculateBounds(); | |
meshFilter.mesh = mesh; | |
} | |
Vector4 GetScreenFromWorld(Vector3 world) | |
{ | |
return Camera.main.WorldToScreenPoint(world); | |
} | |
Vector3 GetWorldFromScreen(Vector4 screen) | |
{ | |
return Camera.main.ScreenToWorldPoint(screen); | |
} | |
Vector2 ToVec2(Vector3 v) { | |
return new Vector2(v.x, v.y); | |
} | |
void FillBuffer(Vector3 pos, ref Color color, ref int currentIndex, Vector3[] vertBuffer, Color[] colorBuffer) { | |
vertBuffer[currentIndex] = pos; | |
colorBuffer[currentIndex] = color; | |
currentIndex++; | |
} | |
float LinearMap(float inVal, float inFrom, float inTo, float outFrom, float outTo) | |
{ | |
float inScale = (inFrom != inTo) ? ((inVal - inFrom) / (inTo - inFrom)) : 0.0f; | |
inScale = Mathf.Clamp(inScale, 0.0f, 1.0f); | |
return Mathf.Lerp(outFrom, outTo, inScale); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment