using UnityEngine; using UnityEditor; using System.Threading.Tasks; public class GenerateNoise : EditorWindow { enum NoiseType { WhiteNoise, WorleyNoise, WorleyEdgeSDF } enum DistanceMetric { L1, L2, LInfinity } NoiseType _type = NoiseType.WhiteNoise; int _resolution = 32; int _texelsPerCell = 8; DistanceMetric _metric = DistanceMetric.L2; float _sdfRadius = 0.5f; [MenuItem("Tools/yum_food/Generate 3D Noise")] static void Open() => GetWindow("Generate 3D Noise"); void OnGUI() { EditorGUILayout.LabelField("Noise Settings", EditorStyles.boldLabel); _type = (NoiseType)EditorGUILayout.EnumPopup("Type", _type); if (_type == NoiseType.WhiteNoise) { _resolution = EditorGUILayout.IntField("Resolution", _resolution); _resolution = Mathf.Clamp(_resolution, 2, 512); } else { _resolution = EditorGUILayout.IntField("Cell Count", _resolution); _resolution = Mathf.Clamp(_resolution, 2, 512); _texelsPerCell = EditorGUILayout.IntSlider("Texels Per Cell", _texelsPerCell, 2, 64); _metric = (DistanceMetric)EditorGUILayout.EnumPopup("Distance Metric", _metric); if (_type == NoiseType.WorleyEdgeSDF) _sdfRadius = EditorGUILayout.Slider("SDF Radius (cells)", _sdfRadius, 0.1f, 2f); EditorGUILayout.HelpBox($"Texture resolution: {_resolution * _texelsPerCell}\u00b3", MessageType.None); } EditorGUILayout.Space(); if (GUILayout.Button("Generate")) DoGenerate(); } void DoGenerate() { int res = _type == NoiseType.WhiteNoise ? _resolution : _resolution * _texelsPerCell; Texture3D tex; switch (_type) { case NoiseType.WorleyNoise: { tex = new Texture3D(res, res, res, TextureFormat.R8, false); 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(Mathf.Clamp01(f1[i]) * 255f); tex.SetPixelData(pixels, 0); break; } case NoiseType.WorleyEdgeSDF: { 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, f2, _sdfRadius); tex.SetPixelData(pixels, 0); break; } default: { tex = new Texture3D(res, res, res, TextureFormat.RGBA32, false); var pixels = new Color32[res * res * res]; GenerateWhiteNoise(pixels); tex.SetPixels32(pixels); break; } } tex.wrapMode = TextureWrapMode.Repeat; tex.filterMode = FilterMode.Bilinear; tex.Apply(); string name; switch (_type) { case NoiseType.WorleyNoise: name = $"WorleyNoise3D_{_metric}_{res}"; break; case NoiseType.WorleyEdgeSDF: name = $"WorleyEdgeSDF3D_{_metric}_{res}"; break; default: name = $"WhiteNoise3D_{res}"; break; } string path = $"Assets/yum_food/3ner/Textures/{name}.asset"; if (!AssetDatabase.IsValidFolder("Assets/yum_food/3ner/Textures")) AssetDatabase.CreateFolder("Assets/yum_food/3ner", "Textures"); AssetDatabase.CreateAsset(tex, path); AssetDatabase.SaveAssets(); EditorGUIUtility.PingObject(tex); Debug.Log($"[GenerateNoise] Saved {_type} ({res}\u00b3) to {path}"); } static void GenerateWhiteNoise(Color32[] pixels) { for (int i = 0; i < pixels.Length; i++) { byte r = (byte)Random.Range(0, 256); byte g = (byte)Random.Range(0, 256); byte b = (byte)Random.Range(0, 256); byte a = (byte)Random.Range(0, 256); pixels[i] = new Color32(r, g, b, a); } } static void GenerateWorleyField(int res, int texelsPerCell, DistanceMetric metric, 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++) for (int cy = 0; cy < cellCount; cy++) for (int cx = 0; cx < cellCount; cx++) { int ci = cx + cellCount * (cy + cellCount * cz); points[ci] = new Vector3( (cx + Random.value) * cellSize, (cy + Random.value) * cellSize, (cz + Random.value) * cellSize); } float rcpMaxDist = rcpCellSize; int count = res * res * res; var f1Arr = new float[count]; var edgeArr = new float[count]; bool isL2 = metric == DistanceMetric.L2; bool isL1 = metric == DistanceMetric.L1; Parallel.For(0, res, z => { for (int y = 0; y < res; y++) for (int x = 0; x < res; x++) { 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; } } float d1 = isL2 ? (float)System.Math.Sqrt(bestDist) : bestDist; // 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; for (int dz = -2; dz <= 2; dz++) for (int dy = -2; dy <= 2; dy++) for (int dx = -2; dx <= 2; dx++) { 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; } }); f1 = f1Arr; edgeDist = edgeArr; } // 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 normalized = edgeDist[i] / sdfRadius * 0.5f + 0.5f; pixels[i] = (byte)Mathf.RoundToInt(Mathf.Clamp01(normalized) * 255f); } } }