diff options
| author | yum <yum.food.vr@gmail.com> | 2026-02-23 20:20:31 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-02-23 20:20:31 -0800 |
| commit | 5fa1c0ec5b2b59db3858a7dfe9f4001eeeff8cc9 (patch) | |
| tree | 5e1d7729e2fe52964c9d7c52aece72b4f2ecb3e5 | |
| parent | 2d205f1f5df12b6e66f6cca1a05152b18d53c0d1 (diff) | |
Add ambient occlusion & normal filtering
| -rwxr-xr-x | 3ner.shader | 8 | ||||
| -rwxr-xr-x | 3ner.shader.meta | 3 | ||||
| -rw-r--r-- | LUTS/dfg_standard.exr | bin | 520151 -> 2052797 bytes | |||
| -rwxr-xr-x | Scripts/make_dfg_lut.py | 4 | ||||
| -rwxr-xr-x | brdf.cginc | 29 | ||||
| -rwxr-xr-x | features.cginc | 4 | ||||
| -rwxr-xr-x | globals.cginc | 8 | ||||
| -rwxr-xr-x | lighting.cginc | 55 | ||||
| -rwxr-xr-x | pbr.cginc | 42 |
9 files changed, 101 insertions, 52 deletions
diff --git a/3ner.shader b/3ner.shader index 10b0e9c..4c1ca2c 100755 --- a/3ner.shader +++ b/3ner.shader @@ -47,6 +47,14 @@ Shader "yum_food/3ner" [IntRange] _Details_UV_Channel("UV Channel", Range(0, 3)) = 0 [HideInInspector] m_end_Details("Details", Float) = 0 //endex + + //ifex _Ambient_Occlusion_Enabled==0 + [HideInInspector] m_start_Ambient_Occlusion("Ambient Occlusion", Float) = 0 + [ThryToggle(_AMBIENT_OCCLUSION)] _Ambient_Occlusion_Enabled("Enable", Float) = 0 + _OcclusionMap("Ambient Occlusion", 2D) = "white" {} + _OcclusionStrength("Occlusion Strength", Range(0, 1)) = 1 + [HideInInspector] m_end_Ambient_Occlusion("Ambient Occlusion", Float) = 0 + //endex [HideInInspector] m_end_Main("Main", Float) = 0 [HideInInspector] m_start_Gimmicks("Gimmicks", Float) = 0 diff --git a/3ner.shader.meta b/3ner.shader.meta index c03debd..e8cfc6a 100755 --- a/3ner.shader.meta +++ b/3ner.shader.meta @@ -17,9 +17,8 @@ ShaderImporter: - _Impostors_Metallic_Gloss_Depth_Atlas: {instanceID: 0} - _Instance_Texture_Offset_Data_Tex: {instanceID: 0} - _Parallax_Heightmap: {instanceID: 0} - - _DFG_LUT: {fileID: 2800000, guid: d6fbf383c1bdb87439939bf17f69d539, type: 3} + - _DFG_LUT: {fileID: 2800000, guid: 76d65cbce584df7449699fb8406f60ea, type: 3} - _Clearcoat_Mask: {instanceID: 0} - - _Cloth_Sheen_DFG_LUT: {instanceID: 0} nonModifiableTextures: [] userData: assetBundleName: diff --git a/LUTS/dfg_standard.exr b/LUTS/dfg_standard.exr Binary files differindex 9542464..f5209c6 100644 --- a/LUTS/dfg_standard.exr +++ b/LUTS/dfg_standard.exr diff --git a/Scripts/make_dfg_lut.py b/Scripts/make_dfg_lut.py index 05794ff..d58f3cf 100755 --- a/Scripts/make_dfg_lut.py +++ b/Scripts/make_dfg_lut.py @@ -182,8 +182,8 @@ def calculate_pixel(coords, resolution, brdf_type, num_samples): u = (x + 0.5) / resolution v = (y + 0.5) / resolution - roughness = saturate(u) - NoV = saturate(v) + NoV = saturate(u) + roughness = saturate(v) if NoV < 1e-4: return x, y, 0.0, 0.0, 0.0 r, g = 0.0, 0.0 @@ -116,7 +116,7 @@ float4 brdf(Pbr pbr, LightData data) { // Direct { - float3 layer_attenuation = 1.0f; + float3 remainder = 1.0f; #if defined(_CLEARCOAT) float Fcc = F_Schlick(data.direct.LoH, cc_f0, f90); @@ -125,10 +125,10 @@ float4 brdf(Pbr pbr, LightData data) { float DFGcc = Fcc * Dcc * Gcc; float3 direct_specular_cc = DFGcc * data.direct.color * data.direct.NoL_cc * pbr.cc_strength; direct_specular_cc *= cc_energy_comp; - direct_specular_cc *= layer_attenuation; + direct_specular_cc *= remainder; direct_specular_cc = max(0, direct_specular_cc); specular += direct_specular_cc; - layer_attenuation *= saturate(1.0f - Fcc * pbr.cc_strength); + remainder *= saturate(1.0f - Fcc * pbr.cc_strength); #endif float3 F = F_Schlick(data.direct.LoH, f0_color, f90); @@ -138,7 +138,7 @@ float4 brdf(Pbr pbr, LightData data) { float3 direct_specular = (D * G) * F; direct_specular *= data.direct.color * data.direct.NoL; direct_specular *= energy_comp; - direct_specular *= layer_attenuation; + direct_specular *= remainder; direct_specular = max(0, direct_specular); specular += direct_specular; @@ -148,7 +148,7 @@ float4 brdf(Pbr pbr, LightData data) { float Fd = Fd_Lambertian(data.direct.NoL); #endif float3 direct_diffuse = Fd * (1.0f - pbr.metallic) * pbr.albedo.xyz * data.direct.color; - direct_diffuse *= layer_attenuation; + direct_diffuse *= remainder; direct_diffuse = max(0, direct_diffuse); diffuse += direct_diffuse; } @@ -156,25 +156,12 @@ float4 brdf(Pbr pbr, LightData data) { // Indirect #if !defined(FURNACE_TEST_DIRECT) && defined(FORWARD_BASE_PASS) { - float3 remainder = 1.0f; - -#if defined(_CLEARCOAT) - float cc_single_scatter = cc_f0 * cc_dfg.x + cc_dfg.y; - float3 indirect_specular_cc = data.indirect.specular_cc * (cc_single_scatter * cc_energy_comp) * pbr.cc_strength; - indirect_specular_cc = max(0, indirect_specular_cc); - specular += indirect_specular_cc; - remainder = saturate(remainder - indirect_specular_cc); -#endif - - float3 specular_tint = lerp(1, pbr.albedo.xyz, pbr.metallic); - // Filament whitepaper section 5.3.4.6 - float3 specular_dfg = dfg.xxx * f0 + dfg.yyy; - float3 indirect_specular = specular_tint * data.indirect.specular * specular_dfg; + float3 specular_dfg = dfg.xxx * f0_color + dfg.yyy; // filament 5.3.4.6 + float3 indirect_specular = data.indirect.specular * specular_dfg; specular += indirect_specular; - //remainder = saturate(remainder - indirect_specular); - float3 indirect_diffuse = pbr.albedo.xyz * data.indirect.diffuse * remainder * (1.0 - pbr.metallic); + float3 indirect_diffuse = pbr.albedo.xyz * data.indirect.diffuse * (1.0 - pbr.metallic); diffuse += indirect_diffuse; } #endif diff --git a/features.cginc b/features.cginc index 3cf46e0..9291f05 100755 --- a/features.cginc +++ b/features.cginc @@ -15,6 +15,10 @@ #pragma shader_feature_local _TESSELLATION_HEIGHTMAP_DIRECTION_CONTROL //endex +//ifex _Ambient_Occlusion_Enabled==0 +#pragma shader_feature_local _AMBIENT_OCCLUSION +//endex + //ifex _Clearcoat_Enabled==0 #pragma shader_feature_local _CLEARCOAT //endex diff --git a/globals.cginc b/globals.cginc index 0c4917d..6bfaace 100755 --- a/globals.cginc +++ b/globals.cginc @@ -6,8 +6,6 @@ SamplerState point_repeat_s; SamplerState linear_repeat_s; SamplerState aniso4_trilinear_repeat_s; -SamplerState aniso8_trilinear_repeat_s; -SamplerState aniso16_trilinear_repeat_s; SamplerState bilinear_repeat_s; SamplerState linear_clamp_s; SamplerState bilinear_clamp_s; @@ -34,6 +32,12 @@ float _Specular_AA_Variance; float _Specular_AA_Threshold; float _BRDF_Specular_Min_Denom; +#if defined(_AMBIENT_OCCLUSION) +texture2D _OcclusionMap; +float4 _OcclusionMap_ST; +float _OcclusionStrength; +#endif // __AMBIENT_OCCLUSION + #if defined(_MARBLE) texture2D _Marble_U_Ramp; texture2D _Marble_V_Ramp; diff --git a/lighting.cginc b/lighting.cginc index 87a3dfc..28ac25b 100755 --- a/lighting.cginc +++ b/lighting.cginc @@ -90,9 +90,9 @@ float4 getDirectLightColorIntensity() { return float4(_LightColor0.xyz, _LightColor0.w); } -float3 getIndirectSpecular(v2f i, float roughness, float3 view_dir, float3 reflect_dir) { +float3 getIndirectSpecular(v2f i, float perceptual_roughness, float3 view_dir, float3 reflect_dir) { UnityGIInput data = InitialiseUnityGIInput(i.worldPos, view_dir); - float3 env_refl = UnityGI_prefilteredRadiance(data, roughness, reflect_dir); + float3 env_refl = UnityGI_prefilteredRadiance(data, perceptual_roughness, reflect_dir); return env_refl; } @@ -133,23 +133,42 @@ float3 yumSH9(float4 n, float3 worldPos, inout LightIndirect light) { dot(light.L01b, n.xyz)); } - // Geomerics SH evaluation - // Extract L0 and optionally incorporate part of L2 - float3 L0 = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w); - float3 L0L2 = float3(unity_SHBr.z, unity_SHBg.z, unity_SHBb.z) / 3.0; - L0 = L0 + L0L2; // useL2 = true - - float3 finalSH; - finalSH.r = shEvaluateDiffuseL1Geomerics(L0.r, unity_SHAr.xyz, n.xyz); - finalSH.g = shEvaluateDiffuseL1Geomerics(L0.g, unity_SHAg.xyz, n.xyz); - finalSH.b = shEvaluateDiffuseL1Geomerics(L0.b, unity_SHAb.xyz, n.xyz); + // Unity gives us the first three bands (L0-L2) of SH coefficients as follows: + // unity_SHA*.w: L0 coefficients + // unity_SHA*.xyz: L1 coefficients + // unity_SHB*: first four of the L2 coefficients + // unity_SHC: last L2 coefficient + + // Parse out coefficients into a simpler but less efficient format. + float3 L00 = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w); + float3 L1_1 = float3(unity_SHAr.x, unity_SHAg.x, unity_SHAb.x); + float3 L10 = float3(unity_SHAr.y, unity_SHAg.y, unity_SHAb.y); + float3 L11 = float3(unity_SHAr.z, unity_SHAg.z, unity_SHAb.z); + float3 L2_2 = float3(unity_SHBr.x, unity_SHBg.x, unity_SHBb.x); + float3 L2_1 = float3(unity_SHBr.y, unity_SHBg.y, unity_SHBb.y); + float3 L20 = float3(unity_SHBr.z, unity_SHBg.z, unity_SHBb.z); + float3 L21 = float3(unity_SHBr.w, unity_SHBg.w, unity_SHBb.w); + float3 L22 = unity_SHC; + + // Equation 13 from "An Efficient Representation for Irradiance Environment + // Maps" by Ramamoorthi and Hanrahan. Note that the order of some + // coefficients is different, and normalization constants have been + // premultiplied by Unity. + float3 L0 = L00; + float3 L1 = L1_1 * n.x + L10 * n.y + L11 * n.z; + float3 L2 = + L2_2 * n.x * n.y + + L2_1 * n.y * n.z + + L20 * n.z * n.z + + L21 * n.x * n.z + + L22 * (n.x * n.x - n.y * n.y); light.L00 = L0; light.L01r = unity_SHAr.xyz; light.L01g = unity_SHAg.xyz; light.L01b = unity_SHAb.xyz; - return finalSH; + return L0 + L1 + L2; } float4 getIndirectDiffuse(v2f i, Pbr pbr, inout LightIndirect light) { @@ -157,6 +176,12 @@ float4 getIndirectDiffuse(v2f i, Pbr pbr, inout LightIndirect light) { #if defined(FORWARD_BASE_PASS) diffuse.xyz += max(0, yumSH9(float4(i.normal, 1.0), i.worldPos, light)); #endif + +#if defined(_AMBIENT_OCCLUSION) + diffuse.xyz *= lerp(1, _OcclusionMap.Sample(bilinear_repeat_s, i.uv01.xy), 1); + //diffuse.xyz = _OcclusionMap.Sample(bilinear_repeat_s, i.uv01.xy); +#endif + return diffuse; } @@ -208,9 +233,9 @@ void GetLighting(v2f i, Pbr pbr, out LightData data) { data.indirect.double_LoV = saturate(2.0f * indirect_LoV * indirect_LoV - 1.0f); data.indirect.diffuse = getIndirectDiffuse(i, pbr, data.indirect); - data.indirect.specular = getIndirectSpecular(i, pbr.roughness, view_dir, -data.indirect.dir); + data.indirect.specular = getIndirectSpecular(i, pbr.roughness_perceptual, view_dir, -data.indirect.dir); #if defined(_CLEARCOAT) - data.indirect.specular_cc = getIndirectSpecular(i, pbr.cc_roughness, view_dir, dir_cc); + data.indirect.specular_cc = getIndirectSpecular(i, saturate(sqrt(pbr.cc_roughness)), view_dir, dir_cc); #if defined(_CLEARCOAT_MASK) float cc_mask = _Clearcoat_Mask.Sample(bilinear_clamp_s, i.uv01.xy).r; data.indirect.specular_cc *= cc_mask; @@ -26,8 +26,11 @@ struct Pbr { #endif }; -#define MIN_PERCEPTUAL_ROUGHNESS 5e-2f -#define MIN_ROUGHNESS 5e-3f +// From filament: min roughness s.t. MIN_PERCEPTUAL_ROUGHNESS^4 > 0 in target +// precision. We use fp32. The smallest non-subnormal is 2^(-126). The 4th +// root of that is ~3.29 * 10^-10. +#define MIN_PERCEPTUAL_ROUGHNESS (3.3E-10) +#define MIN_ROUGHNESS (1.09E-19) #if defined(_PARALLAX_HEIGHTMAP) float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { @@ -115,10 +118,29 @@ float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { } #endif // _PARALLAX_HEIGHTMAP -// TODO consider normal filtering like filamented +// Tokuyashi and Kaplanyan 2019 "Improved Geometric Specular Antialiasing" +float normalFiltering(float3 normal, float perceptual_roughness) { + float3 du = ddx(normal); + float3 dv = ddy(normal); + + // Boxed equation in section 3.2 "Proposed Error Reduction." + float variance = dot(du, du) + dot(dv, dv); + float sigma = 0.5f; // standard deviation of pixel filter kernel in image space + float Sigma = sigma * sigma * variance; + + // Equation 1 in section 4.2 "Constraint for Conservative Isotropic Filtering" + float roughness = perceptual_roughness * perceptual_roughness; + float kappa = 0.18; + roughness = roughness + min(2 * Sigma, kappa); + + return sqrt(roughness); +} + void propagateSmoothness(inout Pbr pbr) { - pbr.roughness_perceptual = max(MIN_PERCEPTUAL_ROUGHNESS, 1.0f - pbr.smoothness); - pbr.roughness = max(MIN_ROUGHNESS, pbr.roughness_perceptual * pbr.roughness_perceptual); + pbr.smoothness = 1.0f - normalFiltering(pbr.normal, 1.0f - pbr.smoothness); + + pbr.roughness_perceptual = clamp(1.0f - pbr.smoothness, MIN_PERCEPTUAL_ROUGHNESS, 1); + pbr.roughness = clamp(pbr.roughness_perceptual * pbr.roughness_perceptual, MIN_ROUGHNESS, 1); #if defined(_CLEARCOAT) pbr.cc_roughness = max(MIN_ROUGHNESS, pbr.cc_roughness * pbr.cc_roughness); #endif @@ -168,25 +190,25 @@ Pbr getPbr(v2f i) { pbr.objPos = imp.objPos; #endif #else - pbr.albedo = _MainTex.Sample(aniso16_trilinear_repeat_s, uv_parallax * _MainTex_ST.xy + _MainTex_ST.zw); + pbr.albedo = _MainTex.Sample(aniso4_trilinear_repeat_s, uv_parallax * _MainTex_ST.xy + _MainTex_ST.zw); pbr.albedo *= _Color; apply_marble(i.worldPos, pbr.albedo.xyz); - float3 normal_tangent = UnpackNormal(_BumpMap.Sample(aniso16_trilinear_repeat_s, uv_parallax * _BumpMap_ST.xy)); + float3 normal_tangent = UnpackNormal(_BumpMap.Sample(aniso4_trilinear_repeat_s, uv_parallax * _BumpMap_ST.xy)); normal_tangent.xy *= _BumpScale; #if defined(_DETAILS) float2 detail_uv = get_uv_by_channel(i, _Details_UV_Channel); - float3 detail_normal = UnpackNormal(_DetailNormalMap.Sample(aniso16_trilinear_repeat_s, detail_uv * _DetailNormalMap_ST.xy)); + float3 detail_normal = UnpackNormal(_DetailNormalMap.Sample(aniso4_trilinear_repeat_s, detail_uv * _DetailNormalMap_ST.xy)); detail_normal.xy *= _DetailNormalMapScale; - float detail_mask = _DetailMask.Sample(aniso16_trilinear_repeat_s, detail_uv * _DetailMask_ST.xy).r; + float detail_mask = _DetailMask.Sample(aniso4_trilinear_repeat_s, detail_uv * _DetailMask_ST.xy).r; detail_normal.xy *= detail_mask; normal_tangent = blendNormalsHill12(normal_tangent, detail_normal); #endif pbr.normal = normalize(mul(normal_tangent, pbr.tbn)); - float4 metallic_gloss = _MetallicGlossMap.Sample(aniso16_trilinear_repeat_s, uv_parallax * _MetallicGlossMap_ST.xy); + float4 metallic_gloss = _MetallicGlossMap.Sample(aniso4_trilinear_repeat_s, uv_parallax * _MetallicGlossMap_ST.xy); pbr.smoothness = metallic_gloss.a * _Glossiness; pbr.metallic = metallic_gloss.r * _Metallic; #endif // _IMPOSTORS |
