diff options
| -rw-r--r-- | 2ner.cginc | 11 | ||||
| -rw-r--r-- | 2ner.shader | 6 | ||||
| -rw-r--r-- | Editor/generate_3d_noise.cs | 257 | ||||
| -rw-r--r-- | Scripts/gen_sdf | 53 | ||||
| -rw-r--r-- | custom30.cginc | 3 | ||||
| -rw-r--r-- | decals.cginc | 10 | ||||
| -rw-r--r-- | features.cginc | 1 | ||||
| -rw-r--r-- | fog.cginc | 28 | ||||
| -rw-r--r-- | globals.cginc | 6 | ||||
| -rw-r--r-- | math.cginc | 7 |
10 files changed, 351 insertions, 31 deletions
@@ -205,9 +205,8 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace _Raymarched_Fog_Density_Exponent,
#endif
#if defined(_RAYMARCHED_FOG_HEIGHT_DENSITY)
- _Raymarched_Fog_Height_Density_Min,
- _Raymarched_Fog_Height_Density_Max,
- _Raymarched_Fog_Height_Density_Power,
+ _Raymarched_Fog_Height_Density_Start,
+ _Raymarched_Fog_Height_Density_Half_Life,
#endif
};
FogResult fog_result = raymarched_fog(i, fog_params);
@@ -250,13 +249,16 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace i.uv01.xy = eye_effect_00.uv;
#endif
-#if defined(_CUSTOM30) && defined(FORWARD_BASE_PASS) || (!defined(_DEPTH_PREPASS) && defined(SHADOW_CASTER_PASS))
+#if defined(_CUSTOM30)
+#if defined(FORWARD_BASE_PASS) || (!defined(_DEPTH_PREPASS) && defined(SHADOW_CASTER_PASS))
#if defined(_CUSTOM30_BASICCUBE)
Custom30Output c30_out = BasicCube(i);
#elif defined(_CUSTOM30_BASICWEDGE)
Custom30Output c30_out = BasicWedge(i);
#elif defined(_CUSTOM30_BASICPLATFORM)
Custom30Output c30_out = BasicPlatform(i);
+#else
+ Custom30Output c30_out = (Custom30Output) 0;
#endif
i.normal = c30_out.normal;
i.worldPos = mul(unity_ObjectToWorld, float4(c30_out.objPos, 1));
@@ -267,6 +269,7 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace depth = c30_out.depth;
#endif
#endif
+#endif
float3x3 tangentToWorld = float3x3(i.tangent, i.binormal, i.normal);
float ssao = 1;
diff --git a/2ner.shader b/2ner.shader index e92c65d..1c6e826 100644 --- a/2ner.shader +++ b/2ner.shader @@ -134,6 +134,7 @@ Shader "yum_food/2ner" [ThryToggle(_CUSTOM30_BASICPLATFORM_CHAMFER)]_Custom30_BasicPlatform_Chamfer("Chamfer", Float) = 0 _Custom30_BasicPlatform_Chamfer_Size("Size", Vector) = (0.36, 0.78, 0.9) [ThryToggle(_CUSTOM30_BASICPLATFORM_Y_ALIGNED)]_Custom30_BasicPlatform_Y_Aligned("Y aligned", Float) = 0 + [ThryToggle(_CUSTOM30_BASICPLATFORM_VERTICAL)]_Custom30_BasicPlatform_Vertical("Vertical", Float) = 0 [HideInInspector] m_end_Custom30_BasicPlatform("Basic platform", Float) = 0 //endex @@ -835,9 +836,8 @@ Shader "yum_food/2ner" [HideInInspector] m_start_Raymarched_Fog_Height_Density("Height density", Float) = 0 [ThryToggle(_RAYMARCHED_FOG_HEIGHT_DENSITY)] _Raymarched_Fog_Height_Density_Enabled("Enable", Float) = 0 - _Raymarched_Fog_Height_Density_Min("Height density min", Float) = 0 - _Raymarched_Fog_Height_Density_Max("Height density max", Float) = 100 - _Raymarched_Fog_Height_Density_Power("Height density power", Float) = 1 + _Raymarched_Fog_Height_Density_Start("Start elevation", Float) = 0 + _Raymarched_Fog_Height_Density_Half_Life("Half life", Float) = 1 [HideInInspector] m_end_Raymarched_Fog_Height_Density("Height density", Float) = 0 [HideInInspector] m_end_Raymarched_Fog("Raymarched fog", Float) = 0 //endex diff --git a/Editor/generate_3d_noise.cs b/Editor/generate_3d_noise.cs index 1a0dca0..16d47e8 100644 --- a/Editor/generate_3d_noise.cs +++ b/Editor/generate_3d_noise.cs @@ -26,6 +26,12 @@ public class WhiteNoiseTextureGenerator : EditorWindow private float domainWarpingStrength = 0.1f; private float domainWarpingScale = 0.5f; + // FBM parameters + private bool enableFBM = false; + private int fbmOctaves = 4; + private float fbmLacunarity = 2.0f; + private float fbmGain = 0.5f; + [MenuItem("Tools/yum_food/White Noise Texture Generator")] public static void ShowWindow() { @@ -55,6 +61,19 @@ public class WhiteNoiseTextureGenerator : EditorWindow EditorGUI.indentLevel--; } + EditorGUILayout.Space(); + GUILayout.Label("FBM (Fractal Brownian Motion)", EditorStyles.boldLabel); + enableFBM = EditorGUILayout.Toggle("Enable FBM", enableFBM); + + if (enableFBM) + { + EditorGUI.indentLevel++; + fbmOctaves = EditorGUILayout.IntSlider("Octaves", fbmOctaves, 1, 10); + fbmLacunarity = EditorGUILayout.Slider("Lacunarity", fbmLacunarity, 1.0f, 4.0f); + fbmGain = EditorGUILayout.Slider("Gain", fbmGain, 0.1f, 1.0f); + EditorGUI.indentLevel--; + } + if (GUILayout.Button("Generate Texture")) { if (textureWidth <= 0 || textureHeight <= 0 || textureDepth <= 0) @@ -72,7 +91,11 @@ public class WhiteNoiseTextureGenerator : EditorWindow TextureFormat format = GetTextureFormat(); Texture3D texture = new Texture3D(textureWidth, textureHeight, textureDepth, format, false); - if (enableDomainWarping) + if (enableFBM) + { + GenerateWithFBM(texture); + } + else if (enableDomainWarping) { GenerateWithDomainWarping(texture); } @@ -199,6 +222,11 @@ public class WhiteNoiseTextureGenerator : EditorWindow float dy = fy - y0; float dz = fz - z0; + // Apply smoothstep to interpolation parameters + float sx = Mathf.SmoothStep(0, 1, dx); + float sy = Mathf.SmoothStep(0, 1, dy); + float sz = Mathf.SmoothStep(0, 1, dz); + // Sample 8 corners Color c000 = colors[x0 + y0 * textureWidth + z0 * textureWidth * textureHeight]; Color c001 = colors[x0 + y0 * textureWidth + z1 * textureWidth * textureHeight]; @@ -209,16 +237,104 @@ public class WhiteNoiseTextureGenerator : EditorWindow Color c110 = colors[x1 + y1 * textureWidth + z0 * textureWidth * textureHeight]; Color c111 = colors[x1 + y1 * textureWidth + z1 * textureWidth * textureHeight]; - // Interpolate - Color c00 = Color.Lerp(c000, c001, dz); - Color c01 = Color.Lerp(c010, c011, dz); - Color c10 = Color.Lerp(c100, c101, dz); - Color c11 = Color.Lerp(c110, c111, dz); + // Interpolate with smoothstepped values + Color c00 = Color.Lerp(c000, c001, sz); + Color c01 = Color.Lerp(c010, c011, sz); + Color c10 = Color.Lerp(c100, c101, sz); + Color c11 = Color.Lerp(c110, c111, sz); + + Color c0 = Color.Lerp(c00, c01, sy); + Color c1 = Color.Lerp(c10, c11, sy); + + return Color.Lerp(c0, c1, sx); + } + + private Color SampleTextureCustomSize(Color[] colors, Vector3 coord, int width, int height, int depth) + { + // Convert to texture space + float fx = coord.x * (width); + float fy = coord.y * (height); + float fz = coord.z * (depth); + + // Trilinear interpolation + int x0 = Mathf.FloorToInt(fx); + int y0 = Mathf.FloorToInt(fy); + int z0 = Mathf.FloorToInt(fz); + int x1 = (x0 + 1) % width; + int y1 = (y0 + 1) % height; + int z1 = (z0 + 1) % depth; + + float dx = fx - x0; + float dy = fy - y0; + float dz = fz - z0; + + // Apply smoothstep to interpolation parameters + float sx = Mathf.SmoothStep(0, 1, dx); + float sy = Mathf.SmoothStep(0, 1, dy); + float sz = Mathf.SmoothStep(0, 1, dz); + + // Sample 8 corners + Color c000 = colors[x0 + y0 * width + z0 * width * height]; + Color c001 = colors[x0 + y0 * width + z1 * width * height]; + Color c010 = colors[x0 + y1 * width + z0 * width * height]; + Color c011 = colors[x0 + y1 * width + z1 * width * height]; + Color c100 = colors[x1 + y0 * width + z0 * width * height]; + Color c101 = colors[x1 + y0 * width + z1 * width * height]; + Color c110 = colors[x1 + y1 * width + z0 * width * height]; + Color c111 = colors[x1 + y1 * width + z1 * width * height]; - Color c0 = Color.Lerp(c00, c01, dy); - Color c1 = Color.Lerp(c10, c11, dy); + // Interpolate with smoothstepped values + Color c00 = Color.Lerp(c000, c001, sz); + Color c01 = Color.Lerp(c010, c011, sz); + Color c10 = Color.Lerp(c100, c101, sz); + Color c11 = Color.Lerp(c110, c111, sz); - return Color.Lerp(c0, c1, dx); + Color c0 = Color.Lerp(c00, c01, sy); + Color c1 = Color.Lerp(c10, c11, sy); + + return Color.Lerp(c0, c1, sx); + } + + private Color[] ApplyDomainWarpingToColors(Color[] baseColors, int width, int height, int depth) + { + Color[] warpedColors = new Color[baseColors.Length]; + + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + Vector3 coord = new Vector3( + (float)x / width, + (float)y / height, + (float)z / depth + ); + + // Apply domain warping + for (int octave = 0; octave < domainWarpingOctaves; octave++) + { + Vector3 sampleCoord = coord * domainWarpingScale; + Color warpValue = SampleTextureCustomSize(baseColors, sampleCoord, width, height, depth); + + // Convert color to offset vector + Vector3 offset = new Vector3( + warpValue.r * 2f - 1f, + warpValue.g * 2f - 1f, + warpValue.b * 2f - 1f + ) * domainWarpingStrength; + + coord += offset; + } + + // Sample final color at warped coordinate + int index = x + y * width + z * width * height; + warpedColors[index] = SampleTextureCustomSize(baseColors, coord, width, height, depth); + } + } + } + + return warpedColors; } private TextureFormat GetTextureFormat() @@ -258,4 +374,127 @@ public class WhiteNoiseTextureGenerator : EditorWindow return Color.white; } } + + private void GenerateWithFBM(Texture3D texture) + { + // Calculate starting resolution based on target size and octaves + float scaleFactor = Mathf.Pow(fbmLacunarity, fbmOctaves - 1); + int currentWidth = Mathf.Max(1, Mathf.RoundToInt(textureWidth / scaleFactor)); + int currentHeight = Mathf.Max(1, Mathf.RoundToInt(textureHeight / scaleFactor)); + int currentDepth = Mathf.Max(1, Mathf.RoundToInt(textureDepth / scaleFactor)); + + // Track previous dimensions + int prevWidth = currentWidth; + int prevHeight = currentHeight; + int prevDepth = currentDepth; + + Color[] baseColors = null; + float amplitude = 1.0f; + float maxAmplitude = 0.0f; + + for (int octave = 0; octave < fbmOctaves; octave++) + { + // Generate noise at current resolution + Color[] octaveColors = new Color[currentWidth * currentHeight * currentDepth]; + for (int i = 0; i < octaveColors.Length; i++) { + octaveColors[i] = GenerateColor(); + } + + if (baseColors == null) { + baseColors = octaveColors; + } else { + for (int z = 0; z < currentDepth; z++) + for (int y = 0; y < currentHeight; y++) + for (int x = 0; x < currentWidth; x++) + { + int index = x + y * currentWidth + z * currentWidth * currentHeight; + Vector3 coord = new Vector3( + (float)x / currentWidth, + (float)y / currentHeight, + (float)z / currentDepth + ); + Color prevColor = SampleTextureCustomSize(baseColors, coord, prevWidth, prevHeight, prevDepth); + octaveColors[index] = prevColor + octaveColors[index] * amplitude; + } + } + + baseColors = octaveColors; + + maxAmplitude += amplitude; + amplitude *= fbmGain; + + // Store current dimensions as previous before updating + prevWidth = currentWidth; + prevHeight = currentHeight; + prevDepth = currentDepth; + + // Increase resolution for next octave + if (octave < fbmOctaves - 1) + { + currentWidth = Mathf.Min(textureWidth, Mathf.RoundToInt(currentWidth * fbmLacunarity)); + currentHeight = Mathf.Min(textureHeight, Mathf.RoundToInt(currentHeight * fbmLacunarity)); + currentDepth = Mathf.Min(textureDepth, Mathf.RoundToInt(currentDepth * fbmLacunarity)); + } + } + + // Ensure final resolution matches target + if (currentWidth != textureWidth || currentHeight != textureHeight || currentDepth != textureDepth) + { + Color[] finalColors = new Color[textureWidth * textureHeight * textureDepth]; + for (int z = 0; z < textureDepth; z++) + { + for (int y = 0; y < textureHeight; y++) + { + for (int x = 0; x < textureWidth; x++) + { + int index = x + y * textureWidth + z * textureWidth * textureHeight; + Vector3 coord = new Vector3( + (float)x / textureWidth, + (float)y / textureHeight, + (float)z / textureDepth + ); + finalColors[index] = SampleTextureCustomSize(baseColors, coord, currentWidth, currentHeight, currentDepth); + } + } + } + baseColors = finalColors; + } + + // Normalize + for (int i = 0; i < baseColors.Length; i++) + { + baseColors[i] /= maxAmplitude; + baseColors[i].a = 1.0f; + } + + texture.SetPixels(baseColors); + } + + private Color GeneratePerlinColor(float x, float y, float z, float scale = 1f) + { + switch (noiseType) + { + case NoiseType.OneDimensional: + return new Color(Mathf.PerlinNoise(x * scale, 0), 0, 0, 1); + case NoiseType.TwoDimensional: + return new Color( + Mathf.PerlinNoise(x * scale, y * scale), + Mathf.PerlinNoise(x * scale + 100, y * scale + 100), + 0, 1); + case NoiseType.ThreeDimensional: + return new Color( + Mathf.PerlinNoise(x * scale, y * scale), + Mathf.PerlinNoise(x * scale + 100, z * scale), + Mathf.PerlinNoise(y * scale + 200, z * scale + 200), + 1); + case NoiseType.FourDimensional: + return new Color( + Mathf.PerlinNoise(x * scale, y * scale), + Mathf.PerlinNoise(x * scale + 100, z * scale), + Mathf.PerlinNoise(y * scale + 200, z * scale + 200), + Mathf.PerlinNoise(x * scale + 300, y * scale + 300)); + default: + return Color.white; + } + } } diff --git a/Scripts/gen_sdf b/Scripts/gen_sdf new file mode 100644 index 0000000..52ef487 --- /dev/null +++ b/Scripts/gen_sdf @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import numpy as np +import cv2 +import argparse +import os + +def compute_sdf(img, scale_factor): + # Convert to binary image if not already + _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) + + # Compute distance transform for both foreground and background + dist_transform_fg = cv2.distanceTransform(binary, cv2.DIST_L2, 5) + dist_transform_bg = cv2.distanceTransform(255 - binary, cv2.DIST_L2, 5) + + # Combine the distance fields and scale by factor + sdf = (dist_transform_fg - dist_transform_bg) / scale_factor + + # Normalize values to [0, 255] range + sdf_min = np.min(sdf) + sdf_max = np.max(sdf) + sdf = ((sdf - sdf_min) * 255 / (sdf_max - sdf_min)) + + return sdf.astype(np.uint8) + +def main(): + parser = argparse.ArgumentParser(description='Generate SDF from black and white image') + parser.add_argument('input_images', nargs='+', help='Path to input image(s)') + parser.add_argument('--scale', type=float, default=1.0, + help='Scale factor for distance (in texels)') + args = parser.parse_args() + + # Process each input image + for input_path in args.input_images: + # Get input and output paths + filename, ext = os.path.splitext(input_path) + output_path = f"{filename}-sdf{ext}" + + # Read input image + img = cv2.imread(input_path, cv2.IMREAD_GRAYSCALE) + if img is None: + print(f"Error: Could not read image {input_path}") + continue + + # Compute SDF with scale factor + sdf = compute_sdf(img, args.scale) + + # Save result + cv2.imwrite(output_path, sdf) + print(f"SDF generated and saved to {output_path}") + +if __name__ == "__main__": + main() diff --git a/custom30.cginc b/custom30.cginc index 2bf8ec2..56392d6 100644 --- a/custom30.cginc +++ b/custom30.cginc @@ -277,6 +277,9 @@ float BasicPlatform_map(float3 p) { #if defined(_CUSTOM30_BASICPLATFORM_Y_ALIGNED) p.xy = p.yx; #endif + #if defined(_CUSTOM30_BASICPLATFORM_VERTICAL) + p.xz = p.zx; + #endif float3 platform_size = _Custom30_BasicPlatform_Size; float box_d = distance_from_box_frame(p, platform_size, _Custom30_BasicPlatform_Frame_D); diff --git a/decals.cginc b/decals.cginc index cd71c73..b4f1151 100644 --- a/decals.cginc +++ b/decals.cginc @@ -89,7 +89,15 @@ float4 getDecalColor(DecalParams params, float2 uv) { float4 sdf_sample = params.mainTex.SampleLevel(linear_repeat_s, uv, 0);
float sd = sdf_sample.r;
sd = params.sdf_invert ? 1 - sd : sd;
- sd = (sd > params.sdf_threshold ? 1 : 0);
+ // The fwidth+smoothstep anti-aliases the glyph outline. See
+ // "Noise is Beautiful" by Gustavson around page 34 for an
+ // explanation of this trick.
+#if 1
+ float step_wd = fwidth(sd) * 0.5;
+ sd = smoothstep(params.sdf_threshold - step_wd, params.sdf_threshold + step_wd, sd);
+#else
+ sd = step(params.sdf_threshold, sd);
+#endif
return params.color * sd;
}
diff --git a/features.cginc b/features.cginc index 79bed6c..4dc350d 100644 --- a/features.cginc +++ b/features.cginc @@ -305,6 +305,7 @@ #pragma shader_feature_local _CUSTOM30_BASICPLATFORM #pragma shader_feature_local _CUSTOM30_BASICPLATFORM_CHAMFER #pragma shader_feature_local _CUSTOM30_BASICPLATFORM_Y_ALIGNED +#pragma shader_feature_local _CUSTOM30_BASICPLATFORM_VERTICAL //endex //ifex _Depth_Prepass_Enabled==0 @@ -21,9 +21,8 @@ struct FogParams { float density_exponent; #endif #if defined(_RAYMARCHED_FOG_HEIGHT_DENSITY) - float height_density_min; - float height_density_max; - float height_density_power; + float height_density_start; + float height_density_half_life; #endif }; @@ -40,8 +39,13 @@ FogResult raymarched_fog(v2f i, FogParams p) const float ro_epsilon = 1E-3; ro += rd * ro_epsilon; + // TODO maybe we can accelerate this? + float perspective_divide = 1.0f / i.pos.w; + float perspective_factor = length(i.eyeVec.xyz * perspective_divide); + const float2 screen_uv = (i.pos.xy + 0.5) / _ScreenParams.xy; float zDepthFromMap = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screen_uv); + float linearZ = GetLinearZFromZDepth_WorksWithMirrors(zDepthFromMap, screen_uv); linearZ = min(1E3, linearZ); @@ -75,7 +79,7 @@ FogResult raymarched_fog(v2f i, FogParams p) float3 noise_coord = (pp + _Time[0] * p.velocity) * p.density_noise_scale.xyz; - float cur_d = p.density_noise.SampleLevel(linear_repeat_s, + float cur_d = p.density_noise.SampleLevel(bilinear_repeat_s, noise_coord, 0); #if defined(_RAYMARCHED_FOG_DENSITY_EXPONENT) @@ -85,10 +89,18 @@ FogResult raymarched_fog(v2f i, FogParams p) cur_d *= p.density * step_size; #if defined(_RAYMARCHED_FOG_HEIGHT_DENSITY) - // Apply height-based density (branchless) - float t = saturate((pp.y - p.height_density_min) / - (p.height_density_max - p.height_density_min)); - cur_d *= 1.0 - t; + float height_clamped = max(pp.y - p.height_density_start, 0); + // if half_life = 2 and start = 0, then + // y=2 -> density = 1/2 + // y=4 -> density = 1/4 + // y=6 -> density = 1/8 + // if half_life = 3 and start = 0, then + // y=3 -> density = 1/2 + // y=6 -> density = 1/4 + // y=9 -> density = 1/8 + float exponent = height_clamped / p.height_density_half_life; + float factor = pow(2, -exponent); + cur_d *= factor; #endif cur_d = saturate(cur_d); diff --git a/globals.cginc b/globals.cginc index 2768202..e1cc6f1 100644 --- a/globals.cginc +++ b/globals.cginc @@ -10,6 +10,7 @@ float4 _CameraDepthTexture_TexelSize; SamplerState point_repeat_s;
SamplerState linear_repeat_s;
+SamplerState bilinear_repeat_s;
SamplerState linear_clamp_s;
SamplerState trilinear_repeat_s;
@@ -549,9 +550,8 @@ float3 _Raymarched_Fog_Velocity; float _Raymarched_Fog_Density_Exponent;
#endif
#if defined(_RAYMARCHED_FOG_HEIGHT_DENSITY)
-float _Raymarched_Fog_Height_Density_Min;
-float _Raymarched_Fog_Height_Density_Max;
-float _Raymarched_Fog_Height_Density_Power;
+float _Raymarched_Fog_Height_Density_Start;
+float _Raymarched_Fog_Height_Density_Half_Life;
#endif
#endif // _RAYMARCHED_FOG
@@ -8,6 +8,10 @@ #define HALF_PI (PI * 0.5)
#define PHI 1.618033989
#define SQRT_2_RCP 0.707106781
+#define TWO_OVER_THREE 0.6666666666666666f
+#define SQRT_3_OVER_2 0.8660254037844386f
+#define EULERS_CONSTANT 2.718281828
+
float pow5(float x)
{
@@ -285,9 +289,6 @@ float3 cmykToRgb(float4 cmyk) { // Cartesian to cube hexagonal coordinates.
// Based on this: https://backdrifting.net/post/064_hex_grids
-#define TWO_OVER_THREE 0.6666666666666666f
-#define SQRT_3_OVER_2 0.8660254037844386f
-
float3 cart_to_hex(float2 cart) {
float p = cart.x;
float q = dot(cart, float2(0.5f, SQRT_3_OVER_2));
|
