yum-archive/2ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum-archive/2ner
0a8d744
master
1#ifndef __YUM_BRDF_INC 2#define __YUM_BRDF_INC 3 4#include "UnityCG.cginc" 5#include "UnityStandardConfig.cginc" 6#include "UnityLightingCommon.cginc" 7 8#include "filamented.cginc" 9#include "math.cginc" 10#include "pema99.cginc" 11#include "yum_pbr.cginc" 12#include "yum_lighting.cginc" 13 14// Define Filament quality levels for proper f90 calculation 15#ifndef FILAMENT_QUALITY 16#define FILAMENT_QUALITY_LOW 0 17#define FILAMENT_QUALITY_NORMAL 1 18#define FILAMENT_QUALITY_HIGH 2 19#define FILAMENT_QUALITY FILAMENT_QUALITY_NORMAL 20#endif 21 22#if defined(_MATERIAL_TYPE_CLOTH) 23float D_Charlie(float roughness, float NoH) { 24 // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF" 25 float invAlpha = 1.0 / roughness; 26 float cos2h = NoH * NoH; 27 float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16 28 return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI); 29} 30#endif 31 32// Cloth visibility term from Neubelt and Pettineo 33float V_Cloth(float NoV, float NoL) { 34 return 1.0 / (4.0 * (NoL + NoV - NoL * NoV)); 35} 36 37float3 specularLobe(v2f i, f2f f, YumPbr pbr, YumLighting light, 38 float3 f0, float3 h, float LoH, float NoH, float NoV, float NoL) 39{ 40#if defined(_MATERIAL_TYPE_CLOTH) 41 float D = D_Charlie(pbr.roughness, NoH); 42 float V = V_Cloth(NoV, NoL); 43 float3 F = _Cloth_Sheen_Color; 44 return (D * V) * F; 45#else 46 // Use Filament's proper f90 calculation for better energy conservation 47#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW 48 const float3 F = F_Schlick(f0, LoH); // f90 = 1.0 49#else 50 float f90 = saturate(dot(f0, (50.0 * 0.33))); 51 const float3 F = F_Schlick(f0, f90, LoH); 52#endif 53 54#if defined(_ANISOTROPY) 55 float anisotropy = _Anisotropy_Strength; 56 // Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces" 57 float3 b = pbr.binormal; 58 float3 t = i.tangent.xyz; 59 float at = max(pbr.roughness * (1.0 + anisotropy), 0.001); 60 float ab = max(pbr.roughness * (1.0 - anisotropy), 0.001); 61 float D = D_GGX_Anisotropic(at, ab, NoH, h, t, b); 62 float ToV = dot(t, light.view_dir); 63 float BoV = dot(b, light.view_dir); 64 float ToL = dot(t, light.dir); 65 float BoL = dot(b, light.dir); 66 float V = V_SmithGGXCorrelated_Anisotropic(at, ab, 67 ToV, BoV, ToL, BoL, NoV, NoL); 68#else 69 float D = D_GGX(pbr.roughness, NoH, h); 70 float V = V_SmithGGXCorrelated_Fast(pbr.roughness, NoV, NoL); 71#endif 72 return (D * V) * F; 73#endif 74} 75 76float computeDielectricF0(float reflectance) { 77 return 0.16 * reflectance * reflectance; 78} 79 80float computeSpecularAO(float NoV, float visibility, float roughness) { 81 // Lagarde and de Rousiers 2014, "Moving Frostbite to PBR" 82 return saturate(pow(NoV + visibility, exp2(-16.0 * roughness - 1.0)) - 1.0 + visibility); 83} 84 85float singleBounceAO(float visibility) { 86 return visibility; // Simplified version 87} 88 89float4 YumBRDF(v2f i, f2f f, const YumLighting light, YumPbr pbr) { 90 const float3 h = normalize(light.view_dir + light.dir); 91 const float LoH = saturate(dot(light.dir, h)); 92 const float NoL = light.NoL; 93#if defined(_WRAPPED_LIGHTING) 94 const float NoL_wrapped_s = light.NoL_wrapped_s; 95 const float NoL_wrapped_d = light.NoL_wrapped_d; 96#else 97 const float NoL_wrapped_s = light.NoL; 98 const float NoL_wrapped_d = light.NoL; 99#endif 100 const float NoV = max(1E-4, dot(pbr.normal, light.view_dir)); 101 const float NoH = saturate(dot(pbr.normal, h)); 102 const float VoL = saturate(dot(light.view_dir, light.dir)); 103 104#if defined(_CLEARCOAT) && (defined(FORWARD_BASE_PASS) || defined(FORWARD_ADD_PASS)) 105 // Clearcoat uses geometric normal (not perturbed by normal maps) 106#if defined(_CLEARCOAT_GEOMETRIC_NORMALS) 107 const float3 cc_normal = normalize(i.normal); 108#else 109 const float3 cc_normal = pbr.normal; 110#endif 111 const float NoH_cc = saturate(dot(cc_normal, h)); 112 const float NoL_cc = saturate(dot(cc_normal, light.dir)); 113 const float NoV_cc = max(1E-4, dot(cc_normal, light.view_dir)); 114 const float cc_mask = _Clearcoat_Mask.SampleLevel(linear_repeat_s, i.uv01.xy, 0); 115#endif 116 117#if defined(_MATERIAL_TYPE_CLOTH) 118 // Cloth specific BRDF 119 float3 direct_cloth; 120 { 121 // Cloth diffuse BRDF - use Fd_Lambert and multiply by PI to match Unity intensities 122 float3 Fd = pbr.albedo * Fd_Lambert() * PI; 123 Fd *= light.attenuation; 124 125 #if defined(_MATERIAL_TYPE_CLOTH_SUBSURFACE) 126 // Energy conservative wrap diffuse for subsurface scattering 127 Fd *= NoL_wrapped_d; 128 // Apply subsurface color 129 Fd *= saturate(_Cloth_Subsurface_Color + NoL_wrapped_d); 130 #endif 131 132 // Cloth specular BRDF - multiply by PI to match Unity intensities 133 float3 Fr = specularLobe(i, f, pbr, light, float3(0.04, 0.04, 0.04), h, LoH, NoH, NoV, NoL_wrapped_s) * PI * light.attenuation; 134 135 #if defined(_MATERIAL_TYPE_CLOTH_SUBSURFACE) 136 // No need to multiply by NoL when using subsurface scattering 137 direct_cloth = (Fd + Fr * NoL_wrapped_s) * light.direct * _Cloth_Direct_Multiplier; 138 #else 139 direct_cloth = (Fd + Fr) * NoL_wrapped_d * light.direct * _Cloth_Direct_Multiplier; 140 #endif 141 } 142#endif 143 144 const float dielectric_f0 = computeDielectricF0(_reflectance); 145 const float3 f0 = lerp(dielectric_f0, pbr.albedo, pbr.metallic); 146 float2 dfg_uv = float2(NoV, pbr.roughness_perceptual); 147 float3 dfg; 148 [branch] 149 if (textureExists(_DFG_LUT)) { 150 dfg = _DFG_LUT.SampleLevel(bilinear_clamp_s, dfg_uv, 0).rgb; 151 } else { 152 dfg = float3(1, 1, 1); 153 } 154 155 float3 direct_standard; 156 { 157 float remainder = 1.0f; 158 159#if defined(_CLEARCOAT) && (defined(FORWARD_BASE_PASS) || defined(FORWARD_ADD_PASS)) 160 float cc_f0 = 0.04f; 161 float cc_roughness = _Clearcoat_Roughness * _Clearcoat_Roughness; // Convert perceptual to linear 162 163 float Fcc = F_Schlick(cc_f0, LoH).x * cc_mask * _Clearcoat_Strength; 164 float Dcc = D_GGX(cc_roughness, NoH_cc, h); 165 float Vcc = V_SmithGGXCorrelated_Fast(cc_roughness, NoV_cc, NoL_cc); 166 167 float3 direct_specular_cc = Dcc * Vcc * Fcc * light.direct * NoL_cc * PI * light.attenuation; 168 direct_specular_cc = max(0, direct_specular_cc); 169 170 // Energy conservation: reduce base layer contribution by clearcoat Fresnel 171 remainder -= Fcc; 172 remainder = max(0, remainder); 173#endif 174 const float3 E = specularDFG(dfg, f0); 175 const float3 energy_compensation = energyCompensation(dfg, f0); 176 177 float3 Fd = pbr.albedo.xyz * (1.0f - pbr.metallic) * Fd_Burley(pbr.roughness, NoV, NoL_wrapped_d, LoH) * PI; 178 Fd *= light.attenuation * pbr.ao * remainder; 179 180 // Multiply by PI to match Unity intensities (same as Filament's implementation) 181 float3 Fr = specularLobe(i, f, pbr, light, f0, h, LoH, NoH, NoV, NoL_wrapped_s) * PI * light.attenuation * remainder; 182 183 // Apply energy compensation to specular term 184 float3 color = Fd * NoL_wrapped_d + Fr * energy_compensation * NoL_wrapped_s; 185#if defined(_CLEARCOAT) && (defined(FORWARD_BASE_PASS) || defined(FORWARD_ADD_PASS)) 186 color += direct_specular_cc; 187#endif 188 direct_standard = color * light.direct; 189 } 190 direct_standard = 0; 191 192#if defined(_MATERIAL_TYPE_CLOTH) 193 float3 indirect_cloth; 194 { 195 // Simple indirect lighting for cloth 196 // Add additional corrective term to account for the fact that vrchat map 197 // makers suck shit and don't use enough reflection probes. 198 float3 Fr = _Cloth_Sheen_Color * light.specular * light.diffuse_luminance; 199 float3 Fd = pbr.albedo * light.diffuse * pbr.ao; 200 201 #if defined(_MATERIAL_TYPE_CLOTH_SUBSURFACE) 202 // Apply subsurface color to indirect diffuse 203 Fd *= _Cloth_Subsurface_Color; 204 #endif 205 206 indirect_cloth = (Fr + Fd) * _Cloth_Indirect_Multiplier; 207 } 208#endif 209 210 float3 indirect_standard = 0; 211 { 212 float3 remainder = 1.0f; 213 214#if defined(_CLEARCOAT) && (defined(FORWARD_BASE_PASS) || defined(FORWARD_ADD_PASS)) 215 // Clearcoat indirect specular 216 float cc_f0 = 0.04f; 217 float cc_roughness_perceptual = _Clearcoat_Roughness; 218 219 // Sample environment for clearcoat reflection using geometric normal 220#if defined(_CLEARCOAT_GEOMETRIC_NORMALS) 221 float3 cc_reflect_dir = reflect(-light.view_dir, normalize(i.normal)); 222#else 223 float3 cc_reflect_dir = reflect(-light.view_dir, cc_normal); 224#endif 225 226 UnityGIInput cc_data; 227 cc_data.worldPos = f.worldPos; 228 cc_data.worldViewDir = light.view_dir; 229 cc_data.probeHDR[0] = unity_SpecCube0_HDR; 230 cc_data.probeHDR[1] = unity_SpecCube1_HDR; 231#if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION) 232 cc_data.boxMin[0] = unity_SpecCube0_BoxMin; 233#endif 234#ifdef UNITY_SPECCUBE_BOX_PROJECTION 235 cc_data.boxMax[0] = unity_SpecCube0_BoxMax; 236 cc_data.probePosition[0] = unity_SpecCube0_ProbePosition; 237 cc_data.boxMax[1] = unity_SpecCube1_BoxMax; 238 cc_data.boxMin[1] = unity_SpecCube1_BoxMin; 239 cc_data.probePosition[1] = unity_SpecCube1_ProbePosition; 240#endif 241 242 float3 cc_env_refl = UnityGI_prefilteredRadiance(cc_data, cc_roughness_perceptual, cc_reflect_dir); 243#if defined(_FALLBACK_CUBEMAP) 244 if (!SceneHasReflections() || _Fallback_Cubemap_Force) { 245 // Set up data for fallback sampling similar to Unity's system 246 247 #ifdef UNITY_SPECCUBE_BOX_PROJECTION 248 cc_reflect_dir = BoxProjectedCubemapDirection(cc_reflect_dir, f.worldPos, /*probe_position=*/0, /*box_min=*/-1, /*box_max=*/1); 249 #endif 250 251 half mip = cc_roughness_perceptual * UNITY_SPECCUBE_LOD_STEPS; 252 float4 envSample = UNITY_SAMPLE_TEXCUBE_LOD(_Fallback_Cubemap, cc_reflect_dir, mip); 253 cc_env_refl = DecodeHDR(envSample, _Fallback_Cubemap_HDR) * _Fallback_Cubemap_Brightness * light.diffuse_luminance; 254 } 255#endif 256 257#if defined(_BRIGHTNESS_CONTROL) 258 cc_env_refl *= _Brightness_Multiplier; 259#endif 260 float Fcc = F_Schlick(cc_f0, NoV_cc).x * cc_mask * _Clearcoat_Strength; 261 float3 indirect_specular_cc = Fcc * cc_env_refl; 262 263 // Energy conservation 264 indirect_standard += indirect_specular_cc; 265 remainder = saturate(remainder - Fcc); 266#endif 267 268 const float3 f0_spec = lerp(dielectric_f0, pbr.albedo.xyz, pbr.metallic); 269 const float3 ibl_specular_reflectance = lerp(dfg.xxx, dfg.yyy, f0_spec); 270 float diffuseAO = pbr.ao; 271 272 float3 Fr = ibl_specular_reflectance * light.specular * remainder; 273 remainder = saturate(remainder - Fr); 274 275 float3 Fd = pbr.albedo.xyz * (1.0f - pbr.metallic) * light.diffuse * pbr.ao * remainder; 276 277 indirect_standard += Fr + Fd; 278 } 279 280#if defined(_MATERIAL_TYPE_CLOTH) 281 float cloth_mask = _Cloth_Mask.Sample(linear_repeat_s, i.uv01.xy); 282 float3 direct = lerp(direct_standard, direct_cloth, cloth_mask); 283 float3 indirect = lerp(indirect_standard, indirect_cloth, cloth_mask); 284#else 285 float3 direct = direct_standard; 286 float3 indirect = indirect_standard; 287#endif 288 289 float4 lit = float4(direct + indirect, pbr.albedo.a); 290 291 float3 lv_specular = 0; 292 [branch] 293 if (_UdonLightVolumeEnabled) { 294 float3 lv_specular = LightVolumeSpecular(pbr.albedo, pbr.smoothness, pbr.metallic, 295 pbr.normal, light.view_dir, light.L00, light.L01r, light.L01g, light.L01b); 296 lit.rgb += lv_specular; 297 298#if defined(_CLEARCOAT) && (defined(FORWARD_BASE_PASS) || defined(FORWARD_ADD_PASS)) 299 float Fcc = F_Schlick(0.04f, NoV_cc) * cc_mask * _Clearcoat_Strength; 300 lit.rgb += lv_specular * Fcc; 301#endif 302 } 303 304 return lit; 305} 306 307#endif // __YUM_BRDF_INC