Parcourir la source

Merge pull request #18946 from antonmic/Anton/MultiViewSpecular

Multi View Shading
antonmic il y a 1 mois
Parent
commit
7a60fe2413
38 fichiers modifiés avec 943 ajouts et 554 suppressions
  1. 149 0
      Gems/Atom/Feature/Common/Assets/LightingPresets/HighContrast/goegap_4k_skyboxcm.exr.assetinfo
  2. 5 2
      Gems/Atom/Feature/Common/Assets/Materials/Pipelines/Common/TransparentPassVertexAndPixel.azsli
  3. 5 2
      Gems/Atom/Feature/Common/Assets/Materials/Pipelines/LowEndPipeline/LowEndForwardPassVertexAndPixel.azsli
  4. 5 2
      Gems/Atom/Feature/Common/Assets/Materials/Pipelines/MainPipeline/ForwardPassVertexAndPixel.azsli
  5. 10 2
      Gems/Atom/Feature/Common/Assets/Materials/Pipelines/Mobile/MobileForwardPassVertexAndPixel.azsli
  6. 6 3
      Gems/Atom/Feature/Common/Assets/Materials/Pipelines/MultiView/MultiViewForwardPassVertexAndPixel.azsli
  7. 25 1
      Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.azsli
  8. 2 0
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/InstancedTransforms.azsli
  9. 10 11
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli
  10. 44 25
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli
  11. 11 5
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/StandardLighting.azsli
  12. 108 98
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/CapsuleLight.azsli
  13. 31 18
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DirectionalLight.azsli
  14. 67 48
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DiskLight.azsli
  15. 53 48
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli
  16. 11 3
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/LightTypesCommon.azsli
  17. 8 1
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Lights.azsli
  18. 33 30
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ltc.azsli
  19. 46 31
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli
  20. 22 11
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PolygonLight.azsli
  21. 73 58
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/QuadLight.azsli
  22. 15 10
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/SimplePointLight.azsli
  23. 18 13
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/SimpleSpotLight.azsli
  24. 1 1
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Microfacet/Brdf.azsli
  25. 5 5
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/BasePBR/BasePBR_LightingBrdf.azsli
  26. 45 26
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/BasePBR/BasePBR_LightingData.azsli
  27. 5 5
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/BasePBR/BasePBR_LightingEval.azsli
  28. 11 11
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/EnhancedPBR/EnhancedPBR_LightingBrdf.azsli
  29. 6 7
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/Skin/Skin_LightingBrdf.azsli
  30. 2 2
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/Skin/Skin_LightingEval.azsli
  31. 8 8
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/StandardPBR/StandardPBR_LightingBrdf.azsli
  32. 2 2
      Gems/Atom/Feature/Common/Assets/Shaders/Materials/StandardPBR/StandardPBR_LightingEval.azsli
  33. 5 2
      Gems/AtomContent/TestData/Assets/TestData/Materials/Types/AutoBrick_ForwardPass.azsl
  34. 5 2
      Gems/AtomContent/TestData/Assets/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl
  35. 71 54
      Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli
  36. 13 3
      Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli
  37. 2 2
      Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli
  38. 5 2
      Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl

+ 149 - 0
Gems/Atom/Feature/Common/Assets/LightingPresets/HighContrast/goegap_4k_skyboxcm.exr.assetinfo

@@ -0,0 +1,149 @@
+<ObjectStream version="3">
+	<Class name="TextureSettings" version="2" type="{980132FF-C450-425D-8AE0-BD96A8486177}">
+		<Class name="AZ::Uuid" field="PresetID" value="{00000000-0000-0000-0000-000000000000}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+		<Class name="Name" field="Preset" value="IBLSkybox" type="{3D2B920C-9EFD-40D5-AAE0-DF131C3D4931}"/>
+		<Class name="unsigned int" field="SizeReduceLevel" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+		<Class name="bool" field="EngineReduce" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
+		<Class name="bool" field="EnableMipmap" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
+		<Class name="unsigned int" field="MipMapGenEval" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+		<Class name="ImageProcessingAtom::MipGenType" field="MipMapGenType" value="5" type="{8524F650-1417-44DA-BBB0-C707A7A1A709}"/>
+		<Class name="bool" field="MaintainAlphaCoverage" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
+		<Class name="AZStd::vector&lt;unsigned int, allocator&gt;" field="MipMapAlphaAdjustments" type="{3349AACD-BE04-50BC-9478-528BF2ACFD55}">
+			<Class name="unsigned int" field="element" value="50" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+			<Class name="unsigned int" field="element" value="50" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+			<Class name="unsigned int" field="element" value="50" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+			<Class name="unsigned int" field="element" value="50" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+			<Class name="unsigned int" field="element" value="50" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+			<Class name="unsigned int" field="element" value="50" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+		</Class>
+		<Class name="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;" field="PlatformSpecificOverrides" type="{74E4843B-0924-583D-8C6E-A37B09BD51FE}">
+			<Class name="AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;" field="element" type="{CAC4E67F-D626-5452-A057-ACB57D53F549}">
+				<Class name="AZStd::string" field="value1" value="android" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+				<Class name="DataPatch" field="value2" type="{BFF7A3F5-9014-4000-92C7-9B2BC7913DA9}">
+					<Class name="AZ::Uuid" field="m_targetClassId" value="{980132FF-C450-425D-8AE0-BD96A8486177}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+					<Class name="unsigned int" field="m_targetClassVersion" value="2" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+					<Class name="AZStd::unordered_map&lt;AddressType, AZStd::any, AZStd::hash&lt;AddressType&gt;, AZStd::equal_to&lt;AddressType&gt;, allocator&gt;" field="m_patch" type="{CEA836FC-77E0-5E46-BD0F-2E5A39D845E9}">
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#4·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#2·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#3·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#0·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#1·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+					</Class>
+				</Class>
+			</Class>
+			<Class name="AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;" field="element" type="{CAC4E67F-D626-5452-A057-ACB57D53F549}">
+				<Class name="AZStd::string" field="value1" value="ios" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+				<Class name="DataPatch" field="value2" type="{BFF7A3F5-9014-4000-92C7-9B2BC7913DA9}">
+					<Class name="AZ::Uuid" field="m_targetClassId" value="{980132FF-C450-425D-8AE0-BD96A8486177}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+					<Class name="unsigned int" field="m_targetClassVersion" value="2" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+					<Class name="AZStd::unordered_map&lt;AddressType, AZStd::any, AZStd::hash&lt;AddressType&gt;, AZStd::equal_to&lt;AddressType&gt;, allocator&gt;" field="m_patch" type="{CEA836FC-77E0-5E46-BD0F-2E5A39D845E9}">
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#4·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#2·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#3·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#0·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#1·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+					</Class>
+				</Class>
+			</Class>
+			<Class name="AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;" field="element" type="{CAC4E67F-D626-5452-A057-ACB57D53F549}">
+				<Class name="AZStd::string" field="value1" value="linux" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+				<Class name="DataPatch" field="value2" type="{BFF7A3F5-9014-4000-92C7-9B2BC7913DA9}">
+					<Class name="AZ::Uuid" field="m_targetClassId" value="{980132FF-C450-425D-8AE0-BD96A8486177}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+					<Class name="unsigned int" field="m_targetClassVersion" value="2" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+					<Class name="AZStd::unordered_map&lt;AddressType, AZStd::any, AZStd::hash&lt;AddressType&gt;, AZStd::equal_to&lt;AddressType&gt;, allocator&gt;" field="m_patch" type="{CEA836FC-77E0-5E46-BD0F-2E5A39D845E9}">
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#4·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#2·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#3·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#0·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#1·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+					</Class>
+				</Class>
+			</Class>
+			<Class name="AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;" field="element" type="{CAC4E67F-D626-5452-A057-ACB57D53F549}">
+				<Class name="AZStd::string" field="value1" value="mac" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+				<Class name="DataPatch" field="value2" type="{BFF7A3F5-9014-4000-92C7-9B2BC7913DA9}">
+					<Class name="AZ::Uuid" field="m_targetClassId" value="{980132FF-C450-425D-8AE0-BD96A8486177}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+					<Class name="unsigned int" field="m_targetClassVersion" value="2" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+					<Class name="AZStd::unordered_map&lt;AddressType, AZStd::any, AZStd::hash&lt;AddressType&gt;, AZStd::equal_to&lt;AddressType&gt;, allocator&gt;" field="m_patch" type="{CEA836FC-77E0-5E46-BD0F-2E5A39D845E9}">
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#4·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#2·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#3·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#0·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+						<Class name="AZStd::pair&lt;AddressType, AZStd::any&gt;" field="element" type="{FED51EB4-F646-51FF-9646-9852CF90F353}">
+							<Class name="AddressType" field="value1" value="AZStd::map&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;({74E4843B-0924-583D-8C6E-A37B09BD51FE})::PlatformSpecificOverrides·0/AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;({CAC4E67F-D626-5452-A057-ACB57D53F549})#1·0/" version="1" type="{90752F2D-CBD3-4EE9-9CDD-447E797C8408}"/>
+							<Class name="AZStd::any" field="value2" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}"/>
+						</Class>
+					</Class>
+				</Class>
+			</Class>
+			<Class name="AZStd::pair&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, DataPatch&gt;" field="element" type="{CAC4E67F-D626-5452-A057-ACB57D53F549}">
+				<Class name="AZStd::string" field="value1" value="pc" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+				<Class name="DataPatch" field="value2" type="{BFF7A3F5-9014-4000-92C7-9B2BC7913DA9}">
+					<Class name="AZ::Uuid" field="m_targetClassId" value="{980132FF-C450-425D-8AE0-BD96A8486177}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+					<Class name="unsigned int" field="m_targetClassVersion" value="2" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
+					<Class name="AZStd::unordered_map&lt;AddressType, AZStd::any, AZStd::hash&lt;AddressType&gt;, AZStd::equal_to&lt;AddressType&gt;, allocator&gt;" field="m_patch" type="{CEA836FC-77E0-5E46-BD0F-2E5A39D845E9}"/>
+				</Class>
+			</Class>
+		</Class>
+		<Class name="AZStd::string" field="OverridingPlatform" value="" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+		<Class name="AZStd::set&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;, AZStd::less&lt;AZStd::basic_string&lt;char, AZStd::char_traits&lt;char&gt;, allocator&gt;&gt;, allocator&gt;" field="Tags" type="{166F208E-DE97-53FE-B349-BDD9FE9B8693}"/>
+	</Class>
+</ObjectStream>
+

+ 5 - 2
Gems/Atom/Feature/Common/Assets/Materials/Pipelines/Common/TransparentPassVertexAndPixel.azsli

@@ -29,13 +29,16 @@ VsOutput VertexShader(VsInput IN, uint instanceId : SV_InstanceID)
 
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+
     // ------- Geometry -> Surface -> Lighting -------
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
     Surface surface = EvaluateSurface(IN, geoData, GetMaterialParameters());
 
-    LightingData lightingData = EvaluateLighting(surface, IN.position, ViewSrg::m_worldPosition.xyz);
+    LightingData lightingData = EvaluateLighting(surface, IN.position, views);
 
     // ------- Output -------
 
@@ -51,7 +54,7 @@ ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
     diffuse += lightingData.emissiveLighting;
 
     // m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
-    float3 specular = lightingData.specularLighting.rgb;
+    float3 specular = lightingData.specularLighting[0].rgb;
     specular = lerp(specular, specular * surface.alpha, surface.opacityAffectsSpecularFactor);
 
     OUT.m_color1.rgb = diffuse + specular;

+ 5 - 2
Gems/Atom/Feature/Common/Assets/Materials/Pipelines/LowEndPipeline/LowEndForwardPassVertexAndPixel.azsli

@@ -28,19 +28,22 @@ VsOutput VertexShader(VsInput IN, uint instanceId : SV_InstanceID)
 #endif
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+    
     // ------- Geometry -> Surface -> Lighting -------
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
     Surface surface = EvaluateSurface(IN, geoData, GetMaterialParameters());
 
-    LightingData lightingData = EvaluateLighting(surface, IN.position, ViewSrg::m_worldPosition.xyz);
+    LightingData lightingData = EvaluateLighting(surface, IN.position, views);
 
     // ------- Output -------
 
     ForwardPassOutput OUT;
 
-    OUT.m_color.rgb = lightingData.diffuseLighting.rgb + lightingData.specularLighting.rgb + lightingData.emissiveLighting.rgb;
+    OUT.m_color.rgb = lightingData.diffuseLighting.rgb + lightingData.specularLighting[0].rgb + lightingData.emissiveLighting.rgb;
     OUT.m_color.a = 1.0;
 
 #if OUTPUT_DEPTH

+ 5 - 2
Gems/Atom/Feature/Common/Assets/Materials/Pipelines/MainPipeline/ForwardPassVertexAndPixel.azsli

@@ -44,13 +44,16 @@ float GetSubsurfaceScatteringFactorAndQuality(Surface surface)
 
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+
     // ------- Geometry -> Surface -> Lighting -------
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
     Surface surface = EvaluateSurface(IN, geoData, GetMaterialParameters());
 
-    LightingData lightingData = EvaluateLighting(surface, IN.position, ViewSrg::m_worldPosition.xyz);
+    LightingData lightingData = EvaluateLighting(surface, IN.position, views);
 
     // ------- Output -------
 
@@ -63,7 +66,7 @@ ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 
     // --- Specular Lighting ---
 
-    OUT.m_specularColor.rgb = lightingData.specularLighting.rgb;
+    OUT.m_specularColor.rgb = lightingData.specularLighting[0].rgb;
     OUT.m_specularColor.a = 1.0;
 
     // --- Roughness and Specular ---

+ 10 - 2
Gems/Atom/Feature/Common/Assets/Materials/Pipelines/Mobile/MobileForwardPassVertexAndPixel.azsli

@@ -28,19 +28,27 @@ VsOutput VertexShader(VsInput IN, uint instanceId : SV_InstanceID)
 #endif
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+
     // ------- Geometry -> Surface -> Lighting -------
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
     Surface surface = EvaluateSurface(IN, geoData, GetMaterialParameters());
 
-    LightingData lightingData = EvaluateLighting(surface, IN.position, ViewSrg::m_worldPosition.xyz);
+    LightingData lightingData = EvaluateLighting(surface, IN.position, views);
 
     // ------- Output -------
 
     ForwardPassOutput OUT;
 
-    real3 color = lightingData.diffuseLighting.rgb + lightingData.specularLighting.rgb + lightingData.emissiveLighting.rgb;
+    real3 color = lightingData.diffuseLighting.rgb + lightingData.emissiveLighting.rgb;
+
+    for(uint i = 0; i < GET_SHADING_VIEW_COUNT; ++i)
+    {
+        color += lightingData.specularLighting[i];
+    }
 
 #if ENABLE_MERGE_FILMIC_TONEMAP
     // Apply manual exposure compensation

+ 6 - 3
Gems/Atom/Feature/Common/Assets/Materials/Pipelines/MultiView/MultiViewForwardPassVertexAndPixel.azsli

@@ -28,19 +28,22 @@ VsOutput VertexShader(VsInput IN, uint instanceId : SV_InstanceID)
 #endif
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+    
     // ------- Geometry -> Surface -> Lighting -------
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
     Surface surface = EvaluateSurface(IN, geoData, GetMaterialParameters());
 
-    LightingData lightingData = EvaluateLighting(surface, IN.position, ViewSrg::m_worldPosition.xyz);
+    LightingData lightingData = EvaluateLighting(surface, IN.position, views);
 
     // ------- Output -------
 
     ForwardPassOutput OUT;
 
-    real3 color = lightingData.diffuseLighting.rgb + lightingData.specularLighting.rgb + lightingData.emissiveLighting.rgb;
+    real3 color = lightingData.diffuseLighting.rgb + lightingData.specularLighting[0].rgb + lightingData.emissiveLighting.rgb;
 
 #if ENABLE_MERGE_FILMIC_TONEMAP
     // Apply manual exposure compensation
@@ -51,6 +54,7 @@ ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 
     // We could add other forms of tonemapping in future via shader options.
     color = TonemapFilmic(color);
+
 #endif
 
     OUT.m_color.rgb = color;
@@ -62,4 +66,3 @@ ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 
     return OUT;
 }
-

+ 25 - 1
Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.azsli

@@ -11,6 +11,7 @@
 #include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
 #include <Atom/Features/VertexUtility.azsli>
 
+#include "../../Shaders/Materials/MaterialInputs/UvSetCount.azsli"
 
 
 struct MaterialParameters {};
@@ -52,10 +53,26 @@ const MaterialParameters GetMaterialParameters()
         output.normal = IN.normal;
         return output;
     }
-        
+
+    VsOutput EvaluateVertexGeometry(
+        float3 position,
+        float3 normal,
+        float4 tangent,
+        float2 uv0,
+        float2 uv1,
+        uint instanceId)
+    {
+        VsOutput output;
+        output.worldPosition = LocalSpaceToWorldSpace(position, instanceId);
+        output.position = WorldSpaceToClipSpace(output.worldPosition);
+        output.normal = normal;
+        return output;
+    }
+
     class PixelGeometryData
     {
         float3 positionWS;
+        float2 uvs[UvSetCount];
         float3 vertexNormal;
     };
 
@@ -64,6 +81,13 @@ const MaterialParameters GetMaterialParameters()
         PixelGeometryData geoData;
         geoData.positionWS = IN.worldPosition;
         geoData.vertexNormal = normalize(IN.normal);
+
+        [unroll]
+        for(uint i = 0; i < UvSetCount; ++i)
+        {
+            geoData.uvs[i] = float2(0, 0);
+        }
+
         return geoData;
     }
 

+ 2 - 0
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/InstancedTransforms.azsli

@@ -8,6 +8,8 @@
   
 #pragma once
 
+#include <viewsrg_all.srgi>
+
 // Functions to support getting object transforms via an instance buffer, which use
 // the ObjectSrg as a fallback if instancing is not enabled.
 // Including this header in a shader indicates that the shader itself will use these

+ 10 - 11
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli

@@ -26,7 +26,7 @@ real3 TransmissionKernel(real t, real3 s)
 // [specific profile for human skin]
 // Analytical integation (approximation) of diffusion profile over radius, could be precomputed in a LUT
 // From d'Eon and Luebke (https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-14-advanced-techniques-realistic-real-time-skin, section 14.4.7)
