From 4bf004c7e9c6eb2edf497b075ee01375ae878ff1 Mon Sep 17 00:00:00 2001 From: yum Date: Sun, 15 Mar 2026 19:44:17 -0700 Subject: Optimize noise baker --- Scripts/Editor/GenerateNoise.cs | 163 +++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 62 deletions(-) (limited to 'Scripts') diff --git a/Scripts/Editor/GenerateNoise.cs b/Scripts/Editor/GenerateNoise.cs index 324b5b5..73a5e20 100644 --- a/Scripts/Editor/GenerateNoise.cs +++ b/Scripts/Editor/GenerateNoise.cs @@ -1,5 +1,6 @@ using UnityEngine; using UnityEditor; +using System.Threading.Tasks; public class GenerateNoise : EditorWindow { @@ -54,7 +55,7 @@ public class GenerateNoise : EditorWindow GenerateWorleyField(res, _texelsPerCell, _metric, out var f1, out _); var pixels = new byte[res * res * res]; for (int i = 0; i < pixels.Length; i++) - pixels[i] = (byte)Mathf.RoundToInt(f1[i] * 255f); + pixels[i] = (byte)Mathf.RoundToInt(Mathf.Clamp01(f1[i]) * 255f); tex.SetPixelData(pixels, 0); break; } @@ -63,7 +64,7 @@ public class GenerateNoise : EditorWindow tex = new Texture3D(res, res, res, TextureFormat.R8, false); GenerateWorleyField(res, _texelsPerCell, _metric, out var f1, out var f2); var pixels = new byte[res * res * res]; - GenerateEdgeSDF(pixels, f1, f2, _sdfRadius); + GenerateEdgeSDF(pixels, f2, _sdfRadius); tex.SetPixelData(pixels, 0); break; } @@ -111,24 +112,12 @@ public class GenerateNoise : EditorWindow } } - static float Distance(Vector3 a, Vector3 b, DistanceMetric metric) - { - float dx = Mathf.Abs(a.x - b.x); - float dy = Mathf.Abs(a.y - b.y); - float dz = Mathf.Abs(a.z - b.z); - switch (metric) - { - case DistanceMetric.L1: return dx + dy + dz; - case DistanceMetric.LInfinity: return Mathf.Max(dx, Mathf.Max(dy, dz)); - default: return Mathf.Sqrt(dx * dx + dy * dy + dz * dz); - } - } - static void GenerateWorleyField(int res, int texelsPerCell, DistanceMetric metric, - out float[] f1, out float[] f2) + out float[] f1, out float[] edgeDist) { int cellCount = Mathf.Max(1, res / texelsPerCell); float cellSize = (float)res / cellCount; + float rcpCellSize = 1f / cellSize; var points = new Vector3[cellCount * cellCount * cellCount]; for (int cz = 0; cz < cellCount; cz++) @@ -142,68 +131,118 @@ public class GenerateNoise : EditorWindow (cz + Random.value) * cellSize); } - float maxDist = cellSize; + float rcpMaxDist = rcpCellSize; int count = res * res * res; - f1 = new float[count]; - f2 = new float[count]; + var f1Arr = new float[count]; + var edgeArr = new float[count]; + bool isL2 = metric == DistanceMetric.L2; + bool isL1 = metric == DistanceMetric.L1; - for (int z = 0; z < res; z++) - for (int y = 0; y < res; y++) - for (int x = 0; x < res; x++) + Parallel.For(0, res, z => { - var pos = new Vector3(x + 0.5f, y + 0.5f, z + 0.5f); - - int cx0 = Mathf.FloorToInt(pos.x / cellSize); - int cy0 = Mathf.FloorToInt(pos.y / cellSize); - int cz0 = Mathf.FloorToInt(pos.z / cellSize); - - float d1 = float.MaxValue; - float d2 = float.MaxValue; - - for (int dz = -1; dz <= 1; dz++) - for (int dy = -1; dy <= 1; dy++) - for (int dx = -1; dx <= 1; dx++) + for (int y = 0; y < res; y++) + for (int x = 0; x < res; x++) { - int ncx = cx0 + dx; - int ncy = cy0 + dy; - int ncz = cz0 + dz; + float px = x + 0.5f, py = y + 0.5f, pz = z + 0.5f; + int cx0 = (int)(px * rcpCellSize); + int cy0 = (int)(py * rcpCellSize); + int cz0 = (int)(pz * rcpCellSize); + + // Pass 1: Find closest point (metric determines cell ownership) + float bestDist = float.MaxValue; + float p1x = 0, p1y = 0, p1z = 0; + int p1nx = 0, p1ny = 0, p1nz = 0; + + for (int dz = -1; dz <= 1; dz++) + for (int dy = -1; dy <= 1; dy++) + for (int dx = -1; dx <= 1; dx++) + { + int ncx = cx0 + dx, ncy = cy0 + dy, ncz = cz0 + dz; + int wcx = ((ncx % cellCount) + cellCount) % cellCount; + int wcy = ((ncy % cellCount) + cellCount) % cellCount; + int wcz = ((ncz % cellCount) + cellCount) % cellCount; + var pt = points[wcx + cellCount * (wcy + cellCount * wcz)]; + float qx = pt.x + (ncx - wcx) * cellSize; + float qy = pt.y + (ncy - wcy) * cellSize; + float qz = pt.z + (ncz - wcz) * cellSize; + + float ex = qx - px, ey = qy - py, ez = qz - pz; + float dist; + if (isL1) + { + float ax = ex < 0 ? -ex : ex, ay = ey < 0 ? -ey : ey, az = ez < 0 ? -ez : ez; + dist = ax + ay + az; + } + else if (isL2) + { + dist = ex * ex + ey * ey + ez * ez; // compare squared, defer sqrt + } + else + { + float ax = ex < 0 ? -ex : ex, ay = ey < 0 ? -ey : ey, az = ez < 0 ? -ez : ez; + dist = ax > ay ? (ax > az ? ax : az) : (ay > az ? ay : az); + } + + if (dist < bestDist) + { + bestDist = dist; + p1x = qx; p1y = qy; p1z = qz; + p1nx = ncx; p1ny = ncy; p1nz = ncz; + } + } - int wcx = ((ncx % cellCount) + cellCount) % cellCount; - int wcy = ((ncy % cellCount) + cellCount) % cellCount; - int wcz = ((ncz % cellCount) + cellCount) % cellCount; + float d1 = isL2 ? (float)System.Math.Sqrt(bestDist) : bestDist; - Vector3 pt = points[wcx + cellCount * (wcy + cellCount * wcz)]; - pt.x += (ncx - wcx) * cellSize; - pt.y += (ncy - wcy) * cellSize; - pt.z += (ncz - wcz) * cellSize; + // Pass 2: Squared distance to nearest bisecting plane (Euclidean regardless + // of metric — linear under trilinear filtering). One sqrt at the end. + float r1x = p1x - px, r1y = p1y - py, r1z = p1z - pz; + float minEdgeSq = float.MaxValue; - float dist = Distance(pos, pt, metric); - if (dist < d1) + for (int dz = -2; dz <= 2; dz++) + for (int dy = -2; dy <= 2; dy++) + for (int dx = -2; dx <= 2; dx++) { - d2 = d1; - d1 = dist; - } - else if (dist < d2) - { - d2 = dist; + int ncx = cx0 + dx, ncy = cy0 + dy, ncz = cz0 + dz; + if (ncx == p1nx && ncy == p1ny && ncz == p1nz) continue; + + int wcx = ((ncx % cellCount) + cellCount) % cellCount; + int wcy = ((ncy % cellCount) + cellCount) % cellCount; + int wcz = ((ncz % cellCount) + cellCount) % cellCount; + var pt = points[wcx + cellCount * (wcy + cellCount * wcz)]; + float qx = pt.x + (ncx - wcx) * cellSize; + float qy = pt.y + (ncy - wcy) * cellSize; + float qz = pt.z + (ncz - wcz) * cellSize; + + float r2x = qx - px, r2y = qy - py, r2z = qz - pz; + float ax = r2x - r1x, ay = r2y - r1y, az = r2z - r1z; + // num = dot(r1 + r2, axis), sign determines which side of the plane + float num = (r1x + r2x) * ax + (r1y + r2y) * ay + (r1z + r2z) * az; + if (num > 0f) + { + // d = num / (2 * |axis|), so d² = num² / (4 * |axis|²) + float denSq = ax * ax + ay * ay + az * az; + float dSq = num * num / (4f * denSq); + if (dSq < minEdgeSq) minEdgeSq = dSq; + } } + + int i = x + res * (y + res * z); + f1Arr[i] = d1 * rcpMaxDist; + edgeArr[i] = (float)System.Math.Sqrt(minEdgeSq) * rcpMaxDist; } + }); - int i = x + res * (y + res * z); - f1[i] = Mathf.Clamp01(d1 / maxDist); - f2[i] = Mathf.Clamp01(d2 / maxDist); - } + f1 = f1Arr; + edgeDist = edgeArr; } - // Convert F2-F1 into a signed distance field relative to the cell boundary (F2-F1 = 0). - // Output: 0.5 = on the boundary, 0 = clamped near-side, 1 = clamped far-side. - // sdfRadius controls how much F2-F1 range maps to [0,1] — smaller = more precision near edges. - static void GenerateEdgeSDF(byte[] pixels, float[] f1, float[] f2, float sdfRadius) + // Convert bisecting-plane distance into [0,1]: 0.5 = on boundary, 1 = deep inside cell. + // sdfRadius controls how much distance maps to [0,1] — smaller = more precision near edges. + static void GenerateEdgeSDF(byte[] pixels, float[] edgeDist, float sdfRadius) { for (int i = 0; i < pixels.Length; i++) { - float sd = f2[i] - f1[i]; - float normalized = sd / sdfRadius * 0.5f + 0.5f; + float normalized = edgeDist[i] / sdfRadius * 0.5f + 0.5f; pixels[i] = (byte)Mathf.RoundToInt(Mathf.Clamp01(normalized) * 255f); } } -- cgit v1.2.3