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 indices = new List(); 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); } }