yum-archive/2ner

A toon shader for Unity's BIRP.

git clone https://git.yummers.dev/yum-archive/2ner

yumbuncha shitttt0a8d744

master
10.9 KiB307 linesraw
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