Shader refactor (no functional changes)

The purpose of this shader refactor is to split various material type
shaders into disparate pieces:

1. The original material type shaders now include an external file with
   the actual shader entry points and structure of the algorithm (e.g.
   depth pass, shadow pass, forward pass) but continue to specify the
   SRGs used
2. Common functionality used across multiple shaders was consolidated
   into routines implemented in the MaterialFunctions folder
   (Materials/Types/MaterialFunctions)
3. The implementation shaders rely on common routines to be
   included/imported prior to inclusion, and by design, make no
   references to any Draw, Object, or Material SRG.

This refactor only includes the Standard and Enhanced material types,
and is, for the most part, a non-functional change. However, the Surface
definition needed to be augmented to include information needed by
lighting later. Modifying the Surface structure enables the lighting
loops to avoid any references to the Material SRG. This completes the
decoupling needed to support future Material canvas work, as well as a
future Material pipeline abstraction (where by the implementation
shaders can be injected by the user, customized per platform, and in
general, are simply decoupled from the materials themselves).

Signed-off-by: Jeremy Ong <jcong@amazon.com>
monroegm-disable-blank-issue-2
Jeremy Ong 4 years ago
parent 742ea34d44
commit 61f915366a

@ -116,19 +116,19 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace)
float2 baseColorUv = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
float3 baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
surface.baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
// ------- Metallic -------
float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex];
float metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
surface.metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
// ------- Specular -------
float2 specularUv = IN.m_uv[MaterialSrg::m_specularF0MapUvIndex];
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
surface.SetAlbedoAndSpecularF0(specularF0Factor);
// ------- Roughness -------

@ -0,0 +1,124 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
struct VSInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
#ifdef MULTILAYER
// This gets set automatically by the system at runtime only if it's available.
// There is a soft naming convention that associates this with o_blendMask_isBound, which will be set to true whenever m_optional_blendMask is available.
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
float4 m_optional_blendMask : COLOR0;
#endif
};
struct VSDepthOutput
{
// "centroid" is needed for SV_Depth to compile
precise linear centroid float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
#ifdef MULTILAYER
float3 m_blendMask : UV3;
#endif
};
VSDepthOutput MainVS(VSInput IN)
{
VSDepthOutput OUT;
float4x4 objectToWorld = GetObjectToWorld();
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
float2 uvs[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
TransformUvs(uvs, OUT.m_uv);
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = GetNormalToWorld();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
#ifdef MULTILAYER
if(o_blendMask_isBound)
{
OUT.m_blendMask = IN.m_optional_blendMask.rgb;
}
else
{
OUT.m_blendMask = float3(0,0,0);
}
#endif
return OUT;
}
struct PSDepthOutput
{
precise float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent, IN.m_tangent };
float3 bitangents[UvSetCount] = { IN.m_bitangent, IN.m_bitangent };
for (int i = 0; i != UvSetCount; ++i)
{
EvaluateTangentFrame(
IN.m_normal,
IN.m_worldPosition,
isFrontFace,
IN.m_uv[i],
i,
IN.m_tangent,
IN.m_bitangent,
tangents[i],
bitangents[i]);
}
#ifdef MULTILAYER
MultilayerSetPixelDepth(IN.m_blendMask, IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth);
#else
SetPixelDepth(IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth);
#endif
}
#ifndef MULTILAYER
float alpha = GetAlpha(IN.m_uv);
MaybeClip(alpha, IN.m_uv);
#endif
return OUT;
}

@ -8,88 +8,13 @@
#include "./EnhancedPBR_Common.azsli"
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#include "MaterialInputs/AlphaInput.azsli"
#include "MaterialInputs/ParallaxInput.azsli"
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/StandardGetAlpha.azsli"
#include "MaterialFunctions/StandardTransformUvs.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/ParallaxDepth.azsli"
#include "MaterialFunctions/StandardMaybeClip.azsli"
struct VSInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
};
struct VSDepthOutput
{
precise linear centroid float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
};
VSDepthOutput MainVS(VSInput IN)
{
VSDepthOutput OUT;
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
// By design, only UV0 is allowed to apply transforms.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
return OUT;
}
struct PSDepthOutput
{
precise float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
}
// Clip Alpha
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
return OUT;
}
#include "DepthPass_WithPS.azsl"