-float3 T(float s) 
+float3 T(float s)
 {
     // dipoles and multipoles are approximated with sums of a small number of Gaussians with variable weights and variances
     return float3(0.233, 0.455, 0.649) * exp(-s*s/0.0064) +
@@ -43,7 +43,7 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
 
 #if ENABLE_TRANSMISSION
 
-    real thickness = 0.0; 
+    real thickness = 0.0;
     TransmissionSurfaceData transmission = surface.transmission;
 
     switch(o_transmission_mode)
@@ -51,13 +51,13 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
         case TransmissionMode::None:
             break;
 
-        case TransmissionMode::ThickObject: 
+        case TransmissionMode::ThickObject:
             // Thick object mode, using back lighting approximation proposed by Brisebois B. C. and Bouchard M. 2011
             // https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/
 
             {
                 thickness = max(transmissionDistance, transmission.thickness);
-                real transmittance = pow( saturate( dot( lightingData.dirToCamera, -normalize( dirToLight + surface.GetVertexNormal() * transmission.GetDistortion() ) ) ), transmission.GetPower() ) * transmission.GetScale();
+                real transmittance = pow( saturate( dot( lightingData.dirToCamera[0], -normalize( dirToLight + surface.GetVertexNormal() * transmission.GetDistortion() ) ) ), transmission.GetPower() ) * transmission.GetScale();
                 real lamberAttenuation = exp(-thickness * transmission.GetAttenuationCoefficient()) * saturate(1.0 - thickness);
                 result = transmittance * lamberAttenuation * lightIntensity;
             }
@@ -66,7 +66,7 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
         case TransmissionMode::ThinObject:
             // Thin object mode, based on Jimenez J. et al, 2010, "Real-Time Realistic Skin Translucency"
             // http://www.iryoku.com/translucency/downloads/Real-Time-Realistic-Skin-Translucency.pdf
-            
+
             {
                 // transmissionDistance < 0.0f means shadows are not enabled --> avoid unnecessary computation
                 if (transmissionDistance < 0.0)
@@ -74,20 +74,20 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
                     break;
                 }
 
-                // Irradiance arround surface point. 
-                // Albedo at front (surface point) is used to approximate irradiance at the back of the 
+                // Irradiance arround surface point.
+                // Albedo at front (surface point) is used to approximate irradiance at the back of the
                 // object (observation 4 in [Jimenez J. et al, 2010])
                 // Increase angle of influence to avoid dark transition regions
                 real3 E = surface.albedo * saturate(transmission.GetTransmissionNdLBias() + dot(-surface.GetVertexNormal(), dirToLight));
 
-                // Transmission distance modulated by hardcoded constant C and the thickness exposed parameter 
+                // Transmission distance modulated by hardcoded constant C and the thickness exposed parameter
                 // (in this case modulating the distance traversed by the light inside the object)
                 const real C = 300.0;
                 real s = transmissionDistance * C * transmission.thickness;
-                
+
                 // Use scattering color to weight thin object transmission color
                 const real3 invScattering = rcp(max(transmission.scatterDistance, real(0.00001)));
-                
+
 #if !USE_HUMAN_SKIN_PROFILE
                 // Generic profile based on scatter color
                 result = TransmissionKernel(s, invScattering) * lightIntensity * E * transmission.GetScale();
@@ -106,4 +106,3 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
 
     return result;
 }
-

+ 44 - 25
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli

@@ -13,15 +13,23 @@
 #include <Atom/Features/LightCulling/LightCullingTileIterator.azsli>
 #include <Atom/Features/PBR/LightingUtils.azsli>
 
+#ifndef MAX_SHADING_VIEWS
+#define MAX_SHADING_VIEWS 1
+#endif
+
+#ifndef GET_SHADING_VIEW_COUNT
+#define GET_SHADING_VIEW_COUNT 1
+#endif
+
 class LightingData
 {
     LightCullingTileIterator tileIterator;
-        
+
     uint lightingChannels;
 
     // Lighting outputs
     real3 diffuseLighting;
-    real3 specularLighting;
+    real3 specularLighting[MAX_SHADING_VIEWS];
     real3 translucentBackLighting;
 
     // Factors for the amount of diffuse and specular lighting applied
@@ -29,17 +37,17 @@ class LightingData
     real3 specularResponse;
 
     // (N . L) to accept below (N . L = 0) in scattering through thin objects
-    real transmissionNdLBias; 
-    
+    real transmissionNdLBias;
+
     // Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
     real shrinkFactor;
 
-    // Attenuation applied to hide artifacts due to low-res shadow maps 
+    // Attenuation applied to hide artifacts due to low-res shadow maps
     real distanceAttenuation;
 
     // Normalized direction from surface to camera
-    real3 dirToCamera;
-    
+    real3 dirToCamera[MAX_SHADING_VIEWS];
+
     // Scaling term to approximate multiscattering contribution in specular BRDF
     real3 multiScatterCompensation;
 
@@ -47,58 +55,69 @@ class LightingData
     real3 emissiveLighting;
 
     // BRDF texture values
-    real2 brdf;
+    real2 brdf[MAX_SHADING_VIEWS];
 
     // Normal . View
-    real NdotV;
+    real NdotV[MAX_SHADING_VIEWS];
 
     // Occlusion factors
     // 0 = dark, 1 = light
     real diffuseAmbientOcclusion;
     real specularOcclusion;
 
-    void Init(real3 positionWS, real3 normal, real roughnessLinear, float3 viewPosition);
+    void Init(float3 positionWS, real3 specularNormal, real roughnessLinear, float3 viewPosition[MAX_SHADING_VIEWS]);
     void CalculateMultiscatterCompensation(real3 specularF0, bool enabled);
     void FinalizeLighting();
     void FinalizeLighting(real3 transmissionTint);
 
-    real GetSpecularNdotV() { return NdotV; }
+    real GetSpecularNdotV() { return NdotV[0]; }
+    real GetSpecularNdotV(uint i) { return NdotV[i]; }
 };
 
-void LightingData::Init(real3 positionWS, real3 normal, real roughnessLinear, float3 viewPosition)
+void LightingData::Init(float3 positionWS, real3 specularNormal, real roughnessLinear, float3 viewPosition[MAX_SHADING_VIEWS])
 {
     diffuseLighting = real3(0.0, 0.0, 0.0);
-    specularLighting = real3(0.0, 0.0, 0.0);
     translucentBackLighting = real3(0.0, 0.0, 0.0);
     multiScatterCompensation = real3(1.0, 1.0, 1.0);
     emissiveLighting = real3(0.0, 0.0, 0.0);
     diffuseAmbientOcclusion = 1.0;
     specularOcclusion = 1.0;
-    
+
     lightingChannels = 0xFFFFFFFF;
 
-    dirToCamera = normalize(viewPosition - positionWS);
+    [unroll]
+    for(uint i = 0; i < GET_SHADING_VIEW_COUNT; ++i)
+    {
+        specularLighting[i] = real3(0.0, 0.0, 0.0);
+        dirToCamera[i] = normalize(viewPosition[i] - positionWS);
+        NdotV[i] = saturate(dot(specularNormal, dirToCamera[i]));
 
-    // sample BRDF map (indexed by smoothness values rather than roughness)
-    NdotV = saturate(dot(normal, dirToCamera));
-    real2 brdfUV = real2(NdotV, (1.0 - roughnessLinear));
-    brdf = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg);
+        // sample BRDF map (indexed by smoothness values rather than roughness)
+        real2 brdfUV = real2(NdotV[i], (1.0 - roughnessLinear));
+        brdf[i] = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg);
+    }
 }
 
 void LightingData::CalculateMultiscatterCompensation(real3 specularF0, bool enabled)
 {
-    multiScatterCompensation = GetMultiScatterCompensation(specularF0, brdf, enabled);
+    // Default to using 0 for brdf viewIndex as we don't want to pay the VGPR cost of
+    // a real3 per view for multiScatterCompensation as it's a minor effect
+    multiScatterCompensation = GetMultiScatterCompensation(specularF0, brdf[0], enabled);
 }
 
 void LightingData::FinalizeLighting()
 {
-    specularLighting *= specularOcclusion;
-    diffuseLighting *= diffuseAmbientOcclusion;
-
-    if(!IsSpecularLightingEnabled())
+    [unroll]
+    for(uint i = 0; i < GET_SHADING_VIEW_COUNT; ++i)
     {
-        specularLighting = real3(0, 0, 0);
+        specularLighting[i] *= specularOcclusion;
+        if(!IsSpecularLightingEnabled())
+        {
+            specularLighting[i] = real3(0, 0, 0);
+        }
     }
+
+    diffuseLighting *= diffuseAmbientOcclusion;
     if(!IsDiffuseLightingEnabled())
     {
         diffuseLighting = real3(0, 0, 0);

+ 11 - 5
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/StandardLighting.azsli

@@ -20,6 +20,7 @@
 #include <Atom/Features/PBR/LightingUtils.azsli>
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
 
+
 // Then define the Diffuse and Specular lighting functions
 real3 GetDiffuseLighting(Surface surface, LightingData lightingData, real3 lightIntensity, real3 dirToLight)
 {
@@ -29,7 +30,7 @@ real3 GetDiffuseLighting(Surface surface, LightingData lightingData, real3 light
     if(o_clearCoat_feature_enabled)
     {
         // Attenuate diffuse term by clear coat's fresnel term to account for energy loss
-        real HdotV = saturate(dot(normalize(dirToLight + lightingData.dirToCamera), lightingData.dirToCamera));
+        real HdotV = saturate(dot(normalize(dirToLight + lightingData.dirToCamera[0]), lightingData.dirToCamera[0]));
         diffuse *= 1.0 - (FresnelSchlick(HdotV, 0.04) * surface.clearCoat.factor);
     }
 #endif
@@ -38,14 +39,19 @@ real3 GetDiffuseLighting(Surface surface, LightingData lightingData, real3 light
     return diffuse;
 }
 
-real3 GetSpecularLighting(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight)
+real3 GetDiffuseLighting(Surface surface, LightingData lightingData, real3 lightIntensity, real3 dirToLight, uint viewIndex)
+{
+    return GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight, 0);
+}
+
+real3 GetSpecularLighting(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight, uint viewIndex)
 {
-    real3 specular = SpecularGGX(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(), surface.roughnessA2, lightingData.multiScatterCompensation);
+    real3 specular = SpecularGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(viewIndex), surface.roughnessA2, lightingData.multiScatterCompensation);
 
 #if ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     {
-        specular = ClearCoatSpecular(dirToLight, lightingData.dirToCamera, surface.clearCoat.normal, surface.clearCoat.factor, surface.clearCoat.roughness, specular);
+        specular = ClearCoatSpecular(dirToLight, lightingData.dirToCamera[viewIndex], surface.clearCoat.normal, surface.clearCoat.factor, surface.clearCoat.roughness, specular);
     }
 #endif
 
@@ -72,7 +78,7 @@ PbrLightingOutput GetPbrLightingOutput(Surface surface, LightingData lightingDat
     PbrLightingOutput lightingOutput;
 
     lightingOutput.m_diffuseColor = real4(lightingData.diffuseLighting, alpha);
-    lightingOutput.m_specularColor = real4(lightingData.specularLighting, 1.0);
+    lightingOutput.m_specularColor = real4(lightingData.specularLighting[0], 1.0);
 
     // albedo, specularF0, roughness, and normals for later passes (specular IBL, Diffuse GI, SSR, AO, etc)
     lightingOutput.m_specularF0 = real4(surface.GetSpecularF0(), surface.roughnessLinear);

+ 108 - 98
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/CapsuleLight.azsli

@@ -12,47 +12,6 @@
 #include <Atom/Features/PBR/Lights/PointLight.azsli>
 
 #if ENABLE_CAPSULE_LIGHTS
-// Get a uniformly distributed point on the surface of a capsule from the provided uniformly distributed 2d point. Uses
-// capsRatio to determine if the point should be on the caps or cylinder to ensure an even distribution. 
-void SampleCapsule(float2 randomPoint, CapsuleLight light, float capToCylinderAreaRatio, float3x3 localToWorld, out float3 outPosition, out float3 outDirection)
-{
-    float3 startToEnd = light.m_direction * light.m_length;
-    float3 endPoint = light.m_startPoint + startToEnd;
-    if (randomPoint.x < capToCylinderAreaRatio)
-    {
-        // Sample on cap
-        float2 spherePoint = randomPoint;
-        spherePoint.x /= capToCylinderAreaRatio; // normalize to 0.0 -> 1.0
-
-        float3 pointDirection = SampleSphere(spherePoint);
-        if (dot(pointDirection, light.m_direction) < 0.0)
-        {
-            // start point cap
-            outPosition = light.m_startPoint + pointDirection * light.m_radius;
-        }
-        else
-        {
-            // end point cap
-            outPosition = endPoint + pointDirection * light.m_radius;
-        }
-        outDirection = pointDirection;
-    }
-    else
-    {
-        // Sample on cylinder
-        float angle = randomPoint.y * PI * 2.0;
-        outDirection.x = 0.0;
-        outDirection.y = sin(angle);
-        outDirection.z = cos(angle);
-        outDirection = mul(outDirection, localToWorld);
-
-        float positionAlongCylinder = (randomPoint.x - capToCylinderAreaRatio) / (1.0 - capToCylinderAreaRatio); // normalize to 0.0 -> 1.0
-        positionAlongCylinder *= light.m_length;
-        
-        float3 positionInCylinder = light.m_startPoint + light.m_direction * positionAlongCylinder;
-        outPosition = positionInCylinder + outDirection * light.m_radius;
-    }
-}
 
 void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData lightingData)
 {
@@ -60,7 +19,7 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
     float3 startPoint = light.m_startPoint;
     float3 startToEnd = light.m_direction * lightLength;
     float3 endPoint = startPoint + startToEnd;
-    
+
     // Do simple distance check to line for falloff and tight culling.
     float3 surfaceToStart = startPoint - surface.position;
     float closestPointDistance = dot(-surfaceToStart, light.m_direction);
@@ -113,7 +72,7 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
         float distanceToStart = length(surfaceToStart);
         float3 surfaceToEnd = endPoint - surface.position;
         float distanceToEnd = length(surfaceToEnd);
-        
+
         // Integration of lambert reflectance of a line segment to get diffuse intensity
         // See https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
         float NdotStart = dot(surface.GetDiffuseNormal(), surfaceToStart) / distanceToStart;
@@ -124,59 +83,99 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
         float3 lightIntensity = (intensity * radiusAttenuation * ratioVisible) * light.m_rgbIntensityCandelas;
         lightingData.diffuseLighting += max(0.0, surface.albedo * lightIntensity);
 
-        // Calculate the reflection of the normal from the view direction
-        float3 reflectionDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
-
-        // Find closest point on light to reflection vector.
-        // See https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
-        float reflDotStartToEnd = dot(reflectionDir, startToEnd);
-        float closestT = (dot(reflectionDir, surfaceToStart) * reflDotStartToEnd - dot(surfaceToStart, startToEnd)) / (dot(startToEnd, startToEnd) - reflDotStartToEnd * reflDotStartToEnd);
-        closestT = saturate(closestT);
-
-        float3 closestIntersectionPoint = startPoint + closestT * startToEnd;
-        float3 posToLight = closestIntersectionPoint - surface.position;
-
         // Transmission contribution
         // We cannot compute the actual transmission distance so we want to:
         // - If transmission mode is thick object -> use transmission thickness parameter instead
         // - If transmission mode is thin object -> ignore back lighting
-        // To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance 
+        // To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance
         const float transmissionDistance = -1.0f;
         // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
         const float attenuationDistance = 0.0f;
 
 #if ENABLE_TRANSMISSION
-        lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), transmissionDistance, attenuationDistance);
+        lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(dirToClosestPoint), transmissionDistance, attenuationDistance);
 #endif
 
-        // Calculate the offset from the nearest point on the reflection vector to the nearest point on the capsule light
-        float3 posToClosestPointAlongReflection = dot(posToLight, reflectionDir) * reflectionDir;
-        float3 lightReflectionOffset = posToLight - posToClosestPointAlongReflection;
-
-        // Adjust the direction to light based on the capsule radius
-        posToLight -= lightReflectionOffset * saturate(light.m_radius / length(lightReflectionOffset));
-        d2 = dot(posToLight, posToLight);
-        d2 = max(light.m_radius * light.m_radius, d2);
-
-        // Adjust the intensity of the light based on the capsule radius to conserve energy
-        float sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_radius, d2);
-
-        // Capsule specular is done just like spherical specular, we just move the position of the sphere along the capsule depending
-        // on the point being shaded. However this means that the intensity needs to be reduced by the ratio of the capsule surface
-        // area to a sphere of the same radius.
-        //capsArea = 4.0 * PI * light.m_radius * light.m_radius;
-        //cylinderArea = 2.0 * PI * light.m_radius * light.m_length;
-        //sphereToCapsuleAreaRatio 
-        //    = capsArea / (capsArea + cylinderArea);
-        //    = 4.0 * PI * radius * radius / (4.0 * PI * radius * radius + 2.0 * PI * radius * length )
-        //    = 2.0 * radius / (2.0 * radius + length)
-        
-        float sphereToCapsuleAreaRatio = 2.0 * light.m_radius / max(2.0 * light.m_radius + light.m_length, EPSILON);
-
-        // Specular contribution
-        lightIntensity = sphereToCapsuleAreaRatio * radiusAttenuation / d2;
-        lightIntensity *= light.m_rgbIntensityCandelas;
-        lightingData.specularLighting += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(posToLight));
+        // Calculate specular lighting for each view
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        {
+            // Calculate the reflection of the normal from the view direction
+            float3 reflectionDir = reflect(-lightingData.dirToCamera[viewIndex], surface.GetSpecularNormal());
+
+            // Find closest point on light to reflection vector.
+            // See https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
+            float reflDotStartToEnd = dot(reflectionDir, startToEnd);
+            float closestT = (dot(reflectionDir, surfaceToStart) * reflDotStartToEnd - dot(surfaceToStart, startToEnd)) / (dot(startToEnd, startToEnd) - reflDotStartToEnd * reflDotStartToEnd);
+            closestT = saturate(closestT);
+
+            float3 closestIntersectionPoint = startPoint + closestT * startToEnd;
+            float3 posToLight = closestIntersectionPoint - surface.position;
+
+            // Calculate the offset from the nearest point on the reflection vector to the nearest point on the capsule light
+            float3 posToClosestPointAlongReflection = dot(posToLight, reflectionDir) * reflectionDir;
+            float3 lightReflectionOffset = posToLight - posToClosestPointAlongReflection;
+
+            // Adjust the direction to light based on the capsule radius
+            posToLight -= lightReflectionOffset * saturate(light.m_radius / length(lightReflectionOffset));
+            d2 = dot(posToLight, posToLight);
+            d2 = max(light.m_radius * light.m_radius, d2);
+
+            // Adjust the intensity of the light based on the capsule radius to conserve energy
+            float sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_radius, d2);
+
+            // Capsule specular is done just like spherical specular, we just move the position of the sphere along the capsule depending
+            // on the point being shaded. However this means that the intensity needs to be reduced by the ratio of the capsule surface
+            // area to a sphere of the same radius.
+            float sphereToCapsuleAreaRatio = 2.0 * light.m_radius / max(2.0 * light.m_radius + light.m_length, EPSILON);
+
+            // Specular contribution
+            float3 viewLightIntensity = sphereToCapsuleAreaRatio * radiusAttenuation / d2;
+            viewLightIntensity *= light.m_rgbIntensityCandelas;
+            lightingData.specularLighting[viewIndex] += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, viewLightIntensity, normalize(posToLight), viewIndex);
+        }
+    }
+}
+
+// Get a uniformly distributed point on the surface of a capsule from the provided uniformly distributed 2d point. Uses
+// capsRatio to determine if the point should be on the caps or cylinder to ensure an even distribution.
+void SampleCapsule(float2 randomPoint, CapsuleLight light, float capToCylinderAreaRatio, float3x3 localToWorld, out float3 outPosition, out float3 outDirection)
+{
+    float3 startToEnd = light.m_direction * light.m_length;
+    float3 endPoint = light.m_startPoint + startToEnd;
+    if (randomPoint.x < capToCylinderAreaRatio)
+    {
+        // Sample on cap
+        float2 spherePoint = randomPoint;
+        spherePoint.x /= capToCylinderAreaRatio; // normalize to 0.0 -> 1.0
+
+        float3 pointDirection = SampleSphere(spherePoint);
+        if (dot(pointDirection, light.m_direction) < 0.0)
+        {
+            // start point cap
+            outPosition = light.m_startPoint + pointDirection * light.m_radius;
+        }
+        else
+        {
+            // end point cap
+            outPosition = endPoint + pointDirection * light.m_radius;
+        }
+        outDirection = pointDirection;
+    }
+    else
+    {
+        // Sample on cylinder
+        float angle = randomPoint.y * PI * 2.0;
+        outDirection.x = 0.0;
+        outDirection.y = sin(angle);
+        outDirection.z = cos(angle);
+        outDirection = mul(outDirection, localToWorld);
+
+        float positionAlongCylinder = (randomPoint.x - capToCylinderAreaRatio) / (1.0 - capToCylinderAreaRatio); // normalize to 0.0 -> 1.0
+        positionAlongCylinder *= light.m_length;
+
+        float3 positionInCylinder = light.m_startPoint + light.m_direction * positionAlongCylinder;
+        outPosition = positionInCylinder + outDirection * light.m_radius;
     }
 }
 
