Skip to content

Instantly share code, notes, and snippets.

@belzecue
Forked from TiliSleepStealer/DoomGlow.cs
Created April 25, 2023 02:34
Show Gist options
  • Save belzecue/dbb565692499413f7b323e533511144d to your computer and use it in GitHub Desktop.
Save belzecue/dbb565692499413f7b323e533511144d to your computer and use it in GitHub Desktop.
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