@ -10,21 +10,6 @@
// SRGs
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
// Pass Output
#include <Atom/Features/PBR/ForwardSubsurfacePassOutput.azsli>
// Utility
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Features/PBR/AlphaUtils.azsli>
// Custom Surface & Lighting
#include <Atom/Features/PBR/Lighting/EnhancedLighting.azsli>
// Decals
#include <Atom/Features/PBR/Decals.azsli>
// ---------- Material Parameters ----------
@ -49,397 +34,13 @@ COMMON_OPTIONS_DETAIL_MAPS()
#include "MaterialInputs/TransmissionInput.azsli"
// ---------- Vertex Shader ----------
struct VSInput
{
// Base fields (required by the template azsli file)...
float3 m_position : POSITION;
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
// Extended fields (only referenced in this azsl file)...
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
};
struct VSOutput
{
// Base fields (required by the template azsli file)...
precise linear centroid float4 m_position : SV_Position;
float3 m_normal: NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV5;
// Extended fields (only referenced in this azsl file)...
float2 m_uv[UvSetCount] : UV1;
float2 m_detailUv[UvSetCount] : UV3;
};
#include <Atom/Features/Vertex/VertexHelper.azsli>
VSOutput EnhancedPbr_ForwardPassVS(VSInput IN)
{
VSOutput OUT;
float3 worldPosition = mul(ObjectSrg::GetWorldMatrix(), float4(IN.m_position, 1.0)).xyz;
// By design, only UV0 is allowed to apply transforms.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
// As seen above our standard practice is to only transform the first UV as that's the one we expect to be used for
// tiling. But for detail maps you could actually use either UV stream for tiling. There is no concern about applying
// the same transform to both UV sets because the detail map feature forces the same UV set to be used for all detail maps.
// Note we might be able to combine these into a single UV similar to what Skin.materialtype does,
// but we would need to address how it works with the parallax code below that indexes into the m_detailUV array.
OUT.m_detailUv[0] = mul(MaterialSrg::m_detailUvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_detailUv[1] = mul(MaterialSrg::m_detailUvMatrix, float3(IN.m_uv1, 1.0)).xy;
// Shadow coords will be calculated in the pixel shader in this case
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
VertexHelper(IN, OUT, worldPosition, skipShadowCoords);
return OUT;
}
// ---------- Pixel Shader ----------
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depth)
{
const float3 vertexNormal = normalize(IN.m_normal);
// ------- Tangents & Bitangets -------
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture) || o_detail_normal_useTexture)
{
PrepareGeneratedTangent(vertexNormal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
}
// ------- Depth & Parallax -------
depth = IN.m_position.z;
bool displacementIsClipped = false;
// Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled
if(ShouldHandleParallax())
{
// GetParallaxInput applies an tangent offset to the UV. We want to apply the same offset to the detailUv (note: this needs to be tested with content)
// The math is: offset = newUv - oldUv; detailUv += offset;
// This is the same as: detailUv -= oldUv; detailUv += newUv;
IN.m_detailUv[MaterialSrg::m_parallaxUvIndex] -= IN.m_uv[MaterialSrg::m_parallaxUvIndex];
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(vertexNormal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, IN.m_position.w, displacementIsClipped);
// Apply second part of the offset to the detail UV (see comment above)
IN.m_detailUv[MaterialSrg::m_parallaxUvIndex] -= IN.m_uv[MaterialSrg::m_parallaxUvIndex];
// Adjust directional light shadow coorinates for parallax correction
if(o_parallax_enablePixelDepthOffset)
{
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
{
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
}
}
}
Surface surface;
surface.position = IN.m_worldPosition;
// ------- Alpha & Clip -------
float2 baseColorUv = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
float2 opacityUv = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
float alpha = GetAlphaInputAndClip(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUv, opacityUv, MaterialSrg::m_sampler, MaterialSrg::m_opacityFactor, o_opacity_source);
// ------- Detail Layer Setup -------
const float2 detailUv = IN.m_detailUv[MaterialSrg::m_detail_allMapsUvIndex];
// When the detail maps and the detail blend mask are on the same UV, they both use the transformed detail UVs because they are 'attached' to each other
const float2 detailBlendMaskUv = (MaterialSrg::m_detail_blendMask_uvIndex == MaterialSrg::m_detail_allMapsUvIndex) ?
IN.m_detailUv[MaterialSrg::m_detail_blendMask_uvIndex] :
IN.m_uv[MaterialSrg::m_detail_blendMask_uvIndex];
const float detailLayerBlendFactor = GetDetailLayerBlendFactor(
MaterialSrg::m_detail_blendMask_texture,
MaterialSrg::m_sampler,
detailBlendMaskUv,
o_detail_blendMask_useTexture,
MaterialSrg::m_detail_blendFactor);
// ------- Normal -------
float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex];
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor;
surface.vertexNormal = vertexNormal;
surface.normal = GetDetailedNormalInputWS(
isFrontFace, IN.m_normal,
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_normalFactor, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, uvMatrix, o_normal_useTexture,
tangents[MaterialSrg::m_detail_allMapsUvIndex], bitangents[MaterialSrg::m_detail_allMapsUvIndex], MaterialSrg::m_detail_normal_texture, MaterialSrg::m_sampler, detailUv, detailLayerNormalFactor, MaterialSrg::m_detail_normal_flipX, MaterialSrg::m_detail_normal_flipY, MaterialSrg::m_detailUvMatrix, o_detail_normal_useTexture);
//--------------------- Base Color ----------------------
// [GFX TODO][ATOM-1761] Figure out how we want our base material to expect channels to be encoded, and apply that to the way we pack alpha.
float detailLayerBaseColorFactor = MaterialSrg::m_detail_baseColor_factor * detailLayerBlendFactor;
float3 baseColor = GetDetailedBaseColorInput(
MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, o_baseColor_useTexture, MaterialSrg::m_baseColor, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode,
MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, detailUv, o_detail_baseColor_useTexture, detailLayerBaseColorFactor);
if(o_parallax_highlightClipping && displacementIsClipped)
{
ApplyParallaxClippingHighlight(baseColor);
}
// ------- Metallic -------
float metallic = 0;
if(!o_enableSubsurfaceScattering) // If subsurface scattering is enabled skip texture lookup for metallic, as this quantity won't be used anyway
{
float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex];
metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
}
// ------- Specular -------
float2 specularUv = IN.m_uv[MaterialSrg::m_specularF0MapUvIndex];
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
// ------- Roughness -------
float2 roughnessUv = IN.m_uv[MaterialSrg::m_roughnessMapUvIndex];
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
surface.CalculateRoughnessA();
// ------- Subsurface -------
float2 subsurfaceUv = IN.m_uv[MaterialSrg::m_subsurfaceScatteringInfluenceMapUvIndex];
float surfaceScatteringFactor = GetSubsurfaceInput(MaterialSrg::m_subsurfaceScatteringInfluenceMap, MaterialSrg::m_sampler, subsurfaceUv, MaterialSrg::m_subsurfaceScatteringFactor);
// ------- Transmission -------
float2 transmissionUv = IN.m_uv[MaterialSrg::m_transmissionThicknessMapUvIndex];
float4 transmissionTintThickness = GeTransmissionInput(MaterialSrg::m_transmissionThicknessMap, MaterialSrg::m_sampler, transmissionUv, MaterialSrg::m_transmissionTintThickness);
surface.transmission.tint = transmissionTintThickness.rgb;
surface.transmission.thickness = transmissionTintThickness.w;
surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams;
surface.transmission.scatterDistance = MaterialSrg::m_scatterDistance;
// ------- Anisotropy -------
if (o_enableAnisotropy)
{
// Convert the angle from [0..1] = [0 .. 180 degrees] to radians [0 .. PI]
const float anisotropyAngle = MaterialSrg::m_anisotropicAngle * PI;
const float anisotropyFactor = MaterialSrg::m_anisotropicFactor;
surface.anisotropy.Init(surface.normal, IN.m_tangent, IN.m_bitangent, anisotropyAngle, anisotropyFactor, surface.roughnessA);
}
// ------- Lighting Data -------
LightingData lightingData;
// Light iterator
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
// Directional light shadow coordinates
lightingData.shadowCoords = IN.m_shadowCoords;
// ------- Emissive -------
float2 emissiveUv = IN.m_uv[MaterialSrg::m_emissiveMapUvIndex];
lightingData.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
// ------- Occlusion -------
lightingData.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
lightingData.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
// ------- Thin Object Light Transmission -------
// Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
lightingData.shrinkFactor = surface.transmission.transmissionParams.x;
// Angle offset for subsurface scattering through thin objects
lightingData.transmissionNdLBias = surface.transmission.transmissionParams.y;
// Attenuation applied to hide artifacts due to low-res shadow maps
lightingData.distanceAttenuation = surface.transmission.transmissionParams.z;
// ------- Clearcoat -------
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
if(o_clearCoat_feature_enabled)
{
if(o_clearCoat_enabled)
{
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, IN.m_uv[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
MaterialSrg::m_clearCoatRoughnessMap, IN.m_uv[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
MaterialSrg::m_clearCoatNormalMap, IN.m_uv[MaterialSrg::m_clearCoatNormalMapUvIndex], IN.m_normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
MaterialSrg::m_sampler, isFrontFace,
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
}
// manipulate base layer f0 if clear coat is enabled
// modify base layer's normal incidence reflectance
// for the derivation of the following equation please refer to:
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
}
// Diffuse and Specular response (used in IBL calculations)
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
if(o_clearCoat_feature_enabled)
{
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
}
// ------- Multiscatter -------
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
// ------- Lighting Calculation -------
// Apply Decals
ApplyDecals(lightingData.tileIterator, surface);
// Apply Direct Lighting
ApplyDirectLighting(surface, lightingData);
// Apply Image Based Lighting (IBL)
ApplyIBL(surface, lightingData);
// Finalize Lighting
lightingData.FinalizeLighting(surface.transmission.tint);
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
// ------- Opacity -------
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
{
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
// not apply.
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
alpha = lerp(fresnelAlpha, alpha, MaterialSrg::m_opacityAffectsSpecularFactor);
}
// Note: lightingOutput rendertargets are not always used as named, particularly m_diffuseColor (target 0) and
// m_specularColor (target 1). Comments below describe the differences when appropriate.
if (o_opacity_mode == OpacityMode::Blended)
{
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_diffuseColor.w = alpha;
}
else if (o_opacity_mode == OpacityMode::TintedTransparent)
{
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
// absorption, and interior color to be specified.
//
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
//
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_specularColor.rgb = baseColor * (1.0 - alpha);
}
else
{
// Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961
uint factorAndQuality = dot(round(float2(saturate(surfaceScatteringFactor), MaterialSrg::m_subsurfaceScatteringQuality) * 255), float2(256, 1));
lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0);
lightingOutput.m_scatterDistance = MaterialSrg::m_scatterDistance;
}
return lightingOutput;
}
ForwardPassOutputWithDepth EnhancedPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutputWithDepth OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
OUT.m_depth = depth;
return OUT;
}
[earlydepthstencil]
ForwardPassOutput EnhancedPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutput OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
#include "MaterialFunctions/EvaluateEnhancedSurface.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/EnhancedParallaxDepth.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardMaybeClip.azsli"
#include "MaterialFunctions/StandardTransformDetailUvs.azsli"
#include "MaterialFunctions/StandardTransformUvs.azsli"
return OUT;
}
#include "EnhancedSurface_ForwardPass.azsl"

@ -16,85 +16,12 @@
#include "MaterialInputs/AlphaInput.azsli"
#include "MaterialInputs/ParallaxInput.azsli"
struct VertexInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
};
struct VertexOutput
{
float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
};
VertexOutput MainVS(VertexInput IN)
{
const float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
VertexOutput OUT;
const float3 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0)).xyz;
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
// By design, only UV0 is allowed to apply transforms.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
return OUT;
}
struct PSDepthOutput
{
float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
OUT.m_depth += PdoShadowMapBias;
}
// Clip Alpha
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
return OUT;
}
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/StandardGetAlpha.azsli"
#include "MaterialFunctions/StandardTransformUvs.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/ParallaxDepth.azsli"
#include "MaterialFunctions/StandardMaybeClip.azsli"
#include "ShadowMap_WithPS.azsl"