@@ -187,7 +186,7 @@ void ValidateCapsuleLight(CapsuleLight light, Surface surface, inout LightingDat
     float capArea =  4.0 * PI * light.m_radius * light.m_radius;
     float cylinderArea = 2.0 * PI * light.m_radius * light.m_length;
     float capToCylinderAreaRatio = capArea / (capArea + cylinderArea);
-    
+
     // Construct local to world matrix for transforming sample points.
     float3x3 localToWorld;
     localToWorld[0] = light.m_direction;
@@ -202,9 +201,13 @@ void ValidateCapsuleLight(CapsuleLight light, Surface surface, inout LightingDat
     }
     localToWorld[1]= cross(localToWorld[0], localToWorld[2]);
 
-    float3 diffuseAcc = float3(0.0, 0.0, 0.0);
-    float3 specularAcc = float3(0.0, 0.0, 0.0);
-    float3 translucentAcc = float3(0.0, 0.0, 0.0);
+    real3 diffuseAcc = float3(0.0, 0.0, 0.0);
+    real3 translucentAcc = float3(0.0, 0.0, 0.0);
+    real3 specularAcc[MAX_SHADING_VIEWS]; 
+
+    [unroll]
+    for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        specularAcc[viewIndex] = float3(0.0, 0.0, 0.0);
 
     for (uint i = 0; i < sampleCount; ++i)
     {
@@ -217,12 +220,17 @@ void ValidateCapsuleLight(CapsuleLight light, Surface surface, inout LightingDat
 
     // Lighting value is in Candela, convert to Lumen for total light output of the light
     float3 intensityLumens = light.m_rgbIntensityCandelas * 4.0 * PI;
+
     // Each of the N samples will contribute intensity / N lumens. However it will radiate in
     // equal directions across the hemisphere, so we need to account for that
     float3 intensity = intensityLumens * INV_PI;
-    
+
     lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * intensity;
-    lightingData.specularLighting += (specularAcc / float(sampleCount)) * intensity;
+
+    [unroll]
+    for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        lightingData.specularLighting[viewIndex] += (specularAcc[viewIndex] / float(sampleCount)) * intensity;
+
 #if ENABLE_TRANSMISSION
     lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensity;
 #endif
@@ -252,15 +260,15 @@ void ApplyCapsuleLightInternal(uint lightIndex, Surface surface, inout LightingD
 }
 
 void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
-{    
+{
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
-                
+
     while(!lightingData.tileIterator.IsDone())
     {
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
-        
+
         ApplyCapsuleLightInternal(currLightIndex, surface, lightingData);
     }
 #else
@@ -270,10 +278,12 @@ void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
     }
 #endif
 }
-#else
+
+#else   // ENABLE_CAPSULE_LIGHTS
+
 void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
-{    
+{
     //Not Enabled
 }
-#endif
 
+#endif  // ENABLE_CAPSULE_LIGHTS

+ 31 - 18
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DirectionalLight.azsli

@@ -28,15 +28,15 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
 
 #if ENABLE_SHADOWS
     if (o_enableShadows && shadowIndex <  SceneSrg::m_directionalLightCount)
-    {           
+    {
 #if ENABLE_TRANSMISSION
         if (o_transmission_mode == TransmissionMode::ThickObject)
         {
             real2 visibilityAndThickness = DirectionalLightShadow::GetVisibilityThickTransmission(shadowIndex, real3(surface.position), surface.vertexNormal, screenUv);
             litRatio = visibilityAndThickness.x;
             transmissionDistance = visibilityAndThickness.y;
-        } 
-        else if (o_transmission_mode == TransmissionMode::ThinObject) 
+        }
+        else if (o_transmission_mode == TransmissionMode::ThinObject)
         {
             real2 visibilityAndThickness = DirectionalLightShadow::GetVisibilityThinTransmission(
                                                 shadowIndex, real3(surface.position), surface.vertexNormal, screenUv, surface.transmission.GetShrinkFactor());
@@ -48,7 +48,7 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
         {
             litRatio = DirectionalLightShadow::GetVisibility(shadowIndex, real3(surface.position), surface.vertexNormal, screenUv);
         }
-#else 
+#else
         litRatio = DirectionalLightShadow::GetVisibility(shadowIndex, surface.position, surface.vertexNormal, screenUv);
 #endif // ENABLE_TRANSMISSION
 
@@ -63,15 +63,6 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
         {
             continue;
         }
-        real3 dirToLight = normalize(real3(-light.m_direction));
-
-        // Adjust the direction of the light based on its angular diameter.
-        real3 reflectionDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
-        real3 lightDirToReflectionDir = reflectionDir - dirToLight;
-        real lightDirToReflectionDirLen = length(lightDirToReflectionDir);
-        lightDirToReflectionDir = lightDirToReflectionDir / lightDirToReflectionDirLen; // normalize the length
-        lightDirToReflectionDirLen = min(real(light.m_angularRadius), lightDirToReflectionDirLen);
-        dirToLight += lightDirToReflectionDir * lightDirToReflectionDirLen;
 
         // [GFX TODO][ATOM-2012] care of multiple directional light
         // Currently shadow check is done only for index == shadowIndex.
@@ -89,11 +80,28 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
 
         // Transmission contribution
 #if ENABLE_TRANSMISSION
-        lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, real3(light.m_rgbIntensityLux), dirToLight, real(currentTransmissionDistance), camToSurfDist);
+        lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, real3(light.m_rgbIntensityLux), normalize(real3(-light.m_direction)), real(currentTransmissionDistance), camToSurfDist);
 #endif
-        
+
+        // Calculate diffuse lighting (same for all views)
+        real3 dirToLight = normalize(real3(-light.m_direction));
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, real3(light.m_rgbIntensityLux), dirToLight) * currentLitRatio;
-        lightingData.specularLighting += GetSpecularLighting(surface, lightingData, real3(light.m_rgbIntensityLux), dirToLight) * currentLitRatio;
+
+        // Calculate specular lighting for each view
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        {
+            // Adjust the direction of the light based on its angular diameter for this view
+            real3 reflectionDir = reflect(-lightingData.dirToCamera[viewIndex], surface.GetSpecularNormal());
+            real3 lightDirToReflectionDir = reflectionDir - dirToLight;
+            real lightDirToReflectionDirLen = length(lightDirToReflectionDir);
+            lightDirToReflectionDir = lightDirToReflectionDir / lightDirToReflectionDirLen; // normalize the length
+            lightDirToReflectionDirLen = min(real(light.m_angularRadius), lightDirToReflectionDirLen);
+            real3 viewDirToLight = dirToLight + lightDirToReflectionDir * lightDirToReflectionDirLen;
+
+            // Add specular lighting for this view
+            lightingData.specularLighting[viewIndex] += GetSpecularLighting(surface, lightingData, real3(light.m_rgbIntensityLux), viewDirToLight, viewIndex) * currentLitRatio;
+        }
 
 #if ENABLE_SHADER_DEBUGGING
         if(IsDebuggingEnabled_PLACEHOLDER() && GetRenderDebugViewMode() == RenderDebugViewMode::CascadeShadows)
@@ -103,11 +111,16 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
 #endif
     }
 
-#if ENABLE_SHADOWS    
+#if ENABLE_SHADOWS
     // Add debug coloring for directional light shadow
     if (o_enableShadows && shadowIndex <  SceneSrg::m_directionalLightCount)
     {
-        lightingData.specularLighting = DirectionalLightShadow::AddDebugColoring(lightingData.specularLighting, shadowIndex, real3(surface.position), surface.vertexNormal);
+        // Apply debug coloring to all views
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        {
+            lightingData.specularLighting[viewIndex] = DirectionalLightShadow::AddDebugColoring(lightingData.specularLighting[viewIndex], shadowIndex, real3(surface.position), surface.vertexNormal);
+        }
     }
 #endif
 }

+ 67 - 48
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DiskLight.azsli

@@ -50,12 +50,12 @@ void ApplyDiskLight(DiskLight light, Surface surface, inout LightingData lightin
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         float radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Find the distance to the closest point on the disk
         float distanceToPlane = dot(posToLight, -light.m_direction);
         float distanceToPlane2 = distanceToPlane * distanceToPlane;
-        float pointOnPlaneToLightDistance = sqrt(distanceToLight2 - distanceToPlane2); // pythagorean theorem 
-        float pointOnPlaneToDiskDistance = max(pointOnPlaneToLightDistance - light.m_diskRadius, 0.0f); 
+        float pointOnPlaneToLightDistance = sqrt(distanceToLight2 - distanceToPlane2); // pythagorean theorem
+        float pointOnPlaneToDiskDistance = max(pointOnPlaneToLightDistance - light.m_diskRadius, 0.0f);
         float distanceToDisk2 = pointOnPlaneToDiskDistance * pointOnPlaneToDiskDistance + distanceToPlane2;
 
         // Update the light direction based on the edges of the disk as visible from this point instead of the center.
@@ -91,10 +91,10 @@ void ApplyDiskLight(DiskLight light, Surface surface, inout LightingData lightin
                 surface.position,
                 -dirToConeTip,
                 surface.vertexNormal);
-             
+
 
             // o_transmission_mode == NONE is not taken into account because GetBackLighting already ignores this case
-#if ENABLE_TRANSMISSION            
+#if ENABLE_TRANSMISSION
             if (o_transmission_mode == TransmissionMode::ThickObject)
             {
                 transmissionDistance = ProjectedShadow::GetThickness(light.m_shadowIndex, surface.position);
@@ -108,13 +108,13 @@ void ApplyDiskLight(DiskLight light, Surface surface, inout LightingData lightin
 #endif
 
         if (useConeAngle && dotWithDirection < light.m_cosInnerConeAngle) // in penumbra
-        {   
+        {
             // Normalize into 0.0 - 1.0 space.
             float penumbraMask = (dotWithDirection - light.m_cosOuterConeAngle) / (light.m_cosInnerConeAngle - light.m_cosOuterConeAngle);
-            
+
             // Apply smoothstep
             penumbraMask = penumbraMask * penumbraMask * (3.0 - 2.0 * penumbraMask);
-            
+
             lightIntensity *= penumbraMask;
         }
 
@@ -126,42 +126,45 @@ void ApplyDiskLight(DiskLight light, Surface surface, inout LightingData lightin
         lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, posToLightDir, transmissionDistance, distanceToLight2);
 #endif
 
-        // Adjust the light direction for specular based on disk size
-
-        // Calculate the reflection off the normal from the view direction
-        float3 reflectionDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
-        float reflectionDotLight = dot(reflectionDir, -light.m_direction);
-        
-        // Let 'Intersection' denote the point where the reflection ray intersects the diskLight plane
-        // As such, posToIntersection denotes the vector from pos to the intersection of the reflection ray and the disk plane:
-        float3 posToIntersection;
-        
-        if (reflectionDotLight >= 0.0001)
-        {
-            // Reflection going towards the light
-            posToIntersection = reflectionDir * distanceToPlane / reflectionDotLight;
-        }
-        else
+        // Calculate specular lighting for each view
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
         {
-            // Reflection going away from the light. Choose a point far off and project it on the plane,
-            // then treat that as the reflection plane intersection.
-            float3 posToFarOffPoint = reflectionDir * distanceToPlane * 10000.0;
-            float3 lightToFarOffPoint = posToFarOffPoint - posToLight;
-            float3 intersectionToFarOffPoint = dot(lightToFarOffPoint, light.m_direction) * light.m_direction;
-            posToIntersection = posToFarOffPoint - intersectionToFarOffPoint;
-        }
-    
-        // Calculate a vector from the reflection vector to the light
-        float3 intersectionToLight = posToLight - posToIntersection;
+            // Calculate the reflection off the normal from the view direction
+            float3 reflectionDir = reflect(-lightingData.dirToCamera[viewIndex], surface.GetSpecularNormal());
+            float reflectionDotLight = dot(reflectionDir, -light.m_direction);
+
+            // Let 'Intersection' denote the point where the reflection ray intersects the diskLight plane
+            // As such, posToIntersection denotes the vector from pos to the intersection of the reflection ray and the disk plane:
+            float3 posToIntersection;
+
+            if (reflectionDotLight >= 0.0001)
+            {
+                // Reflection going towards the light
+                posToIntersection = reflectionDir * distanceToPlane / reflectionDotLight;
+            }
+            else
+            {
+                // Reflection going away from the light. Choose a point far off and project it on the plane,
+                // then treat that as the reflection plane intersection.
+                float3 posToFarOffPoint = reflectionDir * distanceToPlane * 10000.0;
+                float3 lightToFarOffPoint = posToFarOffPoint - posToLight;
+                float3 intersectionToFarOffPoint = dot(lightToFarOffPoint, light.m_direction) * light.m_direction;
+                posToIntersection = posToFarOffPoint - intersectionToFarOffPoint;
+            }
 
-        // Adjust the direction to light based on the bulb size
-        posToLight -= intersectionToLight * saturate(light.m_diskRadius / length(intersectionToLight));
+            // Calculate a vector from the reflection vector to the light
+            float3 intersectionToLight = posToLight - posToIntersection;
 
-        // Adjust the intensity of the light based on the bulb size to conserve energy
-        float diskIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_diskRadius, distanceToLight2);
+            // Adjust the direction to light based on the bulb size
+            float3 viewPosToLight = posToLight - intersectionToLight * saturate(light.m_diskRadius / length(intersectionToLight));
 
-        // Specular contribution
-        lightingData.specularLighting += diskIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(posToLight)) * litRatio;
+            // Adjust the intensity of the light based on the bulb size to conserve energy
+            float diskIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_diskRadius, distanceToLight2);
+
+            // Specular contribution
+            lightingData.specularLighting[viewIndex] += diskIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(viewPosToLight), viewIndex) * litRatio;
+        }
     }
 }
 
