using UnityEngine; using UnityEditor; 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(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, f1, 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 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) { int cellCount = Mathf.Max(1, res / texelsPerCell); float cellSize = (float)res / cellCount; 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 maxDist = cellSize; int count = res * res * res; f1 = new float[count]; f2 = new float[count]; for (int z = 0; z < res; z++) for (int y = 0; y < res; y++) for (int x = 0; x < res; x++) { 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++) { int ncx = cx0 + dx; int ncy = cy0 + dy; int ncz = cz0 + dz; int wcx = ((ncx % cellCount) + cellCount) % cellCount; int wcy = ((ncy % cellCount) + cellCount) % cellCount; int wcz = ((ncz % cellCount) + cellCount) % cellCount; Vector3 pt = points[wcx + cellCount * (wcy + cellCount * wcz)]; pt.x += (ncx - wcx) * cellSize; pt.y += (ncy - wcy) * cellSize; pt.z += (ncz - wcz) * cellSize; float dist = Distance(pos, pt, metric); if (dist < d1) { d2 = d1; d1 = dist; } else if (dist < d2) { d2 = dist; } } int i = x + res * (y + res * z); f1[i] = Mathf.Clamp01(d1 / maxDist); f2[i] = Mathf.Clamp01(d2 / maxDist); } } // 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) { for (int i = 0; i < pixels.Length; i++) { float sd = f2[i] - f1[i]; float normalized = sd / sdfRadius * 0.5f + 0.5f; pixels[i] = (byte)Mathf.RoundToInt(Mathf.Clamp01(normalized) * 255f); } } }