@ -0,0 +1,317 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// SRGs
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
// Pass Output
#include <Atom/Features/PBR/ForwardSubsurfacePassOutput.azsli>
// Utility
#include <Atom/Features/ColorManagement/TransformColor.azsli>
// Custom Surface & Lighting
#include <Atom/Features/PBR/Lighting/EnhancedLighting.azsli>
// Decals
#include <Atom/Features/PBR/Decals.azsli>
// ---------- Vertex Shader ----------
struct VSInput
{
// Base fields (required by the template azsli file)...
float3 m_position : POSITION;
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
// Extended fields (only referenced in this azsl file)...
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
};
struct VSOutput
{
// Base fields (required by the template azsli file)...
precise linear centroid float4 m_position : SV_Position;
float3 m_normal: NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV5;
// Extended fields (only referenced in this azsl file)...
float2 m_uv[UvSetCount] : UV1;
float2 m_detailUv[UvSetCount] : UV3;
};
VSOutput EnhancedPbr_ForwardPassVS(VSInput IN)
{
VSOutput OUT;
float4x4 objectToWorld = GetObjectToWorld();
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
OUT.m_worldPosition = worldPosition.xyz;
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
float2 uv[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
TransformUvs(uv, OUT.m_uv);
float2 detailUv[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
TransformDetailUvs(detailUv, OUT.m_detailUv);
// Shadow coords will be calculated in the pixel shader in this case
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
float3x3 objectToWorldIT = GetNormalToWorld();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
// directional light shadow
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
if (o_enableShadows && !skipShadowCoords && shadowIndex < SceneSrg::m_directionalLightCount)
{
DirectionalLightShadow::GetShadowCoords(
shadowIndex,
worldPosition,
OUT.m_normal,
OUT.m_shadowCoords);
}
return OUT;
}
// ---------- Pixel Shader ----------
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depth)
{
const float3 vertexNormal = normalize(IN.m_normal);
// ------- Tangents & Bitangets -------
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture) || o_detail_normal_useTexture)
{
for (int i = 0; i != UvSetCount; ++i)
{
EvaluateTangentFrame(
IN.m_normal,
IN.m_worldPosition,
isFrontFace,
IN.m_uv[i],
i,
IN.m_tangent,
IN.m_bitangent,
tangents[i],
bitangents[i]);
}
}
// ------- Depth & Parallax -------
depth = IN.m_position.z;
bool displacementIsClipped = false;
// Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled
if(ShouldHandleParallax())
{
EnhancedSetPixelDepth(
IN.m_worldPosition,
IN.m_normal,
tangents,
bitangents,
IN.m_uv,
isFrontFace,
IN.m_detailUv,
IN.m_position.w,
depth,
displacementIsClipped);
// Adjust directional light shadow coorinates for parallax correction
if(o_parallax_enablePixelDepthOffset)
{
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
{
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
}
}
}
Surface surface;
surface.vertexNormal = vertexNormal;
surface.position = IN.m_worldPosition;
// ------- Alpha & Clip -------
// TODO: this often invokes a separate sample of the base color texture which is wasteful
float alpha = GetAlpha(IN.m_uv);
MaybeClip(alpha, IN.m_uv);
EvaluateEnhancedSurface(IN.m_normal, IN.m_uv, IN.m_detailUv, tangents, bitangents, isFrontFace, displacementIsClipped, surface);
// ------- Lighting Data -------
LightingData lightingData;
// Light iterator
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
// Directional light shadow coordinates
lightingData.shadowCoords = IN.m_shadowCoords;
lightingData.emissiveLighting = surface.emissiveLighting;
lightingData.diffuseAmbientOcclusion = surface.diffuseAmbientOcclusion;
lightingData.specularOcclusion = surface.specularOcclusion;
// Diffuse and Specular response (used in IBL calculations)
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
// ------- Thin Object Light Transmission -------
// Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
lightingData.shrinkFactor = surface.transmission.transmissionParams.x;
// Angle offset for subsurface scattering through thin objects
lightingData.transmissionNdLBias = surface.transmission.transmissionParams.y;
// Attenuation applied to hide artifacts due to low-res shadow maps
lightingData.distanceAttenuation = surface.transmission.transmissionParams.z;
if(o_clearCoat_feature_enabled)
{
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
}
// ------- Multiscatter -------
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
// ------- Lighting Calculation -------
// Apply Decals
ApplyDecals(lightingData.tileIterator, surface);
// Apply Direct Lighting
ApplyDirectLighting(surface, lightingData);
// Apply Image Based Lighting (IBL)
ApplyIBL(surface, lightingData);
// Finalize Lighting
lightingData.FinalizeLighting(surface.transmission.tint);
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
// ------- Opacity -------
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
{
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
// not apply.
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
alpha = lerp(fresnelAlpha, alpha, surface.opacityAffectsSpecularFactor);
}
// Note: lightingOutput rendertargets are not always used as named, particularly m_diffuseColor (target 0) and
// m_specularColor (target 1). Comments below describe the differences when appropriate.
if (o_opacity_mode == OpacityMode::Blended)
{
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surface.opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_diffuseColor.w = alpha;
}
else if (o_opacity_mode == OpacityMode::TintedTransparent)
{
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
// absorption, and interior color to be specified.
//
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
//
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surface.opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_specularColor.rgb = surface.baseColor * (1.0 - alpha);
}
else
{
// Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961
uint factorAndQuality = dot(round(float2(saturate(surface.subsurfaceScatteringFactor), surface.subsurfaceScatteringQuality) * 255), float2(256, 1));
lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0);
lightingOutput.m_scatterDistance = surface.scatterDistance;
}
return lightingOutput;
}
ForwardPassOutputWithDepth EnhancedPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutputWithDepth OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
OUT.m_depth = depth;
return OUT;
}
[earlydepthstencil]
ForwardPassOutput EnhancedPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutput OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
OUT.m_scatterDistance = lightingOutput.m_scatterDistance;
return OUT;
}

@ -0,0 +1,41 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/ParallaxMapping.azsli>
#include "../MaterialInputs/ParallaxInput.azsli"
#include <Atom/Features/MatrixUtility.azsli>
void EnhancedSetPixelDepth(
float3 worldPosition,
float3 normal,
float3 tangents[UvSetCount],
float3 bitangents[UvSetCount],
float2 uvs[UvSetCount],
bool isFrontFace,
inout float2 detailUv[UvSetCount],
inout float depthCS,
out float depth,
out bool isClipped)
{
// GetParallaxInput applies an tangent offset to the UV. We want to apply the same offset to the detailUv (note: this needs to be tested with content)
// The math is: offset = newUv - oldUv; detailUv += offset;
// This is the same as: detailUv -= oldUv; detailUv += newUv;
detailUv[MaterialSrg::m_parallaxUvIndex] -= uvs[MaterialSrg::m_parallaxUvIndex];
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depth, depthCS, isClipped);
// Apply second part of the offset to the detail UV (see comment above)
detailUv[MaterialSrg::m_parallaxUvIndex] -= uvs[MaterialSrg::m_parallaxUvIndex];
}