@@ -196,9 +199,15 @@ void ValidateDiskLight(DiskLight light, Surface surface, inout LightingData ligh
 {
     const uint sampleCount = 512;
 
-    float3 diffuseAcc = float3(0.0, 0.0, 0.0);
-    float3 specularAcc = float3(0.0, 0.0, 0.0);
-    float3 translucentAcc = float3(0.0, 0.0, 0.0);
+    real3 diffuseAcc = float3(0.0, 0.0, 0.0);
+    real3 translucentAcc = float3(0.0, 0.0, 0.0);
+    real3 specularAcc[MAX_SHADING_VIEWS];
+    
+    [unroll]
+    for(uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        specularAcc[viewIndex] = float3(0.0, 0.0, 0.0);
+    }
 
     for (uint i = 0; i < sampleCount; ++i)
     {
@@ -206,9 +215,19 @@ void ValidateDiskLight(DiskLight light, Surface surface, inout LightingData ligh
         float3 samplePoint = SampleDisk(randomPoint, light);
         AddSampleContribution(surface, lightingData, samplePoint, light.m_direction, 0.0, diffuseAcc, specularAcc, translucentAcc);
     }
-    
+
     lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * light.m_rgbIntensityCandelas;
-    lightingData.specularLighting += (specularAcc / float(sampleCount)) * light.m_rgbIntensityCandelas;
+    
+    [unroll]
+    for(uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        lightingData.specularLighting[viewIndex] += (specularAcc[viewIndex] / float(sampleCount)) * light.m_rgbIntensityCandelas;
+    }
+
+#if ENABLE_TRANSMISSION
+    lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * light.m_rgbIntensityCandelas;
+#endif
+
 }
 
 void ApplyDiskLightInternal(uint lightIndex, Surface surface, inout LightingData lightingData)
@@ -234,7 +253,7 @@ void ApplyDiskLightInternal(uint lightIndex, Surface surface, inout LightingData
 }
 
 void ApplyDiskLights(Surface surface, inout LightingData lightingData)
-{    
+{
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
 
@@ -242,7 +261,7 @@ void ApplyDiskLights(Surface surface, inout LightingData lightingData)
     {
         uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
-        
+
         ApplyDiskLightInternal(currLightIndex, surface, lightingData);
     }
 #else
@@ -256,7 +275,7 @@ void ApplyDiskLights(Surface surface, inout LightingData lightingData)
 #else
 
 void ApplyDiskLights(Surface surface, inout LightingData lightingData)
-{    
+{
     //Not Enabled
 }
 

+ 53 - 48
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli

@@ -32,8 +32,8 @@
 #endif
 
 real3 GetIblDiffuse(
-    real3 normal, 
-    real3 albedo, 
+    real3 normal,
+    real3 albedo,
     real3 diffuseResponse)
 {
     real3 irradianceDir = MultiplyVectorQuaternion(normal, real4(SceneSrg::m_iblOrientation));
@@ -59,22 +59,22 @@ real3 EnvBRDFApprox( real3 specularF0, real roughnessLinear, real NdotV )
 }
 
 real3 GetIblSpecular(
-    float3 position, 
-    real3 normal, 
-    real3 specularF0, 
-    real roughnessLinear, 
-    real3 dirToCamera, 
+    float3 position,
+    real3 normal,
+    real3 specularF0,
+    real roughnessLinear,
+    real3 dirToCamera,
     real2 brdf,
     ReflectionProbeData reflectionProbe,
     TextureCube reflectionProbeCubemap)
 {
     real3 reflectDir = reflect(-dirToCamera, normal);
-    reflectDir = MultiplyVectorQuaternion(reflectDir, real4(SceneSrg::m_iblOrientation));    
+    reflectDir = MultiplyVectorQuaternion(reflectDir, real4(SceneSrg::m_iblOrientation));
 
     // global
     real3 outSpecular = real3(SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb);
     outSpecular *= (specularF0 * brdf.x + brdf.y);
-    
+
     // reflection probe
     if (reflectionProbe.m_useReflectionProbe)
     {
@@ -101,9 +101,9 @@ real3 GetIblSpecular(
         outSpecular = lerp(outSpecular, probeSpecular, blendAmount);
 #else
         outSpecular = probeSpecular;
-#endif 
+#endif
     }
-    
+
     return outSpecular;
 }
 
@@ -114,7 +114,7 @@ void ApplyIBL(Surface surface, inout LightingData lightingData, bool useDiffuseI
     if(useIbl)
     {
         real globalIblExposure = pow(2.0, real(SceneSrg::m_iblExposure));
-        
+
         if(useDiffuseIbl)
         {
             real3 iblDiffuse = GetIblDiffuse(surface.GetDiffuseNormal(), surface.albedo, lightingData.diffuseResponse);
@@ -123,48 +123,53 @@ void ApplyIBL(Surface surface, inout LightingData lightingData, bool useDiffuseI
 
         if(useSpecularIbl)
         {
-            real3 iblSpecular = GetIblSpecular(surface.position, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessLinear, lightingData.dirToCamera, lightingData.brdf, reflectionProbe, reflectionProbeCubemap);
-            iblSpecular *= lightingData.multiScatterCompensation;
-            
-#if ENABLE_CLEAR_COAT
-            if (o_clearCoat_feature_enabled && surface.clearCoat.factor > 0.0)
+            // Calculate specular lighting for each view
+            [unroll]
+            for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
             {
-                real clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera));
-                clearCoatNdotV = max(clearCoatNdotV, 0.01);  // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles.
-                #ifdef CS_SAMPLERS
-                real2 clearCoatBrdf = real2(PassSrg::m_brdfMap.SampleLevel(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV), 0).rg);
-                #else
-                real2 clearCoatBrdf = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg);
-                #endif
-
-                // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat
-                // coat layer assumed to be dielectric thus don't need multiple scattering compensation
-                real3 clearCoatSpecularF0 = real3(0.04, 0.04, 0.04);
-                real3 clearCoatIblSpecular = GetIblSpecular(surface.position, surface.clearCoat.normal, clearCoatSpecularF0, surface.clearCoat.roughness, lightingData.dirToCamera, clearCoatBrdf, reflectionProbe, reflectionProbeCubemap);
-
-                clearCoatIblSpecular *= surface.clearCoat.factor;
-        
-                // attenuate base layer energy
-                real3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor;
-                iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular;
-            }
+                real3 iblSpecular = GetIblSpecular(surface.position, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessLinear, lightingData.dirToCamera[viewIndex], lightingData.brdf[viewIndex], reflectionProbe, reflectionProbeCubemap);
+                iblSpecular *= lightingData.multiScatterCompensation;
+
+#if ENABLE_CLEAR_COAT
+                if (o_clearCoat_feature_enabled && surface.clearCoat.factor > 0.0)
+                {
+                    real clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera[viewIndex]));
+                    clearCoatNdotV = max(clearCoatNdotV, 0.01);  // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles.
+                    #ifdef CS_SAMPLERS
+                    real2 clearCoatBrdf = real2(PassSrg::m_brdfMap.SampleLevel(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV), 0).rg);
+                    #else
+                    real2 clearCoatBrdf = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg);
+                    #endif
+
+                    // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat
+                    // coat layer assumed to be dielectric thus don't need multiple scattering compensation
+                    real3 clearCoatSpecularF0 = real3(0.04, 0.04, 0.04);
+                    real3 clearCoatIblSpecular = GetIblSpecular(surface.position, surface.clearCoat.normal, clearCoatSpecularF0, surface.clearCoat.roughness, lightingData.dirToCamera[viewIndex], clearCoatBrdf, reflectionProbe, reflectionProbeCubemap);
+
+                    clearCoatIblSpecular *= surface.clearCoat.factor;
+
+                    // attenuate base layer energy
+                    real3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor;
+                    iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular;
+                }
 #endif
 
 #if ENABLE_DUAL_SPECULAR
-            if(o_enableDualSpecular)
-            {
-                #ifdef CS_SAMPLERS
-                real2 dualSpecularBrdf = real2(PassSrg::m_brdfMap.SampleLevel(PassSrg::LinearSampler, GetBRDFTexCoords(surface.dualSpecRoughness, lightingData.GetSpecularNdotV()), 0).rg);
-                #else
-                real2 dualSpecularBrdf = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.dualSpecRoughness, lightingData.GetSpecularNdotV())).rg);
-                #endif
-                real3 dualSpecular = GetIblSpecular(surface.position, surface.GetSpecularNormal(), surface.dualSpecF0.xxx, surface.dualSpecRoughness, lightingData.dirToCamera, dualSpecularBrdf, reflectionProbe, reflectionProbeCubemap);
-                iblSpecular = lerp(iblSpecular, dualSpecular, surface.dualSpecFactor);
-            }
+                if(o_enableDualSpecular)
+                {
+                    #ifdef CS_SAMPLERS
+                    real2 dualSpecularBrdf = real2(PassSrg::m_brdfMap.SampleLevel(PassSrg::LinearSampler, GetBRDFTexCoords(surface.dualSpecRoughness, lightingData.GetSpecularNdotV(viewIndex)), 0).rg);
+                    #else
+                    real2 dualSpecularBrdf = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.dualSpecRoughness, lightingData.GetSpecularNdotV(viewIndex))).rg);
+                    #endif
+                    real3 dualSpecular = GetIblSpecular(surface.position, surface.GetSpecularNormal(), surface.dualSpecF0.xxx, surface.dualSpecRoughness, lightingData.dirToCamera[viewIndex], dualSpecularBrdf, reflectionProbe, reflectionProbeCubemap);
+                    iblSpecular = lerp(iblSpecular, dualSpecular, surface.dualSpecFactor);
+                }
 #endif
 
-            real exposure = reflectionProbe.m_useReflectionProbe ? pow(2.0, real(reflectionProbe.m_exposure)) : globalIblExposure;
-            lightingData.specularLighting += (iblSpecular * exposure);
+                real exposure = reflectionProbe.m_useReflectionProbe ? pow(2.0, real(reflectionProbe.m_exposure)) : globalIblExposure;
+                lightingData.specularLighting[viewIndex] += (iblSpecular * exposure);
+            }
         }
     }
 }

+ 11 - 3
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/LightTypesCommon.azsli

@@ -29,7 +29,7 @@ void AddSampleContribution(
     in real3 lightSampleDirection,
     in real bothDirectionsFactor,
     inout real3 diffuseAcc,
-    inout real3 specularAcc,
+    inout real3 specularAcc[MAX_SHADING_VIEWS],
     inout real3 translucentAcc)
 {
     real3 posToLightSample = lightSamplePoint - real3(surface.position);
@@ -38,8 +38,10 @@ void AddSampleContribution(
 
     // Lambertian emitter
     real intensity = dot(-lightSampleDirection, posToLightSampleDir);
+
     // Handle if the light emits from both sides
     intensity = abs(clamp(intensity, bothDirectionsFactor, 1.0));
+
     // Attenuate with distance
     intensity /= distanceToLight2;
 
@@ -51,13 +53,19 @@ void AddSampleContribution(
     // We cannot compute the actual transmission distance so we want to:
     // - If transmission mode is thick object -> use transmission thickness parameter instead
     // - If transmission mode is thin object -> ignore back lighting
-    // To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance 
+    // To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance
     const real transmissionDistance = -1.0;
+
     // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
     const real attenuationDistance = 0.0;
     translucentAcc += GetBackLighting(surface, lightingData, intensityRgb, posToLightSampleDir, transmissionDistance, attenuationDistance);
 
-    specularAcc += GetSpecularLighting(surface, lightingData, intensityRgb, posToLightSampleDir);
+    // Calculate specular lighting for each view
+    [unroll]
+    for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        specularAcc[viewIndex] += GetSpecularLighting(surface, lightingData, intensityRgb, posToLightSampleDir, viewIndex);
+    }
 }
 
 bool IsSameLightChannel(uint channelA, uint channelB)

+ 8 - 1
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Lights.azsli

@@ -52,7 +52,14 @@ void ApplyDirectLighting(Surface surface, inout LightingData lightingData, float
         real3 lightIntensity = real3(SceneSrg::m_debugLightingIntensity);
         real3 lightDirection = real3(SceneSrg::m_debugLightingDirection);
 
+        // Diffuse lighting is view-independent
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, lightDirection);
-        lightingData.specularLighting += GetSpecularLighting(surface, lightingData, lightIntensity, lightDirection);
+
+        // Calculate specular lighting for each view
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        {
+            lightingData.specularLighting[viewIndex] += GetSpecularLighting(surface, lightingData, lightIntensity, lightDirection, viewIndex);
+        }
     }
 }

+ 33 - 30
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ltc.azsli

