Files
FORT-BO/Assets/Bakery/BakeryVolume.cs
2026-08-06 18:56:32 +03:00

427 lines
15 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.IMGUI.Controls;
#endif
[HelpURL("https://geom.io/bakery/wiki/index.php?title=Manual#Bakery_Volume")]
[ExecuteInEditMode]
public class BakeryVolume : MonoBehaviour
{
public enum Encoding
{
// HDR L1 SH, half-float:
// Tex0 = L0, L1z.r
// Tex1 = L1x, L1z.g
// Tex2 = L1y, L1z.b
Half4,
// LDR L1 SH, 8-bit. Components are stored the same way as in Half4,
// but L1 must be unpacked following way:
// L1n = (L1n * 2 - 1) * L0 * 0.5 + 0.5
RGBA8,
// LDR L1 SH with monochrome directional component (= single color and direction), 8-bit.
// Tex0 = L0 (alpha unused)
// Tex1 = L1xyz (alpha unused)
RGBA8Mono
}
public enum ShadowmaskEncoding
{
RGBA8,
A8
}
public bool enableBaking = true;
public Bounds bounds = new Bounds(Vector3.zero, Vector3.one);
public bool adaptiveRes = true;
public float voxelsPerUnit = 0.5f;
public int resolutionX = 16;
public int resolutionY = 16;
public int resolutionZ = 16;
public Encoding encoding = Encoding.Half4;
public ShadowmaskEncoding shadowmaskEncoding = ShadowmaskEncoding.RGBA8;
public bool firstLightIsAlwaysAlpha = false;
public bool denoise = false;
public bool isGlobal = false;
public Texture3D bakedTexture0, bakedTexture1, bakedTexture2, bakedTexture3, bakedMask;
public bool supportRotationAfterBake;
public bool rotateAroundY;
public bool _rotateAroundXYZ;
public int multiVolumePriority = 0;
public static BakeryVolume globalVolume;
public static bool showAll = false;
#if UNITY_EDITOR
// Visualization
static int probesPreviewCount = 1;
static int probesPreviewCountCached = -1;
static Material probesPreviewMaterial;
static ComputeBuffer probesPreviewPositionBuffer;
static ComputeBuffer probesPreviewArgsBuffer;
static uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
static Vector3[] probesPreviewPositions;
static Vector3 probesPreviewResolution;
public static bool showIndirectOnly = false;
public static bool showProbesPreview = false;
public static float probesPreviewMul = 1.0f;
public static float probesPreviewSize = 0.15f;
public static bool showBakedTexture = true;
static Mesh probePreviewMesh; // shared mesh
static bool callbackSet = false;
#endif
/*private void OnDrawGizmosSelected()
{
if (showProbesPreview) return;
Gizmos.color = new Color(0.4f, 1f, 0.0f, 0.15f);
Gizmos.DrawCube( bounds.center , -bounds.size);
Gizmos.color = new Color(1f, 0.7f, 0.2f, 0.22f);
Gizmos.DrawCube( bounds.center , bounds.size);
}*/
Transform tform;
public Vector3 GetMin()
{
if (rotateAroundY)
{
var sc = GetRotationY();
var p = bounds.min - bounds.center;
return new Vector3(p.x*sc.y + p.z*sc.x,
p.y,
p.x*-sc.x + p.z*sc.y) + bounds.center;
}
return bounds.min;
}
public Vector3 GetMax()
{
if (rotateAroundY)
{
var sc = GetRotationY();
var p = bounds.max - bounds.center;
return new Vector3(p.x*sc.y + p.z*sc.x,
p.y,
p.x*-sc.x + p.z*sc.y) + bounds.center;
}
return bounds.max;
}
Vector3 TransformPoint(Vector3 p, Vector3 center, Vector2 sc)
{
p -= center;
return new Vector3(p.x*sc.y + p.z*sc.x,
p.y,
p.x*-sc.x + p.z*sc.y) + center;
}
public Vector4 GetWorldXZMinMax()
{
var bmin = bounds.min;
var bmax = bounds.max;
if (rotateAroundY)
{
var sc = GetRotationY();
var center = bounds.center;
var p00 = bounds.min;
var p10 = new Vector3(bounds.max.x, 0, bounds.min.z);
var p11 = bounds.max;
var p01 = new Vector3(bounds.min.x, 0, bounds.max.z);
p00 = TransformPoint(p00, center, sc);
p10 = TransformPoint(p10, center, sc);
p11 = TransformPoint(p11, center, sc);
p01 = TransformPoint(p01, center, sc);
float minx = Mathf.Min(Mathf.Min(Mathf.Min(p00.x, p10.x), p11.x), p01.x);
float minz = Mathf.Min(Mathf.Min(Mathf.Min(p00.z, p10.z), p11.z), p01.z);
float maxx = Mathf.Max(Mathf.Max(Mathf.Max(p00.x, p10.x), p11.x), p01.x);
float maxz = Mathf.Max(Mathf.Max(Mathf.Max(p00.z, p10.z), p11.z), p01.z);
return new Vector4(minx, minz, maxx, maxz);
}
return new Vector4(bmin.x, bmin.z, bmax.x, bmax.z);
}
public Vector3 GetMaxXMinZ()
{
if (rotateAroundY)
{
var sc = GetRotationY();
var p = new Vector3(bounds.max.x, 0, bounds.min.z) - bounds.center;
return new Vector3(p.x*sc.y + p.z*sc.x,
p.y,
p.x*-sc.x + p.z*sc.y) + bounds.center;
}
return bounds.max;
}
public Vector3 GetInvSize()
{
var b = bounds;
return new Vector3(1.0f/b.size.x, 1.0f/b.size.y, 1.0f/b.size.z);;
}
public Matrix4x4 GetMatrix()
{
if (tform == null) tform = transform;
return Matrix4x4.TRS(tform.position, tform.rotation, Vector3.one).inverse;
}
public Vector2 GetRotationY()
{
if (!rotateAroundY) return new Vector2(0,1);
if (tform == null) tform = transform;
float a = tform.eulerAngles.y * Mathf.Deg2Rad;
return new Vector2(Mathf.Sin(a), Mathf.Cos(a));
}
public void SetGlobalParams()
{
Shader.SetGlobalTexture("_Volume0", bakedTexture0);
Shader.SetGlobalTexture("_Volume1", bakedTexture1);
Shader.SetGlobalTexture("_Volume2", bakedTexture2);
if (bakedTexture3 != null) Shader.SetGlobalTexture("_Volume3", bakedTexture3);
Shader.SetGlobalTexture("_VolumeMask", bakedMask);
Shader.SetGlobalVector("_GlobalVolumeMin", GetMin());
Shader.SetGlobalVector("_GlobalVolumeInvSize", GetInvSize());
if (supportRotationAfterBake) Shader.SetGlobalMatrix("_GlobalVolumeMatrix", GetMatrix());
if (rotateAroundY) Shader.SetGlobalVector("_GlobalVolumeRY", GetRotationY());
if (bakedTexture0 != null) Shader.SetGlobalVector("_GlobalVolumeVoxelSize", new Vector3(1.0f/bakedTexture0.width, 1.0f/bakedTexture0.height, 1.0f/bakedTexture0.depth));
}
public void UpdateBounds()
{
var pos = transform.position;
var size = bounds.size;
bounds = new Bounds(pos, size);
}
public void OnEnable()
{
if (isGlobal)
{
globalVolume = this;
SetGlobalParams();
}
}
#if UNITY_EDITOR
static void ProbePreviewUpdate()
{
UnityEditor.SceneView.RepaintAll();
}
private void Update()
{
if (!showProbesPreview && callbackSet)
{
EditorApplication.update -= ProbePreviewUpdate;
callbackSet = false;
probesPreviewCountCached = -1;
}
else if (showProbesPreview && !callbackSet)
{
EditorApplication.update -= ProbePreviewUpdate;
EditorApplication.update += ProbePreviewUpdate;
callbackSet = true;
}
if (Selection.activeObject != gameObject || !showProbesPreview)
{
// Reset buffers
probesPreviewCountCached = -1;
return;
}
if (probePreviewMesh == null)
{
probePreviewMesh = GenerateSphereMesh();
}
var bakedTexPreview = showBakedTexture && bakedTexture0 != null;
if (bakedTexPreview)
{
probesPreviewResolution = new Vector3(bakedTexture0.width, bakedTexture0.height, bakedTexture0.depth);
}
else
{
probesPreviewResolution = new Vector3(resolutionX, resolutionY, resolutionZ);
}
probesPreviewCount = (int)(probesPreviewResolution.x * probesPreviewResolution.y * probesPreviewResolution.z);
if (probesPreviewCountCached != probesPreviewCount || probesPreviewMaterial == null)
{
ProbesPreviewCreateResources();
probesPreviewCountCached = probesPreviewCount;
}
var probesSize = probesPreviewSize / Mathf.Max(Mathf.Max(probesPreviewResolution.x / bounds.size.x,
probesPreviewResolution.y / bounds.size.y),
probesPreviewResolution.z / bounds.size.z);
probesPreviewMaterial.SetFloat("_ProbeSize", probesSize);
probesPreviewMaterial.SetFloat("_ProbeMul", probesPreviewMul);
probesPreviewMaterial.SetVector("_GridPosition", bounds.center);
probesPreviewMaterial.SetVector("_GridSize", bounds.size);
probesPreviewMaterial.SetTexture("_VolumePreview0", bakedTexture0);
probesPreviewMaterial.SetTexture("_VolumePreview1", bakedTexture1);
probesPreviewMaterial.SetTexture("_VolumePreview2", bakedTexture2);
probesPreviewMaterial.SetTexture("_VolumePreview3", bakedTexture3);
probesPreviewMaterial.SetTexture("_VolumePreviewMask", bakedMask);
float bakedMaskState = -1;
if (bakedMask != null)
bakedMaskState = shadowmaskEncoding == ShadowmaskEncoding.RGBA8 ? 1 : 2;
if (showIndirectOnly)
bakedMaskState = 0;
probesPreviewMaterial.SetFloat("_ShadowMask", bakedMaskState);
probesPreviewMaterial.SetFloat("_Encoding", (int)encoding);
probesPreviewMaterial.SetFloat("_EmptyProbes", bakedTexPreview ? 0 : 1);
probesPreviewMaterial.SetBuffer("positionBuffer", probesPreviewPositionBuffer);
// bool isMobilePlatform = EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android ||
// EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS;
// if(isMobilePlatform)
// probesPreviewMaterial.EnableKeyword("BAKERY_COMPRESSED_VOLUME_RGBM");
// else
// probesPreviewMaterial.DisableKeyword("BAKERY_COMPRESSED_VOLUME_RGBM");
Graphics.DrawMeshInstancedIndirect(probePreviewMesh, 0, probesPreviewMaterial, bounds, probesPreviewArgsBuffer);
}
private void ProbesPreviewCreateResources()
{
if (probesPreviewMaterial == null)
{
probesPreviewMaterial = new Material(Shader.Find("Hidden/ftVolumePreview"));
}
// Indirect args
if (probesPreviewArgsBuffer != null) probesPreviewArgsBuffer.Release();
probesPreviewArgsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
args[0] = (uint)probePreviewMesh.GetIndexCount(0);
args[1] = (uint)probesPreviewCount;
args[2] = 0;//(uint)probePreviewMesh.GetIndexStart(0);
args[3] = 0;//(uint)probePreviewMesh.GetBaseVertex(0);
probesPreviewArgsBuffer.SetData(args);
// Positions
if (probesPreviewPositionBuffer != null) probesPreviewPositionBuffer.Release();
probesPreviewPositionBuffer = new ComputeBuffer(probesPreviewCount, 12);
probesPreviewPositions = new Vector3[probesPreviewCount];
int index = 0;
for (int x = 0; x < probesPreviewResolution.x; x++)
{
for (int y = 0; y < probesPreviewResolution.y; y++)
{
for (int z = 0; z < probesPreviewResolution.z; z++)
{
probesPreviewPositions[index] = new Vector3((x + 0.5f) / (float)probesPreviewResolution.x - 0.5f,
(y + 0.5f) / (float)probesPreviewResolution.y - 0.5f,
(z + 0.5f) / (float)probesPreviewResolution.z - 0.5f);
index++;
}
}
}
probesPreviewPositionBuffer.SetData(probesPreviewPositions);
}
void OnDestroy()
{
if (probesPreviewPositionBuffer != null) probesPreviewPositionBuffer.Release();
probesPreviewPositionBuffer = null;
if (probesPreviewArgsBuffer != null) probesPreviewArgsBuffer.Release();
probesPreviewArgsBuffer = null;
}
private static Mesh GenerateSphereMesh(int stacks = 4, int slices = 6, float radius = 0.5f)
{
int vertexCount = (stacks + 1) * (slices + 1);
Vector3[] vertices = new Vector3[vertexCount];
Vector3[] normal = new Vector3[vertexCount];
List<int> indices = new List<int>();
Mesh mesh = new Mesh();
int index = 0;
for (int stack = 0; stack <= stacks; stack++)
{
float thetaV = Mathf.PI * ((float)stack / (float)stacks);
float r = radius * Mathf.Sin(thetaV);
float y = radius * Mathf.Cos(thetaV);
for (int slice = 0; slice <= slices; slice++)
{
float thetaH = 2.0f * Mathf.PI * ((float)slice / (float)slices);
float x = r * Mathf.Cos(thetaH);
float z = r * Mathf.Sin(thetaH);
vertices[index] = new Vector3(x, y, z);
index++;
}
}
for (int stack = 0; stack < stacks; stack++)
{
for (int slice = 0; slice < slices; slice++)
{
int count = slice + ((slices + 1) * stack);
indices.Add(count);
indices.Add(count + 1);
indices.Add(count + slices + 2);
indices.Add(count);
indices.Add(count + slices + 2);
indices.Add(count + slices + 1);
}
}
for (int i = 0; i < vertices.Length; i++)
{
normal[i] = vertices[i].normalized;
}
mesh.vertices = vertices;
mesh.normals = normal;
mesh.triangles = indices.ToArray();
//mesh.Optimize();
MeshUtility.Optimize(mesh);
mesh.RecalculateBounds();
return mesh;
}
#endif
void OnDrawGizmos()
{
if (!showAll) return;
Gizmos.color = new Color(1, 1, 1, 0.35f);
var tform = transform;
if (rotateAroundY)
{
var e = tform.eulerAngles;
var r = Quaternion.Euler(0, e.y, 0);
Gizmos.matrix = Matrix4x4.TRS(r * -tform.position + tform.position, r, Vector3.one);
}
Gizmos.DrawWireCube(tform.position, bounds.size);
}
}