// Copyright Epic Games, Inc. All Rights Reserved. #define PATH_TRACING 1 #define ENABLE_SKY_LIGHT 1 #define NEEDS_LIGHTMAP_COORDINATE 0 // This define controls if we use the "g" factor from the SSS profile or not. This is disabled by default because // we can get a better match to how Metahumans are currently dialed with an isotropic phase function. It may be possible to revisit this // if we get a better alignment between raster SSS techniques and the PT random walk. #define USE_SSS_PROFILE_ANISOTROPY 0 #ifdef NEEDS_VERTEX_FACTORY_INTERPOLATION #undef NEEDS_VERTEX_FACTORY_INTERPOLATION #endif // Needed for VertexFactoryInterpolate to interpolate attributes from vertices to hit point #define NEEDS_VERTEX_FACTORY_INTERPOLATION 1 // This should be good enough for path tracing and avoids having to bind an extra buffer #define EYE_ADAPTATION_PREV_FRAME_EXPOSURE 1 // Ensure that SSS albedo comes through in the material #define SUBSTRATE_SSS_MATERIAL_OVERRIDE 0 // This define happens when we compile material with BlendableGBuffer (legacy) and no substrate nodes are used in a materials. // As oftoday, we are then going to compile the material with Substrate disabled. But the payload is still a Substrate payload! // The implementation here will take the legacy code path, but simply encoding the information in the new payload format as needed. #if (SUBSTRATE_ENABLED && !TEMPLATE_USES_SUBSTRATE) // == TEMPLATE_USES_LEGACYGBUFFERDATA from MaterialTemplate, needs to happen here before other #include in this file. #define SUBSTRATE_INLINE_SHADING 1 #endif // The derivatives are used for texture lookup filtering and Substrate's glints #define USE_ANALYTIC_DERIVATIVES 1 #include "/Engine/Private/Common.ush" #include "/Engine/Private/RayTracing/RayTracingCommon.ush" #include "/Engine/Private/RayTracing/RayTracingHitGroupCommon.ush" #include "/Engine/Private/PathTracing/PathTracingShaderUtils.ush" #include "/Engine/Private/PathTracing/Utilities/PathTracingRIS.ush" #include "/Engine/Generated/Material.ush" #include "/Engine/Generated/VertexFactory.ush" #include "/Engine/Private/RayTracing/RayTracingCalcInterpolants.ush" #include "/Engine/Private/ShadingCommon.ush" #include "/Engine/Private/DeferredShadingCommon.ush" #include "/Engine/Private/SubsurfaceProfileCommon.ush" #include "/Engine/Private/BurleyNormalizedSSSCommon.ush" #include "/Engine/Private/PathTracing/PathTracingCommon.ush" #include "/Engine/Private/PathTracing/Material/PathTracingFresnel.ush" #if SUBSTRATE_ENABLED && (TEMPLATE_USES_LEGACYGBUFFERDATA || MATERIAL_IS_SUBSTRATEHIDDENCONVERSION) // Either the material was explicitly setup to use legacy data (blendable g-buffer case) // Or the material is a hidden conversion into a substrate material (adaptive g-buffer case) // In both cases, for the path tracer we prefer to work with the original data directly as this allows taking the // well tested legacy path and simply handle the differences in payload encoding locally rather than // go through the substrate slab structures and reverse engineer the parameters (which is not always possible, particularly for // cases that require promiting transparency into refraction). #define USE_LEGACY_CODEPATH_FOR_SUBSTRATE 1 #else #define USE_LEGACY_CODEPATH_FOR_SUBSTRATE 0 #endif #if (SUBSTRATE_ENABLED && TEMPLATE_USES_SUBSTRATE) || USE_LEGACY_CODEPATH_FOR_SUBSTRATE #define MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING 0 #include "/Engine/Private/Substrate/SubstrateExport.ush" #include "/Engine/Private/PathTracing/Material/PathTracingSubstrateCommon.ush" #endif float AdjustMaterialRoughness(float Roughness, float PathRoughness) { // Modify the payload roughness to minimize difficult caustics // This is inspired by a trick used in the Arnold renderer: // https://cgg.mff.cuni.cz/~jaroslav/gicourse2010/giai2010-02-marcos_fajardo-slides.pdf (slide 39) // https://www.arnoldrenderer.com/research/Arnold_TOG2018.pdf (section 4.2) // NOTE: If approximate caustics are disabled, the path roughness passed in here will be 0.0 which effectively turns off this optimization return max(Roughness, PathRoughness); } #define PATH_TRACER_USE_SUBSTRATE_GLASS_LOBE (MATERIALBLENDING_TRANSLUCENT && (SUBSTRATE_BLENDING_TRANSLUCENT_GREYTRANSMITTANCE || SUBSTRATE_BLENDING_TRANSLUCENT_COLOREDTRANSMITTANCE)) #if REFRACTION_USE_INDEX_OF_REFRACTION || REFRACTION_USE_INDEX_OF_REFRACTION_FROM_F0 #define PATH_TRACER_REFRACTION_ENABLED 1 float GetRefractionIor(FPixelMaterialInputs PixelMaterialInputs, float3 F0) { #if REFRACTION_USE_INDEX_OF_REFRACTION float Ior = GetMaterialRefractionIOR(GetMaterialRefraction(PixelMaterialInputs)); #else // REFRACTION_USE_INDEX_OF_REFRACTION_FROM_F0 float Ior = DielectricF0RGBToIor(F0); #endif return Ior; } float GetRefractionIor(FPixelMaterialInputs PixelMaterialInputs) { #if REFRACTION_USE_INDEX_OF_REFRACTION float Ior = GetMaterialRefractionIOR(GetMaterialRefraction(PixelMaterialInputs)); #else // REFRACTION_USE_INDEX_OF_REFRACTION_FROM_F0 float3 BaseColor = GetMaterialBaseColor(PixelMaterialInputs); float Metallic = GetMaterialMetallic(PixelMaterialInputs); float Specular = GetMaterialSpecular(PixelMaterialInputs); float3 F0 = ComputeF0(Specular, BaseColor, Metallic); float Ior = DielectricF0RGBToIor(F0); #endif return Ior; } float FakeCausticsPathFactor(float Roughness, float PathRoughness) { // The heuristic used here is inspired by the following presentations: // - Kulla & Conty: Revisiting Physically Based Shading at Imageworks // https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf // - Colin Barré-Brisebois - Pica Pica & Nvidia Turing // https://www.ea.com/seed/news/siggraph-2018-picapica-nv-turing // NOTE: this returns 0.0 for PathRoughness <= Roughness which accounts for when we are not using ApproximateCaustics return (1 - Roughness * Roughness) * saturate(PathRoughness - Roughness); } float FakeCaustics(float F0, float Ior, float NoV) { // fake caustics for solid glass, approximates the effect of the boundaries' fresnel float Fr = FresnelReflectance(abs(NoV), Ior, F0); return Pow2(1 - Fr); } float3 ComputeOpticalDepthForHit(float3 LocalSigmaT, bool bIsFrontFace) { // Does the material have any kind of volumetric absorption to apply? // Now track the optical thickness so that we can account for Beer's law along the shadow ray // TODO: to support lights inside glass we would need to track an extra offset, but this is hopefully an uncommon scenario // TODO2: SingleLayerWater needs something similar, but it generally is setup with opaque or masked blend mode and water geometry does not cast shadows ... float Distance = RayTCurrent(); // Front Face: remove contribution from the ray origin to the current hit (assuming the backside will be hit) // Back Face: add contribution from ray origin to current hit (backside) - excess portion not covered by the ray will be removed by the front face hit Distance *= bIsFrontFace ? -1.0 : +1.0; return LocalSigmaT * Distance; } #else #define PATH_TRACER_REFRACTION_ENABLED 0 #endif // REFRACTION_USE_INDEX_OF_REFRACTION || REFRACTION_USE_INDEX_OF_REFRACTION_FROM_F0 float3 DecodeSSSProfileRadius(uint ProfileId, float3 DiffuseColor, float Opacity) { // Burley parameterization float3 SurfaceAlbedo = View.SSProfilesTexture.Load(int3(BSSS_SURFACEALBEDO_OFFSET, ProfileId, 0)).xyz; float3 DiffuseMeanFreePath = DecodeDiffuseMeanFreePath(View.SSProfilesTexture.Load(int3(BSSS_DMFP_OFFSET, ProfileId, 0))).xyz; float WorldUnitScale = DecodeWorldUnitScale(View.SSProfilesTexture.Load(int3(SSSS_TINT_SCALE_OFFSET, ProfileId, 0)).a) * BURLEY_CM_2_MM; // Opacity acts as a per-pixel radius multiplier // NOTE: this seems backwards? Opacity=0 acts like default-lit while Opacity=1 acts like SSS? // NOTE2: Confirm if this interpretation of opacity is correct ... float3 SSSRadius = GetMFPFromDMFPApprox(SurfaceAlbedo, DiffuseColor, Opacity * WorldUnitScale * DiffuseMeanFreePath); return SSSRadius * BURLEY_MM_2_CM; } float DecodeSSSProfileScatteringDistribution(uint ProfileId) { float EncodedScatteringDistribution = View.SSProfilesTexture.Load(int3(SSSS_TRANSMISSION_OFFSET, ProfileId, 0)).z; return DecodeScatteringDistribution(EncodedScatteringDistribution); } #if SUBSTRATE_ENABLED float3 GetSimpleVolumeDiffuseColor(float3 DiffuseColor, float3 MeanFreePath) { // See reference in SubstrateEvaluation.ush // SUBSTRATE_TODO: In the case of thin materials, we should also include a backscattering diffuse portion FParticipatingMedia PM = SubstrateSlabCreateParticipatingMedia(DiffuseColor, MeanFreePath); const float DiffuseToVolumeBlend = SubstrateSlabDiffuseToVolumeBlend(PM); const float3 SlabDirectionalAlbedo = IsotropicMediumSlabEnvDirectionalAlbedoALU(PM); return lerp(DiffuseColor, SlabDirectionalAlbedo, DiffuseToVolumeBlend); } #endif RAY_TRACING_ENTRY_CLOSEST_HIT(PathTracingMaterialCHS, FPackedPathTracingPayload, PackedPayload, FRayTracingIntersectionAttributes, Attributes) { PackedPayload.HitT = RayTCurrent(); ResolvedView = ResolveView(); const float3 TranslatedWorldPosition = TranslatedWorldRayOrigin() + RayTCurrent() * WorldRayDirection(); const float4 SvPosition = TranslatedWorldPositionToSvPosition(TranslatedWorldPosition); CurrentPayloadInputFlags = PackedPayload.GetFlags(); PathTracerSceneDepth = (CurrentPayloadInputFlags & PATH_TRACING_PAYLOAD_INPUT_FLAG_HAS_SCENE_DEPTH) ? PackedPayload.GetSceneDepth() : SCENE_TEXTURES_DISABLED_SCENE_DEPTH_VALUE; #if USE_DBUFFER && MATERIAL_USES_DECAL_LOOKUP if ((CurrentPayloadInputFlags & PATH_TRACING_PAYLOAD_INPUT_FLAG_HAS_SCENE_DEPTH) == 0) { CurrentPayloadDBufferA = PackedPayload.GetDBufferA(); CurrentPayloadDBufferB = PackedPayload.GetDBufferB(); CurrentPayloadDBufferC = PackedPayload.GetDBufferC(); } #endif #if VF_SUPPORTS_RAYTRACING_PREPARE_MATERIAL_PIXEL_PARAMETERS // this is a newer codepath that is both more flexible and allows for more direct calculation compared to the other codepath // TODO: implement such a method for all vertex factories float3 GeoNormal = 0; FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(TranslatedWorldRayOrigin(), WorldRayDirection(), RayTCurrent(), PrimitiveIndex(), Attributes, HitKind(), SvPosition, GeoNormal); #else FVertexFactoryInterpolantsVSToPS Interpolants; float3 GeoNormal = 0; FRaytracingDerivatives RaytracingDerivatives = (FRaytracingDerivatives)0; CalcInterpolants(SvPosition, (FRayCone)0, Attributes, Interpolants, RaytracingDerivatives, GeoNormal); FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, SvPosition); ApplyRaytracingDerivatives(MaterialParameters, RaytracingDerivatives); #endif FPixelMaterialInputs PixelMaterialInputs; const bool bIsFrontFace = HitKind() == HIT_KIND_TRIANGLE_FRONT_FACE; { const float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition); MaterialParameters.CameraVector = -WorldRayDirection(); CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, bIsFrontFace, TranslatedWorldPosition, TranslatedWorldPosition); } const float3 V_World = MaterialParameters.CameraVector; #if SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE #if MATERIAL_IS_SUBSTRATE FSubstratePixelHeader SubstratePixelHeader = MaterialParameters.GetFrontSubstrateHeader(); FSubstrateData SubstrateData = PixelMaterialInputs.GetFrontSubstrateData(); // SUBSTRATE_TODO: These should actually be initialized via the corresponding cvars. To do this properly would require binding FSubstrateSceneData in the path tracing shaders // For now, simply match the default substrate settings FSubstrateIntegrationSettings IntegrationSettings = InitSubstrateIntegrationSettings( false /* bForceFullyRough */, true /* SubstrateStruct.bRoughDiffuse */, -1 /* SubstrateStruct.PeelLayersAboveDepth */, true /* SubstrateStruct.bRoughnessTracking */ ); #if SUBSTRATE_BLENDING_COLOREDTRANSMITTANCEONLY const uint BSDFCount = 0; #else const uint BSDFCount = SubstratePixelHeader.SubstrateTree.BSDFCount; #endif float TotalCoverage = 1; float3 TotalTransmittancePreCoverage = 0; SubstratePixelHeader.SubstrateUpdateTree(SubstrateData, V_World, IntegrationSettings, TotalCoverage, TotalTransmittancePreCoverage); #if PATH_TRACER_REFRACTION_ENABLED // transparency is only used for coverage, transmittance happens through refraction (except for shadows potentially, which is handled below) float3 Transparency = 1 - TotalCoverage; #else // transparency is used for both coverage and transmittance // SUBSTRATE_TODO: Thin case does not seem to be properly handled here, so the TotalTransmittancePreCoverage is recomputed later const float3 Transparency = saturate(lerp(1.0, TotalTransmittancePreCoverage, TotalCoverage)); #endif // PATH_TRACER_REFRACTION_ENABLED #else // MATERIAL_IS_SUBSTRATE // This only happens for invalid materials const float3 Transparency = 0; #endif #endif // SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE FPathTracingPayload Payload = (FPathTracingPayload)0; /** * Set common material attributes for both full and simplified materials **/ #if (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL) Payload.ShadingModelID = SHADINGMODELID_UNLIT; #else Payload.ShadingModelID = GetMaterialShadingModel(PixelMaterialInputs); #endif #if MATERIALBLENDING_ALPHACOMPOSITE Payload.BSDFOpacity = 1.0; Payload.TransparencyColor = 1.0 - GetMaterialOpacity(PixelMaterialInputs); #elif MATERIALBLENDING_ALPHAHOLDOUT Payload.BSDFOpacity = GetMaterialOpacity(PixelMaterialInputs); Payload.TransparencyColor = 1.0 - GetMaterialOpacity(PixelMaterialInputs); Payload.SetHoldout(); HLSL_STATIC_ASSERT(MATERIAL_SHADINGMODEL_UNLIT == 1, "Alpha holdout blend mode requires unlit shading model"); Payload.ShadingModelID = SHADINGMODELID_UNLIT; #elif MATERIALBLENDING_TRANSLUCENT Payload.BSDFOpacity = GetMaterialOpacity(PixelMaterialInputs); Payload.TransparencyColor = 1.0 - Payload.BSDFOpacity; #elif MATERIALBLENDING_ADDITIVE Payload.BSDFOpacity = GetMaterialOpacity(PixelMaterialInputs); Payload.TransparencyColor = 1.0; #elif MATERIALBLENDING_MODULATE Payload.BSDFOpacity = 0.0; Payload.TransparencyColor = GetMaterialEmissive(PixelMaterialInputs); // NOTE: in the case of substrate materials we could have DEFAULT_LIT here HLSL_STATIC_ASSERT(MATERIAL_SHADINGMODEL_UNLIT == 1 || MATERIAL_SHADINGMODEL_DEFAULT_LIT == 1, "Modulate blend mode requires unlit shading model"); Payload.ShadingModelID = SHADINGMODELID_UNLIT; #elif MATERIALBLENDING_MASKED && MATERIAL_DITHER_OPACITY_MASK // dithering emulates real transparency, so switch to translucent // NOTE: the raster path technically takes into account the opacity mask clip value, so the effective transparency should be: // saturate(MaskRaw - ClipValue + 0.5) // (See derivation in DitheredOpacityMaskToOpacity) // However this behavior is surprising to most users and does not exactly match the rasterizer anyway due to how the realtime AA // code performs blending. // Since the goal of dithered opacity is to emulate ordinary transparency, just use the mask input as opacity directly and // ignore the configured clip value. Payload.BSDFOpacity = saturate(GetMaterialMaskInputRaw(PixelMaterialInputs)); Payload.TransparencyColor = 1.0 - Payload.BSDFOpacity; #elif MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED Payload.BSDFOpacity = 1.0; Payload.TransparencyColor = 0.0; #else #error Unknown material blending mode #endif // fetch primitive flags only once // TODO: would be nice to keep this inside MaterialParameters as it is also needed there as well const uint PrimitiveFlags = GetPrimitiveData(MaterialParameters.PrimitiveId).Flags; Payload.PrimitiveLightingChannelMask = GetPrimitive_LightingChannelMask_FromFlags(PrimitiveFlags); Payload.HitT = RayTCurrent(); if (HitKind() == HIT_KIND_TRIANGLE_FRONT_FACE) { Payload.SetFrontFace(); } #if MATERIAL_IS_SKY if (!PackedPayload.IsCameraRay()) { // avoid double counting what was captured by the skylight // also avoid noise from hot spots (they can be properly // importance sampled if a capturing skylight is added) PackedPayload = PackPathTracingPayload(Payload); return; } #endif float GeoNormalSign = MaterialParameters.TwoSidedSign; #if !VF_SUPPORTS_RAYTRACING_PREPARE_MATERIAL_PIXEL_PARAMETERS // Because the geometric normal is computed directly in world space // it doesn't reflect the sign flip from the object transform, so apply it here GeoNormalSign *= GetPrimitive_DeterminantSign_FromFlags(PrimitiveFlags); #endif Payload.WorldGeoNormal = GeoNormalSign * GeoNormal; Payload.WorldSmoothNormal = MaterialParameters.TwoSidedSign * TransformTangentNormalToWorld(MaterialParameters.TangentToWorld, float3(0, 0, 1)); #if (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL) // no need to adjust normal on volumetric shading models #else Payload.WorldSmoothNormal = AdjustShadingNormal(Payload.WorldSmoothNormal, Payload.WorldGeoNormal, WorldRayDirection()); #endif #if SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE // unless proven otherwise, we are a basic unlit material Payload.ShadingModelID = SHADINGMODELID_UNLIT; float BaseBSDFOpacity = 1; //Payload.BSDFOpacity; #if !SUBSTRATE_OPAQUE_MATERIAL Payload.TransparencyColor = Transparency; #endif if (BSDFCount > 0) { for (int Index = 0, Num = SubstratePixelHeader.SharedLocalBases.Count; Index < Num; Index++) { float3 N = SubstratePixelHeader.SharedLocalBases.Normals[Index]; #if !MATERIAL_TANGENTSPACENORMAL N *= MaterialParameters.TwoSidedSign; #endif SubstratePixelHeader.SharedLocalBases.Normals[Index] = AdjustShadingNormal(N, Payload.WorldGeoNormal, WorldRayDirection()); } int BSDFIdx = 0; #define BSDF SubstratePixelHeader.SubstrateTree.BSDFs[BSDFIdx] // Accumulate radiance across all BSDFs Payload.Radiance = 0.0; Substrate_for_unroll(BSDFIdx = 0, BSDFIdx < BSDFCount, ++BSDFIdx) { Payload.Radiance += BSDF.LuminanceWeightV * BSDF_GETEMISSIVE(BSDF); } #if !SUBSTRATE_OPTIMIZED_UNLIT // The slab sampler lets us stochastically choose between the different slab models the path tracer supports // NOTE: Even if the material has a single substrate slab, we might still need this because refraction is handled separately from SSS FRISContext SlabSampler = InitRISContext(PackedPayload.GetStochasticSlabRand()); Substrate_for_unroll(BSDFIdx = 0, BSDFIdx < BSDFCount, ++BSDFIdx) { switch (BSDF_GETTYPE(BSDF)) { case SUBSTRATE_BSDF_TYPE_SLAB: { const float3 WeightV = BSDF.LuminanceWeightV; const float3 TransmittanceN = BSDF.TransmittanceAboveAlongN; const float CoverageAboveAlongN = BSDF.CoverageAboveAlongN; const float3 MaxLobeWeight = BaseBSDFOpacity * WeightV * lerp(1.0f, TransmittanceN, CoverageAboveAlongN); // largest value LobeWeight() could return // Is the slab even visible at all? if (any(MaxLobeWeight > 0.0)) { const float3 N = SubstratePixelHeader.SharedLocalBases.Normals[BSDF_GETSHAREDLOCALBASISID(BSDF)]; const float NoV = saturate(dot(N, V_World)); float3 DiffuseColor = SLAB_DIFFUSEALBEDO(BSDF); float3 F0 = SLAB_F0(BSDF); float3 F90 = SLAB_F90(BSDF) * F0RGBToMicroOcclusion(F0); float3 RoughnessData = 0; RoughnessData.xy = AdjustMaterialRoughness(SLAB_ROUGHNESS(BSDF), PackedPayload.GetPathRoughness()); // SUBSTRATE_TODO: Support Fresnel82? float3 Spec0E = ComputeGGXSpecEnergyTermsRGB(RoughnessData.x, NoV, F0, F90).E; float3 Spec1E = Spec0E; bool bUseClearCoat = false; if (BSDF_GETHASHAZINESS(BSDF)) { const FHaziness Haziness = UnpackHaziness(SLAB_HAZINESS(BSDF)); RoughnessData.z = Haziness.Weight; RoughnessData.y = AdjustMaterialRoughness(Haziness.Roughness, PackedPayload.GetPathRoughness()); if (Haziness.bSimpleClearCoat) { Spec1E = ComputeGGXSpecEnergyTermsRGB(RoughnessData.y, NoV, CLEAR_COAT_F0).E; bUseClearCoat = true; } else { Spec1E = ComputeGGXSpecEnergyTermsRGB(RoughnessData.y, NoV, F0, F90).E; } } float3 MeanFreePath = 0; float PhaseG = 0; float3 LegacySubsurfaceColor = 0; float LegacyOpacity = 1; if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_SIMPLEVOLUME && !BSDF.bIsBottom) { // SUBSTRATE_TODO: This should really be represented by a different BSDF, but for now we are approximating it with a diffuse lobe DiffuseColor = GetSimpleVolumeDiffuseColor(DiffuseColor, SLAB_SSSMFP(BSDF)); } else if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION_PROFILE) { const uint ProfileId = SLAB_SSSPROFILEID(BSDF); const float Scale = SLAB_SSSPROFILERADIUSSCALE(BSDF); MeanFreePath = DecodeSSSProfileRadius(ProfileId, Payload.DiffuseColor, Scale); #if USE_SSS_PROFILE_ANISOTROPY PhaseG = DecodeSSSProfileScatteringDistribution(ProfileId); MeanFreePath *= 1 - PhaseG; // Adjust MFP to preserve appearance #endif } else if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_WRAP || BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_TWO_SIDED_WRAP) { // For the old MATERIAL_SHADINGMODEL_SUBSURFACE and SHADINGMODELID_TWOSIDED_FOLIAGE models, we previously had logic to normalize the DiffuseColor and SubsurfaceColor to ensure the sum is < 1 const float TransmittanceNoL = 1.0f; // Reverse the MFP mapping to get back to the original SubsurfaceColor that was provided to SubstrateConvertLegacyMaterialDynamic const float3 MFPInCm = SLAB_SSSMFP(BSDF); LegacySubsurfaceColor = MeanFreePathToTransmittance(MFPInCm * CENTIMETER_TO_METER, SUBSTRATE_SIMPLEVOLUME_THICKNESS_CM * CENTIMETER_TO_METER); LegacyOpacity = 1.f - abs(SLAB_SSSPHASEANISOTROPY(BSDF)); AdjustPathTracingTwoSidedColorBalance(DiffuseColor, LegacySubsurfaceColor); } else { // Basic substrate slab, legacy preintegrated skin or SIMPLEVOLUME on the bottom slab, just use MFP directly for the random walk MeanFreePath = SLAB_SSSMFP(BSDF); PhaseG = SLAB_SSSPHASEANISOTROPY(BSDF); } // SUBSTRATE_TODO: Other SSSTYPE cases may result in some portion of a "glass" BSDF float FuzzAmount = 0.0; float FuzzRoughness = 1.0; float3 FuzzColor = 0.0; if (BSDF_GETHASFUZZ(BSDF)) { FuzzAmount = SLAB_FUZZ_AMOUNT(BSDF); FuzzColor = SLAB_FUZZ_COLOR(BSDF); FuzzRoughness = SLAB_FUZZ_ROUGHNESS(BSDF); FuzzRoughness = MakeRoughnessSafe(FuzzRoughness, SUBSTRATE_MIN_FUZZ_ROUGHNESS); } const float FuzzE = ComputeSheenEnergyTerms(FuzzRoughness, NoV).E; const float FuzzAttenuation = 1.0 - FuzzAmount * FuzzE; float DiffuseWeight = FuzzAttenuation; float3 Spec0Albedo = FuzzAttenuation * Spec0E; float3 Spec1Albedo = FuzzAttenuation * Spec1E; if (bUseClearCoat) { // clearcoat slab float CoatAttenuation = 1.0 - RoughnessData.z * Spec1E.x; DiffuseWeight *= CoatAttenuation * (1.0 - Luminance(Spec0E)); Spec0Albedo *= CoatAttenuation; Spec1Albedo *= RoughnessData.z; } else { // dual-specular slab DiffuseWeight *= 1.0 - Luminance(lerp(Spec0E, Spec1E, RoughnessData.z)); Spec0Albedo *= 1.0 - RoughnessData.z; Spec1Albedo *= RoughnessData.z; } float Ior = 1; // for refracted lobe only float3 Transmittance = 0; float RefractMix = 0; // percentage mix of the glass lobe #if PATH_TRACER_USE_SUBSTRATE_GLASS_LOBE if (BSDF.bIsBottom) { #if PATH_TRACER_REFRACTION_ENABLED Ior = GetRefractionIor(PixelMaterialInputs, F0); #endif Transmittance = MeanFreePathToTransmittance(MeanFreePath * CENTIMETER_TO_METER, SUBSTRATE_SIMPLEVOLUME_THICKNESS_CM * CENTIMETER_TO_METER); #if MATERIAL_ISTHINSURFACE RefractMix = saturate(Luminance(Transmittance)); #else // blend towards Opaque SSS in the range of [0cm, 8cm] RefractMix = saturate(max3(MeanFreePath.x, MeanFreePath.y, MeanFreePath.z)); #endif } #endif // PATH_TRACER_USE_SUBSTRATE_GLASS_LOBE // SUBSTRATE_TODO: Need to account for glass case here for lobe selection .... const float3 FuzzAlbedo = FuzzAmount * FuzzE * FuzzColor; const float3 SlabAlbedo = MaxLobeWeight * (DiffuseWeight * (DiffuseColor + LegacySubsurfaceColor) + Spec0Albedo + Spec1Albedo + FuzzAlbedo); const float3 RefrAlbedo = MaxLobeWeight * (FuzzAttenuation * 1.0 + FuzzAlbedo); // glass has unit albedo // If we are dealing witha multi-BSDF material, stochastically decide if we need to write to the payload // This means estimating the overal albedo of the slab and selectively writing to the payload // NOTE: Technically, some of the albedo calculation are not needed if we aren't picking between slabs // We rely on the compiler to get rid of dead code here to avoid diverging the logic too far between single and multi-lobe cases float ContribSlab = LobeColorToWeight(SlabAlbedo); if (SlabSampler.Accept((1 - RefractMix) * ContribSlab)) { // We've selected this particular slab - now write it into the payload // NOTE: This could happen more than once, so be careful to write to all fields, even the unused ones! Payload.BSDFOpacity = BaseBSDFOpacity / ContribSlab; Payload.WorldNormal = N; if (SubstrateGetSharedLocalBasisType(SubstratePixelHeader.SharedLocalBases.Types, BSDFIdx) == SUBSTRATE_BASIS_TYPE_TANGENT) { Payload.WorldTangent = SubstratePixelHeader.SharedLocalBases.Tangents[BSDF_GETSHAREDLOCALBASISID(BSDF)]; } Payload.WeightV = BSDF.LuminanceWeightV; Payload.TransmittanceN = BSDF.TransmittanceAboveAlongN; Payload.CoverageAboveAlongN = BSDF.CoverageAboveAlongN; Payload.ShadingModelID = SHADINGMODELID_SUBSTRATE; Payload.Ior = 0.0; Payload.DiffuseColor = DiffuseColor; Payload.SpecularColor = F0; Payload.SpecularEdgeColor = F90; Payload.RoughnessData = RoughnessData; Payload.BottomNormalOct16bits = 0; if (bUseClearCoat) { Payload.ShadingModelID = SHADINGMODELID_CLEAR_COAT; #if CLEAR_COAT_BOTTOM_NORMAL Payload.BottomNormalOct16bits = SLAB_HAZINESS(BSDF) >> 16; #endif } Payload.Anisotropy = BSDF_GETHASANISOTROPY(BSDF) ? SLAB_ANISOTROPY(BSDF) : 0.0; Payload.FuzzAmount = FuzzAmount; Payload.FuzzColor = FuzzColor; Payload.FuzzRoughness = FuzzRoughness; if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_WRAP) { // Legacy support for SHADINGMODELID_SUBSURFACE // The legacy behavior has both diffuse+sss responses separately, but we can combine them to avoid needing a custom lobe // In practice the diffuse and SSS responses are similar, so the difference should be minimal (and is an approximation of the raster behavior anyway) // SUBSTRATE_TODO: If we implement stochastic lobe picking _within_ slabs, we could fine tune this to get back to the original behavior exactly Payload.DiffuseColor += LegacySubsurfaceColor; #if HAVE_GetSubsurfaceMediumMaterialOutput0 Payload.MeanFreePath = GetSubsurfaceMediumMaterialOutput0(MaterialParameters); #else const float SSSRadius = (1 - LegacyOpacity) * 10.0; // simple formula, up to 10cm radius Payload.MeanFreePath = SSSRadius; #endif #if HAVE_GetSubsurfaceMediumMaterialOutput1 float ScatteringDistribution = clamp(GetSubsurfaceMediumMaterialOutput1(MaterialParameters), -0.99f, 0.99f); Payload.PhaseG = ScatteringDistribution; #else Payload.PhaseG = 0; #endif } else if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_TWO_SIDED_WRAP) { // Legacy support for SHADINGMODELID_TWOSIDED_FOLIAGE Payload.ShadingModelID = SHADINGMODELID_TWOSIDED_FOLIAGE; Payload.SetFoliageTransmissionColor(LegacySubsurfaceColor); } else { #if MATERIAL_ISTHINSURFACE // material is thin, make sure we don't use random walk // SUBSTRATE_TODO: derive the proper BSDF for the internal scattering layer inside the slab #else // Setup material for random walk Payload.MeanFreePath = MeanFreePath; Payload.PhaseG = PhaseG; #endif } // Handle special-case specular features Payload.SpecularProfileId = 0; Payload.GlintValue = 1.f; #if SUBSTRATE_COMPLEXPATH if (BSDF_GETHASSPECPROFILE(BSDF)) { Payload.SpecularProfileId = SLAB_SPECPROFILEID(BSDF); } #endif #if SUBSTRATE_COMPLEXSPECIALPATH if (BSDF_GETHASGLINT(BSDF)) { Payload.GlintValue = SLAB_GLINT_VALUE(BSDF); Payload.GlintUV = SLAB_GLINT_UV(BSDF); Payload.GlintUVdx = SLAB_GLINT_UVDDX(BSDF); Payload.GlintUVdy = SLAB_GLINT_UVDDY(BSDF); } #endif } #if PATH_TRACER_USE_SUBSTRATE_GLASS_LOBE // now test for the presence of a refracted lobe float ContribRefr = LobeColorToWeight(RefrAlbedo); if (SlabSampler.Accept(RefractMix * ContribRefr)) { // We've selected this particular slab - now write it into the payload // NOTE: This could happen more than once, so be careful to write to all fields, even the unused ones! Payload.BSDFOpacity = BaseBSDFOpacity / ContribRefr; Payload.WorldNormal = N; if (SubstrateGetSharedLocalBasisType(SubstratePixelHeader.SharedLocalBases.Types, BSDFIdx) == SUBSTRATE_BASIS_TYPE_TANGENT) { Payload.WorldTangent = SubstratePixelHeader.SharedLocalBases.Tangents[BSDF_GETSHAREDLOCALBASISID(BSDF)]; } Payload.WeightV = BSDF.LuminanceWeightV; Payload.TransmittanceN = BSDF.TransmittanceAboveAlongN; Payload.CoverageAboveAlongN = BSDF.CoverageAboveAlongN; #if MATERIAL_ISTHINSURFACE Payload.ShadingModelID = SHADINGMODELID_THIN_TRANSLUCENT; #else Payload.ShadingModelID = SHADINGMODELID_SOLID_GLASS; // Albedo inversion for ambient medium // NOTE: we could technically also include IOR and Phase function - but this basic inversion does most of the work float3 Color = DiffuseColor; // Van de hulst mapping (with improved constants by d'Eon, see formula 48.20) const float b1 = 1.0 / 0.100392; const float b2 = 1.21573; const float3 t1 = (0.5 * b1 + 0.5) + Color * (0.5 * b2 * b1); const float3 t2 = b1 - Color * b1; const float3 s = t1 - sqrt(t1 * t1 - t2); DiffuseColor = 1.0 - s * s; #endif Payload.DiffuseColor = DiffuseColor; // SUBSTRATE_TODO: Add albedo inversion for solid case? Payload.SpecularColor = F0; Payload.SpecularEdgeColor = F90; Payload.RoughnessData = RoughnessData; Payload.BottomNormalOct16bits = 0; Payload.Anisotropy = BSDF_GETHASANISOTROPY(BSDF) ? SLAB_ANISOTROPY(BSDF) : 0.0; Payload.FuzzAmount = FuzzAmount; Payload.FuzzColor = FuzzColor; Payload.FuzzRoughness = FuzzRoughness; Payload.MeanFreePath = MeanFreePath; Payload.PhaseG = PhaseG; Payload.Ior = Ior; // SUBSTRATE_TODO: should these be supported in the Glass case? Payload.SpecularProfileId = 0; Payload.GlintValue = 1.f; // Material is configured as a thin surface, and refraction is disabled #if MATERIAL_ISTHINSURFACE && PATH_TRACER_REFRACTION_ENABLED == 0 { // SUBSTRATE_TODO: Do we really want to keep this? Should maybe always use refraction path? float3 Transmission = Payload.GetTransmittanceColor(); const float NoV = saturate(dot(Payload.WorldNormal, V_World)); float3 Tr = ComputeThinSlabWeights(Transmission, NoV, 0.0, F0RGBToF0(Payload.SpecularColor)).Transmitted; Payload.TransparencyColor += TotalCoverage * Tr; } #endif } #endif // PATH_TRACER_USE_SUBSTRATE_GLASS_LOBE } break; } // NOTE: The cases below are for back-compatibility and cannot be layered case SUBSTRATE_BSDF_TYPE_EYE: { SlabSampler.Accept(1); // dummy, avoids special case below Payload.WorldNormal = SubstratePixelHeader.SharedLocalBases.Normals[BSDF_GETSHAREDLOCALBASISID(BSDF)]; Payload.DiffuseColor = EYE_DIFFUSEALBEDO(BSDF); Payload.SpecularColor = EYE_F0(BSDF); // NOTE: EYE_F90 is always 1.0 (does not exist in legacy model), so don't bother with it Payload.SetEyeRoughness(AdjustMaterialRoughness(EYE_ROUGHNESS(BSDF), PackedPayload.GetPathRoughness())); Payload.MeanFreePath = 0; Payload.PhaseG = 0; if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION_PROFILE) { Payload.MeanFreePath = DecodeSSSProfileRadius(EYE_SSSPROFILEID(BSDF), Payload.DiffuseColor, 1.0); } float IrisMask = EYE_IRISMASK(BSDF); float IrisDistance = EYE_IRISDISTANCE(BSDF); // see logic in non-substrate code below const float3 PlaneNormal = normalize(EYE_IRISPLANENORMAL(BSDF)); const float3 CausticNormal = normalize(lerp(PlaneNormal, -Payload.WorldNormal, IrisMask * IrisDistance)); Payload.SetEyeCausticNormal(CausticNormal); Payload.SetEyeIrisMask(IrisMask); Payload.SetEyeIrisNormal(normalize(EYE_IRISNORMAL(BSDF))); Payload.ShadingModelID = SHADINGMODELID_EYE; Payload.SpecularProfileId = 0; Payload.GlintValue = 1.f; break; } case SUBSTRATE_BSDF_TYPE_HAIR: { SlabSampler.Accept(1); // dummy, avoids special case below // clamp the roughness to whatever came along the path Payload.SetBaseColor(HAIR_BASECOLOR(BSDF)); Payload.SetHairLongitudinalRoughness(AdjustMaterialRoughness(HAIR_ROUGHNESS(BSDF), PackedPayload.GetPathRoughness())); Payload.SetHairAzimuthalRoughness(HAIR_SCATTER(BSDF)); // SUBSTRATE_TODO: Map from scatter param to azimuthal roughness here instead of in the raygen Payload.SetHairSpecular(HAIR_SPECULAR(BSDF)); #if HAIR_STRAND_MESH_FACTORY Payload.WorldSmoothNormal = Payload.WorldNormal = Payload.WorldGeoNormal; Payload.WorldTangent = MaterialParameters.TangentToWorld[2]; Payload.SetHairPrimitiveUV(MaterialParameters.HairPrimitiveUV); #else Payload.SetHairPrimitiveUV(0.5); #endif Payload.ShadingModelID = SHADINGMODELID_HAIR; Payload.SpecularProfileId = 0; Payload.GlintValue = 1.f; break; } case SUBSTRATE_BSDF_TYPE_SINGLELAYERWATER: { SlabSampler.Accept(1); // dummy, avoids special case below Payload.WorldNormal = SubstratePixelHeader.SharedLocalBases.Normals[BSDF_GETSHAREDLOCALBASISID(BSDF)]; Payload.BSDFOpacity = 1; Payload.ShadingModelID = SHADINGMODELID_SOLID_GLASS; float3 BaseColor = SLW_BASECOLOR(BSDF); float Metallic = SLW_METALLIC(BSDF); float Specular = SLW_SPECULAR(BSDF); float Roughness = SLW_ROUGHNESS(BSDF); float3 Extinction = 0.0; #if SUBSTRATE_INLINE_SINGLELAYERWATER //float Opacity = SLW_TOPMATERIALOPACITY(BSDF); // TODO: Account for this with an extra random decision? //float3 WaterAlbedo = SLW_WATERALBEDO(BSDF); // TODO: Account for scattering in refractive mediums Extinction = SLW_WATEREXTINCTION(BSDF); //float PhaseG = SLW_WATERPHASEG(BSDF); //float3 ColorBehindWater = SLW_COLORSCALEBEHINDWATER(BSDF); #endif Payload.WeightV = BSDF.LuminanceWeightV; Payload.TransmittanceN = BSDF.TransmittanceAboveAlongN; Payload.CoverageAboveAlongN = BSDF.CoverageAboveAlongN; Payload.DiffuseColor = ComputeDiffuseAlbedo(BaseColor, Metallic); Payload.SpecularColor = ComputeF0(Specular, BaseColor, Metallic); Payload.RoughnessData.x = AdjustMaterialRoughness(Roughness, PackedPayload.GetPathRoughness()); Payload.RoughnessData.yz = 0; Payload.Ior = DielectricF0ToIor(DielectricSpecularToF0(Specular)); Payload.MeanFreePath = rcp(max(Extinction, 1e-5)); Payload.FuzzAmount = 0; Payload.FuzzRoughness = 0; Payload.FuzzColor = 0; Payload.SpecularProfileId = 0; Payload.GlintValue = 1.f; break; } } // switch on slab tyype } // for loop over BSDFs // normalize the weight for picking a given slab Payload.BSDFOpacity *= SlabSampler.GetNormalization(); #undef BSDF #endif // !SUBSTRATE_OPTIMIZED_UNLIT } // if (BSDFCount > 0) #else // SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.Radiance = GetMaterialEmissive(PixelMaterialInputs); Payload.Radiance *= Payload.BSDFOpacity; // pre-multiply // NOTE: Emissive contribution is turned off below for non-two-sided materials Payload.WorldNormal = MaterialParameters.WorldNormal; #if !MATERIAL_TANGENTSPACENORMAL // already flipped if the material was in tangent space, so add the flip if it wasn't Payload.WorldNormal *= MaterialParameters.TwoSidedSign; #endif #if (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL) // volumetric shading doesn't need to adjust normal #else Payload.WorldNormal = AdjustShadingNormal(Payload.WorldNormal, Payload.WorldGeoNormal, WorldRayDirection()); #endif // Store the results in local variables and reuse instead of calling the functions multiple times. half3 BaseColor = GetMaterialBaseColor(PixelMaterialInputs); half Metallic = GetMaterialMetallic(PixelMaterialInputs); half Specular = GetMaterialSpecular(PixelMaterialInputs); half Roughness = GetMaterialRoughness(PixelMaterialInputs); float Ior = 0.0; #if MATERIALBLENDING_TRANSLUCENT && PATH_TRACER_REFRACTION_ENABLED && (MATERIAL_SHADINGMODEL_DEFAULT_LIT || MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT) // NOTE: only default-lit and thin-translucent use ior, the other material models only support plain transparency // This is an attempt to limit the complexity of the material eval/sample API which must take into account an extra lobe if supporting refraction Ior = GetRefractionIor(PixelMaterialInputs); #endif #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.DiffuseColor = ComputeDiffuseAlbedo(BaseColor, Metallic); Payload.SpecularColor = ComputeF0(Specular, BaseColor, Metallic); Payload.RoughnessData = float3(Roughness, Roughness, 0); // reset unused features Payload.SpecularEdgeColor = 1; Payload.WeightV = 1; Payload.CoverageAboveAlongN = 0; Payload.TransmittanceN = 1; Payload.GlintValue = 1; Payload.SpecularProfileId = 0; #else Payload.BaseColor = BaseColor; Payload.Specular = Specular; Payload.Metallic = Metallic; Payload.Roughness = Roughness; #endif #if MATERIAL_USES_ANISOTROPY Payload.WorldTangent = CalculateAnisotropyTangent(MaterialParameters, PixelMaterialInputs); Payload.Anisotropy = GetMaterialAnisotropy(PixelMaterialInputs); #endif #if HAIR_STRAND_MESH_FACTORY Payload.WorldSmoothNormal = Payload.WorldNormal = Payload.WorldGeoNormal; Payload.WorldTangent = MaterialParameters.TangentToWorld[2]; // Take into account per-pixel normal shading variation // Note: For hair shading the tangent is actually stored as the normal input Payload.WorldTangent = normalize(MaterialParameters.WorldNormal); #endif #if (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL) if (Payload.ShadingModelID == SHADINGMODELID_UNLIT) { // encode the directionality using the specular field #if TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL Payload.Anisotropy = GetMaterialTranslucencyDirectionalLightingIntensity(); #else Payload.Anisotropy = 0; #endif #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.DiffuseColor = BaseColor; Payload.SpecularColor = 0; Payload.RoughnessData = 1; #else Payload.Specular = 0; Payload.Metallic = 0; Payload.Roughness = 1; #endif } else #elif MATERIAL_SHADINGMODEL_UNLIT if (Payload.ShadingModelID == SHADINGMODELID_UNLIT) { // Regular unlit material, make sure the volumetric component is zero'ed out #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.DiffuseColor = 0; Payload.SpecularColor = 0; #else Payload.BaseColor = 0; Payload.Specular = 0; Payload.Metallic = 0; #endif } else #endif // MATERIAL_SHADINGMODEL_UNLIT #if MATERIAL_SHADINGMODEL_CLEAR_COAT if (Payload.ShadingModelID == SHADINGMODELID_CLEAR_COAT) { const float ClearCoat = GetMaterialCustomData0(PixelMaterialInputs); const float ClearCoatRoughness = GetMaterialCustomData1(PixelMaterialInputs); #if CLEAR_COAT_BOTTOM_NORMAL #if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0 #if MATERIAL_TANGENTSPACENORMAL float3 BottomNormal = normalize(TransformTangentVectorToWorld(MaterialParameters.TangentToWorld, ClearCoatBottomNormal0(MaterialParameters))); #else float3 BottomNormal = ClearCoatBottomNormal0(MaterialParameters); #endif // if we got a custom normal for the clearcoat, adjust it now BottomNormal = AdjustShadingNormal(BottomNormal, Payload.WorldGeoNormal, WorldRayDirection()); #else float3 BottomNormal = Payload.WorldNormal; #endif #else float3 BottomNormal = Payload.WorldNormal; #endif #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.RoughnessData.y = ClearCoatRoughness; // adjusted later Payload.RoughnessData.z = ClearCoat; float2 oct2 = UnitVectorToOctahedron(Payload.WorldNormal); float2 oct1 = UnitVectorToOctahedron(BottomNormal); float2 BottomNormalOct = ((oct1 - oct2) * 0.25) + (128.0 / 255.0); Payload.BottomNormalOct16bits = PackR8(BottomNormalOct.x) | (PackR8(BottomNormalOct.y) << 8); #else Payload.SetClearCoat(ClearCoat); Payload.SetClearCoatRoughness(AdjustMaterialRoughness(ClearCoatRoughness, PackedPayload.GetPathRoughness())); Payload.SetClearCoatBottomNormal(BottomNormal); #endif } else #endif // MATERIAL_SHADINGMODEL_CLEAR_COAT #if MATERIAL_SHADINGMODEL_CLOTH if (Payload.ShadingModelID == SHADINGMODELID_CLOTH) { const float3 ClothColor = GetMaterialSubsurfaceData(PixelMaterialInputs).rgb; const float Fuzz = saturate(GetMaterialCustomData0(PixelMaterialInputs)); #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE // the default substrate model includes a fuzz layer, so we can do it this way // Additionally, tune the look like in SubstrateConvertLegacyMaterialDynamic Payload.SpecularColor *= 1 - Fuzz; Payload.FuzzColor = ClothColor; Payload.FuzzAmount = Fuzz; Payload.FuzzRoughness = 0.5 * Roughness + 0.5; // (adjust range because the legacy model has a very different response) Payload.ShadingModelID = SHADINGMODELID_SUBSTRATE; #else Payload.SetClothColor(ClothColor); Payload.SetClothAmount(Fuzz); #endif } else #endif // MATERIAL_SHADINGMODEL_CLOTH #if MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE if (Payload.ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE) { const float3 SubsurfaceColor = GetMaterialSubsurfaceData(PixelMaterialInputs).rgb; #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.SetFoliageTransmissionColor(SubsurfaceColor); #else Payload.SetSubsurfaceColor(SubsurfaceColor); #endif } else #endif // MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE #if MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN if (Payload.ShadingModelID == SHADINGMODELID_SUBSURFACE || Payload.ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN) { float3 SubsurfaceColor = GetMaterialSubsurfaceData(PixelMaterialInputs).rgb; #if HAVE_GetSubsurfaceMediumMaterialOutput0 float3 SSSRadius = GetSubsurfaceMediumMaterialOutput0(MaterialParameters); #else // TODO: is this accurate enough? hard to measure exactly since the raster path uses a very different approach const float Opacity = GetMaterialOpacity(PixelMaterialInputs); const float SSSRadius = (1 - Opacity) * 10.0; // simple formula, up to 10cm radius #endif #if HAVE_GetSubsurfaceMediumMaterialOutput1 float PhaseG = clamp(GetSubsurfaceMediumMaterialOutput1(MaterialParameters), -0.99f, 0.99f); #else float PhaseG = 0.0; #endif #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE AdjustPathTracingTwoSidedColorBalance(Payload.DiffuseColor, SubsurfaceColor); // default substrate slab includes SSS Payload.ShadingModelID = SHADINGMODELID_SUBSTRATE; Payload.DiffuseColor += SubsurfaceColor; // no distinction between diffuse portion and SSS portion (could try using stochastic blending here instead?) Payload.MeanFreePath = SSSRadius; Payload.PhaseG = PhaseG; #else Payload.ShadingModelID = SHADINGMODELID_SUBSURFACE; Payload.SetSubsurfaceColor(SubsurfaceColor); Payload.SetSubsurfaceRadius(SSSRadius); Payload.SetSubsurfacePhaseFunction(PhaseG); #endif } else #endif // MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN #if MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE if (Payload.ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE) { const uint ProfileId = ExtractSubsurfaceProfileInt(GetMaterialSubsurfaceDataRaw(PixelMaterialInputs).a); // Decode dual roughness info float Lobe0Roughness = 0; float Lobe1Roughness = 0; float LobeMix = 0; // NOTE: we pass 1.0 for opacity because we don't really want to fade-out the dual roughness as opacity is lowered GetSubsurfaceProfileDualSpecular(ProfileId, Roughness, 1.0, Lobe0Roughness, Lobe1Roughness, LobeMix); Lobe0Roughness = AdjustMaterialRoughness(Lobe0Roughness, PackedPayload.GetPathRoughness()); Lobe1Roughness = AdjustMaterialRoughness(Lobe1Roughness, PackedPayload.GetPathRoughness()); #if HAVE_GetSubsurfaceMediumMaterialOutput0 float3 SSSRadius = GetSubsurfaceMediumMaterialOutput0(MaterialParameters); #else float3 SSSRadius = 0; if (GetSubsurfaceProfileUseBurley(ProfileId)) { // Decode SSS radius const float Opacity = GetMaterialOpacity(PixelMaterialInputs); const float3 DiffuseColor = ComputeDiffuseAlbedo(BaseColor, Metallic); SSSRadius = DecodeSSSProfileRadius(ProfileId, DiffuseColor, Opacity); } #endif #if HAVE_GetSubsurfaceMediumMaterialOutput1 float PhaseG = clamp(GetSubsurfaceMediumMaterialOutput1(MaterialParameters), -0.99f, 0.99f); #elif USE_SSS_PROFILE_ANISOTROPY // NOTE: this is not currently used float PhaseG = DecodeSSSProfileScatteringDistribution(ProfileId); SSSRadius *= 1 - PhaseG; #else float PhaseG = 0; #endif #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE // substrate slab includes SSS and dual roughness already Payload.ShadingModelID = SHADINGMODELID_SUBSTRATE; Payload.MeanFreePath = SSSRadius; Payload.PhaseG = PhaseG; Payload.RoughnessData = float3(Lobe0Roughness, Lobe1Roughness, LobeMix); #else Payload.SetSubsurfaceRadius(SSSRadius); Payload.SetSubsurfacePhaseFunction(PhaseG); Payload.SetDualRoughnessSpecular(Lobe0Roughness, Lobe1Roughness, LobeMix); #endif } else #endif // MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE #if MATERIAL_SHADINGMODEL_SINGLELAYERWATER if (Payload.ShadingModelID == SHADINGMODELID_SINGLELAYERWATER) { // For single layer water we always want to treat it as front facing when evaluating parameters // as hitting back face is treated as being underwater and we get completely different parameters. FMaterialPixelParameters WaterMaterialParameters = MaterialParameters; WaterMaterialParameters.TwoSidedSign = 1; const float3 ScatteringCoeff = max(0.0f, DFDemote(GetSingleLayerWaterMaterialOutput0(WaterMaterialParameters))); const float3 AbsorptionCoeff = max(0.0f, DFDemote(GetSingleLayerWaterMaterialOutput1(WaterMaterialParameters))); const float PhaseG = clamp(DFDemote(GetSingleLayerWaterMaterialOutput2(WaterMaterialParameters).x), -0.99f, 0.99f); const float3 WaterExtinction = ScatteringCoeff + AbsorptionCoeff; const float3 WaterAlbedo = select(WaterExtinction > 0.0, ScatteringCoeff / WaterExtinction, float3(0.0, 0.0, 0.0)); Payload.ShadingModelID = SHADINGMODELID_SOLID_GLASS; // replace shading model to avoid extra cases in raygen #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.MeanFreePath = rcp(max(1e-5, WaterExtinction)); #else Payload.SetExtinction(WaterExtinction); #endif Payload.SetGlassAlbedo(WaterAlbedo); Payload.SetGlassPhase(PhaseG); Payload.Ior = DielectricF0ToIor(DielectricSpecularToF0(Specular)); // we are using actual refraction, so don't apply any transparency Payload.TransparencyColor = 0.0; Payload.BSDFOpacity = 1.0; } else #endif // MATERIAL_SHADINGMODEL_SINGLELAYERWATER #if MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT if (Payload.ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) { const float3 TransmittanceColor = GetThinTranslucentMaterialOutput0(MaterialParameters); // The material is a blend of glass and default_lit response, model it stochastically if (Payload.BSDFOpacity < PackedPayload.GetStochasticSlabRand()) { // Pick glass lobe - shade as glass (leave SHADINGMODELID_THIN_TRANSLUCENT on) Payload.Ior = Ior; #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.MeanFreePath = TransmittanceToMeanFreePath(TransmittanceColor, SUBSTRATE_SIMPLEVOLUME_THICKNESS_CM * CENTIMETER_TO_METER) * METER_TO_CENTIMETER; Payload.DiffuseColor = 0; #else Payload.SetTransmittanceColor(TransmittanceColor); #endif } else { // Pick solid lobe -- toggle back to default_lit response // Ior stays 0.0 since this mode does not include any refraction #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.ShadingModelID = SHADINGMODELID_SUBSTRATE; #else Payload.ShadingModelID = SHADINGMODELID_DEFAULT_LIT; #endif } float SurfaceCoverage = GetThinTranslucentMaterialOutput1(MaterialParameters); #if PATH_TRACER_REFRACTION_ENABLED if (Ior > 0.0) { // In this mode we get rough refraction, so any transparency is only due to coverage (affecting both solid and glass lobes) Payload.TransparencyColor = 1 - SurfaceCoverage; } else #endif { // In this mode we just get straight transparency const float3 V = WorldRayDirection(); const float3 N = normalize(Payload.WorldNormal); const float VoN = abs(dot(V, N)); const float F0 = F0RGBToF0(ComputeF0(Specular, BaseColor, Metallic)); // This is the portion of transparency due to glass. We always compute it because we don't need to model the amount // of transparency stochastically. /// BSDFOpacity here still represents the blend between solid and glass behaviors. Payload.TransparencyColor = (1 - Payload.BSDFOpacity) * ComputeThinSlabWeights(TransmittanceColor, VoN, 0.0, F0).Transmitted; // Now account for overall coverage (allows the combination of solid/glass to become transparent) Payload.TransparencyColor = lerp(1.0, Payload.TransparencyColor, SurfaceCoverage); } // Now we can replace the BSDFOpacity with coverage Payload.BSDFOpacity = SurfaceCoverage; } else #endif // MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT #if MATERIAL_SHADINGMODEL_HAIR if (Payload.ShadingModelID == SHADINGMODELID_HAIR) { Payload.SetBaseColor(BaseColor); Payload.SetHairLongitudinalRoughness(AdjustMaterialRoughness(Roughness, PackedPayload.GetPathRoughness())); Payload.SetHairAzimuthalRoughness(Metallic); // SUBSTRATE_TODO: Map from scatter param to azimuthal roughness here instead of in the raygen Payload.SetHairSpecular(Specular); #if HAIR_STRAND_MESH_FACTORY Payload.SetHairPrimitiveUV(MaterialParameters.HairPrimitiveUV); #else Payload.SetHairPrimitiveUV(0.5); #endif } else #endif // MATERIAL_SHADINGMODEL_HAIR #if MATERIAL_SHADINGMODEL_EYE if (Payload.ShadingModelID == SHADINGMODELID_EYE) { // This is all based on logic from ShadingModelsMaterial.ush const float IrisMask = saturate(GetMaterialCustomData0(PixelMaterialInputs)); const float IrisDistance = saturate(GetMaterialCustomData1(PixelMaterialInputs)); Payload.SetEyeIrisMask(IrisMask); #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.SpecularColor = ComputeF0(Specular, BaseColor, 0); #else Payload.Metallic = 0.0; #endif #if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0 // Blend in the negative intersection normal to create some concavity // Not great as it ties the concavity to the convexity of the cornea surface // No good justification for that. On the other hand, if we're just looking to // introduce some concavity, this does the job. const float3 PlaneNormal = normalize(GetTangentOutput0(MaterialParameters)); const float3 CausticNormal = normalize(lerp(PlaneNormal, -Payload.WorldNormal, IrisMask * IrisDistance)); Payload.SetEyeCausticNormal(CausticNormal); // NOTE: calling AdjustShadingNormal on the custom normal does not seem necessary since this is only used for a diffuse calculation #else const float3 PlaneNormal = Payload.WorldNormal; Payload.SetEyeCausticNormal(Payload.WorldNormal); #endif #if IRIS_NORMAL // on // Match BasePassPixelShader logic, Specular part of g-buffer is re-use, so hard code the term #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.SpecularColor = DielectricSpecularToF0(0.25); #else Payload.Specular = 0.25; #endif #if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0 float3 IrisNormal = normalize( ClearCoatBottomNormal0(MaterialParameters) ); #if MATERIAL_TANGENTSPACENORMAL IrisNormal = normalize( TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, IrisNormal ) ); #endif #else float3 IrisNormal = PlaneNormal; #endif Payload.SetEyeIrisNormal(IrisNormal); #else // IRIS_NORMAL = off Payload.SetEyeIrisNormal(PlaneNormal); #endif #if HAVE_GetSubsurfaceMediumMaterialOutput0 float3 SSSRadius = GetSubsurfaceMediumMaterialOutput0(MaterialParameters); #else float3 SSSRadius = 0; const uint ProfileId = ExtractSubsurfaceProfileInt(GetMaterialSubsurfaceDataRaw(PixelMaterialInputs).a); if (GetSubsurfaceProfileUseBurley(ProfileId)) { // Decode SSS radius const float Opacity = GetMaterialOpacity(PixelMaterialInputs); const float3 DiffuseColor = ComputeDiffuseAlbedo(BaseColor, Metallic); SSSRadius = DecodeSSSProfileRadius(ProfileId, DiffuseColor, Opacity); } #endif #if HAVE_GetSubsurfaceMediumMaterialOutput1 float PhaseG = clamp(GetSubsurfaceMediumMaterialOutput1(MaterialParameters), -0.99f, 0.99f); #else float PhaseG = 0; #endif #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.MeanFreePath = SSSRadius; Payload.PhaseG = PhaseG; #else Payload.SetSubsurfaceRadius(SSSRadius); Payload.SetSubsurfacePhaseFunction(PhaseG); #endif } else #endif // MATERIAL_SHADINGMODEL_EYE #if MATERIAL_SHADINGMODEL_DEFAULT_LIT if (Payload.ShadingModelID == SHADINGMODELID_DEFAULT_LIT) { #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.ShadingModelID = SHADINGMODELID_SUBSTRATE; #endif #if PATH_TRACER_REFRACTION_ENABLED // only allow refraction for default lit materials since we need space for the absorption amount if (Ior > 0.0) { // Our material is a blend between glass and default_lit -- use stochastic blending between the two Payload.TransparencyColor = 0.0; if (PackedPayload.GetStochasticSlabRand() < Payload.BSDFOpacity) { // solid case - keep default_lit shading model } else { // glass // we are using actual refraction, so disable transparency Payload.Ior = Ior; Payload.ShadingModelID = SHADINGMODELID_SOLID_GLASS; #if HAVE_GetAbsorptionMediumMaterialOutput0 float3 Extinction = PathTracingGlassTransmittanceToExtinction(GetAbsorptionMediumMaterialOutput0(MaterialParameters)); #else // Make the glass totally clear if no custom medium is set float Extinction = 0; #endif #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.MeanFreePath = rcp(max(Extinction, 1e-5f)); #else Payload.SetExtinction(Extinction); #endif Payload.SetGlassAlbedo(0.0); } Payload.BSDFOpacity = 1.0; // cancels out with the probability of selecting the lobe } #endif } else #endif // MATERIAL_SHADINGMODEL_DEFAULT_LIT { // terminal case for the conditionals above } // adjust after everything else (because SSS profile case needs to decode the dual spec info from the unmodified roughness) #if USE_LEGACY_CODEPATH_FOR_SUBSTRATE Payload.RoughnessData.x = AdjustMaterialRoughness(Payload.RoughnessData.x, PackedPayload.GetPathRoughness()); Payload.RoughnessData.y = AdjustMaterialRoughness(Payload.RoughnessData.y, PackedPayload.GetPathRoughness()); #else Payload.Roughness = AdjustMaterialRoughness(Payload.Roughness, PackedPayload.GetPathRoughness()); #endif #endif // SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE #if MATERIAL_TWOSIDED == 0 if (MaterialParameters.TwoSidedSign < 0) { // when viewing the surface from "inside", don't include emission Payload.Radiance = 0; } #endif // Only opaque surfaces can receive decals. This is both for compatibility with raster and improving performance when we have stacks of transparent surfaces #if MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED if ((PrimitiveFlags & PRIMITIVE_SCENE_DATA_FLAG_DECAL_RECEIVER) != 0) { #if USE_DBUFFER Payload.SetDecalReceiver(MATERIALDECALRESPONSEMASK); # if MATERIAL_USES_DECAL_LOOKUP Payload.SetUseDBufferLookup(); # endif #else Payload.SetDecalReceiver(0x07); #endif } #endif #if MATERIALBLENDING_ALPHAHOLDOUT // the material is already a holdout, no need to do anything else #else if (PackedPayload.IsCameraRay()) { if ((PrimitiveFlags & PRIMITIVE_SCENE_DATA_FLAG_HOLDOUT) != 0 && ResolvedView.bPrimitiveAlphaHoldoutEnabled) { if (Payload.Ior != 0.0) { // If the surface is marked as refractive then treat it as opaque for holdout Payload.BSDFOpacity = 1.0; } else { // keep the opacity from the material, it might indicate partial transparency // so we should get a partial holdout } Payload.SetHoldout(); // zero-out the material contribution (including base-color and specular since UNLIT is used for volumetric lighting modes - see above) Payload.ShadingModelID = SHADINGMODELID_UNLIT; Payload.Radiance = 0; Payload.Anisotropy = 0; Payload.SetBaseColor(0.0); } } #endif #if USES_WORLD_POSITION_OFFSET // WPO moves the position which invalidates the normal. This means the only way to compute a correctly oriented normal is for the shader to orient it properly // The path tracer normally keeps the WorldSmoothNormal and WorldNormal separately, but this is not possible in this case. To avoid issues from having these two // normals being un-aligned with each other, simply copy WorldNormal to WorldSmoothNormal when WPO is in use. // See repro in UE-343656 #if ALWAYS_EVALUATE_WORLD_POSITION_OFFSET // always handle WPO #else // only if the primitive is using it if ((PrimitiveFlags & PRIMITIVE_SCENE_DATA_FLAG_EVALUATE_WORLD_POSITION_OFFSET) != 0u) #endif { Payload.WorldSmoothNormal = Payload.WorldNormal; } #endif #if MATERIAL_VIRTUALTEXTURE_FEEDBACK if (PackedPayload.IsCameraRay()) { // Virtual texturing feedback logic (camera rays only for now) FinalizeVirtualTextureFeedback( MaterialParameters.VirtualTextureFeedback, MaterialParameters.SvPosition, View.VTFeedbackBuffer ); } #endif // Output depth for solid, masked // or when `output depth and velocity' is set and Opacity >= Opacity mask clip value to match the behavior of the rasterizer path. #if (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED) Payload.SetOutputDepth(); #elif (TRANSLUCENT_WRITING_VELOCITY && \ (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE || MATERIALBLENDING_MODULATE || MATERIALBLENDING_ALPHACOMPOSITE || MATERIALBLENDING_ALPHAHOLDOUT)) if (GetMaterialOpacity(PixelMaterialInputs) >= GetMaterialOpacityMaskClipValue()) { Payload.SetOutputDepth(); } #endif PackedPayload = PackPathTracingPayload(Payload); } #if USE_MATERIAL_ANY_HIT_SHADER RAY_TRACING_ENTRY_ANY_HIT(PathTracingMaterialAHS, FPackedPathTracingPayload, PackedPayload, FRayTracingIntersectionAttributes, Attributes) { #if MATERIALBLENDING_MODULATE || (MATERIALBLENDING_MASKED && MATERIAL_DITHER_OPACITY_MASK) || MATERIALBLENDING_ALPHACOMPOSITE || MATERIALBLENDING_TRANSLUCENT if (!PackedPayload.IsVisibilityRay()) { if (PackedPayload.GetFlags() & PATH_TRACING_PAYLOAD_INPUT_FLAG_IGNORE_TRANSLUCENT) { IgnoreHit(); } // not a shadow ray -- don't need to run the AHS logic return; } if (PackedPayload.CheckDuplicateAHS(RayTCurrent())) { // We just processed this exact hit, don't double-count it IgnoreHit(); return; } #endif // This is the only case which actually needs to run the full material ResolvedView = ResolveView(); const float3 TranslatedWorldPosition = TranslatedWorldRayOrigin() + RayTCurrent() * WorldRayDirection(); const float4 SvPosition = TranslatedWorldPositionToSvPosition(TranslatedWorldPosition); CurrentPayloadInputFlags = PackedPayload.GetFlags(); PathTracerSceneDepth = (CurrentPayloadInputFlags & PATH_TRACING_PAYLOAD_INPUT_FLAG_HAS_SCENE_DEPTH) ? PackedPayload.GetSceneDepth() : SCENE_TEXTURES_DISABLED_SCENE_DEPTH_VALUE; #if VF_SUPPORTS_RAYTRACING_PREPARE_MATERIAL_PIXEL_PARAMETERS // this is a newer codepath that is both more flexible and allows for more direct calculation compared to the other codepath // TODO: implement such a method for all vertex factories float3 GeoNormal = 0; FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(TranslatedWorldRayOrigin(), WorldRayDirection(), RayTCurrent(), PrimitiveIndex(), Attributes, HitKind(), SvPosition, GeoNormal); #else FVertexFactoryInterpolantsVSToPS Interpolants; float3 GeoNormal = 0; CalcInterpolants((FRayCone)0, Attributes, Interpolants, GeoNormal); FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, SvPosition); #endif FPixelMaterialInputs PixelMaterialInputs; const bool bIsFrontFace = HitKind() == HIT_KIND_TRIANGLE_FRONT_FACE; { const float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition); MaterialParameters.CameraVector = -WorldRayDirection(); CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, bIsFrontFace, TranslatedWorldPosition, TranslatedWorldPosition); } #if MATERIALBLENDING_MASKED && !MATERIAL_DITHER_OPACITY_MASK // Regardless of payload flags -- we always apply this if (GetMaterialMask(PixelMaterialInputs) < 0) { IgnoreHit(); } #endif // the following blend modes need to process shadows after having executed the material #if MATERIALBLENDING_MODULATE || (MATERIALBLENDING_MASKED && MATERIAL_DITHER_OPACITY_MASK) || MATERIALBLENDING_ALPHACOMPOSITE || MATERIALBLENDING_TRANSLUCENT PackedPayload.UpdateDuplicateAHS(RayTCurrent()); { // TODO: Cleanup this indentation level #if SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE #if MATERIAL_IS_SUBSTRATE FSubstratePixelHeader SubstratePixelHeader = MaterialParameters.GetFrontSubstrateHeader(); FSubstrateData SubstrateData = PixelMaterialInputs.GetFrontSubstrateData(); FSubstrateIntegrationSettings IntegrationSettings = InitSubstrateIntegrationSettings( false /* bForceFullyRough */, true /* SubstrateStruct.bRoughDiffuse */, -1 /* SubstrateStruct.PeelLayersAboveDepth */, true /* SubstrateStruct.bRoughnessTracking */ ); float TotalCoverage = 1; float3 TotalTransmittancePreCoverage = 0; const float3 V_World = MaterialParameters.CameraVector; SubstratePixelHeader.SubstrateUpdateTree(SubstrateData, V_World, IntegrationSettings, TotalCoverage, TotalTransmittancePreCoverage); #if MATERIALBLENDING_TRANSLUCENT && (PATH_TRACER_REFRACTION_ENABLED || MATERIAL_ISTHINSURFACE) // SUBSTRATE_TODO: refraction handling float PathRoughness = PackedPayload.GetPathRoughness(); // SUBSTRATE_TODO: This does not match the logic in DistortAccumulatePS.usf // The idea here is to take F0 from the bottom refraction layers since this matches most closely what the refraction logic below does (only bottom slabs can refract). // The distortion approximation on the raster side instead averages F0 across layers which may produce different results. // In both cases - we get a potentially strange answer when there is more than one bottom slab that produces refraction // SUBSTRATE_TODO: How should we handle MFP when there are multiple bottom slabs? float F0 = 0.0f; float3 RoughnessData = 0.0; float NoV = 0.0f; float3 LocalSigmaT = 0.0f; float Ior = 1.0; float RefractMix = 0.0; Substrate_for_unroll(int BSDFIdx = 0, BSDFIdx < SubstratePixelHeader.SubstrateTree.BSDFCount, ++BSDFIdx) { #define BSDF SubstratePixelHeader.SubstrateTree.BSDFs[BSDFIdx] if (BSDF.bIsBottom) { // SUBSTRATE_TODO: Improve this to correctly deal with multiple bottom slabs (in the case of non-parameter blended horizontal blends) float3 N = SubstratePixelHeader.SharedLocalBases.Normals[BSDF_GETSHAREDLOCALBASISID(BSDF)]; NoV = dot(N, V_World); switch (BSDF_GETTYPE(BSDF)) { case SUBSTRATE_BSDF_TYPE_SLAB: { // SUBSTRATE_TODO: Also consider the MFP to see how much this slab is contributing to refraction .... F0 = F0RGBToF0(SubstrateGetBSDFSpecularF0(BSDF)); RoughnessData.x = SLAB_ROUGHNESS(BSDF); if (BSDF_GETHASHAZINESS(BSDF)) { // NOTE: It should be safe to assume that the clearcoat case cannot happen here as it is only triggered by legacy clearcoat materials which are unlikely to have refraction const FHaziness Haziness = UnpackHaziness(SLAB_HAZINESS(BSDF)); RoughnessData.y = Haziness.Roughness; RoughnessData.z = Haziness.Weight; } #if PATH_TRACER_USE_SUBSTRATE_GLASS_LOBE switch (BSDF_GETSSSTYPE(BSDF)) { case SSS_TYPE_DIFFUSION: case SSS_TYPE_SIMPLEVOLUME: { // only non-SSS slabs float3 MeanFreePath = SLAB_SSSMFP(BSDF); LocalSigmaT = rcp(max(MeanFreePath, 0.0001)); #if PATH_TRACER_REFRACTION_ENABLED Ior = GetRefractionIor(PixelMaterialInputs, F0); #endif #if MATERIAL_ISTHINSURFACE float3 Transmittance = MeanFreePathToTransmittance(MeanFreePath * CENTIMETER_TO_METER, SUBSTRATE_SIMPLEVOLUME_THICKNESS_CM * CENTIMETER_TO_METER); RefractMix = saturate(Luminance(Transmittance)); #else // blend towards Opaque SSS in the range of [0cm, 1cm] RefractMix = saturate(max3(MeanFreePath.x, MeanFreePath.y, MeanFreePath.z)); #endif break; } default: { // All other SSS types (including none) do not product any transmission and should be treated as opaque break; } } break; #endif // PATH_TRACER_USE_SUBSTRATE_GLASS_LOBE } case SUBSTRATE_BSDF_TYPE_SINGLELAYERWATER: { RoughnessData.x = SLW_ROUGHNESS(BSDF); RoughnessData.yz = 0; #if SUBSTRATE_INLINE_SINGLELAYERWATER LocalSigmaT = SLW_WATEREXTINCTION(BSDF); #endif float F0 = DielectricSpecularToF0(SLW_SPECULAR(BSDF)); Ior = DielectricF0ToIor(F0); RefractMix = 1; break; } } } #undef BSDF } #if MATERIAL_ISTHINSURFACE // Thin Glass Fake Caustics // SUBSTRATE_TODO: Revisit this -- TotalTransmittancePreCoverage should probably account for this in the thin case float3 Transmission = exp(-LocalSigmaT * SUBSTRATE_SIMPLEVOLUME_THICKNESS_CM); float3 Tr = RefractMix * ComputeThinSlabWeights(Transmission, NoV, Ior, F0).Transmitted; #else HLSL_STATIC_ASSERT(PATH_TRACER_REFRACTION_ENABLED == 1, "Expect refraction to be true here"); // Solid Glass Fake Caustics float Tr = RefractMix * FakeCaustics(F0, Ior, NoV); // Account for transmission PackedPayload.SetTau(PackedPayload.GetTau() + TotalCoverage * RefractMix * ComputeOpticalDepthForHit(LocalSigmaT, bIsFrontFace)); #endif // If material is doing refraction, apply an extra factor to account for ApproximateCaustics #if PATH_TRACER_REFRACTION_ENABLED if (Ior > 0.0) { Tr *= lerp(FakeCausticsPathFactor(RoughnessData.x, PathRoughness), FakeCausticsPathFactor(RoughnessData.y, PathRoughness), RoughnessData.z); } #endif const float3 Transparency = saturate(lerp(1.0, Tr, TotalCoverage)); #else // MATERIALBLENDING_TRANSLUCENT && (PATH_TRACER_REFRACTION_ENABLED || MATERIAL_ISTHINSURFACE) // All other blend modes, do simple transparency handling const float3 Transparency = saturate(lerp(1.0, TotalTransmittancePreCoverage, TotalCoverage)); #endif #else // MATERIAL_IS_SUBSTRATE // TODO: What cases (if any) end up here? const float3 Transparency = 0.0; #endif // MATERIAL_IS_SUBSTRATE #else // SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE #if MATERIALBLENDING_MODULATE const float3 Transparency = GetMaterialEmissive(PixelMaterialInputs); #elif MATERIALBLENDING_ALPHACOMPOSITE const float Opacity = GetMaterialOpacity(PixelMaterialInputs); const float Transparency = 1 - Opacity; #elif MATERIALBLENDING_MASKED && MATERIAL_DITHER_OPACITY_MASK // See MATERIAL_DITHER_OPACITY_MASK comment below const float Opacity = saturate(GetMaterialMaskInputRaw(PixelMaterialInputs)); const float Transparency = 1 - Opacity; #elif MATERIALBLENDING_TRANSLUCENT const float Opacity = GetMaterialOpacity(PixelMaterialInputs); #if MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT // We can only get colored shadows for thin translucent (solid translucent case happens via absorption only) float3 Transparency = 1 - Opacity; #else float Transparency = 1 - Opacity; #endif uint ShadingModelID = GetMaterialShadingModel(PixelMaterialInputs); #if MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT if (ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) { float3 Transmission = GetThinTranslucentMaterialOutput0(MaterialParameters); #if PATH_TRACER_REFRACTION_ENABLED float Ior = GetRefractionIor(PixelMaterialInputs); #else float Ior = 0.0; #endif float3 V = WorldRayDirection(); float3 N = normalize(MaterialParameters.WorldNormal); float VoN = abs(dot(V, N)); if (Opacity < 1.0) { float PathRoughness = PackedPayload.GetPathRoughness(); float Roughness = GetMaterialRoughness(PixelMaterialInputs); if (Ior > 0.0 && PathRoughness <= Roughness) { // not using fast caustic approximation - treat as opaque PackedPayload.SetRayThroughput(0.0); return; } // compute transmission through the slab (fresnel + absorption) float3 BaseColor = GetMaterialBaseColor(PixelMaterialInputs); float Metallic = GetMaterialMetallic(PixelMaterialInputs); float Specular = GetMaterialSpecular(PixelMaterialInputs); float F0 = F0RGBToF0(ComputeF0(Specular, BaseColor, Metallic)); Transparency *= ComputeThinSlabWeights(Transmission, VoN, Ior, F0).Transmitted; // fake caustic approximation (see comments below) #if PATH_TRACER_REFRACTION_ENABLED if (Ior > 0.0) { Transparency *= FakeCausticsPathFactor(Roughness, PathRoughness); } #endif } // Fade out the Transparency as a function of the SurfaceCoverage. const float SurfaceCoverage = GetThinTranslucentMaterialOutput1(MaterialParameters); Transparency = lerp(1.0.xxx, Transparency, SurfaceCoverage); } else #endif // MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT #if MATERIAL_SHADINGMODEL_DEFAULT_LIT && PATH_TRACER_REFRACTION_ENABLED if (ShadingModelID == SHADINGMODELID_DEFAULT_LIT) { // Is refraction enabled? float Ior = GetRefractionIor(PixelMaterialInputs); if (Transparency > 0 && Ior > 0.0) { // current hit has some refraction float PathRoughness = PackedPayload.GetPathRoughness(); float Roughness = GetMaterialRoughness(PixelMaterialInputs); float3 BaseColor = GetMaterialBaseColor(PixelMaterialInputs); float Metallic = GetMaterialMetallic(PixelMaterialInputs); float Specular = GetMaterialSpecular(PixelMaterialInputs); float F0 = F0RGBToF0(ComputeF0(Specular, BaseColor, Metallic)); float3 N = normalize(MaterialParameters.WorldNormal); float NoV = dot(WorldRayDirection(), N); Transparency *= FakeCaustics(F0, Ior, NoV) * FakeCausticsPathFactor(Roughness, PathRoughness); #if HAVE_GetAbsorptionMediumMaterialOutput0 // Does the material have any kind of volumetric absorption to apply? float3 TransmittanceColor = GetAbsorptionMediumMaterialOutput0(MaterialParameters); float3 LocalSigmaT = PathTracingGlassTransmittanceToExtinction(TransmittanceColor); PackedPayload.SetTau(PackedPayload.GetTau() + ComputeOpticalDepthForHit(LocalSigmaT, bIsFrontFace)); #endif // HAVE_GetAbsorptionMediumMaterialOutput0 } } else #endif // MATERIAL_SHADINGMODEL_DEFAULT_LIT { // base case for shadingmodel if/else } #else // MATERIALBLENDING_* #error Unhandled blending mode! #endif #endif // SUBSTRATE_ENABLED && !USE_LEGACY_CODEPATH_FOR_SUBSTRATE // Update the ray throughput (it is packed simply into the payload since we don't need to carry any other information across hits) float3 RayThroughput = PackedPayload.GetRayThroughput(); RayThroughput *= Transparency; PackedPayload.SetRayThroughput(RayThroughput); if (any(RayThroughput > 0)) { // keep tracing if we still have some energy left IgnoreHit(); } } #endif } #endif // USE_MATERIAL_ANY_HIT_SHADER