433 lines
14 KiB
C#
433 lines
14 KiB
C#
//------------------------------------------------------------------------------------------------------------------
|
|
// Volumetric Lights
|
|
// Created by Kronnect
|
|
//------------------------------------------------------------------------------------------------------------------
|
|
|
|
using System;
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
namespace VolumetricLights {
|
|
|
|
public partial class VolumetricLight : MonoBehaviour {
|
|
|
|
const int SIDES = 32;
|
|
readonly List<Vector3> vertices = new List<Vector3>(32);
|
|
readonly List<int> indices = new List<int>(32);
|
|
|
|
public float generatedRange = -1;
|
|
public float generatedTipRadius = -1;
|
|
public float generatedSpotAngle = -1;
|
|
public float generatedBaseRadius;
|
|
public float generatedAreaWidth, generatedAreaHeight, generatedAreaFrustumAngle, generatedAreaFrustumMultiplier;
|
|
public LightType generatedType;
|
|
|
|
bool CheckMesh() {
|
|
|
|
if (meshRenderer != null && !autoToggle) {
|
|
bool isEnabled = lightComp.enabled || alwaysOn;
|
|
meshRenderer.enabled = isEnabled;
|
|
}
|
|
|
|
if (!useCustomSize) {
|
|
#if UNITY_EDITOR
|
|
areaWidth = lightComp.areaSize.x;
|
|
areaHeight = lightComp.areaSize.y;
|
|
#endif
|
|
customRange = lightComp.range;
|
|
}
|
|
|
|
bool needMesh = false;
|
|
if (mf == null) {
|
|
mf = GetComponent<MeshFilter>();
|
|
}
|
|
if (mf == null || mf.sharedMesh == null) {
|
|
needMesh = true;
|
|
}
|
|
|
|
switch (lightComp.type) {
|
|
case LightType.Spot:
|
|
if (needMesh || generatedType != LightType.Spot || customRange != generatedRange || lightComp.spotAngle != generatedSpotAngle || tipRadius != generatedTipRadius) {
|
|
GenerateConeMesh();
|
|
return true;
|
|
}
|
|
break;
|
|
case LightType.Point:
|
|
if (needMesh || generatedType != LightType.Point || customRange != generatedRange) {
|
|
GenerateSphereMesh();
|
|
return true;
|
|
}
|
|
break;
|
|
case LightType.Rectangle:
|
|
if (needMesh || generatedType != LightType.Rectangle || customRange != generatedRange || areaWidth != generatedAreaWidth || areaHeight != generatedAreaHeight || frustumAngle != generatedAreaFrustumAngle) {
|
|
GenerateCubeMesh();
|
|
return true;
|
|
}
|
|
break;
|
|
case LightType.Disc:
|
|
if (needMesh || generatedType != LightType.Disc || customRange != generatedRange || areaWidth != generatedAreaWidth || frustumAngle != generatedAreaFrustumAngle) {
|
|
GenerateCylinderMesh();
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
if (meshRenderer != null) {
|
|
meshRenderer.enabled = false;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UpdateMesh(string name) {
|
|
mf = GetComponent<MeshFilter>();
|
|
if (mf == null) {
|
|
mf = gameObject.AddComponent<MeshFilter>();
|
|
}
|
|
|
|
Mesh mesh = mf.sharedMesh;
|
|
if (mesh == null) {
|
|
mesh = new Mesh();
|
|
} else {
|
|
mesh.Clear();
|
|
}
|
|
mesh.name = name;
|
|
mesh.SetVertices(vertices);
|
|
mesh.SetIndices(indices, MeshTopology.Triangles, 0);
|
|
mf.mesh = mesh;
|
|
|
|
meshRenderer = GetComponent<MeshRenderer>();
|
|
if (meshRenderer == null) {
|
|
meshRenderer = gameObject.AddComponent<MeshRenderer>();
|
|
meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
|
meshRenderer.receiveShadows = false;
|
|
} else if (!autoToggle) {
|
|
meshRenderer.enabled = true;
|
|
}
|
|
}
|
|
|
|
void NormalizeScale() {
|
|
Transform parent = transform.parent;
|
|
if (parent == null) return;
|
|
Vector3 lossyScale = parent.lossyScale;
|
|
if ((lossyScale.x != 1 || lossyScale.y != 1 || lossyScale.z != 1) && lossyScale.x != 0 && lossyScale.y != 0 && lossyScale.z != 0) {
|
|
lossyScale.x = 1f / lossyScale.x;
|
|
lossyScale.y = 1f / lossyScale.y;
|
|
lossyScale.z = 1f / lossyScale.z;
|
|
if (transform.localScale != lossyScale) {
|
|
transform.localScale = lossyScale;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#region Cone mesh generation
|
|
|
|
void GenerateConeMesh() {
|
|
|
|
NormalizeScale();
|
|
|
|
vertices.Clear();
|
|
indices.Clear();
|
|
|
|
generatedType = LightType.Spot;
|
|
generatedTipRadius = tipRadius;
|
|
generatedSpotAngle = lightComp.spotAngle;
|
|
generatedRange = customRange;
|
|
generatedBaseRadius = Mathf.Tan(lightComp.spotAngle * Mathf.Deg2Rad * 0.5f) * customRange;
|
|
|
|
Vector3 p = Vector3.zero;
|
|
|
|
// Top & Bottom vertices
|
|
for (int k = 0; k < SIDES; k++) {
|
|
float r = 2f * Mathf.PI * k / SIDES;
|
|
float cos = Mathf.Cos(r);
|
|
float sin = Mathf.Sin(r);
|
|
p.x = cos * generatedTipRadius;
|
|
p.y = sin * generatedTipRadius;
|
|
p.z = 0;
|
|
vertices.Add(p);
|
|
p.x = cos * generatedBaseRadius;
|
|
p.y = sin * generatedBaseRadius;
|
|
p.z = customRange;
|
|
vertices.Add(p);
|
|
}
|
|
|
|
// Build faces
|
|
int vertexCount = SIDES * 2;
|
|
for (int k = 0; k < vertexCount; k += 2) {
|
|
int tl = k;
|
|
int bl = k + 1;
|
|
int tr = (k + 2) % vertexCount;
|
|
int br = (k + 3) % vertexCount;
|
|
// First tri
|
|
indices.Add(tl);
|
|
indices.Add(tr);
|
|
indices.Add(bl);
|
|
// Second tri
|
|
indices.Add(tr);
|
|
indices.Add(br);
|
|
indices.Add(bl);
|
|
}
|
|
|
|
// Build top cap
|
|
vertices.Add(Vector3.zero);
|
|
int topCapCenterIndex = vertexCount;
|
|
for (int k = 0; k < vertexCount; k += 2) {
|
|
// First tri
|
|
indices.Add(k);
|
|
indices.Add(topCapCenterIndex);
|
|
indices.Add((k + 2) % vertexCount);
|
|
}
|
|
|
|
// Build bottom cap
|
|
vertices.Add(new Vector3(0, 0, customRange));
|
|
int bottomCapCenterIndex = vertexCount + 1;
|
|
for (int k = 0; k < vertexCount; k += 2) {
|
|
// First tri
|
|
indices.Add(bottomCapCenterIndex);
|
|
indices.Add(k + 1);
|
|
indices.Add((k + 3) % vertexCount);
|
|
}
|
|
|
|
UpdateMesh("Capped Cone");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Cube mesh generation
|
|
|
|
static readonly Vector3[] faceVerticesForward = {
|
|
new Vector3 (0.5f, -0.5f, 1f),
|
|
new Vector3 (0.5f, 0.5f, 1f),
|
|
new Vector3 (-0.5f, -0.5f, 1f),
|
|
new Vector3 (-0.5f, 0.5f, 1f)
|
|
};
|
|
static readonly Vector3[] faceVerticesBack = {
|
|
new Vector3 (-0.5f, -0.5f, 0f),
|
|
new Vector3 (-0.5f, 0.5f, 0f),
|
|
new Vector3 (0.5f, -0.5f, 0f),
|
|
new Vector3 (0.5f, 0.5f, 0f)
|
|
};
|
|
static readonly Vector3[] faceVerticesLeft = {
|
|
new Vector3 (-0.5f, -0.5f, 1f),
|
|
new Vector3 (-0.5f, 0.5f, 1f),
|
|
new Vector3 (-0.5f, -0.5f, 0f),
|
|
new Vector3 (-0.5f, 0.5f, 0f)
|
|
};
|
|
static readonly Vector3[] faceVerticesRight = {
|
|
new Vector3 (0.5f, -0.5f, 0f),
|
|
new Vector3 (0.5f, 0.5f, 0f),
|
|
new Vector3 (0.5f, -0.5f, 1f),
|
|
new Vector3 (0.5f, 0.5f, 1f)
|
|
};
|
|
static readonly Vector3[] faceVerticesTop = {
|
|
new Vector3 (-0.5f, 0.5f, 0f),
|
|
new Vector3 (-0.5f, 0.5f, 1f),
|
|
new Vector3 (0.5f, 0.5f, 0f),
|
|
new Vector3 (0.5f, 0.5f, 1f)
|
|
};
|
|
static readonly Vector3[] faceVerticesBottom = {
|
|
new Vector3 (0.5f, -0.5f, 0f),
|
|
new Vector3 (0.5f, -0.5f, 1f),
|
|
new Vector3 (-0.5f, -0.5f, 0f),
|
|
new Vector3 (-0.5f, -0.5f, 1f)
|
|
};
|
|
|
|
void GenerateCubeMesh() {
|
|
|
|
NormalizeScale();
|
|
|
|
generatedType = LightType.Rectangle;
|
|
generatedRange = customRange;
|
|
generatedAreaWidth = areaWidth;
|
|
generatedAreaHeight = areaHeight;
|
|
|
|
generatedAreaFrustumAngle = frustumAngle;
|
|
generatedAreaFrustumMultiplier = 1f + Mathf.Tan(frustumAngle * Mathf.Deg2Rad);
|
|
|
|
vertices.Clear();
|
|
indices.Clear();
|
|
|
|
AddFace(faceVerticesBack);
|
|
AddFace(faceVerticesForward);
|
|
AddFace(faceVerticesLeft);
|
|
AddFace(faceVerticesRight);
|
|
AddFace(faceVerticesTop);
|
|
AddFace(faceVerticesBottom);
|
|
|
|
UpdateMesh("Box");
|
|
}
|
|
|
|
void AddFace(Vector3[] faceVertices) {
|
|
int index = vertices.Count;
|
|
for (int k = 0; k < faceVertices.Length; k++) {
|
|
Vector3 v = faceVertices[k];
|
|
v.x *= generatedAreaWidth;
|
|
v.y *= generatedAreaHeight;
|
|
if (v.z > 0) {
|
|
v.x *= generatedAreaFrustumMultiplier;
|
|
v.y *= generatedAreaFrustumMultiplier;
|
|
}
|
|
v.z *= generatedRange;
|
|
vertices.Add(v);
|
|
}
|
|
indices.Add(index);
|
|
indices.Add(index + 1);
|
|
indices.Add(index + 3);
|
|
indices.Add(index + 3);
|
|
indices.Add(index + 2);
|
|
indices.Add(index);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Sphere mesh generation
|
|
|
|
void GenerateSphereMesh() {
|
|
|
|
NormalizeScale();
|
|
|
|
generatedRange = customRange;
|
|
generatedType = LightType.Point;
|
|
|
|
vertices.Clear();
|
|
indices.Clear();
|
|
|
|
const int nbLong = 24;
|
|
const int nbLat = 16;
|
|
|
|
const float _2pi = Mathf.PI * 2f;
|
|
|
|
vertices.Add(Vector3.up * generatedRange);
|
|
for (int lat = 0; lat < nbLat; lat++) {
|
|
float a1 = Mathf.PI * (float)(lat + 1) / (nbLat + 1);
|
|
float sin1 = Mathf.Sin(a1);
|
|
float cos1 = Mathf.Cos(a1);
|
|
|
|
for (int lon = 0; lon <= nbLong; lon++) {
|
|
float a2 = _2pi * (float)(lon == nbLong ? 0 : lon) / nbLong;
|
|
float sin2 = Mathf.Sin(a2);
|
|
float cos2 = Mathf.Cos(a2);
|
|
|
|
vertices.Add(new Vector3(sin1 * cos2, cos1, sin1 * sin2) * generatedRange);
|
|
}
|
|
}
|
|
vertices.Add(Vector3.down * generatedRange);
|
|
|
|
//Top Cap
|
|
for (int lon = 0; lon < nbLong; lon++) {
|
|
indices.Add(lon + 2);
|
|
indices.Add(lon + 1);
|
|
indices.Add(0);
|
|
}
|
|
|
|
//Middle
|
|
for (int lat = 0; lat < nbLat - 1; lat++) {
|
|
for (int lon = 0; lon < nbLong; lon++) {
|
|
int current = lon + lat * (nbLong + 1) + 1;
|
|
int next = current + nbLong + 1;
|
|
|
|
indices.Add(current);
|
|
indices.Add(current + 1);
|
|
indices.Add(next + 1);
|
|
|
|
indices.Add(current);
|
|
indices.Add(next + 1);
|
|
indices.Add(next);
|
|
}
|
|
}
|
|
|
|
//Bottom Cap
|
|
int vertexCount = vertices.Count;
|
|
for (int lon = 0; lon < nbLong; lon++) {
|
|
indices.Add(vertexCount - 1);
|
|
indices.Add(vertexCount - (lon + 2) - 1);
|
|
indices.Add(vertexCount - (lon + 1) - 1);
|
|
}
|
|
|
|
UpdateMesh("Sphere");
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Cylinder mesh generation
|
|
|
|
void GenerateCylinderMesh() {
|
|
|
|
NormalizeScale();
|
|
|
|
generatedType = LightType.Disc;
|
|
generatedRange = customRange;
|
|
generatedAreaWidth = generatedAreaHeight = areaWidth;
|
|
generatedAreaFrustumAngle = frustumAngle;
|
|
generatedAreaFrustumMultiplier = 1f + Mathf.Tan(frustumAngle * Mathf.Deg2Rad);
|
|
|
|
vertices.Clear();
|
|
indices.Clear();
|
|
|
|
Vector3 p = Vector3.zero;
|
|
|
|
// Top & Bottom vertices
|
|
for (int k = 0; k < SIDES; k++) {
|
|
float r = 2f * Mathf.PI * k / SIDES;
|
|
float cos = Mathf.Cos(r);
|
|
float sin = Mathf.Sin(r);
|
|
p.x = cos * generatedAreaWidth;
|
|
p.y = sin * generatedAreaWidth;
|
|
p.z = 0;
|
|
vertices.Add(p);
|
|
p.x *= generatedAreaFrustumMultiplier;
|
|
p.y *= generatedAreaFrustumMultiplier;
|
|
p.z = generatedRange;
|
|
vertices.Add(p);
|
|
}
|
|
|
|
// Build faces
|
|
int vertexCount = SIDES * 2;
|
|
for (int k = 0; k < vertexCount; k += 2) {
|
|
int tl = k;
|
|
int bl = k + 1;
|
|
int tr = (k + 2) % vertexCount;
|
|
int br = (k + 3) % vertexCount;
|
|
// First tri
|
|
indices.Add(tl);
|
|
indices.Add(tr);
|
|
indices.Add(bl);
|
|
// Second tri
|
|
indices.Add(tr);
|
|
indices.Add(br);
|
|
indices.Add(bl);
|
|
}
|
|
|
|
// Build top cap
|
|
vertices.Add(Vector3.zero);
|
|
int topCapCenterIndex = vertexCount;
|
|
for (int k = 0; k < vertexCount; k += 2) {
|
|
// First tri
|
|
indices.Add(k);
|
|
indices.Add(topCapCenterIndex);
|
|
indices.Add((k + 2) % vertexCount);
|
|
}
|
|
|
|
// Build bottom cap
|
|
vertices.Add(new Vector3(0, 0, generatedRange));
|
|
int bottomCapCenterIndex = vertexCount + 1;
|
|
for (int k = 0; k < vertexCount; k += 2) {
|
|
// First tri
|
|
indices.Add(bottomCapCenterIndex);
|
|
indices.Add(k + 1);
|
|
indices.Add((k + 3) % vertexCount);
|
|
}
|
|
|
|
UpdateMesh("Cylinder");
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
|
|
}
|