@ -0,0 +1,149 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/PBR/Surfaces/EnhancedSurface.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#include "StandardGetAlpha.azsli"
void EvaluateEnhancedSurface(
float3 normal,
float2 uvs[UvSetCount],
float2 detailUvs[UvSetCount],
float3 tangents[UvSetCount],
float3 bitangents[UvSetCount],
bool isFrontFace,
bool displacementIsClipped,
inout Surface surface)
{
// ------- Detail Layer Setup -------
const float2 detailUv = detailUvs[MaterialSrg::m_detail_allMapsUvIndex];
// When the detail maps and the detail blend mask are on the same UV, they both use the transformed detail UVs because they are 'attached' to each other
const float2 detailBlendMaskUv = (MaterialSrg::m_detail_blendMask_uvIndex == MaterialSrg::m_detail_allMapsUvIndex) ?
detailUvs[MaterialSrg::m_detail_blendMask_uvIndex] :
uvs[MaterialSrg::m_detail_blendMask_uvIndex];
const float detailLayerBlendFactor = GetDetailLayerBlendFactor(
MaterialSrg::m_detail_blendMask_texture,
MaterialSrg::m_sampler,
detailBlendMaskUv,
o_detail_blendMask_useTexture,
MaterialSrg::m_detail_blendFactor);
// ------- Normal -------
float2 normalUv = uvs[MaterialSrg::m_normalMapUvIndex];
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor;
surface.normal = GetDetailedNormalInputWS(
isFrontFace, normal,
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_normalFactor, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, uvMatrix, o_normal_useTexture,
tangents[MaterialSrg::m_detail_allMapsUvIndex], bitangents[MaterialSrg::m_detail_allMapsUvIndex], MaterialSrg::m_detail_normal_texture, MaterialSrg::m_sampler, detailUv, detailLayerNormalFactor, MaterialSrg::m_detail_normal_flipX, MaterialSrg::m_detail_normal_flipY, MaterialSrg::m_detailUvMatrix, o_detail_normal_useTexture);
//--------------------- Base Color ----------------------
// [GFX TODO][ATOM-1761] Figure out how we want our base material to expect channels to be encoded, and apply that to the way we pack alpha.
float detailLayerBaseColorFactor = MaterialSrg::m_detail_baseColor_factor * detailLayerBlendFactor;
float2 baseColorUv = uvs[MaterialSrg::m_baseColorMapUvIndex];
surface.baseColor = GetDetailedBaseColorInput(
MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, o_baseColor_useTexture, MaterialSrg::m_baseColor, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode,
MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, detailUv, o_detail_baseColor_useTexture, detailLayerBaseColorFactor);
if(o_parallax_highlightClipping && displacementIsClipped)
{
ApplyParallaxClippingHighlight(surface.baseColor);
}
// ------- Metallic -------
surface.metallic = 0;
if(!o_enableSubsurfaceScattering) // If subsurface scattering is enabled skip texture lookup for metallic, as this quantity won't be used anyway
{
float2 metallicUv = uvs[MaterialSrg::m_metallicMapUvIndex];
surface.metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
}
// ------- Specular -------
float2 specularUv = uvs[MaterialSrg::m_specularF0MapUvIndex];
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
surface.SetAlbedoAndSpecularF0(specularF0Factor);
// ------- Roughness -------
float2 roughnessUv = uvs[MaterialSrg::m_roughnessMapUvIndex];
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
surface.CalculateRoughnessA();
// ------- Subsurface -------
float2 subsurfaceUv = uvs[MaterialSrg::m_subsurfaceScatteringInfluenceMapUvIndex];
surface.subsurfaceScatteringFactor = GetSubsurfaceInput(MaterialSrg::m_subsurfaceScatteringInfluenceMap, MaterialSrg::m_sampler, subsurfaceUv, MaterialSrg::m_subsurfaceScatteringFactor);
surface.subsurfaceScatteringQuality = MaterialSrg::m_subsurfaceScatteringQuality;
surface.scatterDistance = MaterialSrg::m_scatterDistance;
// ------- Transmission -------
float2 transmissionUv = uvs[MaterialSrg::m_transmissionThicknessMapUvIndex];
float4 transmissionTintThickness = GeTransmissionInput(MaterialSrg::m_transmissionThicknessMap, MaterialSrg::m_sampler, transmissionUv, MaterialSrg::m_transmissionTintThickness);
surface.transmission.tint = transmissionTintThickness.rgb;
surface.transmission.thickness = transmissionTintThickness.w;
surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams;
surface.transmission.scatterDistance = MaterialSrg::m_scatterDistance;
// ------- Anisotropy -------
if (o_enableAnisotropy)
{
// Convert the angle from [0..1] = [0 .. 180 degrees] to radians [0 .. PI]
const float anisotropyAngle = MaterialSrg::m_anisotropicAngle * PI;
const float anisotropyFactor = MaterialSrg::m_anisotropicFactor;
surface.anisotropy.Init(surface.normal, tangents[0], bitangents[0], anisotropyAngle, anisotropyFactor, surface.roughnessA);
}
// ------- Emissive -------
float2 emissiveUv = uvs[MaterialSrg::m_emissiveMapUvIndex];
surface.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
// ------- Occlusion -------
surface.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvs[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
surface.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, uvs[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
// ------- Clearcoat -------
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
if(o_clearCoat_feature_enabled)
{
if(o_clearCoat_enabled)
{
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, uvs[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
MaterialSrg::m_clearCoatRoughnessMap, uvs[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
MaterialSrg::m_clearCoatNormalMap, uvs[MaterialSrg::m_clearCoatNormalMapUvIndex], normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
MaterialSrg::m_sampler, isFrontFace,
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
}
// manipulate base layer f0 if clear coat is enabled
// modify base layer's normal incidence reflectance
// for the derivation of the following equation please refer to:
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
}
surface.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor;
}

@ -0,0 +1,95 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/PBR/Surfaces/StandardSurface.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#include "StandardGetAlpha.azsli"
void EvaluateStandardSurface(
float3 normal,
float2 uv[UvSetCount],
float3 tangents[UvSetCount],
float3 bitangents[UvSetCount],
bool isFrontFace,
bool displacementIsClipped,
inout Surface surface)
{
// ------- Normal -------
float2 normalUv = uv[MaterialSrg::m_normalMapUvIndex];
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
surface.normal = GetNormalInputWS(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, isFrontFace, normal,
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], uvMatrix, o_normal_useTexture, MaterialSrg::m_normalFactor);
// ------- Base Color -------
float2 baseColorUv = uv[MaterialSrg::m_baseColorMapUvIndex];
float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
surface.baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
if(o_parallax_highlightClipping && displacementIsClipped)
{
ApplyParallaxClippingHighlight(surface.baseColor);
}
// ------- Metallic -------
float2 metallicUv = uv[MaterialSrg::m_metallicMapUvIndex];
surface.metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
// ------- Specular -------
float2 specularUv = uv[MaterialSrg::m_specularF0MapUvIndex];
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
surface.SetAlbedoAndSpecularF0(specularF0Factor);
// ------- Roughness -------
float2 roughnessUv = uv[MaterialSrg::m_roughnessMapUvIndex];
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
surface.CalculateRoughnessA();
// ------- Emissive -------
float2 emissiveUv = uv[MaterialSrg::m_emissiveMapUvIndex];
surface.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
// ------- Occlusion -------
surface.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
surface.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, uv[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
// ------- Clearcoat -------
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
if(o_clearCoat_feature_enabled)
{
if(o_clearCoat_enabled)
{
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, uv[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
MaterialSrg::m_clearCoatRoughnessMap, uv[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
MaterialSrg::m_clearCoatNormalMap, uv[MaterialSrg::m_clearCoatNormalMapUvIndex], normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
MaterialSrg::m_sampler, isFrontFace,
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
}
// manipulate base layer f0 if clear coat is enabled
// modify base layer's normal incidence reflectance
// for the derivation of the following equation please refer to:
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
}
// ------- Opacity -------
surface.opacityAffectsSpecularFactor = MaterialSrg::m_opacityAffectsSpecularFactor;
}

@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// The built-in tangent frame evaluation forwards the tangent frame interpolanted from the vertex
// data streams for UV-index 0. For UV-index 1, the tangent frame is computed from UV surface gradients.
void EvaluateTangentFrame(
float3 normal,
float3 worldPosition,
bool isFrontFace,
float2 uv,
int uvIndex,
// The input tangent and bitangent vectors are optional and used to forward data from interpolants
float3 IN_tangent,
float3 IN_bitangent,
float3 OUT_tangent,
float3 OUT_bitangent)
{
if (DrawSrg::GetTangentAtUv(uvIndex) == 0)
{
OUT_tangent = IN_tangent;
OUT_bitangent = IN_bitangent;
}
else
{
SurfaceGradientNormalMapping_Init(normal, worldPosition, !isFrontFace); \
SurfaceGradientNormalMapping_GenerateTB(uv, OUT_tangent, OUT_bitangent); \
}
}

@ -0,0 +1,35 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/ParallaxMapping.azsli>
#include "../MaterialInputs/ParallaxInput.azsli"
#include <Atom/Features/MatrixUtility.azsli>
void MultilayerSetPixelDepth(
float blendMask,
float3 worldPosition,
float3 normal,
float3 tangents[UvSetCount],
float3 bitangents[UvSetCount],
float2 uvs[UvSetCount],
bool isFrontFace,
out float depth)
{
s_blendMaskFromVertexStream = blendMask;
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
float parallaxOverallOffset = MaterialSrg::m_displacementMax;
float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin;
GetParallaxInput(
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
parallaxOverallFactor, parallaxOverallOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depth);
}

@ -0,0 +1,51 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/ParallaxMapping.azsli>
#include "../MaterialInputs/ParallaxInput.azsli"
#include <Atom/Features/MatrixUtility.azsli>
void SetPixelDepth(
inout float3 worldPosition,
float3 normal,
float3 tangents[UvSetCount],
float3 bitangents[UvSetCount],
inout float2 uvs[UvSetCount],
bool isFrontFace,
inout float depthNDC)
{
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depthNDC);
}
void SetPixelDepth(
inout float3 worldPosition,
float3 normal,
float3 tangents[UvSetCount],
float3 bitangents[UvSetCount],
inout float2 uvs[UvSetCount],
bool isFrontFace,
inout float depthCS,
inout float depthNDC,
out bool isClipped)
{
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(
normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex],
MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
uvs[MaterialSrg::m_parallaxUvIndex], worldPosition, depthNDC, depthCS, isClipped);
}

@ -0,0 +1,17 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "../MaterialInputs/AlphaInput.azsli"
float GetAlpha(float2 uvs[UvSetCount])
{
// Alpha
float2 baseColorUV = uvs[MaterialSrg::m_baseColorMapUvIndex];
float2 opacityUV = uvs[MaterialSrg::m_opacityMapUvIndex];
return MaterialSrg::m_opacityFactor * SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
}

@ -0,0 +1,12 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
float3x3 GetNormalToWorld()
{
return ObjectSrg::GetWorldMatrixInverseTranspose();
}

@ -0,0 +1,12 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
float4x4 GetObjectToWorld()
{
return ObjectSrg::GetWorldMatrix();
}

@ -0,0 +1,14 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/PBR/AlphaUtils.azsli>
void MaybeClip(float alpha, float2 uvs[UvSetCount])
{
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
}

@ -0,0 +1,18 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
void TransformDetailUvs(in float2 IN[UvSetCount], out float2 OUT[UvSetCount])
{
// Our standard practice is to only transform the first UV as that's the one we expect to be used for
// tiling. But for detail maps you could actually use either UV stream for tiling. There is no concern about applying
// the same transform to both UV sets because the detail map feature forces the same UV set to be used for all detail maps.
// Note we might be able to combine these into a single UV similar to what Skin.materialtype does,
// but we would need to address how it works with the parallax code below that indexes into the m_detailUV array.
OUT[0] = mul(MaterialSrg::m_detailUvMatrix, float3(IN[0], 1.0)).xy;
OUT[1] = mul(MaterialSrg::m_detailUvMatrix, float3(IN[1], 1.0)).xy;
}

@ -0,0 +1,14 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
void TransformUvs(in float2 IN[UvSetCount], out float2 OUT[UvSetCount])
{
// By design, only UV0 is allowed to apply transforms.
OUT[0] = mul(MaterialSrg::m_uvMatrix, float3(IN[0], 1.0)).xy;
OUT[1] = IN[1];
}

@ -0,0 +1,127 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <scenesrg.srgi>
#include <Atom/Features/Shadow/Shadow.azsli>
struct VertexInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
#ifdef MULTILAYER
// This gets set automatically by the system at runtime only if it's available.
// There is a soft naming convention that associates this with o_blendMask_isBound, which will be set to true whenever m_optional_blendMask is available.
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
float4 m_optional_blendMask : COLOR0;
#endif
};
struct VertexOutput
{
// "centroid" is needed for SV_Depth to compile
linear centroid float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
#ifdef MULTILAYER
float3 m_blendMask : UV3;
#endif
};
VertexOutput MainVS(VertexInput IN)
{
const float4x4 objectToWorld = GetObjectToWorld();
VertexOutput OUT;
const float3 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0)).xyz;
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
float2 uv[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
TransformUvs(uv, OUT.m_uv);
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = GetNormalToWorld();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
#ifdef MULTILAYER
if(o_blendMask_isBound)
{
OUT.m_blendMask = IN.m_optional_blendMask.rgb;
}
else
{
OUT.m_blendMask = float3(0,0,0);
}
#endif
return OUT;
}
struct PSDepthOutput
{
float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent, IN.m_tangent };
float3 bitangents[UvSetCount] = { IN.m_bitangent, IN.m_bitangent };
for (int i = 0; i != UvSetCount; ++i)
{
EvaluateTangentFrame(
IN.m_normal,
IN.m_worldPosition,
isFrontFace,
IN.m_uv[i],
i,
IN.m_tangent,
IN.m_bitangent,
tangents[i],
bitangents[i]);
}
#ifdef MULTILAYER
MultilayerSetPixelDepth(IN.m_blendMask, IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth);
#else
SetPixelDepth(IN.m_worldPosition, IN.m_normal, tangents, bitangents, IN.m_uv, isFrontFace, OUT.m_depth);
#endif
OUT.m_depth += PdoShadowMapBias;
}
#ifndef MULTILAYER
float alpha = GetAlpha(IN.m_uv);
MaybeClip(alpha, IN.m_uv);
#endif
return OUT;
}

