#ifndef __PBR_INC #define __PBR_INC #include "filamented.cginc" #include "data.cginc" #include "decal.cginc" #include "instancing.cginc" #include "interpolators.cginc" #include "texture_utils.cginc" #if defined(_PARALLAX_HEIGHTMAP) float2 parallax_offset(float2 uv, float3 view_dir_world, float3x3 tbn) { float3 view_dir_tangent = mul(tbn, view_dir_world); float view_z = max(view_dir_tangent.z, 1e-3f); float2 uv_step = view_dir_tangent.xy / view_z * _Parallax_Heightmap_Scale; #if defined(_PARALLAX_HEIGHTMAP_RAY_MARCHING) // Adapt steps by angle to keep cost down while preserving glancing detail. float angle = saturate(view_z); float base_steps = _Parallax_Heightmap_Ray_Marching_Steps; float step_count = lerp(base_steps * 1.5, base_steps * 0.75, angle); step_count = clamp(step_count, 2.0, max(base_steps, 2.0)); float2 delta_uv = uv_step / step_count; float delta_depth = 1.0 / step_count; 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; cur_height = (cur_height - (1.0 - _Parallax_Heightmap_Bias)); // If starting inside geometry, march backwards bool inside = cur_depth < cur_height; if (!inside) { delta_uv = -delta_uv; delta_depth = -delta_depth; } float prev_depth = cur_depth; float prev_height = cur_height; [loop] for (int i = 0; i < (int)step_count; i++) { bool was_inside = cur_depth < cur_height; if (was_inside != inside) break; prev_depth = cur_depth; prev_height = cur_height; prev_uv = cur_uv; 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 = (cur_height - (1.0 - _Parallax_Heightmap_Bias)); } // Short binary refine between last two samples to tighten the hit float before = prev_height - prev_depth; float2 low_uv = prev_uv; float low_depth = prev_depth; float low_sign = before; float2 high_uv = cur_uv; float high_depth = cur_depth; [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; 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; if (mid_sign == 0.0 || sign(mid_sign) == sign(low_sign)) { low_uv = 0.5 * (low_uv + high_uv); low_depth = mid_depth; low_sign = mid_sign; } else { high_uv = 0.5 * (low_uv + high_uv); high_depth = mid_depth; } } 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; height = saturate(height - _Parallax_Heightmap_Bias); return uv_step * height; #endif } #endif // _PARALLAX_HEIGHTMAP // 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 saturate(sqrt(roughness)); } void propagateSmoothness(inout Pbr pbr) { 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 } void apply_marble(float3 world_pos, inout float3 albedo) { #if defined(_MARBLE) float3 uvw = world_pos * _Marble_Scale; float noise_r = sin_noise_3d_fbm(uvw + _Time[0], _Marble_Octaves, 2.0f, _Marble_Strength); float noise_g = sin_noise_3d_fbm(uvw+3.1 + _Time[0], _Marble_Octaves, 2.0f, _Marble_Strength); float noise_b = sin_noise_3d_fbm(uvw+3.7 + _Time[0], _Marble_Octaves, 2.0f, _Marble_Strength); float3 r = _Marble_U_Ramp.Sample(linear_repeat_s, float2(noise_r, 0)); float3 g = _Marble_V_Ramp.Sample(linear_repeat_s, float2(noise_g, 0)); float3 b = _Marble_W_Ramp.Sample(linear_repeat_s, float2(noise_b, 0)); albedo = r + g + b; #endif } Pbr getPbr(v2f i) { Pbr pbr = (Pbr) 0; float3 n = normalize(i.normal); float3 t = normalize(i.tangent.xyz); t = normalize(t - n * dot(n, t)); // Gram-Schmidt to avoid skew float3 b = normalize(cross(n, t)) * i.tangent.w; pbr.tbn = float3x3(t, b, n); #if defined(_UV_SCROLL) i.uv01.xy += getTime() * _UV_Scroll_Speed; #endif // _UV_SCROLL #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(OUTLINES_PASS) && defined(_OUTLINES) pbr.albedo = _Outlines_Color; #else pbr.albedo = _MainTex.Sample(aniso4_trilinear_repeat_s, uv_parallax * _MainTex_ST.xy + _MainTex_ST.zw); pbr.albedo *= _Color; #endif apply_marble(i.worldPos, pbr.albedo.xyz); 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(aniso4_trilinear_repeat_s, detail_uv * _DetailNormalMap_ST.xy)); detail_normal.xy *= _DetailNormalMapScale; 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 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; applyDecals(i, pbr, normal_tangent); pbr.normal = normalize(mul(normal_tangent, pbr.tbn)); #if defined(_BENT_NORMALS) float3 bent_ts = UnpackNormal(_Bent_Normals_Map.Sample( aniso4_trilinear_repeat_s, uv_parallax * _Bent_Normals_Map_ST.xy + _Bent_Normals_Map_ST.zw)); pbr.bent_normal = normalize(mul(bent_ts, pbr.tbn)); #endif #if defined(_CLEARCOAT) pbr.cc_roughness = _Clearcoat_Roughness; pbr.cc_roughness_perceptual = sqrt(pbr.cc_roughness); pbr.cc_strength = _Clearcoat_Strength; #endif propagateSmoothness(pbr); #if defined(_AMBIENT_OCCLUSION) pbr.ao = saturate(lerp(1.0, _OcclusionMap.Sample(bilinear_repeat_s, i.uv01.xy).r, _OcclusionStrength)); #endif #if defined(_EMISSIONS) && defined(FORWARD_BASE_PASS) float3 emission_tint = _EmissionColor; float3 emission_color = _EmissionMap.Sample(trilinear_repeat_s, i.uv01.xy * _EmissionMap_ST.xy + _EmissionMap_ST.zw); float emission_mask = _EmissionMask.Sample(trilinear_repeat_s, i.uv01.xy * _EmissionMask_ST.xy + _EmissionMask_ST.zw).r; pbr.emission = emission_tint * emission_color * emission_mask; #endif return pbr; } #endif // __PBR_INC