#ifndef __PBR_INC #define __PBR_INC #include "filamented.cginc" #include "globals.cginc" #include "instancing.cginc" #include "interpolators.cginc" #include "texture_utils.cginc" struct Pbr { float4 albedo; float3 normal; float3x3 tbn; float smoothness; float roughness_perceptual; float roughness; float metallic; #if defined(_CLEARCOAT) float cc_roughness; float cc_strength; #endif #if defined(_CLOTH_SHEEN) float cl_strength; float3 cl_color; #endif }; #define MIN_PERCEPTUAL_ROUGHNESS 5e-2f #define MIN_ROUGHNESS 5e-3f #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 // TODO consider normal filtering like filamented 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); #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; instancing_frag(i); 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 pbr.albedo = _MainTex.Sample(aniso16_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)); 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)); detail_normal.xy *= _DetailNormalMapScale; float detail_mask = _DetailMask.Sample(aniso16_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); pbr.smoothness = metallic_gloss.a * _Glossiness; pbr.metallic = metallic_gloss.r * _Metallic; #if defined(_CLEARCOAT) pbr.cc_roughness = _Clearcoat_Roughness; pbr.cc_strength = _Clearcoat_Strength; #endif #if defined(_CLOTH_SHEEN) pbr.cl_strength = _Cloth_Sheen_Strength; pbr.cl_color = _Cloth_Sheen_Color; #endif propagateSmoothness(pbr); return pbr; } #endif // __PBR_INC