@ -21,104 +21,11 @@ COMMON_OPTIONS_PARALLAX(o_layer3_)
#include "./StandardMultilayerPBR_Common.azsli"
struct VSInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
// This gets set automatically by the system at runtime only if it's available.
// There is a soft naming convention that associates this with o_blendMask_isBound, which will be set to true whenever m_optional_blendMask is available.
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
float4 m_optional_blendMask : COLOR0;
};
struct VSDepthOutput
{
precise linear centroid float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
float3 m_blendMask : UV3;
};
VSDepthOutput MainVS(VSInput IN)
{
VSDepthOutput OUT;
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
// By design, only UV0 is allowed to apply transforms.
// Note there are additional UV transforms that happen for each layer, but we defer that step to the pixel shader to avoid bloating the vertex output buffer.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
if(o_blendMask_isBound)
{
OUT.m_blendMask = IN.m_optional_blendMask.rgb;
}
else
{
OUT.m_blendMask = float3(0,0,0);
}
return OUT;
}
struct PSDepthOutput
{
precise float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
s_blendMaskFromVertexStream = IN.m_blendMask;
float depth;
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
float parallaxOverallOffset = MaterialSrg::m_displacementMax;
float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin;
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], parallaxOverallFactor, parallaxOverallOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth);
OUT.m_depth = depth;
}
return OUT;
}
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/StandardTransformUVs.azsli"
#include "MaterialFunctions/MultilayerParallaxDepth.azsli"
#define MULTILAYER
#include "DepthPass_WithPS.azsl"

@ -450,16 +450,16 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
// ------- Combine Albedo, roughness, specular, roughness ---------
float3 baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights);
float3 specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights);
float3 metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights);
surface.baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights);
float specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights);
surface.metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights);
if(o_parallax_highlightClipping && displacementIsClipped)
{
ApplyParallaxClippingHighlight(baseColor);
ApplyParallaxClippingHighlight(surface.baseColor);
}
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
surface.SetAlbedoAndSpecularF0(specularF0Factor);
surface.roughnessLinear = BlendLayers(lightingInputLayer1.m_roughness, lightingInputLayer2.m_roughness, lightingInputLayer3.m_roughness, blendWeights);
surface.CalculateRoughnessA();

@ -5,7 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <scenesrg.srgi>
#include <viewsrg.srgi>
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
@ -21,103 +21,11 @@ COMMON_OPTIONS_PARALLAX(o_layer3_)
#include "StandardMultilayerPBR_Common.azsli"
struct VertexInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
// This gets set automatically by the system at runtime only if it's available.
// There is a soft naming convention that associates this with o_blendMask_isBound, which will be set to true whenever m_optional_blendMask is available.
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
float4 m_optional_blendMask : COLOR0;
};
struct VertexOutput
{
float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
float3 m_blendMask : UV3;
};
VertexOutput MainVS(VertexInput IN)
{
const float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
VertexOutput OUT;
const float3 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0)).xyz;
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
// By design, only UV0 is allowed to apply transforms.
// Note there are additional UV transforms that happen for each layer, but we defer that step to the pixel shader to avoid bloating the vertex output buffer.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
if(o_blendMask_isBound)
{
OUT.m_blendMask = IN.m_optional_blendMask.rgb;
}
else
{
OUT.m_blendMask = float3(0,0,0);
}
return OUT;
}
struct PSDepthOutput
{
float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
s_blendMaskFromVertexStream = IN.m_blendMask;
float depthNDC;
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
float parallaxOverallOffset = MaterialSrg::m_displacementMax;
float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin;
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], parallaxOverallFactor, parallaxOverallOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depthNDC);
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/StandardTransformUvs.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/MultilayerParallaxDepth.azsli"
OUT.m_depth = depthNDC;
}
return OUT;
}
#define MULTILAYER
#include "ShadowMap_WithPS.azsl"

