diff options
| author | yum <yum.food.vr@gmail.com> | 2026-03-17 15:49:21 -0700 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-03-17 15:49:25 -0700 |
| commit | 1784064c7a39a69203e8975167addf1915f940bd (patch) | |
| tree | 4adc272435efcf54ac7ed8399aa33acbf422a959 | |
| parent | 019c24186c87fd747aae1512abf4d4690e3aca07 (diff) | |
Add faster 3-in 1-out hasher for domain warping
Goes from ~1.7 ms/frame to ~1.1 ms/frame in 10-octave microbenchmark.
| -rwxr-xr-x | 3ner.shader | 3 | ||||
| -rw-r--r-- | Scripts/Editor/GenerateNoise.cs | 9 | ||||
| -rwxr-xr-x | features.cginc | 1 | ||||
| -rwxr-xr-x | globals.cginc | 4 | ||||
| -rwxr-xr-x | math.cginc | 67 | ||||
| -rwxr-xr-x | pbr.cginc | 16 |
6 files changed, 70 insertions, 30 deletions
diff --git a/3ner.shader b/3ner.shader index 569ddc5..6af7ec8 100755 --- a/3ner.shader +++ b/3ner.shader @@ -39,7 +39,7 @@ Shader "yum_food/3ner" _Glossiness("Smoothness", Range(0, 1)) = 0.5 [HideInInspector] m_start_IBL("Image-based lighting", Float) = 0 - _Exposure_Occlusion("Exposure Occlusion", Range(0.001, 10)) = 0.2 + _Exposure_Occlusion("Exposure Occlusion", Range(0.001, 10)) = 1 //ifex _Bent_Normals_Enabled==0 [HideInInspector] m_start_Bent_Normals("Bent Normals", Float) = 0 @@ -1013,7 +1013,6 @@ Shader "yum_food/3ner" //ifex _Kintsugi_Enabled==0 [HideInInspector] m_start_Kintsugi("Kintsugi", Float) = 0 [ThryToggle(_KINTSUGI)] _Kintsugi_Enabled("Enable", Float) = 0 - _Kintsugi_Noise("Noise", 3D) = "gray" {} [ThryToggle(_KINTSUGI_PROCEDURAL)] _Kintsugi_Procedural("Procedural noise", Float) = 0 [ThryToggle(_KINTSUGI_NOISE_INVERT)] _Kintsugi_Noise_Inverted("Invert noise", Float) = 0 _Kintsugi_Scale("Scale", Vector) = (1, 1, 1, 0) diff --git a/Scripts/Editor/GenerateNoise.cs b/Scripts/Editor/GenerateNoise.cs index 73a5e20..472d5ff 100644 --- a/Scripts/Editor/GenerateNoise.cs +++ b/Scripts/Editor/GenerateNoise.cs @@ -47,11 +47,12 @@ public class GenerateNoise : EditorWindow int res = _type == NoiseType.WhiteNoise ? _resolution : _resolution * _texelsPerCell; Texture3D tex; + bool makeMipMaps = true; switch (_type) { case NoiseType.WorleyNoise: { - tex = new Texture3D(res, res, res, TextureFormat.R8, false); + tex = new Texture3D(res, res, res, TextureFormat.R8, makeMipMaps); GenerateWorleyField(res, _texelsPerCell, _metric, out var f1, out _); var pixels = new byte[res * res * res]; for (int i = 0; i < pixels.Length; i++) @@ -61,7 +62,7 @@ public class GenerateNoise : EditorWindow } case NoiseType.WorleyEdgeSDF: { - tex = new Texture3D(res, res, res, TextureFormat.R8, false); + tex = new Texture3D(res, res, res, TextureFormat.R8, makeMipMaps); GenerateWorleyField(res, _texelsPerCell, _metric, out var f1, out var f2); var pixels = new byte[res * res * res]; GenerateEdgeSDF(pixels, f2, _sdfRadius); @@ -70,7 +71,7 @@ public class GenerateNoise : EditorWindow } default: { - tex = new Texture3D(res, res, res, TextureFormat.RGBA32, false); + tex = new Texture3D(res, res, res, TextureFormat.RGB24, makeMipMaps); var pixels = new Color32[res * res * res]; GenerateWhiteNoise(pixels); tex.SetPixels32(pixels); @@ -79,7 +80,7 @@ public class GenerateNoise : EditorWindow } tex.wrapMode = TextureWrapMode.Repeat; - tex.filterMode = FilterMode.Bilinear; + tex.filterMode = FilterMode.Trilinear; tex.Apply(); string name; diff --git a/features.cginc b/features.cginc index 79c5001..c94f7ab 100755 --- a/features.cginc +++ b/features.cginc @@ -16,7 +16,6 @@ //ifex _Kintsugi_Enabled==0 #pragma shader_feature_local _KINTSUGI -#pragma shader_feature_local _KINTSUGI_PROCEDURAL #pragma shader_feature_local _KINTSUGI_NOISE_INVERT #pragma shader_feature_local _KINTSUGI_DOMAIN_WARPING //endex diff --git a/globals.cginc b/globals.cginc index d32feeb..4873108 100755 --- a/globals.cginc +++ b/globals.cginc @@ -84,6 +84,7 @@ float _Bent_Normals_Strength; #endif // _BENT_NORMALS #if defined(_MARBLE) +Texture3D _Marble_Noise; texture2D _Marble_Post_Ramp; float3 _Marble_Scale; float _Marble_Octaves; @@ -102,7 +103,6 @@ float3 _Marble_Offset; #endif // _MARBLE_OFFSET #if defined(_KINTSUGI) -texture3D _Kintsugi_Noise; float3 _Kintsugi_Scale; float _Kintsugi_Threshold; float _Kintsugi_Width; @@ -112,7 +112,7 @@ float _Kintsugi_Metallic; #endif // _KINTSUGI #if defined(_KINTSUGI_DOMAIN_WARPING) -texture3D _Kintsugi_Domain_Warping_Noise; +Texture3D _Kintsugi_Domain_Warping_Noise; float3 _Kintsugi_Domain_Warping_Scale; float _Kintsugi_Domain_Warping_Strength; float _Kintsugi_Domain_Warping_Octaves; @@ -19,6 +19,9 @@ #define F1_TO_F3(x) float3((x), (x), (x)) +// Remaps [0, INT_MAX] to [0, 1] +#define UINT_TO_UNIT (1.0 / 4294967296.0) + float sin_noise_3d(float3 uvw) { return sin(uvw[0]) * sin(uvw[1]) * sin(uvw[2]); } @@ -111,24 +114,48 @@ float4 alpha_blend(float4 front, float4 back) { return float4(front.rgb * front.a + back.rgb * (1 - front.a), front.a + back.a * (1 - front.a)); } -// Cheap procedural 3D hash -> [0,1]^3. Based on the "hashwithoutsine" family. +// 3 in 3 out hash. Based on the "hashwithoutsine" family. float3 hash33_fast(float3 p) { p = frac(p * float3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yxz + 33.33); return frac((p.xxy + p.yxx) * p.zyx); } +// O'Neill-style PCG 32-bit output permutation (RXS-M-XS). +uint pcg32(uint input) { + uint state = input * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +// 3 in 1 out hash. Mix three lanes in integer space, then PCG-finalize once. +uint hash31_u32(uint3 p) { + uint seed = p.x * 0x2c1b3c6du; + seed ^= p.y * 0x297a2d39u; + seed ^= p.z * 0x1b56c4e9u; + return pcg32(seed); +} + +// Float wrapper for arbitrary inputs. +float hash31_ff(float3 p) { + return hash31_u32(asuint(p)) * UINT_TO_UNIT; +} + +float hash31_if(int3 p) { + return hash31_u32(p) * UINT_TO_UNIT; +} + // Procedural value noise in [0,1]^3 — trilinear interpolation of hashed corners. float3 value_noise3(float3 p) { - float3 i = floor(p); + int3 i = (int3)floor(p); float3 f = frac(p); float3 u = f * f * (3.0 - 2.0 * f); return lerp( - lerp(lerp(hash33_fast(i + float3(0, 0, 0)), hash33_fast(i + float3(1, 0, 0)), u.x), - lerp(hash33_fast(i + float3(0, 1, 0)), hash33_fast(i + float3(1, 1, 0)), u.x), u.y), - lerp(lerp(hash33_fast(i + float3(0, 0, 1)), hash33_fast(i + float3(1, 0, 1)), u.x), - lerp(hash33_fast(i + float3(0, 1, 1)), hash33_fast(i + float3(1, 1, 1)), u.x), u.y), + lerp(lerp(hash31_if(i + int3(0, 0, 0)), hash31_if(i + int3(1, 0, 0)), u.x), + lerp(hash31_if(i + int3(0, 1, 0)), hash31_if(i + int3(1, 1, 0)), u.x), u.y), + lerp(lerp(hash31_if(i + int3(0, 0, 1)), hash31_if(i + int3(1, 0, 1)), u.x), + lerp(hash31_if(i + int3(0, 1, 1)), hash31_if(i + int3(1, 1, 1)), u.x), u.y), u.z); } @@ -149,22 +176,31 @@ float3 domain_warp_procedural(float3 uvw, float strength, return noise; } +float3 value_noise_3d_tex(Texture3D tex, SamplerState s, float3 p) { + float w, h, d; + tex.GetDimensions(w, h, d); + float3 res = float3(w, h, d); + + p *= res; + float3 i = floor(p); + float3 f = frac(p); + float3 u = f * f * (3.0 - 2.0 * f); + + return tex.Sample(s, (i + 0.5 + u) / res).rgb; +} + // Domain warping using a 3D noise texture. Texture should have an EV of -// 0.5. -float3 domain_warp_3d_tex(texture3D noise_tex, float3 uvw, float strength, - uint octaves, float lacunarity, float gain) { +// 0.5. Uses cubic interpolation between lattice points (same semantics as +// domain_warp_procedural / value_noise3). +float3 domain_warp_3d_tex(Texture3D noise_tex, SamplerState s, float3 uvw, + float strength, uint octaves, float lacunarity, float gain) { float3 noise = 0; float g = 1; - float3 uvw_dx = ddx(uvw); - float3 uvw_dy = ddy(uvw); - for (uint ii = 0; ii < octaves; ++ii) { - noise += noise_tex.SampleGrad(aniso4_trilinear_repeat_s, uvw + noise * strength, uvw_dx, uvw_dy).rgb * g; + noise += value_noise_3d_tex(noise_tex, s, uvw + noise * strength) * g; uvw *= lacunarity; g *= gain; - uvw_dx *= lacunarity; - uvw_dy *= lacunarity; } // Normalize: geometric series 1 + r + ... + r^{n-1} = (1 - r^n) / (1 - r) @@ -204,4 +240,3 @@ float voronoi_edge_distance(float3 x) { } #endif // __MATH_INC - @@ -132,8 +132,19 @@ void apply_marble(float3 world_pos, inout float3 albedo) { uvw += offset; +#if 1 float3 noise = domain_warp_procedural(uvw, _Marble_Strength, _Marble_Octaves, _Marble_Lacunarity, _Marble_Gain); +#elif 1 + float3 noise = hash31_fast(uvw*32); +#elif 0 + float3 noise = value_noise3(uvw); +#elif 0 + float3 noise = value_noise_3d_tex(_Marble_Noise, trilinear_repeat_s, uvw); +#else + float3 noise = domain_warp_3d_tex(_Marble_Noise, trilinear_repeat_s, uvw, + _Marble_Strength, _Marble_Octaves, _Marble_Lacunarity, _Marble_Gain); +#endif noise = _Marble_Post_Ramp.Sample(linear_clamp_s, float2(noise.x, 0)); @@ -153,12 +164,7 @@ void apply_kintsugi(float3 world_pos, inout float3 albedo, inout float smoothnes uvw += warp; #endif -#if defined(_KINTSUGI_PROCEDURAL) float mask = voronoi_edge_distance(uvw) + 0.5f; -#else - float mask = _Kintsugi_Noise.Sample(aniso4_trilinear_repeat_s, uvw); -#endif - float width = max(fwidth(mask) * 0.5f, _Kintsugi_Width); float threshold = _Kintsugi_Threshold; mask = smoothstep(threshold - width, threshold + width, mask); |