@@ -1,7 +1,7 @@
 /* begin-license-attribution:
 
 This code is based in part on third-party work(s), see below for applicable
-terms and attribution information.  Modifications copyright (c) Amazon.com, 
+terms and attribution information.  Modifications copyright (c) Amazon.com,
 Inc. or its affiliates or licensors.
 
 begin-original-license-text:
@@ -12,7 +12,7 @@ All rights reserved.
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 
-* If you use (or adapt) the source code in your own work, please include a 
+* If you use (or adapt) the source code in your own work, please include a
 reference to the paper:
 
 Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
@@ -376,6 +376,7 @@ float LtcEvaluateSpecularUnscaled(
 //   ltcMat - The LTC matrix for specular, or identity for diffuse.
 //   p[4] - The 4 light positions relative to the surface position.
 //   doubleSided - If the quad emits light from both sides
+//   viewIndex - The index of the view being processed
 //   diffuse - The output diffuse response for the quad light
 //   specular - The output specular response for the quad light
 void LtcQuadEvaluate(
@@ -385,6 +386,7 @@ void LtcQuadEvaluate(
     in Texture2D<float2> ltcAmpMatrix,
     in float3 p[4],
     in bool doubleSided,
+    in uint viewIndex,
     out float diffuseOut,
     out float3 specularOut)
 {
@@ -395,7 +397,7 @@ void LtcQuadEvaluate(
     float3 polygon[5];
 
     // Transform the points of the light into the space of the normal's hemisphere and clip to the hemisphere
-    int vertexCount = LtcQuadTransformAndClip(surface.GetDefaultNormal(), lightingData.dirToCamera, p, polygon);
+    int vertexCount = LtcQuadTransformAndClip(surface.GetDefaultNormal(), lightingData.dirToCamera[viewIndex], p, polygon);
     if (vertexCount == 0)
     {
         // Entire light is below the horizon.
@@ -405,7 +407,7 @@ void LtcQuadEvaluate(
     // IntegrateQuadDiffuse is a cheap approximation compared to specular.
     float diffuse = IntegrateQuadDiffuse(polygon, vertexCount, doubleSided);
 
-    float2 ltcCoords = LtcCoords(dot(surface.GetDefaultNormal(), lightingData.dirToCamera), surface.roughnessLinear);
+    float2 ltcCoords = LtcCoords(dot(surface.GetDefaultNormal(), lightingData.dirToCamera[viewIndex]), surface.roughnessLinear);
     float specular = LtcEvaluateSpecularUnscaled(ltcCoords, ltcMatrix, polygon, vertexCount, doubleSided);
 
     // Apply BRDF scale terms (BRDF magnitude and Schlick Fresnel)
@@ -415,14 +417,14 @@ void LtcQuadEvaluate(
     float2 schlick = ltcAmpMatrix.Sample(SceneSrg::LtcSampler, ltcCoords).xy;
     #endif
     float3 specularRgb = specular * (schlick.x * surface.GetSpecularF0() + (1.0 - surface.GetSpecularF0()) * schlick.y);
-    
+
 #if ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     {
-        int vertexCountCc = LtcQuadTransformAndClip(surface.clearCoat.normal, lightingData.dirToCamera, p, polygon);
+        int vertexCountCc = LtcQuadTransformAndClip(surface.clearCoat.normal, lightingData.dirToCamera[viewIndex], p, polygon);
         if (vertexCountCc > 0)
         {
-            float2 ltcCoordsCc = LtcCoords(dot(surface.clearCoat.normal, lightingData.dirToCamera), surface.clearCoat.roughness);
+            float2 ltcCoordsCc = LtcCoords(dot(surface.clearCoat.normal, lightingData.dirToCamera[viewIndex]), surface.clearCoat.roughness);
             float clearCoatSpecular = LtcEvaluateSpecularUnscaled(ltcCoordsCc, ltcMatrix, polygon, vertexCountCc, doubleSided);
 
             // Apply BRDF scale terms (BRDF magnitude and Schlick Fresnel)
@@ -493,7 +495,7 @@ void EvaluatePolyEdge(in float3 p0, in float3 p1, in float3x3 ltcMat, inout floa
         float3 clipPoint = ClipEdge(p1, p0);
         diffuse += IntegrateEdgeDiffuse(normalize(prevClipPoint), normalize(clipPoint));
         diffuse += IntegrateEdgeDiffuse(normalize(clipPoint), normalize(p1));
-        
+
         clipPoint = mul(ltcMat, clipPoint);
         specular += IntegrateEdge(normalize(mul(ltcMat, prevClipPoint)), normalize(clipPoint));
         specular += IntegrateEdge(normalize(clipPoint), normalize(mul(ltcMat, p1)));
@@ -540,10 +542,10 @@ void LtcPolygonEvaluateInitialPoints(
 {
     // Prepare initial values
     p0 = mul(orthonormalMat, positions[startIdx].xyz - surfacePosition); // First point in polygon
-    
+
     prevClipPoint = float3(0.0, 0.0, 0.0); // Used to hold previous clip point when polygon dips below horizon.
     closePoint = p0;
-    
+
     // Handle if the first point is below the horizon.
     if (p0.z < 0.0)
     {
@@ -565,23 +567,24 @@ void LtcPolygonEvaluateInitialPoints(
 
         p0 = firstPoint; // Restore the original p0
     }
-    
+
 }
 
 // Evaluates the LTC result of an arbitrary polygon lighting a surface position.
-//   pos - The surface position
-//   normal - The surface normal
-//   dirToView - Normalized direction from the surface to the view
-//   ltcMat - The LTC matrix for specular, or identity for diffuse.
+//   surface - The surface properties
+//   lightingData - The lighting data containing view directions
+//   ltcMatrix - The LTC matrix texture
+//   ltcAmpMatrix - The LTC amplitude matrix texture
 //   positions - The buffer where the polygon positions are
 //   startIdx - The index of the first polygon position
 //   endIdx - The index of the point directly after the last polygon position
-//   diffuse - The output diffuse response for the polygon light
-//   specular - The output specular response for the polygon light
+//   viewIndex - The index of the view being processed
+//   diffuseOut - The output diffuse response for the polygon light
+//   specularRgbOut - The output specular response for the polygon light
 // The most complicated aspect of this function is clipping the polygon against the horizon of the surface point. See
 // EvaluatePolyEdge() above for details on the general concept. However, this function must deal with the case of the
-// first point being below the horizon. In that case, it needs to search in reverse from the end for the first point 
-// above the horizon, and save the intersection point between the above and below points so it can be used in 
+// first point being below the horizon. In that case, it needs to search in reverse from the end for the first point
+// above the horizon, and save the intersection point between the above and below points so it can be used in
 // EvaluatePolyEdge() later. During this search it also adjusts the end point index as necessary to avoid processing
 // those points that are below the horizon.
 void LtcPolygonEvaluate(
@@ -592,6 +595,7 @@ void LtcPolygonEvaluate(
     in StructuredBuffer<float4> positions,
     in uint startIdx,
     in uint endIdx,
+    in uint viewIndex,
     out float diffuseOut,
     out float3 specularRgbOut
 )
@@ -608,7 +612,7 @@ void LtcPolygonEvaluate(
     uint originalEndIdx = endIdx; // Original endIdx may be needed for clearcoat
 
     // Rotate ltc matrix
-    float3x3 orthonormalMat = BuildViewAlignedOrthonormalBasis(surface.GetDefaultNormal(), lightingData.dirToCamera);
+    float3x3 orthonormalMat = BuildViewAlignedOrthonormalBasis(surface.GetDefaultNormal(), lightingData.dirToCamera[viewIndex]);
 
     // Evaluate the starting point (p0), previous point, and point used to close the polygon
     float3 p0, prevClipPoint, closePoint;
@@ -619,15 +623,15 @@ void LtcPolygonEvaluate(
     {
         diffuseOut = 0.0f;
         specularRgbOut = float3(0.0f, 0.0f, 0.0f);
-        return; 
+        return;
     }
-    
+
     float diffuse = 0.0;
     float specular = 0.0;
 
-    float2 ltcCoords = LtcCoords(dot(surface.GetDefaultNormal(), lightingData.dirToCamera), surface.roughnessLinear);
+    float2 ltcCoords = LtcCoords(dot(surface.GetDefaultNormal(), lightingData.dirToCamera[viewIndex]), surface.roughnessLinear);
     float3x3 ltcMat = LtcMatrix(ltcMatrix, ltcCoords);
-    
+
     // Evaluate all the points
     for (uint curIdx = startIdx + 1; curIdx < endIdx; ++curIdx)
     {
@@ -635,7 +639,7 @@ void LtcPolygonEvaluate(
         EvaluatePolyEdge(p0, p1, ltcMat, prevClipPoint, diffuse, specular);
         p0 = p1;
     }
-    
+
     EvaluatePolyEdge(p0, closePoint, ltcMat, prevClipPoint, diffuse, specular);
 
     // Note: negated due to winding order
@@ -654,7 +658,7 @@ void LtcPolygonEvaluate(
     if(o_clearCoat_feature_enabled)
     {
         // Rotate ltc matrix
-        float3x3 orthonormalMatCc = BuildViewAlignedOrthonormalBasis(surface.clearCoat.normal, lightingData.dirToCamera);
+        float3x3 orthonormalMatCc = BuildViewAlignedOrthonormalBasis(surface.clearCoat.normal, lightingData.dirToCamera[viewIndex]);
 
         // restore original endIdx and re-evaluate initial points with matrix based on the clearcoat normal.
         endIdx = originalEndIdx;
@@ -665,9 +669,9 @@ void LtcPolygonEvaluate(
         {
             float specularCc = 0.0;
 
-            float2 ltcCoordsCc = LtcCoords(dot(surface.clearCoat.normal, lightingData.dirToCamera), surface.clearCoat.roughness);
+            float2 ltcCoordsCc = LtcCoords(dot(surface.clearCoat.normal, lightingData.dirToCamera[viewIndex]), surface.clearCoat.roughness);
             float3x3 ltcMatCc = LtcMatrix(ltcMatrix, ltcCoordsCc);
-            
+
             // Evaluate all the points
             for (uint curIdx = startIdx + 1; curIdx < endIdx; ++curIdx)
             {
@@ -675,7 +679,7 @@ void LtcPolygonEvaluate(
                 EvaluatePolyEdgeSpecularOnly(p0, p1, ltcMatCc, prevClipPoint, specularCc);
                 p0 = p1;
             }
-            
+
             EvaluatePolyEdgeSpecularOnly(p0, closePoint, ltcMatCc, prevClipPoint, specularCc);
 
             // Note: negated due to winding order
@@ -699,5 +703,4 @@ void LtcPolygonEvaluate(
 
     diffuseOut = diffuse;
     specularRgbOut = specularRgb;
-
 }

+ 46 - 31
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli

@@ -20,7 +20,7 @@
 
 int GetPointLightShadowCubemapFace(const float3 targetPos, const float3 lightPos)
 {
-    const float3 toPoint = targetPos - lightPos;    
+    const float3 toPoint = targetPos - lightPos;
     const float maxElement = max(abs(toPoint.z), max(abs(toPoint.x), abs(toPoint.y)));
     if (toPoint.x == -maxElement)
     {
@@ -29,7 +29,7 @@ int GetPointLightShadowCubemapFace(const float3 targetPos, const float3 lightPos
     else if (toPoint.x == maxElement)
     {
         return 1;
-    }   
+    }
     else if (toPoint.y == -maxElement)
     {
         return 2;
@@ -42,31 +42,31 @@ int GetPointLightShadowCubemapFace(const float3 targetPos, const float3 lightPos
     {
         return 4;
     }
-    else 
+    else
     {
         return 5;
-    }   
+    }
 }
 
-// PointLight::m_shadowIndices actually consists of uint16_t x 6 on the CPU, but visible as a uint32_t x 3 on the GPU. 
+// PointLight::m_shadowIndices actually consists of uint16_t x 6 on the CPU, but visible as a uint32_t x 3 on the GPU.
 // This function returns the proper uint16_t value given an input face in the range 0-5
 int UnpackPointLightShadowIndex(const PointLight light, const int face)
 {
     const int index = face >> 1;
     const int shiftAmount = (face & 1) * 16;
     return (light.m_shadowIndices[index] >> shiftAmount) & 0xFFFF;
-} 
+}
 
 uint ComputeShadowIndex(const PointLight light, const Surface surface)
 {
     // shadow map size and bias are the same across all shadowmaps used by a specific point light, so just grab the first one
     const uint lightIndex0 = UnpackPointLightShadowIndex(light, 0);
     const float shadowmapSize = ViewSrg::m_projectedFilterParams[lightIndex0].m_shadowmapSize;
-    
+
     // Note that the normal bias offset could potentially move the shadowed position from one map to another map inside the same point light shadow.
     const float normalBias = ViewSrg::m_projectedShadows[lightIndex0].m_normalShadowBias;
     const float3 biasedPosition = surface.position + ComputeNormalShadowOffset(normalBias, surface.vertexNormal, shadowmapSize);
-    
+
     const int shadowCubemapFace = GetPointLightShadowCubemapFace(biasedPosition, light.m_position);
     return UnpackPointLightShadowIndex(light, shadowCubemapFace);
 }
@@ -79,14 +79,14 @@ void ApplyPointLight(PointLight light, Surface surface, inout LightingData light
     real posToLightDist = length(posToLight);
     real d2 = dot(posToLight, posToLight); // light distance squared
     real falloff = d2 * real(light.m_invAttenuationRadiusSquared);
-    
+
     // Only calculate shading if light is in range
     if (falloff < 1.0)
     {
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         real radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Standard quadratic falloff
         d2 = max(0.001 * 0.001, d2); // clamp the light to at least 1mm away to avoid extreme values.
         real3 lightIntensity = (real3(light.m_rgbIntensityCandelas) / d2) * radiusAttenuation;
@@ -132,22 +132,25 @@ void ApplyPointLight(PointLight light, Surface surface, inout LightingData light
         lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), transmissionDistance, posToLightDist);
 #endif
 
-        // Adjust the light direcion for specular based on bulb size
+        // Calculate specular lighting for each view
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        {
+            // Calculate the reflection off the normal from the view direction
+            real3 reflectionDir = reflect(-lightingData.dirToCamera[viewIndex], surface.GetSpecularNormal());
 
-        // Calculate the reflection off the normal from the view direction
-        real3 reflectionDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
+            // Calculate a vector from the reflection vector to the light
+            real3 reflectionPosToLight = posToLight - dot(posToLight, reflectionDir) * reflectionDir;
 
-        // Calculate a vector from the reflection vector to the light
-        real3 reflectionPosToLight = posToLight - dot(posToLight, reflectionDir) * reflectionDir;
+            // Adjust the direction to light based on the bulb size
+            real3 viewPosToLight = posToLight - reflectionPosToLight * saturate(real(light.m_bulbRadius) / length(reflectionPosToLight));
 
-        // Adjust the direction to light based on the bulb size
-        posToLight -= reflectionPosToLight * saturate(real(light.m_bulbRadius) / length(reflectionPosToLight));
-        
-        // Adjust the intensity of the light based on the bulb size to conserve energy
-        real sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, real(light.m_bulbRadius), d2);
+            // Adjust the intensity of the light based on the bulb size to conserve energy
+            real sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, real(light.m_bulbRadius), d2);
 
-        // Specular contribution
-        lightingData.specularLighting += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(posToLight)) * litRatio;
+            // Specular contribution
+            lightingData.specularLighting[viewIndex] += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(viewPosToLight), viewIndex) * litRatio;
+        }
     }
 }
 
@@ -164,9 +167,15 @@ void ValidatePointLight(PointLight light, Surface surface, inout LightingData li
 {
     const uint sampleCount = 512;
 
-    real3 diffuseAcc = real3(0.0, 0.0, 0.0);
-    real3 specularAcc = real3(0.0, 0.0, 0.0);
-    real3 translucentAcc = real3(0.0, 0.0, 0.0);
+    real3 diffuseAcc = float3(0.0, 0.0, 0.0);
+    real3 translucentAcc = float3(0.0, 0.0, 0.0);
+    real3 specularAcc[MAX_SHADING_VIEWS];
+    
+    [unroll]
+    for(uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        specularAcc[viewIndex] = float3(0.0, 0.0, 0.0);
+    }
 
     for (uint i = 0; i < sampleCount; ++i)
     {
@@ -178,12 +187,18 @@ void ValidatePointLight(PointLight light, Surface surface, inout LightingData li
 
     // Lighting value is in Candela, convert to Lumen for total light output of the light
     real3 intensityLumens = real3(light.m_rgbIntensityCandelas) * 4.0 * PI;
+
     // Each of the N samples will contribute intensity / N lumens. However it will radiate in
     // equal directions across the hemisphere, so we need to account for that
     real3 intensity = intensityLumens * INV_PI;
 
     lightingData.diffuseLighting += (diffuseAcc / real(sampleCount)) * intensity;
-    lightingData.specularLighting += (specularAcc / real(sampleCount)) * intensity;
+
+    [unroll]
+    for(uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        lightingData.specularLighting[viewIndex] += (specularAcc[viewIndex] / real(sampleCount)) * intensity;
+    }
 
 #if ENABLE_TRANSMISSION
     lightingData.translucentBackLighting += (translucentAcc / real(sampleCount)) * intensity;
@@ -216,12 +231,12 @@ void ApplyPointLights(Surface surface, inout LightingData lightingData)
 {
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
-    
-    while( !lightingData.tileIterator.IsDone() ) 
-    { 
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+
+    while( !lightingData.tileIterator.IsDone() )
+    {
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
-    
+
         ApplyPointLightInternal(currLightIndex, surface, lightingData);
     }
 #else

+ 22 - 11
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PolygonLight.azsli

@@ -19,7 +19,7 @@ void ApplyPoylgonLight(PolygonLight light, Surface surface, inout LightingData l
     float3 posToLight = light.m_position - surface.position;
     float distanceToLight2 = dot(posToLight, posToLight); // light distance squared
     float falloff = distanceToLight2 * abs(light.m_invAttenuationRadiusSquared);
-    
+
     if (falloff > 1.0f)
     {
         return; // light out of range
@@ -43,19 +43,30 @@ void ApplyPoylgonLight(PolygonLight light, Surface surface, inout LightingData l
     float radiusAttenuation = 1.0 - (falloff * falloff);
     radiusAttenuation = radiusAttenuation * radiusAttenuation;
 
-    float diffuse = 0.0;
-    float3 specularRgb = 0.0;
+    // Scale by inverse surface area of hemisphere (1/2pi), attenuation, and light intensity
+    float3 intensity = 0.5 * INV_PI * radiusAttenuation * abs(light.m_rgbIntensityNits);
 
-    LtcPolygonEvaluate(surface, lightingData, SceneSrg::m_ltcMatrix, SceneSrg::m_ltcAmplification, ViewSrg::m_polygonLightPoints, startIndex, endIndex, diffuse, specularRgb);
+    // Calculate lighting for each view
+    [unroll]
+    for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        float diffuse = 0.0;
+        float3 specularRgb = 0.0;
 
-    diffuse = doubleSided ? abs(diffuse) : max(0.0, diffuse);
-    specularRgb = doubleSided ? abs(specularRgb) : max(0.0, specularRgb);
+        LtcPolygonEvaluate(surface, lightingData, SceneSrg::m_ltcMatrix, SceneSrg::m_ltcAmplification,
+                           ViewSrg::m_polygonLightPoints, startIndex, endIndex, viewIndex, diffuse, specularRgb);
 
-    // Scale by inverse surface area of hemisphere (1/2pi), attenuation, and light intensity
-    float3 intensity = 0.5 * INV_PI * radiusAttenuation * abs(light.m_rgbIntensityNits);
+        diffuse = doubleSided ? abs(diffuse) : max(0.0, diffuse);
+        specularRgb = doubleSided ? abs(specularRgb) : max(0.0, specularRgb);
 
-    lightingData.diffuseLighting += surface.albedo * diffuse * intensity;
-    lightingData.specularLighting += specularRgb * intensity;
+        // Only add diffuse lighting once (for the first view)
+        if (viewIndex == 0)
+        {
+            lightingData.diffuseLighting += surface.albedo * diffuse * intensity;
+        }
+
+        lightingData.specularLighting[viewIndex] += specularRgb * intensity;
+    }
 }
 
 void ApplyPolygonLights(Surface surface, inout LightingData lightingData)
@@ -68,7 +79,7 @@ void ApplyPolygonLights(Surface surface, inout LightingData lightingData)
             if(!IsSameLightChannel(lightingData.lightingChannels, light.m_lightingChannelMask))
             {
                 continue;
-            }            
+            }
             ApplyPoylgonLight(light, surface, lightingData);
         }
     }

+ 73 - 58
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/QuadLight.azsli

@@ -21,7 +21,7 @@ float RectangleSolidAngle(float3 v0, float3 v1, float3 v2, float3 v3)
     float3 n1 = normalize(cross(v1, v2));
     float3 n2 = normalize(cross(v2, v3));
     float3 n3 = normalize(cross(v3, v0));
-    
+
     float g0 = acos(dot (-n0, n1));
     float g1 = acos(dot (-n1, n2));
     float g2 = acos(dot (-n2, n3));
@@ -36,7 +36,7 @@ float3 RayLightPlaneIntersection(in float3 pos, in float3 rayDirection, in float
 {
     float3 lightToPos = pos - lightOrigin;
     float reflectionDotLight = dot(rayDirection, -lightDirection);
-    
+
     float distanceToPlane = dot(lightToPos, lightDirection);
     if (reflectionDotLight >= 0.0001)
     {
@@ -106,16 +106,30 @@ void ApplyQuadLight(QuadLight light, Surface surface, inout LightingData lightin
         if (!useFastApproximation && o_enableQuadLightLTC)
         {
             float3 p[4] = {p0, p1, p2, p3};
-            
+
             float diffuse = 0.0;
             float3 specular = float3(0.0, 0.0, 0.0); // specularF0 used in LtcQuadEvaluate which is a float3
-            LtcQuadEvaluate(surface, lightingData, SceneSrg::m_ltcMatrix, SceneSrg::m_ltcAmplification, p, doubleSided, diffuse, specular);
 
-            // Scale by inverse surface area of hemisphere (1/2pi), attenuation, and light intensity
-            float3 intensity = 0.5 * INV_PI * radiusAttenuation * light.m_rgbIntensityNits;
+            // Calculate lighting for each view
+            [unroll]
+            for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+            {
+                float viewDiffuse = 0.0;
+                float3 viewSpecular = float3(0.0, 0.0, 0.0);
 
-            lightingData.diffuseLighting += surface.albedo * diffuse * intensity;
-            lightingData.specularLighting += specular * intensity;
+                LtcQuadEvaluate(surface, lightingData, SceneSrg::m_ltcMatrix, SceneSrg::m_ltcAmplification, p, doubleSided, viewIndex, viewDiffuse, viewSpecular);
+
+                // Scale by inverse surface area of hemisphere (1/2pi), attenuation, and light intensity
+                float3 intensity = 0.5 * INV_PI * radiusAttenuation * light.m_rgbIntensityNits;
+
+                // Only add diffuse lighting once (for the first view)
+                if (viewIndex == 0)
+                {
+                    lightingData.diffuseLighting += surface.albedo * viewDiffuse * intensity;
+                }
+
+                lightingData.specularLighting[viewIndex] += viewSpecular * intensity;
+            }
         }
         else if (useFastApproximation && o_enableQuadLightApprox)
         {
@@ -134,7 +148,7 @@ void ApplyQuadLight(QuadLight light, Surface surface, inout LightingData lightin
             // Each position contributes 1/5 of the light (4 corners + center)
             float3 intensity = solidAngle * 0.2 * radiusAttenuation * light.m_rgbIntensityNits;
 
-            lightingData.diffuseLighting += 
+            lightingData.diffuseLighting +=
             (
                 GetDiffuseLighting(surface, lightingData, intensity, p0) +
                 GetDiffuseLighting(surface, lightingData, intensity, p1) +
@@ -142,12 +156,12 @@ void ApplyQuadLight(QuadLight light, Surface surface, inout LightingData lightin
                 GetDiffuseLighting(surface, lightingData, intensity, p3) +
                 GetDiffuseLighting(surface, lightingData, intensity, dirToLightCenter)
             );
-            
+
             // Transmission contribution
             // We cannot compute the actual transmission distance so we want to:
             // - If transmission mode is thick object -> use transmission thickness parameter instead
             // - If transmission mode is thin object -> ignore back lighting
-            // To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance 
+            // To detect and apply this behavior in the GetBackLighting function, we need to use a negative transmissionDistance
             const float transmissionDistance = -1.0f;
             // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
             const float attenuationDistance = 0.0f;
@@ -162,45 +176,35 @@ void ApplyQuadLight(QuadLight light, Surface surface, inout LightingData lightin
                 GetBackLighting(surface, lightingData, intensity, dirToLightCenter, transmissionDistance, attenuationDistance)
             );
 #endif
-            
-            // Calculate specular by choosing a single representative point on the light's surface based on the reflection ray
-            // Then adjusting it's brightness based on different factors.
-
-            // Calculate the reflection ray from the view direction and surface normal
-            float3 reflectionDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
-            
-            // First find the reflection-plane intersection, then find the closest point on the rectangle to that intersection.
-            float2 halfSize = float2(light.m_halfWidth, light.m_halfHeight);
-            float3 positionOnPlane = RayLightPlaneIntersection(surface.position, reflectionDir, light.m_position, lightDirection);
-            float3 lightPositionWorld = ClosestPointRect(positionOnPlane, light.m_position, light.m_leftDir, light.m_upDir, halfSize);
-            float3 lightPositionLocal = lightPositionWorld - surface.position;
-
-            // It's possible that the reflection-plane intersection is far from where the reflection ray passes the light when the light
-            // is at a grazing angle. This can cause the "closest" point to tend towards the corners of the rectangle. To correct this,
-            // we find the closest point on the reflection line to this first attempt, and use it for a second attempt.
-            float3 localPositionOnLine = reflectionDir * dot(reflectionDir, lightPositionLocal);
-            lightPositionLocal = ClosestPointRect(localPositionOnLine, posToLight, light.m_leftDir, light.m_upDir, halfSize);
-
-            float3 dirToLight = normalize(lightPositionLocal);
-            float lightPositionDist2 = dot(lightPositionLocal, lightPositionLocal);
-
-            // Adjust the specular intensity based on the roughness, solid angle, and distance to the light.
-            // The following are approximations that attempt to mimic ground truth results
-
-            // Rough surfaces reflect light from their entire hemisphere, so they should use the solid angle. This causes nearby quad lights to look more
-            // accurate at the cost of far away quad lights having dimmer specular than they should.
-            float rough = surface.roughnessA; // shorten the name for readability.
-            float roughnessAdjustment = lerp(0.0, solidAngle, pow(rough, 1.5));
-            // Decrease intensity when the light position doesn't line up well with the dominant reflection direction.
-            float3 dominantDir = GetSpecularDominantDirection(surface.GetSpecularNormal(), reflectionDir, rough);
-            roughnessAdjustment *= max(0.0f, dot(dirToLight, dominantDir)) * INV_PI; // INV_PI is here to make brightness match ground truth result more accurately for nearby lights.
-            // Smooth surfaces need to increase light intensity with distance to maintain constant perceptual
-            // intensity depending on the solid angle of the light.
-            float solidAngleCoverage = solidAngle / (2.0 * PI); // Adjust solid angle to 0-1 range of how much of the hemisphere is covered.
-            float distanceAdjustment = 1.0 + sqrt(lightPositionDist2) * (1.0 - rough * rough * solidAngleCoverage);
-
-            float3 specularintensity = light.m_rgbIntensityNits * roughnessAdjustment * distanceAdjustment;
-            lightingData.specularLighting += GetSpecularLighting(surface, lightingData, specularintensity, dirToLight) * radiusAttenuation;
+
+            // Calculate specular lighting for each view
+            [unroll]
+            for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+            {
+                // Calculate the reflection ray from the view direction and surface normal
+                float3 reflectionDir = reflect(-lightingData.dirToCamera[viewIndex], surface.GetSpecularNormal());
+
+                // First find the reflection-plane intersection, then find the closest point on the rectangle to that intersection.
+                float2 halfSize = float2(light.m_halfWidth, light.m_halfHeight);
+                float3 positionOnPlane = RayLightPlaneIntersection(surface.position, reflectionDir, light.m_position, lightDirection);
+                float3 lightPositionWorld = ClosestPointRect(positionOnPlane, light.m_position, light.m_leftDir, light.m_upDir, halfSize);
+                float3 lightPositionLocal = lightPositionWorld - surface.position;
+
+                // It's possible that the reflection-plane intersection is far from where the reflection ray passes the light when the light
+                // is at a grazing angle. This can cause the "closest" point to tend towards the corners of the rectangle. To correct this,
+                // we find the closest point on the reflection line to this first attempt, and use it for a second attempt.
+                float3 localPositionOnLine = reflectionDir * dot(reflectionDir, lightPositionLocal);
+                lightPositionLocal = ClosestPointRect(localPositionOnLine, posToLight, light.m_leftDir, light.m_upDir, halfSize);
+
+                float3 dirToLight = normalize(lightPositionLocal);
+                float lightPositionDist2 = dot(lightPositionLocal, lightPositionLocal);
+
+                // Adjust the intensity of the light based on the bulb size to conserve energy
+                float quadIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, 0.0, lightPositionDist2);
+
+                // Specular contribution
+                lightingData.specularLighting[viewIndex] += quadIntensityNormalization * GetSpecularLighting(surface, lightingData, intensity, dirToLight, viewIndex);
+            }
         }
     }
 }
@@ -221,9 +225,15 @@ void ValidateQuadLight(QuadLight light, Surface surface, inout LightingData ligh
 
     const uint sampleCount = 512;
 
-    float3 diffuseAcc = float3(0.0, 0.0, 0.0);
-    float3 specularAcc = float3(0.0, 0.0, 0.0);
-    float3 translucentAcc = float3(0.0, 0.0, 0.0);
+    real3 diffuseAcc = float3(0.0, 0.0, 0.0);
+    real3 translucentAcc = float3(0.0, 0.0, 0.0);
+    real3 specularAcc[MAX_SHADING_VIEWS];
+    
+    [unroll]
+    for(uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        specularAcc[viewIndex] = float3(0.0, 0.0, 0.0);
+    }
 
     bool emitsBothDirections = (light.m_flags & EmitsBothDirections) > 0;
     float bothDirectionsFactor = emitsBothDirections ? -1.0 : 0.0;
@@ -239,7 +249,12 @@ void ValidateQuadLight(QuadLight light, Surface surface, inout LightingData ligh
     float3 intensityCandelas = light.m_rgbIntensityNits * area;
 
     lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * intensityCandelas;
-    lightingData.specularLighting += (specularAcc / float(sampleCount)) * intensityCandelas;
+
+    [unroll]
+    for(uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        lightingData.specularLighting[viewIndex] += (specularAcc[viewIndex] / float(sampleCount)) * intensityCandelas;
+    }
 
 #if ENABLE_TRANSMISSION
     lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensityCandelas;
@@ -272,10 +287,10 @@ void ApplyQuadLights(Surface surface, inout LightingData lightingData)
 {
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
-                
-    while( !lightingData.tileIterator.IsDone() ) 
-    { 
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+
+    while( !lightingData.tileIterator.IsDone() )
+    {
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
 
         ApplyQuadLightInternal(currLightIndex, surface, lightingData);

+ 15 - 10
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/SimplePointLight.azsli

@@ -15,14 +15,14 @@ void ApplySimplePointLight(SimplePointLight light, Surface surface, inout Lighti
     real3 posToLight = real3(light.m_position - surface.position);
     real d2 = dot(posToLight, posToLight); // light distance squared
     real falloff = d2 * real(light.m_invAttenuationRadiusSquared);
-    
+
     // Only calculate shading if light is in range
     if (falloff < 1.0f)
     {
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         real radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Standard quadratic falloff
         d2 = max(0.001 * 0.001, d2); // clamp the light to at least 1mm away to avoid extreme values.
         real3 lightIntensity = (real3(light.m_rgbIntensityCandelas) / d2) * radiusAttenuation;
@@ -31,8 +31,13 @@ void ApplySimplePointLight(SimplePointLight light, Surface surface, inout Lighti
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, posToLightDir);
 
-        // Specular contribution
-        lightingData.specularLighting += GetSpecularLighting(surface, lightingData, lightIntensity, posToLightDir);
+        // Calculate specular lighting for each view
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        {
+            // Specular contribution
+            lightingData.specularLighting[viewIndex] += GetSpecularLighting(surface, lightingData, lightIntensity, posToLightDir, viewIndex);
+        }
     }
 }
 
@@ -52,12 +57,12 @@ void ApplySimplePointLightInternal(uint lightIndex, Surface surface, inout Light
 void ApplySimplePointLights(Surface surface, inout LightingData lightingData)
 {
 #if ENABLE_LIGHT_CULLING
-    lightingData.tileIterator.LoadAdvance();    
-    while( !lightingData.tileIterator.IsDone() ) 
-    { 
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+    lightingData.tileIterator.LoadAdvance();
+    while( !lightingData.tileIterator.IsDone() )
+    {
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
-    
+
         ApplySimplePointLightInternal(currLightIndex, surface, lightingData);
     }
 #else
@@ -65,7 +70,7 @@ void ApplySimplePointLights(Surface surface, inout LightingData lightingData)
     // For perf we cap light count. If it was not set by the pipeline it will use the value specified below
     // This is only applicable if ENABLE_LIGHT_CULLING is disabled (i.e no gpu culling)
     #ifndef ENABLE_SIMPLE_POINTLIGHTS_CAP
-        #define ENABLE_SIMPLE_POINTLIGHTS_CAP 20 
+        #define ENABLE_SIMPLE_POINTLIGHTS_CAP 20
     #endif
 
     // Since there's no GPU culling for simple point lights, we rely on culling done by CPU

+ 18 - 13
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/SimpleSpotLight.azsli

@@ -17,7 +17,7 @@
 void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout LightingData lightingData)
 {
     real3 posToLight = real3(light.m_position - surface.position);
-    
+
     real3 dirToLight = normalize(posToLight);
     real dotWithDirection = dot(dirToLight, -real3(light.m_direction));
 
@@ -37,7 +37,7 @@ void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout Lighting
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         real radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Standard quadratic falloff
         d2 = max(0.001 * 0.001, d2); // clamp the light to at least 1mm away to avoid extreme values.
         real3 lightIntensity = (real3(light.m_rgbIntensityCandelas) / d2) * radiusAttenuation;
@@ -73,21 +73,26 @@ void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout Lighting
         }
 
         if (dotWithDirection < cosInnerConeAngle) // in penumbra
-        {   
+        {
             // Normalize into 0.0 - 1.0 space.
             real penumbraMask = (dotWithDirection - real(light.m_cosOuterConeAngle)) / (cosInnerConeAngle - real(light.m_cosOuterConeAngle));
-            
+
             // Apply smoothstep
             penumbraMask = penumbraMask * penumbraMask * (3.0 - 2.0 * penumbraMask);
-            
+
             lightIntensity *= penumbraMask;
         }
 
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, posToLightDir) * litRatio;
 
-        // Specular contribution
-        lightingData.specularLighting += GetSpecularLighting(surface, lightingData, lightIntensity, posToLightDir);
+        // Calculate specular lighting for each view
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+        {
+            // Specular contribution
+            lightingData.specularLighting[viewIndex] += GetSpecularLighting(surface, lightingData, lightIntensity, posToLightDir, viewIndex) * litRatio;
+        }
     }
 }
 
@@ -108,12 +113,12 @@ void ApplySimpleSpotLights(Surface surface, inout LightingData lightingData)
 {
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
-                
-    while( !lightingData.tileIterator.IsDone() ) 
-    { 
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+
+    while( !lightingData.tileIterator.IsDone() )
+    {
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
-    
+
         ApplySimpleSpotLightInternal(currLightIndex, surface, lightingData);
     }
 #else
@@ -121,7 +126,7 @@ void ApplySimpleSpotLights(Surface surface, inout LightingData lightingData)
     // For perf we cap light count. If it was not set by the pipeline it will use the value specified below
     // This is only applicable if ENABLE_LIGHT_CULLING is disabled (i.e no gpu culling)
     #ifndef ENABLE_SIMPLE_SPOTLIGHTS_CAP
-        #define ENABLE_SIMPLE_SPOTLIGHTS_CAP 20 
+        #define ENABLE_SIMPLE_SPOTLIGHTS_CAP 20
     #endif
 
     // Since there's no GPU culling for simple spot lights, we rely on culling done by CPU

+ 1 - 1
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Microfacet/Brdf.azsli

@@ -166,7 +166,7 @@ real3 ClearCoatGGX(real NdotH, real HdotL, real NdotL, real3 normal, real roughn
     G = max(0.0, G);
 
     // the common denominator 4 * NdotL * NdotV is included in the simplified G term
-    return D * G * F * NdotL;
+    return real(D) * G * F * NdotL;
 }
 
 real3 ClearCoatSpecular(const real3 dirToLight, const real3 dirToCamera, const real3 surfaceClearCoatNormal, real surfaceClearCoatFactor, real surfaceClearCoatRoughness, const real3 specular)

+ 5 - 5
Gems/Atom/Feature/Common/Assets/Shaders/Materials/BasePBR/BasePBR_LightingBrdf.azsli

@@ -16,7 +16,7 @@
 #endif
 
 #ifndef GetSpecularLighting
-#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight)      GetSpecularLighting_BasePBR(surface, lightingData, lightIntensity, dirToLight)
+#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight, viewIndex)      GetSpecularLighting_BasePBR(surface, lightingData, lightIntensity, dirToLight, viewIndex)
 #endif
 
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
@@ -36,12 +36,12 @@ real3 GetDiffuseLighting_BasePBR(Surface surface, LightingData lightingData, rea
     return diffuse;
 }
 
-real3 GetSpecularLighting_BasePBR(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight)
+real3 GetSpecularLighting_BasePBR(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight, uint viewIndex)
 {
-#if ENABLE_MOBILEBRDF    
-    real3 specular = SpecularGGXMobile(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessA2, surface.roughnessA, surface.roughnessLinear); 
+#if ENABLE_MOBILEBRDF
+    real3 specular = SpecularGGXMobile(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessA2, surface.roughnessA, surface.roughnessLinear);
 #else
-    real3 specular = SpecularGGX(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(), surface.roughnessA2, lightingData.multiScatterCompensation);
+    real3 specular = SpecularGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(viewIndex), surface.roughnessA2, lightingData.multiScatterCompensation);
 #endif
     specular *= lightIntensity;
     return specular;

+ 45 - 26
Gems/Atom/Feature/Common/Assets/Shaders/Materials/BasePBR/BasePBR_LightingData.azsli

@@ -10,7 +10,7 @@
 
 // This #define magic lets you use the LightingData class in this file without making it the final LightingData
 // used in your shader. Simply #define LightingData to your custom definition before including this file
-// 
+//
 #ifndef LightingData
 #define LightingData        LightingData_BasePBR
 #endif
@@ -21,25 +21,33 @@
 #include <Atom/Features/PBR/LightingUtils.azsli>
 #include <Atom/Features/PBR/LightingOptions.azsli>
 
+#ifndef MAX_SHADING_VIEWS
+#define MAX_SHADING_VIEWS 1
+#endif
+
+#ifndef GET_SHADING_VIEW_COUNT
+#define GET_SHADING_VIEW_COUNT 1
+#endif
+
 class LightingData_BasePBR
 {
     LightCullingTileIterator tileIterator;
-    
+
     uint lightingChannels;
-    
+
     // Lighting outputs
     real3 diffuseLighting;
-    real3 specularLighting;
+    real3 specularLighting[MAX_SHADING_VIEWS];
 
     // Factors for the amount of diffuse and specular lighting applied
     real3 diffuseResponse;
-   
+
     // Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
     real shrinkFactor;
 
     // Normalized direction from surface to camera
-    float3 dirToCamera;
-    
+    float3 dirToCamera[MAX_SHADING_VIEWS];
+
     // Scaling term to approximate multiscattering contribution in specular BRDF
     real3 multiScatterCompensation;
 
@@ -47,10 +55,10 @@ class LightingData_BasePBR
     real3 emissiveLighting;
 
     // BRDF texture values
-    real2 brdf;
+    real2 brdf[MAX_SHADING_VIEWS];
 
     // Normal . View
-    float NdotV;
+    float NdotV[MAX_SHADING_VIEWS];
 
 #if ENABLE_TRANSMISSION
     real3 translucentBackLighting;
@@ -60,16 +68,16 @@ class LightingData_BasePBR
     real diffuseAmbientOcclusion;
     real specularOcclusion;
 
-    void Init(float3 positionWS, real3 specularNormal, real roughnessLinear, float3 viewPosition);
+    void Init(float3 positionWS, real3 specularNormal, real roughnessLinear, float3 viewPosition[MAX_SHADING_VIEWS]);
     void CalculateMultiscatterCompensation(real3 specularF0, bool enabled);
     void FinalizeLighting(Surface surface);
-    float GetSpecularNdotV() { return NdotV; }
+    float GetSpecularNdotV() { return NdotV[0]; }
+    float GetSpecularNdotV(uint i) { return NdotV[i]; }
 };
 
-void LightingData_BasePBR::Init(float3 positionWS, real3 specularNormal, real roughnessLinear, float3 viewPosition)
+void LightingData_BasePBR::Init(float3 positionWS, real3 specularNormal, real roughnessLinear, float3 viewPosition[MAX_SHADING_VIEWS])
 {
     diffuseLighting = real3(0.0, 0.0, 0.0);
-    specularLighting = real3(0.0, 0.0, 0.0);
     multiScatterCompensation = real3(1.0, 1.0, 1.0);
     emissiveLighting = real3(0.0, 0.0, 0.0);
 #if ENABLE_TRANSMISSION
@@ -77,35 +85,46 @@ void LightingData_BasePBR::Init(float3 positionWS, real3 specularNormal, real ro
 #endif
     diffuseAmbientOcclusion = 1.0;
     specularOcclusion = 1.0;
-        
+
     lightingChannels = 0xFFFFFFFF;
 
-    dirToCamera = normalize(viewPosition - positionWS);
+    [unroll]
+    for(uint i = 0; i < GET_SHADING_VIEW_COUNT; ++i)
+    {
+        specularLighting[i] = real3(0.0, 0.0, 0.0);
+        dirToCamera[i] = normalize(viewPosition[i] - positionWS);
+        NdotV[i] = saturate(dot(specularNormal, dirToCamera[i]));
 
-    // sample BRDF map (indexed by smoothness values rather than roughness)
-    NdotV = saturate(dot(specularNormal, dirToCamera));
-    float2 brdfUV = float2(NdotV, (1.0 - roughnessLinear));
+        // sample BRDF map (indexed by smoothness values rather than roughness)
+        float2 brdfUV = float2(NdotV[i], (1.0 - roughnessLinear));
     #ifdef CS_SAMPLERS
-    brdf = real2(PassSrg::m_brdfMap.SampleLevel(PassSrg::LinearSampler, brdfUV, 0).rg);
+        brdf[i] = real2(PassSrg::m_brdfMap.SampleLevel(PassSrg::LinearSampler, brdfUV, 0).rg);
     #else
-    brdf = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg);
+        brdf[i] = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg);
     #endif
+    }
 }
 
 void LightingData_BasePBR::CalculateMultiscatterCompensation(real3 specularF0, bool enabled)
 {
-    multiScatterCompensation = GetMultiScatterCompensation(specularF0, brdf, enabled);
+    // Default to using 0 for brdf viewIndex as we don't want to pay the VGPR cost of
+    // a real3 per view for multiScatterCompensation as it's a minor effect
+    multiScatterCompensation = GetMultiScatterCompensation(specularF0, brdf[0], enabled);
 }
 
 void LightingData_BasePBR::FinalizeLighting(Surface surface)
 {
-    specularLighting *= specularOcclusion;
-    diffuseLighting *= diffuseAmbientOcclusion;
-
-    if(!IsSpecularLightingEnabled())
+    [unroll]
+    for(uint i = 0; i < GET_SHADING_VIEW_COUNT; ++i)
     {
-        specularLighting = real3(0, 0, 0);
+        specularLighting[i] *= specularOcclusion;
+        if(!IsSpecularLightingEnabled())
+        {
+            specularLighting[i] = real3(0, 0, 0);
+        }
     }
+
+    diffuseLighting *= diffuseAmbientOcclusion;
     if(!IsDiffuseLightingEnabled())
     {
         diffuseLighting = real3(0, 0, 0);

+ 5 - 5
Gems/Atom/Feature/Common/Assets/Shaders/Materials/BasePBR/BasePBR_LightingEval.azsli

@@ -19,7 +19,7 @@
 #include <Atom/Features/PBR/Lights/IblForward.azsli>
 #include <Atom/Features/PBR/Decals.azsli>
 
-void InitializeLightingData_BasePBR(inout Surface surface, float4 screenPosition, float3 viewPosition, inout LightingData lightingData)
+void InitializeLightingData_BasePBR(inout Surface surface, float4 screenPosition, float3 viewPositions[MAX_SHADING_VIEWS], inout LightingData lightingData)
 {
     // Light iterator
 #if ENABLE_LIGHT_CULLING
@@ -28,10 +28,10 @@ void InitializeLightingData_BasePBR(inout Surface surface, float4 screenPosition
     lightingData.tileIterator.InitNoCulling();
 #endif
 
-    lightingData.Init(surface.position, surface.GetSpecularNormal(), surface.roughnessLinear, viewPosition);
+    lightingData.Init(surface.position, surface.GetSpecularNormal(), surface.roughnessLinear, viewPositions);
 
     lightingData.lightingChannels = ObjectSrg::m_lightingChannelMask;
-    
+
     // Diffuse and Specular response (used in IBL calculations)
     real3 specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.GetSpecularF0(), surface.roughnessLinear);
     lightingData.diffuseResponse = 1.0 - specularResponse;
@@ -55,10 +55,10 @@ void CalculateLighting_BasePBR(inout Surface surface, float4 screenPosition, ino
     lightingData.FinalizeLighting(surface);
 }
 
-LightingData EvaluateLighting_BasePBR(inout Surface surface, float4 screenPosition, float3 viewPosition)
+LightingData EvaluateLighting_BasePBR(inout Surface surface, float4 screenPosition, float3 viewPositions[MAX_SHADING_VIEWS])
 {
     LightingData lightingData;
-    InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
+    InitializeLightingData_BasePBR(surface, screenPosition, viewPositions, lightingData);
     CalculateLighting_BasePBR(surface, screenPosition, lightingData);
     return lightingData;
 }

+ 11 - 11
Gems/Atom/Feature/Common/Assets/Shaders/Materials/EnhancedPBR/EnhancedPBR_LightingBrdf.azsli

@@ -12,11 +12,11 @@
 // used in your shader. Simply #define GetDiffuseLighting & GetSpecularLighting to your custom definition before including this file
 //
 #ifndef GetDiffuseLighting
-#define GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight)       GetDiffuseLighting_EnhancedPBR(surface, lightingData, lightIntensity, dirToLight)
+#define GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight)                  GetDiffuseLighting_EnhancedPBR(surface, lightingData, lightIntensity, dirToLight)
 #endif
 
 #ifndef GetSpecularLighting
-#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight)      GetSpecularLighting_EnhancedPBR(surface, lightingData, lightIntensity, dirToLight)
+#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight, viewIndex)      GetSpecularLighting_EnhancedPBR(surface, lightingData, lightIntensity, dirToLight, viewIndex)
 #endif
 
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
@@ -37,7 +37,7 @@ real3 GetDiffuseLighting_EnhancedPBR(Surface surface, LightingData lightingData,
     if(o_enableSubsurfaceScattering)
     {
         // Use diffuse brdf contains double Fresnel (enter/exit surface) terms if subsurface scattering is enabled
-        diffuse = NormalizedDisneyDiffuse(surface.albedo, surface.GetDiffuseNormal(), lightingData.dirToCamera, dirToLight, surface.roughnessLinear);
+        diffuse = NormalizedDisneyDiffuse(surface.albedo, surface.GetDiffuseNormal(), lightingData.dirToCamera[0], dirToLight, surface.roughnessLinear);
     }
     else
     {
@@ -48,7 +48,7 @@ real3 GetDiffuseLighting_EnhancedPBR(Surface surface, LightingData lightingData,
     if(o_clearCoat_feature_enabled)
     {
         // Attenuate diffuse term by clear coat's fresnel term to account for energy loss
-        real HdotV = saturate(dot(normalize(dirToLight + lightingData.dirToCamera), lightingData.dirToCamera));
+        real HdotV = saturate(dot(normalize(dirToLight + lightingData.dirToCamera[0]), lightingData.dirToCamera[0]));
         diffuse *= 1.0 - (FresnelSchlick(HdotV, 0.04) * surface.clearCoat.factor);
     }
 #endif
@@ -57,30 +57,30 @@ real3 GetDiffuseLighting_EnhancedPBR(Surface surface, LightingData lightingData,
     return diffuse;
 }
 
-real3 GetSpecularLighting_EnhancedPBR(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight)
+real3 GetSpecularLighting_EnhancedPBR(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight, uint viewIndex)
 {
     real3 specular;
     if (o_enableAnisotropy)
     {
-        specular = AnisotropicGGX( lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.anisotropy.tangent, surface.anisotropy.bitangent, surface.anisotropy.anisotropyFactors,
-                       surface.GetSpecularF0(), lightingData.GetSpecularNdotV(), lightingData.multiScatterCompensation );
+        specular = AnisotropicGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.anisotropy.tangent, surface.anisotropy.bitangent, surface.anisotropy.anisotropyFactors,
+                       surface.GetSpecularF0(), lightingData.GetSpecularNdotV(viewIndex), lightingData.multiScatterCompensation);
     }
     else
     {
 #if ENABLE_MOBILEBRDF
-        specular = SpecularGGXMobile(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessA2, surface.roughnessA, surface.roughnessLinear);
+        specular = SpecularGGXMobile(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessA2, surface.roughnessA, surface.roughnessLinear);
 #else
-        specular = SpecularGGX(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(), surface.roughnessA2, lightingData.multiScatterCompensation);
+        specular = SpecularGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(viewIndex), surface.roughnessA2, lightingData.multiScatterCompensation);
 #endif
     }
 
 #if ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     {
-        specular = ClearCoatSpecular(dirToLight, lightingData.dirToCamera, surface.clearCoat.normal, surface.clearCoat.factor, surface.clearCoat.roughness, specular);
+        specular = ClearCoatSpecular(dirToLight, lightingData.dirToCamera[viewIndex], surface.clearCoat.normal, surface.clearCoat.factor, surface.clearCoat.roughness, specular);
     }
 #endif
-    
+
     specular *= lightIntensity;
     return specular;
 }

+ 6 - 7
Gems/Atom/Feature/Common/Assets/Shaders/Materials/Skin/Skin_LightingBrdf.azsli

@@ -12,11 +12,11 @@
 // used in your shader. Simply #define GetDiffuseLighting & GetSpecularLighting to your custom definition before including this file
 //
 #ifndef GetDiffuseLighting
-#define GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight)       GetDiffuseLighting_Skin(surface, lightingData, lightIntensity, dirToLight)
+#define GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight)                  GetDiffuseLighting_Skin(surface, lightingData, lightIntensity, dirToLight)
 #endif
 
 #ifndef GetSpecularLighting
-#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight)      GetSpecularLighting_Skin(surface, lightingData, lightIntensity, dirToLight)
+#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight, viewIndex)      GetSpecularLighting_Skin(surface, lightingData, lightIntensity, dirToLight, viewIndex)
 #endif
 
 #include "../BasePBR/BasePBR_LightingBrdf.azsli"
@@ -32,19 +32,19 @@
 // Then define the Diffuse and Specular lighting functions
 real3 GetDiffuseLighting_Skin(Surface surface, LightingData lightingData, real3 lightIntensity, real3 dirToLight)
 {
-    real3 diffuse = NormalizedDisneyDiffuse(surface.albedo, surface.GetDiffuseNormal(), lightingData.dirToCamera, dirToLight, surface.roughnessLinear);
+    real3 diffuse = NormalizedDisneyDiffuse(surface.albedo, surface.GetDiffuseNormal(), lightingData.dirToCamera[0], dirToLight, surface.roughnessLinear);
     diffuse *= lightIntensity;
     return diffuse;
 }
 
 
-real3 GetSpecularLighting_Skin(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight)
+real3 GetSpecularLighting_Skin(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight, uint viewIndex)
 {
-    real3 specular = SpecularGGX(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(), surface.roughnessA2, lightingData.multiScatterCompensation);
+    real3 specular = SpecularGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(viewIndex), surface.roughnessA2, lightingData.multiScatterCompensation);
 
     if(o_enableDualSpecular)
     {
-        real3 dualSpecular = SpecularGGX(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.dualSpecF0.xxx, lightingData.GetSpecularNdotV(), surface.dualSpecRoughnessA2, lightingData.multiScatterCompensation);
+        real3 dualSpecular = SpecularGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.dualSpecF0.xxx, lightingData.GetSpecularNdotV(viewIndex), surface.dualSpecRoughnessA2, lightingData.multiScatterCompensation);
         specular = lerp(specular, dualSpecular, surface.dualSpecFactor);
     }
 
@@ -52,4 +52,3 @@ real3 GetSpecularLighting_Skin(Surface surface, LightingData lightingData, const
 
     return specular;
 }
-

+ 2 - 2
Gems/Atom/Feature/Common/Assets/Shaders/Materials/Skin/Skin_LightingEval.azsli

@@ -17,7 +17,7 @@
 
 #include "../BasePBR/BasePBR_LightingEval.azsli"
 
-void InitializeLightingData_SkinPBR(inout Surface surface, float4 screenPosition, float3 viewPosition, inout LightingData lightingData)
+void InitializeLightingData_SkinPBR(inout Surface surface, float4 screenPosition, float3 viewPosition[MAX_SHADING_VIEWS], inout LightingData lightingData)
 {
     // --- Base PBR ---
     InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
@@ -27,7 +27,7 @@ void InitializeLightingData_SkinPBR(inout Surface surface, float4 screenPosition
     lightingData.specularOcclusion = surface.specularOcclusion;
 }
 
-LightingData EvaluateLighting_SkinPBR(inout Surface surface, float4 screenPosition, float3 viewPosition)
+LightingData EvaluateLighting_SkinPBR(inout Surface surface, float4 screenPosition, float3 viewPosition[MAX_SHADING_VIEWS])
 {
     LightingData lightingData;
     InitializeLightingData_SkinPBR(surface, screenPosition, viewPosition, lightingData);

+ 8 - 8
Gems/Atom/Feature/Common/Assets/Shaders/Materials/StandardPBR/StandardPBR_LightingBrdf.azsli

@@ -12,11 +12,11 @@
 // used in your shader. Simply #define GetDiffuseLighting & GetSpecularLighting to your custom definition before including this file
 //
 #ifndef GetDiffuseLighting
-#define GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight)       GetDiffuseLighting_StandardPBR(surface, lightingData, lightIntensity, dirToLight)
+#define GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight)                  GetDiffuseLighting_StandardPBR(surface, lightingData, lightIntensity, dirToLight)
 #endif
 
 #ifndef GetSpecularLighting
-#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight)      GetSpecularLighting_StandardPBR(surface, lightingData, lightIntensity, dirToLight)
+#define GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight, viewIndex)      GetSpecularLighting_StandardPBR(surface, lightingData, lightIntensity, dirToLight, viewIndex)
 #endif
 
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
@@ -39,7 +39,7 @@ real3 GetDiffuseLighting_StandardPBR(Surface surface, LightingData lightingData,
     if(o_clearCoat_feature_enabled)
     {
         // Attenuate diffuse term by clear coat's fresnel term to account for energy loss
-        real HdotV = saturate(dot(normalize(dirToLight + lightingData.dirToCamera), lightingData.dirToCamera));
+        real HdotV = saturate(dot(normalize(dirToLight + lightingData.dirToCamera[0]), lightingData.dirToCamera[0]));
         diffuse *= 1.0 - (FresnelSchlick(HdotV, 0.04) * surface.clearCoat.factor);
     }
 #endif
@@ -48,18 +48,18 @@ real3 GetDiffuseLighting_StandardPBR(Surface surface, LightingData lightingData,
     return diffuse;
 }
 
-real3 GetSpecularLighting_StandardPBR(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight)
+real3 GetSpecularLighting_StandardPBR(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight, uint viewIndex)
 {
-#if ENABLE_MOBILEBRDF    
-    real3 specular = SpecularGGXMobile(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessA2, surface.roughnessA, surface.roughnessLinear); 
+#if ENABLE_MOBILEBRDF
+    real3 specular = SpecularGGXMobile(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), surface.roughnessA2, surface.roughnessA, surface.roughnessLinear);
 #else
-    real3 specular = SpecularGGX(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(), surface.roughnessA2, lightingData.multiScatterCompensation);
+    real3 specular = SpecularGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(viewIndex), surface.roughnessA2, lightingData.multiScatterCompensation);
 #endif
 
 #if ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     {
-        specular = ClearCoatSpecular(dirToLight, lightingData.dirToCamera, surface.clearCoat.normal, surface.clearCoat.factor, surface.clearCoat.roughness, specular);
+        specular = ClearCoatSpecular(dirToLight, lightingData.dirToCamera[viewIndex], surface.clearCoat.normal, surface.clearCoat.factor, surface.clearCoat.roughness, specular);
     }
 #endif
 

+ 2 - 2
Gems/Atom/Feature/Common/Assets/Shaders/Materials/StandardPBR/StandardPBR_LightingEval.azsli

@@ -17,7 +17,7 @@
 
 #include "../BasePBR/BasePBR_LightingEval.azsli"
 
-void InitializeLightingData_StandardPBR(inout Surface surface, float4 screenPosition, float3 viewPosition, inout LightingData lightingData)
+void InitializeLightingData_StandardPBR(inout Surface surface, float4 screenPosition, float3 viewPosition[MAX_SHADING_VIEWS], inout LightingData lightingData)
 {
     // --- Base PBR ---
     InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
@@ -37,7 +37,7 @@ void InitializeLightingData_StandardPBR(inout Surface surface, float4 screenPosi
 #endif
 }
 
-LightingData EvaluateLighting_StandardPBR(inout Surface surface, float4 screenPosition, float3 viewPosition)
+LightingData EvaluateLighting_StandardPBR(inout Surface surface, float4 screenPosition, float3 viewPosition[MAX_SHADING_VIEWS])
 {
     LightingData lightingData;
     InitializeLightingData_StandardPBR(surface, screenPosition, viewPosition, lightingData);

+ 5 - 2
Gems/AtomContent/TestData/Assets/TestData/Materials/Types/AutoBrick_ForwardPass.azsl

@@ -132,6 +132,9 @@ DepthResult GetDepth(Texture2D heightmap, sampler mapSampler, float2 uv, float2
 
 ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+
     real4x4 objectToWorld = real4x4(GetObjectToWorldMatrix(IN.m_instanceId));
     real3x3 objectToWorldIT = real3x3(GetObjectToWorldMatrixInverseTranspose(IN.m_instanceId));
 
@@ -202,13 +205,13 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN)
 
     // Light iterator
     lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
-    lightingData.Init(surface.position, surface.normal, surface.roughnessLinear, ViewSrg::m_worldPosition.xyz);
+    lightingData.Init(surface.position, surface.normal, surface.roughnessLinear, views);
 
     // Shadow
     lightingData.diffuseAmbientOcclusion = 1.0f - surfaceDepth * AutoBrickSrg::m_aoFactor;
 
     // Diffuse and Specular response
-    lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
+    lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.specularF0, surface.roughnessLinear);
     lightingData.diffuseResponse = 1.0f - lightingData.specularResponse;
 
     const float alpha = 1.0f;

+ 5 - 2
Gems/AtomContent/TestData/Assets/TestData/Materials/Types/MinimalPBR_ForwardPass.azsl

@@ -61,6 +61,9 @@ VSOutput MinimalPBR_MainPassVS(VSInput IN, uint instanceId : SV_InstanceID)
 
 ForwardPassOutput MinimalPBR_MainPassPS(VSOutput IN)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+
     real4x4 objectToWorld = real4x4(GetObjectToWorldMatrix(IN.m_instanceId));
     real3x3 objectToWorldIT = real3x3(GetObjectToWorldMatrixInverseTranspose(IN.m_instanceId));
 
@@ -93,11 +96,11 @@ ForwardPassOutput MinimalPBR_MainPassPS(VSOutput IN)
 
     // Light iterator
     lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
-    lightingData.Init(surface.position, surface.normal, surface.roughnessLinear, ViewSrg::m_worldPosition.xyz);
+    lightingData.Init(surface.position, surface.normal, surface.roughnessLinear, views);
 
 
     // Diffuse and Specular response
-    lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
+    lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.specularF0, surface.roughnessLinear);
     lightingData.diffuseResponse = 1.0f - lightingData.specularResponse;
 
     const float alpha = 1.0f;

+ 71 - 54
Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli

@@ -33,16 +33,16 @@ enum class HairLightingModel {GGX, Marschner, Kajiya};
 option HairLightingModel o_hairLightingModel = HairLightingModel::Marschner;
 
 //==============================================================================
-float3 GetSpecularLighting(Surface surface, LightingData lightingData, const float3 lightIntensity, const float3 dirToLight)
+float3 GetSpecularLighting(Surface surface, LightingData lightingData, const real3 lightIntensity, const real3 dirToLight, uint viewIndex)
 {
     float3 specular = float3(1, 0, 1);  // purple - error color
     if (o_hairLightingModel == HairLightingModel::GGX)
     {
-        specular = SpecularGGX(lightingData.dirToCamera, dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(), surface.roughnessA2, lightingData.multiScatterCompensation);
+        specular = SpecularGGX(lightingData.dirToCamera[viewIndex], dirToLight, surface.GetSpecularNormal(), surface.GetSpecularF0(), lightingData.GetSpecularNdotV(viewIndex), surface.roughnessA2, lightingData.multiScatterCompensation);
     }
     else if(o_hairLightingModel == HairLightingModel::Marschner)
     {
-        specular = HairMarschnerBSDF(surface, lightingData, dirToLight);
+        specular = HairMarschnerBSDF(surface, lightingData, dirToLight, viewIndex);
     }
     // [To Do] - add the Kajiya-Kay lighting model option here in order to connect to Atom lighting loop
 
@@ -105,32 +105,36 @@ float3 GetDiffuseLighting(Surface surface, LightingData lightingData, float3 lig
 
 void UpdateLightingParameters(
     inout LightingData lightingData, 
-    float3 positionWS, float3 normal, float roughnessLinear)
+    float3 positionWS, float3 normal, float roughnessLinear, const float3 views[MAX_SHADING_VIEWS])
 {
-    lightingData.dirToCamera = normalize(ViewSrg::m_worldPosition.xyz - positionWS);
 
-    // sample BRDF map (indexed by smoothness values rather than roughness)
-    lightingData.NdotV = saturate(dot(normal, lightingData.dirToCamera));
-
-    float2 brdfUV = float2(lightingData.GetSpecularNdotV(), (1.0f - roughnessLinear));
+    [unroll]
+    for(uint i = 0; i < GET_SHADING_VIEW_COUNT; ++i)
+    {
+        lightingData.dirToCamera[i] = normalize(views[i] - positionWS);
 
-    lightingData.brdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg;
+        // sample BRDF map (indexed by smoothness values rather than roughness)
+        lightingData.NdotV[i] = saturate(dot(normal, lightingData.dirToCamera[i]));
+        
+        float2 brdfUV = float2(lightingData.GetSpecularNdotV(i), (1.0f - roughnessLinear));
+        lightingData.brdf[i] = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg;
+    }
 }
 
 void SetNormalAndUpdateLightingParams(
-    in float3 tangent, in float3 dirToLight, 
+    in float3 tangent, in float3 dirToLight, in const float3 views[MAX_SHADING_VIEWS],
     inout Surface surface, 
     inout LightingData lightingData)
 {
     float3 biNormal;
     if (o_hairLightingModel == HairLightingModel::GGX)
     {   // Towards half vector but never cross fully (more weight to camera direction)
-        float3 halfDir = normalize( dirToLight + 1.2 * lightingData.dirToCamera);
+        float3 halfDir = normalize( dirToLight + 1.2 * lightingData.dirToCamera[0]);
         biNormal = normalize(cross(tangent, halfDir));
     }
     else
     {   // Face forward towards the camera
-        biNormal = normalize(cross(tangent, lightingData.dirToCamera));   
+        biNormal = normalize(cross(tangent, lightingData.dirToCamera[0]));   
     }
 
     float3 projectedNormal = cross(biNormal, tangent);
@@ -138,7 +142,7 @@ void SetNormalAndUpdateLightingParams(
     surface.vertexNormal = surface.normal; // [To Do] - support proper vertex normals in the hair shader.
 
     // Next is important in order to set NdotV and other PBR settings - needs to be set once per light
-    UpdateLightingParameters(lightingData, surface.position, surface.GetSpecularNormal(), surface.roughnessLinear);
+    UpdateLightingParameters(lightingData, surface.position, surface.GetSpecularNormal(), surface.roughnessLinear, views);
 
     // Diffuse and Specular response
     lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.GetSpecularF0(), surface.roughnessLinear);
@@ -154,7 +158,7 @@ void SetNormalAndUpdateLightingParams(
 //==============================================================================
 //                         Simple Point Light 
 //==============================================================================
-void ApplySimplePointLight(SimplePointLight light, Surface surface, inout LightingData lightingData)
+void ApplySimplePointLight(SimplePointLight light, Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {  
     float3 posToLight = light.m_position - surface.position;
     float d2 = dot(posToLight, posToLight); // light distance squared
@@ -172,13 +176,15 @@ void ApplySimplePointLight(SimplePointLight light, Surface surface, inout Lighti
         float3 lightIntensity = (light.m_rgbIntensityCandelas / d2) * radiusAttenuation;
 
         float3 dirToLight = normalize(posToLight);
-        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, surface, lightingData);
+        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, views, surface, lightingData);
 
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight);
 
         // Specular contribution
-        lightingData.specularLighting += GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight);
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+            lightingData.specularLighting[viewIndex] += GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight, viewIndex);
     }
 }
 
@@ -187,7 +193,7 @@ void ApplySimplePointLight(SimplePointLight light, Surface surface, inout Lighti
 // and once done, all light types can use the culling. 
 // Not that the current culling scheme assumes light types lists order and is bogus if 
 // some light types are not used or out of order (Atom's To Do list)
-void ApplyCulledSimplePointLights(Surface surface, inout LightingData lightingData)
+void ApplyCulledSimplePointLights(Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {
     lightingData.tileIterator.LoadAdvance();
 
@@ -198,24 +204,24 @@ void ApplyCulledSimplePointLights(Surface surface, inout LightingData lightingDa
         lightingData.tileIterator.LoadAdvance();
 
         SimplePointLight light = ViewSrg::m_simplePointLights[currLightIndex];
-        ApplySimplePointLight(light, surface, lightingData);
+        ApplySimplePointLight(light, surface, lightingData, views);
         ++lightCount;
     }
 }
 
-void ApplyAllSimplePointLights(Surface surface, inout LightingData lightingData)
+void ApplyAllSimplePointLights(Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {
     for (int l = 0; l < ViewSrg::m_simplePointLightCount; ++l)
     {
         SimplePointLight light = ViewSrg::m_simplePointLights[l];
-        ApplySimplePointLight(light, surface, lightingData);
+        ApplySimplePointLight(light, surface, lightingData, views);
     }
 }
 
 //==============================================================================
 //                           Simple Spot Light 
 //==============================================================================
-void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout LightingData lightingData)
+void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {
     float3 posToLight = light.m_position - surface.position;
     float3 dirToLight = normalize(posToLight);
@@ -252,7 +258,7 @@ void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout Lighting
             lightIntensity *= penumbraMask;
         }
 
-        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, surface, lightingData);
+        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, views, surface, lightingData);
 
         // Tranmission contribution
         lightingData.translucentBackLighting += GetHairBackLighting(surface, lightingData, lightIntensity, dirToLight, 1.0);
@@ -261,22 +267,24 @@ void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout Lighting
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight);
 
         // Specular contribution
-        lightingData.specularLighting += GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight);
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+            lightingData.specularLighting[viewIndex] += GetSpecularLighting(surface, lightingData, lightIntensity, dirToLight, viewIndex);
     }
 }
 
-void ApplyAllSimpleSpotLights(Surface surface, inout LightingData lightingData)
+void ApplyAllSimpleSpotLights(Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {
     for (int l = 0; l < ViewSrg::m_simpleSpotLightCount; ++l)
     {
         SimpleSpotLight light = ViewSrg::m_simpleSpotLights[l];
-        ApplySimpleSpotLight(light, surface, lightingData);
+        ApplySimpleSpotLight(light, surface, lightingData, views);
     }
 }
 //==============================================================================
 //                              Point Light 
 //==============================================================================
-void ApplyPointLight(PointLight light, Surface surface, inout LightingData lightingData)
+void ApplyPointLight(PointLight light, Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {
     float3 posToLight = light.m_position - surface.position;
     float d2 = dot(posToLight, posToLight); // light distance squared
@@ -294,7 +302,7 @@ void ApplyPointLight(PointLight light, Surface surface, inout LightingData light
         float3 lightIntensity = (light.m_rgbIntensityCandelas / d2) * radiusAttenuation;
 
         float3 dirToLight = normalize(posToLight);
-        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, surface, lightingData);
+        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, views, surface, lightingData);
 
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight);
@@ -305,7 +313,7 @@ void ApplyPointLight(PointLight light, Surface surface, inout LightingData light
         // Adjust the light direcion for specular based on bulb size
 
         // Calculate the reflection off the normal from the view direction
-        float3 reflectionDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
+        float3 reflectionDir = reflect(-lightingData.dirToCamera[0], surface.GetSpecularNormal());
 
         // Calculate a vector from the reflection vector to the light
         float3 reflectionPosToLight = posToLight - dot(posToLight, reflectionDir) * reflectionDir;
@@ -317,16 +325,18 @@ void ApplyPointLight(PointLight light, Surface surface, inout LightingData light
         float sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_bulbRadius, d2);
 
         // Specular contribution
-        lightingData.specularLighting += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(posToLight));
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+            lightingData.specularLighting[viewIndex] += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(posToLight), viewIndex);
     }
 }
 
-void ApplyAllPointLights(Surface surface, inout LightingData lightingData)
+void ApplyAllPointLights(Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {
     for (int l = 0; l < ViewSrg::m_pointLightCount; ++l)
     {
         PointLight light = ViewSrg::m_pointLights[l];
-        ApplyPointLight(light, surface, lightingData);
+        ApplyPointLight(light, surface, lightingData, views);
     }
 }
 
@@ -335,9 +345,9 @@ void ApplyAllPointLights(Surface surface, inout LightingData lightingData)
 //==============================================================================
 #include <Atom/Features/PBR/Lights/DiskLight.azsli>
 
-void ApplyAllDiskLights(Surface surface, inout LightingData lightingData)
+void ApplyAllDiskLights(Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {    
-    SetNormalAndUpdateLightingParams(surface.tangent, lightingData.dirToCamera, surface, lightingData);
+    SetNormalAndUpdateLightingParams(surface.tangent, lightingData.dirToCamera[0], views, surface, lightingData);
     for (int l = 0; l < ViewSrg::m_diskLightCount; ++l)
     {
         DiskLight light = ViewSrg::m_diskLights[l];
@@ -348,14 +358,14 @@ void ApplyAllDiskLights(Surface surface, inout LightingData lightingData)
 //==============================================================================
 //                            Directional Lights
 //==============================================================================
-void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, float4 screenUv)
+void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, float4 screenUv, const float3 views[MAX_SHADING_VIEWS])
 {
     // Shadowed check
     const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
     float litRatio = 1.0f;
     float backShadowRatio = 0.0f;
 
-    SetNormalAndUpdateLightingParams(surface.tangent, -lightingData.dirToCamera, surface, lightingData);
+    SetNormalAndUpdateLightingParams(surface.tangent, -lightingData.dirToCamera[0], views, surface, lightingData);
 
     if (o_enableShadows && shadowIndex <  SceneSrg::m_directionalLightCount)
     {
@@ -373,7 +383,7 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
         float3 dirToLight = normalize(-light.m_direction);
 
         // Adjust the direction of the light based on its angular diameter.
-        float3 reflectionDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
+        float3 reflectionDir = reflect(-lightingData.dirToCamera[0], surface.GetSpecularNormal());
         float3 lightDirToReflectionDir = reflectionDir - dirToLight;
         float lightDirToReflectionDirLen = length(lightDirToReflectionDir);
         lightDirToReflectionDir = lightDirToReflectionDir / lightDirToReflectionDirLen; // normalize the length
@@ -395,8 +405,11 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
         }
 
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight) * currentLitRatio;
-        lightingData.specularLighting += GetSpecularLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight) * currentLitRatio;
         lightingData.translucentBackLighting += GetHairBackLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight, currentBackShadowRatio);
+
+        [unroll]
+        for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+            lightingData.specularLighting[viewIndex] += GetSpecularLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight, viewIndex) * currentLitRatio;
     }
 }
 
@@ -415,16 +428,16 @@ float3 ApplyIblDiffuse(Surface surface, LightingData lightingData)
     float3 irradianceDir = MultiplyVectorQuaternion(surface.GetDiffuseNormal(), SceneSrg::m_iblOrientation);
     float3 diffuseSample = SceneSrg::m_diffuseEnvMap.Sample(SceneSrg::m_samplerEnv, GetCubemapCoords(irradianceDir)).rgb;
 
-//    float3 diffuseLighting = HairDiffuseLambertian(surface.albedo, surface.GetDiffuseNormal(), lightingData.dirToCamera) * lightingData.diffuseResponse * diffuseSample;
+//    float3 diffuseLighting = HairDiffuseLambertian(surface.albedo, surface.GetDiffuseNormal(), lightingData.dirToCamera[0]) * lightingData.diffuseResponse * diffuseSample;
 //    float3 diffuseLighting = GetDiffuseLighting(surface, lightingData, diffuseSample, surface.GetDiffuseNormal());
 
     // Notice the multiplication with inverse thickness used as a measure of occlusion
     return lightingData.diffuseResponse * surface.albedo * diffuseSample * (1.0f - surface.thickness);
 }
 
-float3 ApplyIblSpecular(Surface surface, LightingData lightingData)
+float3 ApplyIblSpecular(Surface surface, LightingData lightingData, const uint viewIndex)
 {
-    float3 reflectDir = reflect(-lightingData.dirToCamera, surface.GetSpecularNormal());
+    float3 reflectDir = reflect(-lightingData.dirToCamera[0], surface.GetSpecularNormal());
     reflectDir = MultiplyVectorQuaternion(reflectDir, SceneSrg::m_iblOrientation);    
 
     // global
@@ -432,24 +445,29 @@ float3 ApplyIblSpecular(Surface surface, LightingData lightingData)
         SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), 
         GetRoughnessMip(surface.roughnessLinear)).rgb;
 
-    float3 specularLighting = GetSpecularLighting(surface, lightingData, specularSample, reflectDir);
+    float3 specularLighting = GetSpecularLighting(surface, lightingData, specularSample, reflectDir, viewIndex);
     return specularLighting;
 }
 
 // Remark: IBL is still WIP and this part will change in the near future
-void ApplyIBL(Surface surface, inout LightingData lightingData)
+void ApplyIBL(Surface surface, inout LightingData lightingData, const float3 views[MAX_SHADING_VIEWS])
 {
 //    float3 normal = normalize(float3(surface.tangent.z, -surface.tangent.x, surface.tangent.y));
 //    SetNormalAndUpdateLightingParams(surface.tangent, normal, surface, lightingData);
-    SetNormalAndUpdateLightingParams(surface.tangent, lightingData.dirToCamera, surface, lightingData);
+    SetNormalAndUpdateLightingParams(surface.tangent, lightingData.dirToCamera[0], views, surface, lightingData);
 
     float3 iblDiffuse = ApplyIblDiffuse(surface, lightingData);
-    float3 iblSpecular = ApplyIblSpecular(surface, lightingData);
 
     // Adjust IBL lighting by exposure.
     float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure);
     lightingData.diffuseLighting += (iblDiffuse * iblExposureFactor * lightingData.diffuseAmbientOcclusion);
-    lightingData.specularLighting += (iblSpecular * iblExposureFactor);
+
+    [unroll]
+    for (uint viewIndex = 0; viewIndex < GET_SHADING_VIEW_COUNT; ++viewIndex)
+    {
+        float3 iblSpecular = ApplyIblSpecular(surface, lightingData, viewIndex);
+        lightingData.specularLighting[viewIndex] += (iblSpecular * iblExposureFactor);
+    }
 }
 
 
@@ -458,35 +476,34 @@ void ApplyIBL(Surface surface, inout LightingData lightingData)
 //                       Light Types Application
 // 
 //==============================================================================
-void ApplyLighting(inout Surface surface, inout LightingData lightingData, float4 screenCoords)
+void ApplyLighting(inout Surface surface, inout LightingData lightingData, float4 screenCoords, const float3 views[MAX_SHADING_VIEWS])
 {
     // Shadow coordinates generation for the directional light
     const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
 
-
     // Light loops application.
     // If culling is used, the order of the calls must match the light types list order 
 //    ApplyDecals(lightingData.tileIterator, surface);
 
     if (o_enableDirectionalLights)
     {
-        ApplyDirectionalLights(surface, lightingData, screenCoords);
+        ApplyDirectionalLights(surface, lightingData, screenCoords, views);
     }
 
     if (o_enablePunctualLights)
     {
-        ApplyAllSimplePointLights(surface, lightingData);
-        ApplyAllSimpleSpotLights(surface, lightingData);
+        ApplyAllSimplePointLights(surface, lightingData, views);
+        ApplyAllSimpleSpotLights(surface, lightingData, views);
     }
 
     if (o_enableAreaLights)
     {
-        ApplyAllPointLights(surface, lightingData);
-        ApplyAllDiskLights(surface, lightingData);
+        ApplyAllPointLights(surface, lightingData, views);
+        ApplyAllDiskLights(surface, lightingData, views);
     }
 
     if (o_enableIBL)
     {
-        ApplyIBL(surface, lightingData); 
+        ApplyIBL(surface, lightingData, views); 
     }
 }

+ 13 - 3
Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli

@@ -192,6 +192,9 @@ float3 CalculateLighting(
     float3 vPositionWS, float3 vViewDirWS, float3 vTangent, 
     float thickness, in HairShadeParams material )
 {    
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+
     //-------- Surface init --------
     Surface surface;
 
@@ -226,11 +229,18 @@ float3 CalculateLighting(
     lightingData.tileIterator.Init(screenCoords, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
 
     // The normal assignment will be overriden afterwards per light
-    lightingData.Init(surface.position, surface.GetSpecularNormal(), surface.roughnessLinear, ViewSrg::m_worldPosition.xyz);
+    lightingData.Init(surface.position, surface.GetSpecularNormal(), surface.roughnessLinear, views);
+
+    ApplyLighting(surface, lightingData, screenCoords, views);
+
+    float3 ligthingOutput = lightingData.diffuseLighting;
 
-    ApplyLighting(surface, lightingData, screenCoords);
+    for(uint i = 0; i < GET_SHADING_VIEW_COUNT; ++i)
+    {
+        ligthingOutput += lightingData.specularLighting[i];
+    }
 
-    return lightingData.diffuseLighting + lightingData.specularLighting;
+    return ligthingOutput;
 }
 
 float3 TressFXShading(float2 pixelCoord, float depth, float3 tangent, float3 baseColor, float thickness, int shaderParamIndex)

+ 2 - 2
Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli

@@ -146,12 +146,12 @@ float3 N_TRT(Surface surface, float cos_O, float3 cos_Ld, float f0)
 //------------------------------------------------------------------------------
 //                    The BSDF lighting function used by Hair
 //------------------------------------------------------------------------------
-float3 HairMarschnerBSDF(Surface surface, LightingData lightingData, const float3 dirToLight)
+float3 HairMarschnerBSDF(Surface surface, LightingData lightingData, const float3 dirToLight, uint viewIndex)
 {
     //-------------- Lighting Parameters Calculations ---------------
     // Incoming and outgoing light directions
     float3 Wi = normalize(dirToLight);                 // Incident light direction
-    float3 Wr = normalize(lightingData.dirToCamera);   // Reflected light measurement direction AKA reflection                                        
+    float3 Wr = normalize(lightingData.dirToCamera[viewIndex]);   // Reflected light measurement direction AKA reflection                                        
     float3 T = normalize(surface.tangent);             // Hair tangent
 
     // Incident light and reflection direction projected along the tangent

+ 5 - 2
Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl

@@ -102,6 +102,9 @@ void GatherSurfaceDataFromClipmaps(
 
 ForwardPassOutput TerrainPBR_MainPassPS(VSOutput input)
 {
+    float3 views[MAX_SHADING_VIEWS];
+    views[0] = ViewSrg::m_worldPosition.xyz;    // Assume one view for forward pass for now
+
     // ------- Surface -------
     Surface surface;
     surface.position = input.m_worldPosition.xyz;
@@ -162,13 +165,13 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput input)
 
     // Light iterator
     lightingData.tileIterator.Init(input.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
-    lightingData.Init(surface.position, surface.normal, surface.roughnessLinear, ViewSrg::m_worldPosition.xyz);
+    lightingData.Init(surface.position, surface.normal, surface.roughnessLinear, views);
 
     // Shadow, Occlusion
     lightingData.diffuseAmbientOcclusion = detailSurface.m_occlusion;
 
     // Diffuse and Specular response
-    lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
+    lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.specularF0, surface.roughnessLinear);
     lightingData.diffuseResponse = 1.0f - lightingData.specularResponse;
 
     const float alpha = 1.0f;