@ -8,91 +8,13 @@
#include "./StandardPBR_Common.azsli"
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#include "MaterialInputs/AlphaInput.azsli"
#include "MaterialInputs/ParallaxInput.azsli"
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/StandardGetAlpha.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/StandardTransformUvs.azsli"
#include "MaterialFunctions/ParallaxDepth.azsli"
#include "MaterialFunctions/StandardMaybeClip.azsli"
struct VSInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
};
struct VSDepthOutput
{
// "centroid" is needed for SV_Depth to compile
precise linear centroid float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
};
VSDepthOutput MainVS(VSInput IN)
{
VSDepthOutput OUT;
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
// By design, only UV0 is allowed to apply transforms.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
return OUT;
}
struct PSDepthOutput
{
precise float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
}
// Alpha
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
return OUT;
}
#include "DepthPass_WithPS.azsl"

@ -10,23 +10,7 @@
#include "StandardPBR_Common.azsli"
// SRGs
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
// Pass Output
#include <Atom/Features/PBR/ForwardPassOutput.azsli>
// Utility
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Features/PBR/AlphaUtils.azsli>
// Custom Surface & Lighting
#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
// Decals
#include <Atom/Features/PBR/Decals.azsli>
// ---------- Material Parameters ----------
@ -43,320 +27,12 @@ COMMON_OPTIONS_EMISSIVE()
// Alpha
#include "MaterialInputs/AlphaInput.azsli"
// ---------- Vertex Shader ----------
struct VSInput
{
// Base fields (required by the template azsli file)...
float3 m_position : POSITION;
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
// Extended fields (only referenced in this azsl file)...
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
};
struct VSOutput
{
// Base fields (required by the template azsli file)...
// "centroid" is needed for SV_Depth to compile
precise linear centroid float4 m_position : SV_Position;
float3 m_normal: NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV3;
// Extended fields (only referenced in this azsl file)...
float2 m_uv[UvSetCount] : UV1;
};
#include <Atom/Features/Vertex/VertexHelper.azsli>
VSOutput StandardPbr_ForwardPassVS(VSInput IN)
{
VSOutput OUT;
float3 worldPosition = mul(ObjectSrg::GetWorldMatrix(), float4(IN.m_position, 1.0)).xyz;
// By design, only UV0 is allowed to apply transforms.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
// Shadow coords will be calculated in the pixel shader in this case
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
VertexHelper(IN, OUT, worldPosition, skipShadowCoords);
return OUT;
}
// ---------- Pixel Shader ----------
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC)
{
const float3 vertexNormal = normalize(IN.m_normal);
// ------- Tangents & Bitangets -------
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
if (ShouldHandleParallax() || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture))
{
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
}
// ------- Depth & Parallax -------
depthNDC = IN.m_position.z;
bool displacementIsClipped = false;
if(ShouldHandleParallax())
{
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depthNDC, IN.m_position.w, displacementIsClipped);
// Adjust directional light shadow coordinates for parallax correction
if(o_parallax_enablePixelDepthOffset)
{
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
{
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
}
}
}
Surface surface;
surface.position = IN.m_worldPosition.xyz;
// ------- Alpha & Clip -------
float2 baseColorUv = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
float2 opacityUv = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
float alpha = GetAlphaInputAndClip(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUv, opacityUv, MaterialSrg::m_sampler, MaterialSrg::m_opacityFactor, o_opacity_source);
// ------- Normal -------
float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex];
float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms.
surface.vertexNormal = vertexNormal;
surface.normal = GetNormalInputWS(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, isFrontFace, IN.m_normal,
tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], uvMatrix, o_normal_useTexture, MaterialSrg::m_normalFactor);
// ------- Base Color -------
float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
float3 baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
if(o_parallax_highlightClipping && displacementIsClipped)
{
ApplyParallaxClippingHighlight(baseColor);
}
// ------- Metallic -------
float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex];
float metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture);
// ------- Specular -------
float2 specularUv = IN.m_uv[MaterialSrg::m_specularF0MapUvIndex];
float specularF0Factor = GetSpecularInput(MaterialSrg::m_specularF0Map, MaterialSrg::m_sampler, specularUv, MaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
// ------- Roughness -------
float2 roughnessUv = IN.m_uv[MaterialSrg::m_roughnessMapUvIndex];
surface.roughnessLinear = GetRoughnessInput(MaterialSrg::m_roughnessMap, MaterialSrg::m_sampler, roughnessUv, MaterialSrg::m_roughnessFactor,
MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture);
surface.CalculateRoughnessA();
// ------- Lighting Data -------
LightingData lightingData;
// Light iterator
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
// Directional light shadow coordinates
lightingData.shadowCoords = IN.m_shadowCoords;
// ------- Emissive -------
float2 emissiveUv = IN.m_uv[MaterialSrg::m_emissiveMapUvIndex];
lightingData.emissiveLighting = GetEmissiveInput(MaterialSrg::m_emissiveMap, MaterialSrg::m_sampler, emissiveUv, MaterialSrg::m_emissiveIntensity, MaterialSrg::m_emissiveColor.rgb, o_emissiveEnabled, o_emissive_useTexture);
// ------- Occlusion -------
lightingData.diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_diffuseOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_diffuseOcclusionMapUvIndex], MaterialSrg::m_diffuseOcclusionFactor, o_diffuseOcclusion_useTexture);
lightingData.specularOcclusion = GetOcclusionInput(MaterialSrg::m_specularOcclusionMap, MaterialSrg::m_sampler, IN.m_uv[MaterialSrg::m_specularOcclusionMapUvIndex], MaterialSrg::m_specularOcclusionFactor, o_specularOcclusion_useTexture);
// ------- Clearcoat -------
// [GFX TODO][ATOM-14603]: Clean up the double uses of these clear coat flags
if(o_clearCoat_feature_enabled)
{
if(o_clearCoat_enabled)
{
float3x3 uvMatrix = MaterialSrg::m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
GetClearCoatInputs(MaterialSrg::m_clearCoatInfluenceMap, IN.m_uv[MaterialSrg::m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_clearCoatFactor, o_clearCoat_factor_useTexture,
MaterialSrg::m_clearCoatRoughnessMap, IN.m_uv[MaterialSrg::m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_clearCoatRoughness, o_clearCoat_roughness_useTexture,
MaterialSrg::m_clearCoatNormalMap, IN.m_uv[MaterialSrg::m_clearCoatNormalMapUvIndex], IN.m_normal, o_clearCoat_normal_useTexture, MaterialSrg::m_clearCoatNormalStrength,
uvMatrix, tangents[MaterialSrg::m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_clearCoatNormalMapUvIndex],
MaterialSrg::m_sampler, isFrontFace,
surface.clearCoat.factor, surface.clearCoat.roughness, surface.clearCoat.normal);
}
// manipulate base layer f0 if clear coat is enabled
// modify base layer's normal incidence reflectance
// for the derivation of the following equation please refer to:
// https://google.github.io/filament/Filament.md.html#materialsystem/clearcoatmodel/baselayermodification
float3 f0 = (1.0 - 5.0 * sqrt(surface.specularF0)) / (5.0 - sqrt(surface.specularF0));
surface.specularF0 = lerp(surface.specularF0, f0 * f0, surface.clearCoat.factor);
}
// Diffuse and Specular response (used in IBL calculations)
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
if(o_clearCoat_feature_enabled)
{
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
}
// ------- Multiscatter -------
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
// ------- Lighting Calculation -------
// Apply Decals
ApplyDecals(lightingData.tileIterator, surface);
// Apply Direct Lighting
ApplyDirectLighting(surface, lightingData);
// Apply Image Based Lighting (IBL)
ApplyIBL(surface, lightingData);
// Finalize Lighting
lightingData.FinalizeLighting();
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
// ------- Opacity -------
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
{
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
// not apply.
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
alpha = lerp(fresnelAlpha, alpha, MaterialSrg::m_opacityAffectsSpecularFactor);
}
if (o_opacity_mode == OpacityMode::Blended)
{
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_diffuseColor.w = alpha;
}
else if (o_opacity_mode == OpacityMode::TintedTransparent)
{
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
// absorption, and interior color to be specified.
//
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
//
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, MaterialSrg::m_opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_specularColor.rgb = baseColor * (1.0 - alpha);
}
else
{
lightingOutput.m_diffuseColor.w = -1; // Disable subsurface scattering
}
return lightingOutput;
}
ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutputWithDepth OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
#ifdef UNIFIED_FORWARD_OUTPUT
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
OUT.m_depth = depth;
#else
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
OUT.m_depth = depth;
#endif
return OUT;
}
[earlydepthstencil]
ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutput OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
#include "MaterialFunctions/EvaluateStandardSurface.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/ParallaxDepth.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardMaybeClip.azsli"
#include "MaterialFunctions/StandardTransformUvs.azsli"
#ifdef UNIFIED_FORWARD_OUTPUT
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
#else
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
#endif
return OUT;
}
#include "StandardSurface_ForwardPass.azsl"

