diff options
| -rwxr-xr-x | 3ner.shader | 47 | ||||
| -rwxr-xr-x | Scripts/gaussianize | 61 | ||||
| -rw-r--r-- | data.cginc | 3 | ||||
| -rw-r--r-- | debug.cginc | 6 | ||||
| -rwxr-xr-x | features.cginc | 6 | ||||
| -rwxr-xr-x | globals.cginc | 16 | ||||
| -rwxr-xr-x | pbr.cginc | 352 |
7 files changed, 317 insertions, 174 deletions
diff --git a/3ner.shader b/3ner.shader index b656b26..63e8118 100755 --- a/3ner.shader +++ b/3ner.shader @@ -665,6 +665,28 @@ Shader "yum_food/3ner" [HideInInspector] m_end_Glitter("Glitter", Float) = 0 //endex + //ifex _Parallax_Heightmap_Enabled==0 + [HideInInspector] m_start_Parallax_Heightmap("Parallax Heightmap", Float) = 0 + [ThryToggle(_PARALLAX_HEIGHTMAP)] _Parallax_Heightmap_Enabled("Enable", Float) = 0 + _Parallax_Heightmap_Scale("Scale", Float) = 1 + _Parallax_Heightmap_Bias("Neutral point", Float) = 0.5 + + //ifex _Parallax_Heightmap_Texture_Enabled==0 + [HideInInspector] m_start_Parallax_Heightmap_Texture("Heightmap", Float) = 0 + [ThryToggle(_PARALLAX_HEIGHTMAP_TEXTURE)] _Parallax_Heightmap_Texture_Enabled("Enable", Float) = 0 + _Parallax_Heightmap("Texture", 2D) = "gray" {} + [HideInInspector] m_end_Parallax_Heightmap_Texture("Heightmap", Float) = 0 + //endex + + //ifex _Parallax_Heightmap_Ray_Marching_Enabled==0 + [HideInInspector] m_start_Parallax_Heightmap_Ray_Marching("Parallax Heightmap Ray Marching", Float) = 0 + [ThryToggle(_PARALLAX_HEIGHTMAP_RAY_MARCHING)] _Parallax_Heightmap_Ray_Marching_Enabled("Enable", Float) = 0 + [IntRange] _Parallax_Heightmap_Ray_Marching_Steps("Steps", Range(1, 10)) = 5 + [HideInInspector] m_end_Parallax_Heightmap_Ray_Marching("Parallax Heightmap Ray Marching", Float) = 0 + //endex + [HideInInspector] m_end_Parallax_Heightmap("Parallax Heightmap", Float) = 0 + //endex + //ifex _Burley_Tiling_Enabled==0 [HideInInspector] m_start_Burley_Tiling("Burley Tiling", Float) = 0 [ThryToggle(_BURLEY_TILING)] _Burley_Tiling_Enabled("Enable", Float) = 0 @@ -697,6 +719,14 @@ Shader "yum_food/3ner" [HideInInspector] m_end_Burley_Tiling_Normal("Normal", Float) = 0 //endex + //ifex _Burley_Tiling_Heightmap_Enabled==0 + [HideInInspector] m_start_Burley_Tiling_Heightmap("Heightmap", Float) = 0 + [ThryToggle(_BURLEY_TILING_HEIGHTMAP)] _Burley_Tiling_Heightmap_Enabled("Enable", Float) = 0 + _Burley_Tiling_Heightmap("Texture", 2D) = "gray" {} + _Burley_Tiling_Heightmap_LUT("LUT", 2D) = "white" {} + [HideInInspector] m_end_Burley_Tiling_Heightmap("Heightmap", Float) = 0 + //endex + [HideInInspector] m_end_Burley_Tiling("Burley Tiling", Float) = 0 //endex @@ -1150,6 +1180,7 @@ Shader "yum_food/3ner" [ThryToggle(_DEBUG_VIEW_WORLD_SPACE_NORMALS)] _Debug_View_World_Space_Normals("World space normals", Float) = 0 [ThryToggle(_DEBUG_VIEW_OBJECT_SPACE_NORMALS)] _Debug_View_Object_Space_Normals("Object space normals", Float) = 0 [ThryToggle(_DEBUG_VIEW_METALLIC_GLOSS)] _Debug_View_Metallic_Gloss("Metallic gloss", Float) = 0 + [ThryToggle(_DEBUG_VIEW_HEIGHT)] _Debug_View_Height("Height", Float) = 0 [ThryToggle(_DEBUG_VIEW_DEPTH)] _Debug_View_Depth("Depth", Float) = 0 [ThryToggle(_DEBUG_VIEW_DIRECT_NOH)] _Debug_View_Direct_NoH("Direct NoH", Float) = 0 [ThryToggle(_DEBUG_VIEW_DIRECT_LOH)] _Debug_View_Direct_LoH("Direct LoH", Float) = 0 @@ -1195,22 +1226,6 @@ Shader "yum_food/3ner" [HideInInspector] m_end_Instancing("Instancing", Float) = 0 //endex - //ifex _Parallax_Heightmap_Enabled==0 - [HideInInspector] m_start_Parallax_Heightmap("Parallax Heightmap", Float) = 0 - [ThryToggle(_PARALLAX_HEIGHTMAP)] _Parallax_Heightmap_Enabled("Enable", Float) = 0 - _Parallax_Heightmap("Heightmap", 2D) = "gray" {} - _Parallax_Heightmap_Scale("Scale", Float) = 1 - _Parallax_Heightmap_Bias("Neutral point", Float) = 0.5 - - //ifex _Parallax_Heightmap_Ray_Marching_Enabled==0 - [HideInInspector] m_start_Parallax_Heightmap_Ray_Marching("Parallax Heightmap Ray Marching", Float) = 0 - [ThryToggle(_PARALLAX_HEIGHTMAP_RAY_MARCHING)] _Parallax_Heightmap_Ray_Marching_Enabled("Enable", Float) = 0 - [IntRange] _Parallax_Heightmap_Ray_Marching_Steps("Steps", Range(1, 10)) = 5 - [HideInInspector] m_end_Parallax_Heightmap_Ray_Marching("Parallax Heightmap Ray Marching", Float) = 0 - //endex - [HideInInspector] m_end_Parallax_Heightmap("Parallax Heightmap", Float) = 0 - //endex - //ifex _Shadow_Caster_Enabled==0 [HideInInspector] m_start_Shadow_Caster("Shadow caster pass", Float) = 0 [ThryToggle(_SHADOW_CASTER)] _Shadow_Caster_Enabled("Enable", Float) = 1 diff --git a/Scripts/gaussianize b/Scripts/gaussianize index 2ca915d..620e05d 100755 --- a/Scripts/gaussianize +++ b/Scripts/gaussianize @@ -72,6 +72,58 @@ def save_image(image: np.ndarray, output_path: Path): raise ValueError(f"Unsupported output format '{output_path.suffix}'. Use .exr or .png.") +def rgb_to_ycrcb(image: np.ndarray) -> np.ndarray: + """Convert RGB in [0, 1] to full-range YCrCb in [0, 1].""" + rgb = np.clip(image, 0.0, 1.0) + r = rgb[:, :, 0] + g = rgb[:, :, 1] + b = rgb[:, :, 2] + + y = 0.299 * r + 0.587 * g + 0.114 * b + cb = 0.5 + (b - y) * 0.564 + cr = 0.5 + (r - y) * 0.713 + return np.stack((y, cr, cb), axis=-1) + + +def ycrcb_to_rgb(image: np.ndarray) -> np.ndarray: + """Convert full-range YCrCb in [0, 1] back to RGB in [0, 1].""" + ycrcb = np.asarray(image, dtype=np.float64) + y = ycrcb[:, :, 0] + cr = ycrcb[:, :, 1] - 0.5 + cb = ycrcb[:, :, 2] - 0.5 + + r = y + 1.402 * cr + g = y - 0.714136 * cr - 0.344136 * cb + b = y + 1.772 * cb + return np.clip(np.stack((r, g, b), axis=-1), 0.0, 1.0) + + +def subtract_low_frequencies(channel: np.ndarray, cutoff_cycles: float = 8.0) -> np.ndarray: + """Subtract a smooth periodic low-frequency component from a scalar image.""" + if cutoff_cycles <= 0.0: + raise ValueError(f"cutoff_cycles must be > 0, got {cutoff_cycles}") + + channel = np.asarray(channel, dtype=np.float64) + height, width = channel.shape + + fy = np.fft.fftfreq(height) + fx = np.fft.fftfreq(width) + radius = np.sqrt((fy[:, np.newaxis] * height) ** 2 + (fx[np.newaxis, :] * width) ** 2) + + low_mask = np.exp(-((radius / cutoff_cycles) ** 2)) + low_pass = np.fft.ifft2(np.fft.fft2(channel) * low_mask).real + + # Remove only the spatial trend and keep the original average luminance. + return channel - low_pass + float(np.mean(low_pass)) + + +def homogenize_luminance(image: np.ndarray, cutoff_cycles: float = 8.0) -> np.ndarray: + """Reduce very-low-frequency luminance variation while preserving chroma.""" + ycrcb = rgb_to_ycrcb(image) + ycrcb[:, :, 0] = subtract_low_frequencies(ycrcb[:, :, 0], cutoff_cycles=cutoff_cycles) + return ycrcb_to_rgb(ycrcb) + + class TruncatedGaussian: """Truncated Gaussian distribution for histogram transformation.""" @@ -450,6 +502,11 @@ def main(): action="store_true", help="Print detailed progress information" ) + parser.add_argument( + "--homo", + action="store_true", + help="Homogenize luminance before gaussianizing by removing very-low-frequency Y in YCrCb space" + ) args = parser.parse_args() @@ -467,6 +524,10 @@ def main(): image = load_image(args.input) quantization_step = 0.0 if args.input.suffix.lower() == ".exr" else (1.0 / 255.0) + if args.homo: + print("Homogenizing luminance in YCrCb space...") + image = homogenize_luminance(image) + # Gaussianize the texture print("Applying per-channel Gaussianization...") gaussianized, inverse_luts = gaussianize_texture( @@ -31,6 +31,9 @@ struct Pbr { float cc_roughness_perceptual; float cc_strength; #endif +#if defined(_PARALLAX_HEIGHTMAP) || defined(_BURLEY_TILING_HEIGHTMAP) + float height; +#endif #if defined(_DEBUG_VIEW_TANGENT_SPACE_NORMALS) float3 tangent_normal; #endif diff --git a/debug.cginc b/debug.cginc index 6510fdd..f6c8f15 100644 --- a/debug.cginc +++ b/debug.cginc @@ -14,6 +14,12 @@ float4 apply_debug_view(v2f i, Pbr pbr, LightData light_data, BrdfData bd, return float4((normalOS + 1.0f) * 0.5f, 1); #elif defined(_DEBUG_VIEW_METALLIC_GLOSS) return float4(pbr.metallic, pbr.smoothness, 0, 1); +#elif defined(_DEBUG_VIEW_HEIGHT) + #if defined(_PARALLAX_HEIGHTMAP) || defined(_BURLEY_TILING_HEIGHTMAP) + return float4(F1_TO_F3(pbr.height), 1); + #else + return float4(0, 0, 0, 1); + #endif #elif defined(_DEBUG_VIEW_DEPTH) float2 screen_uv = i.pos.xy / _ScreenParams.xy; float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screen_uv); diff --git a/features.cginc b/features.cginc index 09a2c30..312af41 100755 --- a/features.cginc +++ b/features.cginc @@ -82,6 +82,7 @@ #pragma shader_feature_local _DEBUG_VIEW_WORLD_SPACE_NORMALS #pragma shader_feature_local _DEBUG_VIEW_OBJECT_SPACE_NORMALS #pragma shader_feature_local _DEBUG_VIEW_METALLIC_GLOSS +#pragma shader_feature_local _DEBUG_VIEW_HEIGHT #pragma shader_feature_local _DEBUG_VIEW_DEPTH #pragma shader_feature_local _DEBUG_VIEW_DIRECT_NOH #pragma shader_feature_local _DEBUG_VIEW_DIRECT_LOH @@ -111,9 +112,7 @@ //ifex _Parallax_Heightmap_Enabled==0 #pragma shader_feature_local _PARALLAX_HEIGHTMAP -//endex - -//ifex _Parallax_Heightmap_Ray_Marching_Enabled==0 +#pragma shader_feature_local _PARALLAX_HEIGHTMAP_TEXTURE #pragma shader_feature_local _PARALLAX_HEIGHTMAP_RAY_MARCHING //endex @@ -243,6 +242,7 @@ //ifex _Burley_Tiling_Enabled==0 #pragma shader_feature_local _BURLEY_TILING +#pragma shader_feature_local _BURLEY_TILING_HEIGHTMAP #pragma shader_feature_local _BURLEY_TILING_SMOOTHNESS #pragma shader_feature_local _BURLEY_TILING_SMOOTHNESS_INVERT #pragma shader_feature_local _BURLEY_TILING_NORMAL diff --git a/globals.cginc b/globals.cginc index 88fb663..a475083 100755 --- a/globals.cginc +++ b/globals.cginc @@ -273,16 +273,21 @@ UNITY_INSTANCING_BUFFER_END(InstanceProps) #endif // _INSTANCE_TEXTURE_OFFSET && UNITY_INSTANCING_ENABLED #if defined(_PARALLAX_HEIGHTMAP) -texture2D _Parallax_Heightmap; -float4 _Parallax_Heightmap_ST; float _Parallax_Heightmap_Scale; float _Parallax_Heightmap_Bias; -#endif // _PARALLAX_HEIGHTMAP + +#if defined(_PARALLAX_HEIGHTMAP_TEXTURE) +texture2D _Parallax_Heightmap; +float4 _Parallax_Heightmap_ST; +#endif // _PARALLAX_HEIGHTMAP_TEXTURE #if defined(_PARALLAX_HEIGHTMAP_RAY_MARCHING) float _Parallax_Heightmap_Ray_Marching_Steps; #endif // _PARALLAX_HEIGHTMAP_RAY_MARCHING +#endif // _PARALLAX_HEIGHTMAP + + #define DECAL_UV_MODE_REPEAT 0 #define DECAL_UV_MODE_MIRROR 1 #define DECAL_UV_MODE_CLAMP 2 @@ -636,4 +641,9 @@ texture2D _Burley_Tiling_Normal_Map_LUT; float _Burley_Tiling_Normal_Strength; #endif // _BURLEY_TILING_NORMAL +#if defined(_BURLEY_TILING_HEIGHTMAP) +texture2D _Burley_Tiling_Heightmap; +texture2D _Burley_Tiling_Heightmap_LUT; +#endif // _BURLEY_TILING_HEIGHTMAP + #endif // __GLOBALS_INC @@ -9,6 +9,178 @@ #include "letter_grid.cginc" #include "texture_utils.cginc" +#if defined(_BURLEY_TILING) +float2 burley_tri_to_cart(float2 tri_coord) { + return float2( + tri_coord.x + tri_coord.y * 0.5f, + tri_coord.y * SQRT_3_OVER_2); +} + +float3 burley_apply_blend_gamma(float3 weights, float gamma) { + weights = pow(weights, gamma); + return weights / (weights.x + weights.y + weights.z); +} + +// Equation 4 (first half). +float3 burley_soft_clipping_lower_half(float3 x_hat, float w_hat) { + float linear_start = 0.25f * (2.0f - w_hat); + float3 linear_value = (x_hat - 0.5f) / w_hat + 0.5f; + float3 linear_mask = step(float3(linear_start, linear_start, linear_start), x_hat); + + if (w_hat >= TWO_OVER_THREE) { + float3 t = x_hat / (2.0f - w_hat); + float3 quadratic = 8.0f * (1.0f / w_hat - 1.0f) * t * t + (3.0f - 2.0f / w_hat) * t; + return lerp(quadratic, linear_value, linear_mask); + } + + float quadratic_start = 0.25f * (2.0f - 3.0f * w_hat); + float3 d = (x_hat - quadratic_start) / w_hat; + float3 quadratic = d * d; + float3 quadratic_mask = step(float3(quadratic_start, quadratic_start, quadratic_start), x_hat); + float3 result = quadratic * quadratic_mask; + return lerp(result, linear_value, linear_mask); +} + +// Equation 4. +float3 burley_soft_clipping_contrast(float3 x_hat, float w_hat) { + float3 upper_mask = step(0.5f, x_hat); + float3 lower_x = min(x_hat, 1.0f - x_hat); + float3 lower_y = burley_soft_clipping_lower_half(lower_x, w_hat); + return lerp(lower_y, 1.0f - lower_y, upper_mask); +} + +float3 burley_apply_soft_clipping(float3 gaussian_color, float3 weights) { + float w_hat = sqrt(dot(weights, weights)); + return burley_soft_clipping_contrast(gaussian_color, w_hat); +} + +float3 burley_degaussianize(texture2D lut, float3 gaussian_color, bool decode_srgb = true) { + float2 uv_r = float2(gaussian_color.r, 0.5f); + float2 uv_g = float2(gaussian_color.g, 0.5f); + float2 uv_b = float2(gaussian_color.b, 0.5f); + float lut_r = lut.Sample(linear_clamp_s, uv_r).r; + float lut_g = lut.Sample(linear_clamp_s, uv_g).g; + float lut_b = lut.Sample(linear_clamp_s, uv_b).b; + float3 restored = float3(lut_r, lut_g, lut_b); + return decode_srgb ? srgb_to_linear(restored) : restored; +} + +struct BurleyPatchTransform { + float2 uv; + float2 dx; + float2 dy; + float2x2 uv_to_patch; +}; + +BurleyPatchTransform burley_make_patch_transform(float2 uv, float2 uv_dx, float2 uv_dy, + float2 tri_vertex, float input_scale) { + float3 cube_id = float3(tri_vertex.x, tri_vertex.y, -tri_vertex.x - tri_vertex.y); + float3 tile_rand3 = hash33_fast(cube_id); + float2 vertex_uv = burley_tri_to_cart(tri_vertex); + // Map the unit-radius hex support to the unit square so arbitrary rotation + // stays within bounds. + float2 local_uv = (uv - vertex_uv) * 0.5f; + // Apply input scaling. + local_uv *= input_scale; + float2 sample_dx = uv_dx * (0.5f * input_scale); + float2 sample_dy = uv_dy * (0.5f * input_scale); + // Rotate. + float theta = hash31_ff(tile_rand3) * TAU; + float2x2 rot = float2x2(cos(theta), -sin(theta), sin(theta), cos(theta)); + local_uv = mul(rot, local_uv); + sample_dx = mul(rot, sample_dx); + sample_dy = mul(rot, sample_dy); + // Apply randomized offset, staying within bounds. + // The scaled-and-rotated footprint is bounded by [-Input_Scale / 2, Input_Scale / 2], + // so we can offset by [(1 - Input_Scale) / 2]. + float2 random_offset = (tile_rand3.yz * 2.0f - 1.0f) * (0.5f * (1.0f - input_scale)); + local_uv += random_offset; + // Finally, remap onto [0, 1]. + local_uv += 0.5f; + + BurleyPatchTransform patch; + patch.uv = local_uv; + patch.dx = sample_dx; + patch.dy = sample_dy; + patch.uv_to_patch = rot * (0.5f * input_scale); + return patch; +} + +float4 burley_sample_patch(texture2D tex, BurleyPatchTransform patch) { + return tex.SampleGrad( + aniso4_trilinear_repeat_s, patch.uv, patch.dx, patch.dy); +} + +struct BurleyTilingContext { + BurleyPatchTransform patch_0; + BurleyPatchTransform patch_1; + BurleyPatchTransform patch_2; + float3 weights; + float2 base_uv; + float uv_scale; +}; + +static BurleyTilingContext _burley_ctx; + +void burley_tiling_setup(float2 base_uv) { + _burley_ctx.base_uv = base_uv; + float2 uv = base_uv - 0.5; + // Scale so that any rotation remains within [0, 1] bounds. + uv *= TWO_OVER_SQRT_3; + uv /= _Burley_Tiling_Output_Scale; + _burley_ctx.uv_scale = TWO_OVER_SQRT_3 / _Burley_Tiling_Output_Scale; + float3 hex_coord = cart_to_hex(uv); + float2 tri_coord = hex_coord.yz; + float2 tri_cell = floor(tri_coord); + float2 tri_frac = tri_coord - tri_cell; + float2 vertex_0; + float2 vertex_1; + float2 vertex_2; + float3 baryc; + if (tri_frac.x + tri_frac.y < 1.0f) { + vertex_0 = tri_cell; + vertex_1 = tri_cell + float2(1.0f, 0.0f); + vertex_2 = tri_cell + float2(0.0f, 1.0f); + baryc = float3(1.0f - (tri_frac.x + tri_frac.y), tri_frac.x, tri_frac.y); + } else { + vertex_0 = tri_cell + 1.0f; + vertex_1 = tri_cell + float2(0.0f, 1.0f); + vertex_2 = tri_cell + float2(1.0f, 0.0f); + baryc = float3(tri_frac.x + tri_frac.y - 1.0f, 1.0f - tri_frac.x, 1.0f - tri_frac.y); + } + + float input_scale = _Burley_Tiling_Input_Scale; + _burley_ctx.weights = burley_apply_blend_gamma(baryc, _Burley_Tiling_Blend_Gamma); + float2 uv_dx = ddx(uv); + float2 uv_dy = ddy(uv); + _burley_ctx.patch_0 = burley_make_patch_transform(uv, uv_dx, uv_dy, vertex_0, input_scale); + _burley_ctx.patch_1 = burley_make_patch_transform(uv, uv_dx, uv_dy, vertex_1, input_scale); + _burley_ctx.patch_2 = burley_make_patch_transform(uv, uv_dx, uv_dy, vertex_2, input_scale); +} +#endif // _BURLEY_TILING + +#if defined(_PARALLAX_HEIGHTMAP_TEXTURE) || defined(_BURLEY_TILING_HEIGHTMAP) +float heightmap_sample(float2 uv) { + #if defined(_BURLEY_TILING_HEIGHTMAP) + float2 delta = (uv - _burley_ctx.base_uv) * _burley_ctx.uv_scale; + float4 s0 = _Burley_Tiling_Heightmap.SampleGrad(aniso4_trilinear_repeat_s, + _burley_ctx.patch_0.uv + mul(_burley_ctx.patch_0.uv_to_patch, delta), + _burley_ctx.patch_0.dx, _burley_ctx.patch_0.dy); + float4 s1 = _Burley_Tiling_Heightmap.SampleGrad(aniso4_trilinear_repeat_s, + _burley_ctx.patch_1.uv + mul(_burley_ctx.patch_1.uv_to_patch, delta), + _burley_ctx.patch_1.dx, _burley_ctx.patch_1.dy); + float4 s2 = _Burley_Tiling_Heightmap.SampleGrad(aniso4_trilinear_repeat_s, + _burley_ctx.patch_2.uv + mul(_burley_ctx.patch_2.uv_to_patch, delta), + _burley_ctx.patch_2.dx, _burley_ctx.patch_2.dy); + float4 blend = s0 * _burley_ctx.weights.x + s1 * _burley_ctx.weights.y + s2 * _burley_ctx.weights.z; + return burley_degaussianize(_Burley_Tiling_Heightmap_LUT, + burley_apply_soft_clipping(blend.rgb, _burley_ctx.weights), false).r; + #elif defined(_PARALLAX_HEIGHTMAP_TEXTURE) + return _Parallax_Heightmap.Sample(linear_repeat_s, uv * _Parallax_Heightmap_ST.xy + _Parallax_Heightmap_ST.zw).r; + #endif +} +#endif // _PARALLAX_HEIGHTMAP_TEXTURE || _BURLEY_TILING_HEIGHTMAP + #if defined(_PARALLAX_HEIGHTMAP) float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { float3 view_dir_tangent = mul(tbn, view_dir_world); @@ -28,8 +200,7 @@ float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { float2 cur_uv = uv; float2 prev_uv = uv; float cur_depth = 0.0; - float cur_height = 1.0 - _Parallax_Heightmap.Sample(linear_repeat_s, - cur_uv * _Parallax_Heightmap_ST.xy + _Parallax_Heightmap_ST.zw).r; + float cur_height = 1.0 - heightmap_sample(cur_uv); cur_height = (cur_height - (1.0 - _Parallax_Heightmap_Bias)); // If starting inside geometry, march backwards @@ -53,8 +224,7 @@ float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { cur_uv += delta_uv; cur_depth += delta_depth; - cur_height = 1.0 - _Parallax_Heightmap.Sample(linear_repeat_s, - cur_uv * _Parallax_Heightmap_ST.xy + _Parallax_Heightmap_ST.zw).r; + cur_height = 1.0 - heightmap_sample(cur_uv); cur_height = (cur_height - (1.0 - _Parallax_Heightmap_Bias)); } @@ -68,8 +238,7 @@ float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { [unroll(2)] for (int j = 0; j < 2; j++) { - float mid_height = 1.0 - _Parallax_Heightmap.Sample(linear_repeat_s, - 0.5 * (low_uv + high_uv) * _Parallax_Heightmap_ST.xy + _Parallax_Heightmap_ST.zw).r; + float mid_height = 1.0 - heightmap_sample(0.5 * (low_uv + high_uv)); mid_height = (mid_height - (1.0 - _Parallax_Heightmap_Bias)); float mid_depth = 0.5 * (low_depth + high_depth); float mid_sign = mid_height - mid_depth; @@ -86,8 +255,7 @@ float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { float2 refine_uv = 0.5 * (low_uv + high_uv); return refine_uv - uv; #else - float2 heightmap_uv = uv * _Parallax_Heightmap_ST.xy + _Parallax_Heightmap_ST.zw; - float height = _Parallax_Heightmap.Sample(linear_repeat_s, heightmap_uv).r; + float height = heightmap_sample(uv); height = saturate(height - _Parallax_Heightmap_Bias); return uv_step * height; @@ -182,152 +350,20 @@ void apply_letter_grid(v2f i, inout Pbr pbr) { #endif } +void apply_burley_tiling(inout Pbr pbr, inout float3 normal_tangent) { #if defined(_BURLEY_TILING) -float2 burley_tri_to_cart(float2 tri_coord) { - return float2( - tri_coord.x + tri_coord.y * 0.5f, - tri_coord.y * SQRT_3_OVER_2); -} - -float3 burley_apply_blend_gamma(float3 weights, float gamma) { - weights = pow(weights, gamma); - return weights / (weights.x + weights.y + weights.z); -} - -// Equation 4 (first half). -float3 burley_soft_clipping_lower_half(float3 x_hat, float w_hat) { - float linear_start = 0.25f * (2.0f - w_hat); - float3 linear_value = (x_hat - 0.5f) / w_hat + 0.5f; - float3 linear_mask = step(float3(linear_start, linear_start, linear_start), x_hat); - - if (w_hat >= TWO_OVER_THREE) { - float3 t = x_hat / (2.0f - w_hat); - float3 quadratic = 8.0f * (1.0f / w_hat - 1.0f) * t * t + (3.0f - 2.0f / w_hat) * t; - return lerp(quadratic, linear_value, linear_mask); - } - - float quadratic_start = 0.25f * (2.0f - 3.0f * w_hat); - float3 d = (x_hat - quadratic_start) / w_hat; - float3 quadratic = d * d; - float3 quadratic_mask = step(float3(quadratic_start, quadratic_start, quadratic_start), x_hat); - float3 result = quadratic * quadratic_mask; - return lerp(result, linear_value, linear_mask); -} - -// Equation 4. -float3 burley_soft_clipping_contrast(float3 x_hat, float w_hat) { - float3 upper_mask = step(0.5f, x_hat); - float3 lower_x = min(x_hat, 1.0f - x_hat); - float3 lower_y = burley_soft_clipping_lower_half(lower_x, w_hat); - return lerp(lower_y, 1.0f - lower_y, upper_mask); -} - -float3 burley_apply_soft_clipping(float3 gaussian_color, float3 weights) { - float w_hat = sqrt(dot(weights, weights)); - return burley_soft_clipping_contrast(gaussian_color, w_hat); -} - -float3 burley_degaussianize(texture2D lut, float3 gaussian_color, bool decode_srgb = true) { - float2 uv_r = float2(gaussian_color.r, 0.5f); - float2 uv_g = float2(gaussian_color.g, 0.5f); - float2 uv_b = float2(gaussian_color.b, 0.5f); - float lut_r = lut.Sample(linear_clamp_s, uv_r).r; - float lut_g = lut.Sample(linear_clamp_s, uv_g).g; - float lut_b = lut.Sample(linear_clamp_s, uv_b).b; - float3 restored = float3(lut_r, lut_g, lut_b); - return decode_srgb ? srgb_to_linear(restored) : restored; -} - -struct BurleyPatchTransform { - float2 uv; - float2 dx; - float2 dy; -}; - -BurleyPatchTransform burley_make_patch_transform(float2 uv, float2 uv_dx, float2 uv_dy, - float2 tri_vertex, float input_scale) { - float3 cube_id = float3(tri_vertex.x, tri_vertex.y, -tri_vertex.x - tri_vertex.y); - float3 tile_rand3 = hash33_fast(cube_id); - float2 vertex_uv = burley_tri_to_cart(tri_vertex); - // Map the unit-radius hex support to the unit square so arbitrary rotation - // stays within bounds. - float2 local_uv = (uv - vertex_uv) * 0.5f; - // Apply input scaling. - local_uv *= input_scale; - float2 sample_dx = uv_dx * (0.5f * input_scale); - float2 sample_dy = uv_dy * (0.5f * input_scale); - // Rotate. - float theta = hash31_ff(tile_rand3) * TAU; - float2x2 rot = float2x2(cos(theta), -sin(theta), sin(theta), cos(theta)); - local_uv = mul(rot, local_uv); - sample_dx = mul(rot, sample_dx); - sample_dy = mul(rot, sample_dy); - // Apply randomized offset, staying within bounds. - // The scaled-and-rotated footprint is bounded by [-Input_Scale / 2, Input_Scale / 2], - // so we can offset by [(1 - Input_Scale) / 2]. - float2 random_offset = (tile_rand3.yz * 2.0f - 1.0f) * (0.5f * (1.0f - input_scale)); - local_uv += random_offset; - // Finally, remap onto [0, 1]. - local_uv += 0.5f; + float3 weights = _burley_ctx.weights; - BurleyPatchTransform patch; - patch.uv = local_uv; - patch.dx = sample_dx; - patch.dy = sample_dy; - return patch; -} - -float4 burley_sample_patch(texture2D tex, BurleyPatchTransform patch) { - return tex.SampleGrad( - aniso4_trilinear_repeat_s, patch.uv, patch.dx, patch.dy); -} -#endif // _BURLEY_TILING - -void apply_burley_tiling(v2f i, inout Pbr pbr, inout float3 normal_tangent) { -#if defined(_BURLEY_TILING) - // Center at 0. - float2 uv = i.uv01.xy - 0.5; - // Scale so that any rotation remains within [0, 1] bounds. - uv *= TWO_OVER_SQRT_3; - uv /= _Burley_Tiling_Output_Scale; - float3 hex_coord = cart_to_hex(uv); - float2 tri_coord = hex_coord.yz; - float2 tri_cell = floor(tri_coord); - float2 tri_frac = tri_coord - tri_cell; - float2 vertex_0; - float2 vertex_1; - float2 vertex_2; - float3 baryc; - if (tri_frac.x + tri_frac.y < 1.0f) { - vertex_0 = tri_cell; - vertex_1 = tri_cell + float2(1.0f, 0.0f); - vertex_2 = tri_cell + float2(0.0f, 1.0f); - baryc = float3(1.0f - (tri_frac.x + tri_frac.y), tri_frac.x, tri_frac.y); - } else { - vertex_0 = tri_cell + 1.0f; - vertex_1 = tri_cell + float2(0.0f, 1.0f); - vertex_2 = tri_cell + float2(1.0f, 0.0f); - baryc = float3(tri_frac.x + tri_frac.y - 1.0f, 1.0f - tri_frac.x, 1.0f - tri_frac.y); - } - - float input_scale = _Burley_Tiling_Input_Scale; - float3 weights = burley_apply_blend_gamma(baryc, _Burley_Tiling_Blend_Gamma); - float2 uv_dx = ddx(uv); - float2 uv_dy = ddy(uv); - BurleyPatchTransform patch_0_transform = burley_make_patch_transform(uv, uv_dx, uv_dy, vertex_0, input_scale); - BurleyPatchTransform patch_1_transform = burley_make_patch_transform(uv, uv_dx, uv_dy, vertex_1, input_scale); - BurleyPatchTransform patch_2_transform = burley_make_patch_transform(uv, uv_dx, uv_dy, vertex_2, input_scale); - - float4 patch_0 = burley_sample_patch(_Burley_Tiling_Maintex, patch_0_transform); - float4 patch_1 = burley_sample_patch(_Burley_Tiling_Maintex, patch_1_transform); - float4 patch_2 = burley_sample_patch(_Burley_Tiling_Maintex, patch_2_transform); + float4 patch_0 = burley_sample_patch(_Burley_Tiling_Maintex, _burley_ctx.patch_0); + float4 patch_1 = burley_sample_patch(_Burley_Tiling_Maintex, _burley_ctx.patch_1); + float4 patch_2 = burley_sample_patch(_Burley_Tiling_Maintex, _burley_ctx.patch_2); float4 gaussian_blend = patch_0 * weights.x + patch_1 * weights.y + patch_2 * weights.z; pbr.albedo.xyz = burley_degaussianize(_Burley_Tiling_Maintex_LUT, burley_apply_soft_clipping(gaussian_blend.rgb, weights)); #if defined(_BURLEY_TILING_SMOOTHNESS) - float4 patch_0_smoothness = burley_sample_patch(_Burley_Tiling_Smoothness_Map, patch_0_transform); - float4 patch_1_smoothness = burley_sample_patch(_Burley_Tiling_Smoothness_Map, patch_1_transform); - float4 patch_2_smoothness = burley_sample_patch(_Burley_Tiling_Smoothness_Map, patch_2_transform); + float4 patch_0_smoothness = burley_sample_patch(_Burley_Tiling_Smoothness_Map, _burley_ctx.patch_0); + float4 patch_1_smoothness = burley_sample_patch(_Burley_Tiling_Smoothness_Map, _burley_ctx.patch_1); + float4 patch_2_smoothness = burley_sample_patch(_Burley_Tiling_Smoothness_Map, _burley_ctx.patch_2); float4 smoothness_blend = patch_0_smoothness * weights.x + patch_1_smoothness * weights.y + patch_2_smoothness * weights.z; pbr.smoothness = burley_degaussianize(_Burley_Tiling_Smoothness_Map_LUT, burley_apply_soft_clipping(smoothness_blend, weights)).r; #if defined(_BURLEY_TILING_SMOOTHNESS_INVERT) @@ -337,9 +373,9 @@ void apply_burley_tiling(v2f i, inout Pbr pbr, inout float3 normal_tangent) { #if defined(_BURLEY_TILING_NORMAL) // TODO whiteout blending? - float4 patch_0_normal = burley_sample_patch(_Burley_Tiling_Normal_Map, patch_0_transform); - float4 patch_1_normal = burley_sample_patch(_Burley_Tiling_Normal_Map, patch_1_transform); - float4 patch_2_normal = burley_sample_patch(_Burley_Tiling_Normal_Map, patch_2_transform); + float4 patch_0_normal = burley_sample_patch(_Burley_Tiling_Normal_Map, _burley_ctx.patch_0); + float4 patch_1_normal = burley_sample_patch(_Burley_Tiling_Normal_Map, _burley_ctx.patch_1); + float4 patch_2_normal = burley_sample_patch(_Burley_Tiling_Normal_Map, _burley_ctx.patch_2); float4 normal_blend = patch_0_normal * weights.x + patch_1_normal * weights.y + patch_2_normal * weights.z; normal_tangent = burley_degaussianize(_Burley_Tiling_Normal_Map_LUT, burley_apply_soft_clipping(normal_blend.rgb, weights), false); normal_tangent.xy = normal_tangent.xy * 2 - 1; @@ -364,12 +400,24 @@ Pbr getPbr(v2f i) { i.uv01.xy += getTime() * _UV_Scroll_Speed; #endif // _UV_SCROLL +#if defined(_BURLEY_TILING) + burley_tiling_setup(i.uv01.xy); +#endif + #if defined(_PARALLAX_HEIGHTMAP) float2 uv_parallax = i.uv01.xy + parallax_offset(i.uv01.xy, normalize(i.eyeVec.xyz), pbr.tbn); #else float2 uv_parallax = i.uv01.xy; #endif // _PARALLAX_HEIGHTMAP +#if defined(_PARALLAX_HEIGHTMAP) || defined(_BURLEY_TILING_HEIGHTMAP) + pbr.height = heightmap_sample(i.uv01.xy); +#endif + +#if defined(_BURLEY_TILING) + burley_tiling_setup(uv_parallax); +#endif + #if defined(OUTLINES_PASS) && defined(_OUTLINES) pbr.albedo = _Outlines_Color; #else @@ -396,7 +444,7 @@ Pbr getPbr(v2f i) { apply_marble(i.worldPos, pbr.albedo.xyz); apply_kintsugi(i.worldPos, pbr.albedo.xyz, pbr.smoothness, pbr.metallic); apply_letter_grid(i, pbr); - apply_burley_tiling(i, pbr, normal_tangent); + apply_burley_tiling(pbr, normal_tangent); applyDecals(i, pbr, normal_tangent); |