@ -9,93 +9,16 @@
#include <scenesrg.srgi>
#include "StandardPBR_Common.azsli"
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#include <Atom/Features/Shadow/Shadow.azsli>
#include "MaterialInputs/AlphaInput.azsli"
#include "MaterialInputs/ParallaxInput.azsli"
struct VertexInput
{
float3 m_position : POSITION;
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
#include "MaterialFunctions/StandardGetObjectToWorld.azsli"
#include "MaterialFunctions/StandardGetNormalToWorld.azsli"
#include "MaterialFunctions/StandardGetAlpha.azsli"
#include "MaterialFunctions/StandardTransformUvs.azsli"
#include "MaterialFunctions/EvaluateTangentFrame.azsli"
#include "MaterialFunctions/ParallaxDepth.azsli"
#include "MaterialFunctions/StandardMaybeClip.azsli"
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
};
struct VertexOutput
{
// "centroid" is needed for SV_Depth to compile
linear centroid float4 m_position : SV_Position;
float2 m_uv[UvSetCount] : UV1;
// only used for parallax depth calculation
float3 m_normal : NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
};
VertexOutput MainVS(VertexInput IN)
{
const float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
VertexOutput OUT;
const float3 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0)).xyz;
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
// By design, only UV0 is allowed to apply transforms.
OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy;
OUT.m_uv[1] = IN.m_uv1;
if(ShouldHandleParallaxInDepthShaders())
{
OUT.m_worldPosition = worldPosition.xyz;
float3x3 objectToWorldIT = ObjectSrg::GetWorldMatrixInverseTranspose();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
}
return OUT;
}
struct PSDepthOutput
{
float m_depth : SV_Depth;
};
PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
{
PSDepthOutput OUT;
OUT.m_depth = IN.m_position.z;
if(ShouldHandleParallaxInDepthShaders())
{
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents);
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
OUT.m_depth += PdoShadowMapBias;
}
// Alpha
float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex];
float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex];
float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source);
CheckClipping(alpha, MaterialSrg::m_opacityFactor);
return OUT;
}
#include "ShadowMap_WithPS.azsl"

@ -0,0 +1,306 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
// Pass Output
#include <Atom/Features/PBR/ForwardPassOutput.azsli>
// Utility
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Features/PBR/AlphaUtils.azsli>
// Custom Surface & Lighting
#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
// Decals
#include <Atom/Features/PBR/Decals.azsli>
// ---------- Vertex Shader ----------
struct VSInput
{
// Base fields (required by the template azsli file)...
float3 m_position : POSITION;
float3 m_normal : NORMAL;
float4 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
// Extended fields (only referenced in this azsl file)...
float2 m_uv0 : UV0;
float2 m_uv1 : UV1;
};
struct VSOutput
{
// Base fields (required by the template azsli file)...
// "centroid" is needed for SV_Depth to compile
precise linear centroid float4 m_position : SV_Position;
float3 m_normal: NORMAL;
float3 m_tangent : TANGENT;
float3 m_bitangent : BITANGENT;
float3 m_worldPosition : UV0;
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV3;
// Extended fields (only referenced in this azsl file)...
float2 m_uv[UvSetCount] : UV1;
};
#include <Atom/Features/Vertex/VertexHelper.azsli>
VSOutput StandardPbr_ForwardPassVS(VSInput IN)
{
VSOutput OUT;
float4x4 objectToWorld = GetObjectToWorld();
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
OUT.m_worldPosition = worldPosition.xyz;
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
float2 uvs[UvSetCount] = { IN.m_uv0, IN.m_uv1 };
TransformUvs(uvs, OUT.m_uv);
// Shadow coords will be calculated in the pixel shader in this case
bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset;
float3x3 objectToWorldIT = GetNormalToWorld();
ConstructTBN(IN.m_normal, IN.m_tangent, IN.m_bitangent, objectToWorld, objectToWorldIT, OUT.m_normal, OUT.m_tangent, OUT.m_bitangent);
// directional light shadow
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
if (o_enableShadows && !skipShadowCoords && shadowIndex < SceneSrg::m_directionalLightCount)
{
DirectionalLightShadow::GetShadowCoords(
shadowIndex,
worldPosition,
OUT.m_normal,
OUT.m_shadowCoords);
}
return OUT;
}
// ---------- Pixel Shader ----------
PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC)
{
const float3 vertexNormal = normalize(IN.m_normal);
// ------- Tangents & Bitangents -------
float3 tangents[UvSetCount] = { IN.m_tangent, IN.m_tangent };
float3 bitangents[UvSetCount] = { IN.m_bitangent, IN.m_bitangent };
if (ShouldHandleParallax() || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture))
{
for (int i = 0; i != UvSetCount; ++i)
{
EvaluateTangentFrame(
IN.m_normal,
IN.m_worldPosition,
isFrontFace,
IN.m_uv[i],
i,
IN.m_tangent,
IN.m_bitangent,
tangents[i],
bitangents[i]);
}
}
// ------- Depth & Parallax -------
depthNDC = IN.m_position.z;
bool displacementIsClipped = false;
if(ShouldHandleParallax())
{
SetPixelDepth(
IN.m_worldPosition,
IN.m_normal,
tangents,
bitangents,
IN.m_uv,
isFrontFace,
IN.m_position.w,
depthNDC,
displacementIsClipped);
// Adjust directional light shadow coordinates for parallax correction
if(o_parallax_enablePixelDepthOffset)
{
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
{
DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords);
}
}
}
Surface surface;
surface.vertexNormal = vertexNormal;
surface.position = IN.m_worldPosition.xyz;
// ------- Alpha & Clip -------
// TODO: this often invokes a separate sample of the base color texture which is wasteful
float alpha = GetAlpha(IN.m_uv);
MaybeClip(alpha, IN.m_uv);
EvaluateStandardSurface(IN.m_normal, IN.m_uv, tangents, bitangents, isFrontFace, displacementIsClipped, surface);
// ------- Lighting Data -------
LightingData lightingData;
// Light iterator
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
// Directional light shadow coordinates
lightingData.shadowCoords = IN.m_shadowCoords;
// Surface lighting properties
lightingData.emissiveLighting = surface.emissiveLighting;
lightingData.diffuseAmbientOcclusion = surface.diffuseAmbientOcclusion;
lightingData.specularOcclusion = surface.specularOcclusion;
// Diffuse and Specular response (used in IBL calculations)
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
lightingData.diffuseResponse = 1.0 - lightingData.specularResponse;
if(o_clearCoat_feature_enabled)
{
// Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04
lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor);
}
// ------- Multiscatter -------
lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation);
// ------- Lighting Calculation -------
// Apply Decals
ApplyDecals(lightingData.tileIterator, surface);
// Apply Direct Lighting
ApplyDirectLighting(surface, lightingData);
// Apply Image Based Lighting (IBL)
ApplyIBL(surface, lightingData);
// Finalize Lighting
lightingData.FinalizeLighting();
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
// ------- Opacity -------
if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent)
{
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
// not apply.
float fresnelAlpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x;
alpha = lerp(fresnelAlpha, alpha, surface.opacityAffectsSpecularFactor);
}
if (o_opacity_mode == OpacityMode::Blended)
{
// [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when
// specular is being added to diffuse just because we're calling render target 0 "diffuse".
// For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular
// to be added on top of the diffuse, but then the diffuse must be pre-multiplied.
// It's done this way because surface transparency doesn't really change specular response (eg, glass).
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surface.opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_diffuseColor.w = alpha;
}
else if (o_opacity_mode == OpacityMode::TintedTransparent)
{
// See OpacityMode::Blended above for the basic method. TintedTransparent adds onto the above concept by supporting
// colored alpha. This is currently a very basic calculation that uses the baseColor as a multiplier with strength
// determined by the alpha. We'll modify this later to be more physically accurate and allow surface depth,
// absorption, and interior color to be specified.
//
// The technique uses dual source blending to allow two separate sources to be part of the blending equation
// even though ultimately only a single render target is being written to. m_diffuseColor is render target 0 and
// m_specularColor render target 1, and the blend mode is (dest * source1color) + (source * 1.0).
//
// This means that m_specularColor.rgb (source 1) is multiplied against the destination, then
// m_diffuseColor.rgb (source) is added to that, and the final result is stored in render target 0.
lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse
// Add specular. m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
float3 specular = lightingOutput.m_specularColor.rgb;
specular = lerp(specular, specular * lightingOutput.m_diffuseColor.w, surface.opacityAffectsSpecularFactor);
lightingOutput.m_diffuseColor.rgb += specular;
lightingOutput.m_specularColor.rgb = surface.baseColor * (1.0 - alpha);
}
else
{
lightingOutput.m_diffuseColor.w = -1; // Disable subsurface scattering
}
return lightingOutput;
}
ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutputWithDepth OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
#ifdef UNIFIED_FORWARD_OUTPUT
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
OUT.m_depth = depth;
#else
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
OUT.m_depth = depth;
#endif
return OUT;
}
[earlydepthstencil]
ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : SV_IsFrontFace)
{
ForwardPassOutput OUT;
float depth;
PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth);
#ifdef UNIFIED_FORWARD_OUTPUT
OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb;
OUT.m_color.a = lightingOutput.m_diffuseColor.a;
#else
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
OUT.m_specularColor = lightingOutput.m_specularColor;
OUT.m_specularF0 = lightingOutput.m_specularF0;
OUT.m_albedo = lightingOutput.m_albedo;
OUT.m_normal = lightingOutput.m_normal;
#endif
return OUT;
}

@ -24,12 +24,31 @@ class Surface
float3 position; //!< Position in world-space
float3 normal; //!< Normal in world-space
float3 vertexNormal; //!< Vertex normal in world-space
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
float3 specularF0; //!< Fresnel f0 spectral value of the surface
float3 baseColor; //!< Surface base color
float3 metallic; //!< Surface metallic property
float roughnessLinear; //!< Perceptually linear roughness value authored by artists. Must be remapped to roughnessA before use
float roughnessA; //!< Actual roughness value ( a.k.a. "alpha roughness") to be used in microfacet calculations
float roughnessA2; //!< Alpha roughness ^ 2 (i.e. roughnessA * roughnessA), used in GGX, cached here for perfromance
//! Subsurface scattering parameters
float subsurfaceScatteringFactor;
float subsurfaceScatteringQuality;
float3 scatterDistance;
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
// not apply.
float opacityAffectsSpecularFactor;
//! Surface lighting inputs
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
float3 specularF0; //!< Fresnel f0 spectral value of the surface
float3 emissiveLighting; //!< Emissive lighting
float diffuseAmbientOcclusion; //!< Diffuse ambient occlusion factor - [0, 1] :: [Dark, Bright]
float specularOcclusion; //!< Specular occlusion factor - [0, 1] :: [Dark, Bright]
//! Applies specular anti-aliasing to roughnessA2
void ApplySpecularAA();
@ -37,7 +56,7 @@ class Surface
void CalculateRoughnessA();
//! Sets albedo and specularF0 using metallic workflow
void SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic);
void SetAlbedoAndSpecularF0(float specularF0Factor);
};
@ -76,7 +95,7 @@ void Surface::CalculateRoughnessA()
}
}
void Surface::SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic)
void Surface::SetAlbedoAndSpecularF0(float specularF0Factor)
{
float3 dielectricSpecularF0 = MaxDielectricSpecularF0 * specularF0Factor;

@ -25,12 +25,26 @@ class Surface
precise float3 position; //!< Position in world-space
float3 normal; //!< Normal in world-space
float3 vertexNormal; //!< Vertex normal in world-space
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
float3 specularF0; //!< Fresnel f0 spectral value of the surface
float3 baseColor; //!< Surface base color
float metallic; //!< Surface metallic property
float roughnessLinear; //!< Perceptually linear roughness value authored by artists. Must be remapped to roughnessA before use
float roughnessA; //!< Actual roughness value ( a.k.a. "alpha roughness") to be used in microfacet calculations
float roughnessA2; //!< Alpha roughness ^ 2 (i.e. roughnessA * roughnessA), used in GGX, cached here for perfromance
// Increase opacity at grazing angles for surfaces with a low m_opacityAffectsSpecularFactor.
// For m_opacityAffectsSpecularFactor values close to 0, that indicates a transparent surface
// like glass, so it becomes less transparent at grazing angles. For m_opacityAffectsSpecularFactor
// values close to 1.0, that indicates the absence of a surface entirely, so this effect should
// not apply.
float opacityAffectsSpecularFactor;
//! Surface lighting data
float3 albedo; //!< Albedo color of the non-metallic material, will be multiplied against the diffuse lighting value
float3 specularF0; //!< Fresnel f0 spectral value of the surface
float3 emissiveLighting; //!< Emissive lighting
float diffuseAmbientOcclusion; //!< Diffuse ambient occlusion factor - [0, 1] :: [Dark, Bright]
float specularOcclusion; //!< Specular occlusion factor - [0, 1] :: [Dark, Bright]
//! Applies specular anti-aliasing to roughnessA2
void ApplySpecularAA();
@ -38,7 +52,7 @@ class Surface
void CalculateRoughnessA();
//! Sets albedo and specularF0 using metallic workflow
void SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic);
void SetAlbedoAndSpecularF0(float specularF0Factor);
};
// Specular Anti-Aliasing technique from this paper:
@ -75,7 +89,7 @@ void Surface::CalculateRoughnessA()
}
}
void Surface::SetAlbedoAndSpecularF0(float3 baseColor, float specularF0Factor, float metallic)
void Surface::SetAlbedoAndSpecularF0(float specularF0Factor)
{
float3 dielectricSpecularF0 = MaxDielectricSpecularF0 * specularF0Factor;

@ -144,18 +144,20 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN)
identityUvMatrix);
IN.m_uv += tangentOffset.m_offsetTS.xy;
Surface surface;
float3 baseColor = float3(1,1,1);
surface.baseColor = float3(1,1,1);
const float noise = AutoBrickSrg::m_noise.Sample(AutoBrickSrg::m_sampler, IN.m_uv).r;
float distanceFromBrick = GetNormalizedDistanceFromBrick(IN.m_uv);
if(distanceFromBrick > AutoBrickSrg::m_brickColorBleed)
{
baseColor = AutoBrickSrg::m_lineColor * lerp(1.0, noise, AutoBrickSrg::m_lineNoiseFactor);
surface.baseColor = AutoBrickSrg::m_lineColor * lerp(1.0, noise, AutoBrickSrg::m_lineNoiseFactor);
}
else
{
baseColor = AutoBrickSrg::m_brickColor * lerp(1.0, noise, AutoBrickSrg::m_brickNoiseFactor);
surface.baseColor = AutoBrickSrg::m_brickColor * lerp(1.0, noise, AutoBrickSrg::m_brickNoiseFactor);
}
float surfaceDepth;
@ -164,8 +166,6 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN)
const float3 normal = TangentSpaceToWorld(surfaceNormal, normalize(IN.m_normal), normalize(IN.m_tangent), normalize(IN.m_bitangent));
// ------- Surface -------
Surface surface;
// Position, Normal, Roughness
surface.position = IN.m_worldPosition.xyz;
@ -175,9 +175,9 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN)
surface.CalculateRoughnessA();
// Albedo, SpecularF0
const float metallic = 0.0f;
const float specularF0Factor = 0.5f;
surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
surface.metallic = 0.0f;
float specularF0Factor = 0.5f;
surface.SetAlbedoAndSpecularF0(specularF0Factor);
// Clear Coat
surface.clearCoat.InitializeToZero();

@ -68,8 +68,10 @@ ForwardPassOutput MinimalPBR_MainPassPS(VSOutput IN)
surface.CalculateRoughnessA();
// Albedo, SpecularF0
const float specularF0Factor = 0.5f;
surface.SetAlbedoAndSpecularF0(MinimalPBRSrg::m_baseColor, specularF0Factor, MinimalPBRSrg::m_metallic);
surface.baseColor = MinimalPBRSrg::m_baseColor;
surface.metallic = MinimalPBRSrg::m_metallic;
float specularF0Factor = 0.5f;
surface.SetAlbedoAndSpecularF0(specularF0Factor);
// Clear Coat
surface.clearCoat.InitializeToZero();

Loading…
Cancel
Save