Răsfoiți Sursa

Merge pull request #18946 from antonmic/Anton/MultiViewSpecular

Multi View Shading
antonmic 1 lună în urmă
părinte
comite
7a60fe2413
38 a modificat fișierele cu 943 adăugiri și 554 ștergeri
  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)
 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 -------
     // ------- Geometry -> Surface -> Lighting -------
 
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
 
     Surface surface = EvaluateSurface(IN, geoData, 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 -------
     // ------- Output -------
 
 
@@ -51,7 +54,7 @@ ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
     diffuse += lightingData.emissiveLighting;
     diffuse += lightingData.emissiveLighting;
 
 
     // m_opacityAffectsSpecularFactor controls how much the alpha masks out specular contribution.
     // 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);
     specular = lerp(specular, specular * surface.alpha, surface.opacityAffectsSpecularFactor);
 
 
     OUT.m_color1.rgb = diffuse + specular;
     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
 #endif
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 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 -------
     // ------- Geometry -> Surface -> Lighting -------
 
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
 
     Surface surface = EvaluateSurface(IN, geoData, 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 -------
     // ------- Output -------
 
 
     ForwardPassOutput OUT;
     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;
     OUT.m_color.a = 1.0;
 
 
 #if OUTPUT_DEPTH
 #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)
 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 -------
     // ------- Geometry -> Surface -> Lighting -------
 
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
 
     Surface surface = EvaluateSurface(IN, geoData, 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 -------
     // ------- Output -------
 
 
@@ -63,7 +66,7 @@ ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 
 
     // --- Specular Lighting ---
     // --- Specular Lighting ---
 
 
-    OUT.m_specularColor.rgb = lightingData.specularLighting.rgb;
+    OUT.m_specularColor.rgb = lightingData.specularLighting[0].rgb;
     OUT.m_specularColor.a = 1.0;
     OUT.m_specularColor.a = 1.0;
 
 
     // --- Roughness and Specular ---
     // --- 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
 #endif
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 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 -------
     // ------- Geometry -> Surface -> Lighting -------
 
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
 
     Surface surface = EvaluateSurface(IN, geoData, 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 -------
     // ------- Output -------
 
 
     ForwardPassOutput OUT;
     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
 #if ENABLE_MERGE_FILMIC_TONEMAP
     // Apply manual exposure compensation
     // 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
 #endif
 ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 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 -------
     // ------- Geometry -> Surface -> Lighting -------
 
 
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
     PixelGeometryData geoData = EvaluatePixelGeometry(IN, isFrontFace, GetMaterialParameters());
 
 
     Surface surface = EvaluateSurface(IN, geoData, 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 -------
     // ------- Output -------
 
 
     ForwardPassOutput OUT;
     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
 #if ENABLE_MERGE_FILMIC_TONEMAP
     // Apply manual exposure compensation
     // 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.
     // We could add other forms of tonemapping in future via shader options.
     color = TonemapFilmic(color);
     color = TonemapFilmic(color);
+
 #endif
 #endif
 
 
     OUT.m_color.rgb = color;
     OUT.m_color.rgb = color;
@@ -62,4 +66,3 @@ ForwardPassOutput PixelShader(VsOutput IN, bool isFrontFace : SV_IsFrontFace)
 
 
     return OUT;
     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/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
 #include <Atom/Features/VertexUtility.azsli>
 #include <Atom/Features/VertexUtility.azsli>
 
 
+#include "../../Shaders/Materials/MaterialInputs/UvSetCount.azsli"
 
 
 
 
 struct MaterialParameters {};
 struct MaterialParameters {};
@@ -52,10 +53,26 @@ const MaterialParameters GetMaterialParameters()
         output.normal = IN.normal;
         output.normal = IN.normal;
         return output;
         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
     class PixelGeometryData
     {
     {
         float3 positionWS;
         float3 positionWS;
+        float2 uvs[UvSetCount];
         float3 vertexNormal;
         float3 vertexNormal;
     };
     };
 
 
@@ -64,6 +81,13 @@ const MaterialParameters GetMaterialParameters()
         PixelGeometryData geoData;
         PixelGeometryData geoData;
         geoData.positionWS = IN.worldPosition;
         geoData.positionWS = IN.worldPosition;
         geoData.vertexNormal = normalize(IN.normal);
         geoData.vertexNormal = normalize(IN.normal);
+
+        [unroll]
+        for(uint i = 0; i < UvSetCount; ++i)
+        {
+            geoData.uvs[i] = float2(0, 0);
+        }
+
         return geoData;
         return geoData;
     }
     }
 
 

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

@@ -8,6 +8,8 @@
   
   
 #pragma once
 #pragma once
 
 
+#include <viewsrg_all.srgi>
+
 // Functions to support getting object transforms via an instance buffer, which use
 // Functions to support getting object transforms via an instance buffer, which use
 // the ObjectSrg as a fallback if instancing is not enabled.
 // the ObjectSrg as a fallback if instancing is not enabled.
 // Including this header in a shader indicates that the shader itself will use these
 // 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]
 // [specific profile for human skin]
 // Analytical integation (approximation) of diffusion profile over radius, could be precomputed in a LUT
 // 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)
 // 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
     // 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) +
     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
 #if ENABLE_TRANSMISSION
 
 
-    real thickness = 0.0; 
+    real thickness = 0.0;
     TransmissionSurfaceData transmission = surface.transmission;
     TransmissionSurfaceData transmission = surface.transmission;
 
 
     switch(o_transmission_mode)
     switch(o_transmission_mode)
@@ -51,13 +51,13 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
         case TransmissionMode::None:
         case TransmissionMode::None:
             break;
             break;
 
 
-        case TransmissionMode::ThickObject: 
+        case TransmissionMode::ThickObject:
             // Thick object mode, using back lighting approximation proposed by Brisebois B. C. and Bouchard M. 2011
             // 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/
             // https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/
 
 
             {
             {
                 thickness = max(transmissionDistance, transmission.thickness);
                 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);
                 real lamberAttenuation = exp(-thickness * transmission.GetAttenuationCoefficient()) * saturate(1.0 - thickness);
                 result = transmittance * lamberAttenuation * lightIntensity;
                 result = transmittance * lamberAttenuation * lightIntensity;
             }
             }
@@ -66,7 +66,7 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
         case TransmissionMode::ThinObject:
         case TransmissionMode::ThinObject:
             // Thin object mode, based on Jimenez J. et al, 2010, "Real-Time Realistic Skin Translucency"
             // 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
             // http://www.iryoku.com/translucency/downloads/Real-Time-Realistic-Skin-Translucency.pdf
-            
+
             {
             {
                 // transmissionDistance < 0.0f means shadows are not enabled --> avoid unnecessary computation
                 // transmissionDistance < 0.0f means shadows are not enabled --> avoid unnecessary computation
                 if (transmissionDistance < 0.0)
                 if (transmissionDistance < 0.0)
@@ -74,20 +74,20 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
                     break;
                     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])
                 // object (observation 4 in [Jimenez J. et al, 2010])
                 // Increase angle of influence to avoid dark transition regions
                 // Increase angle of influence to avoid dark transition regions
                 real3 E = surface.albedo * saturate(transmission.GetTransmissionNdLBias() + dot(-surface.GetVertexNormal(), dirToLight));
                 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)
                 // (in this case modulating the distance traversed by the light inside the object)
                 const real C = 300.0;
                 const real C = 300.0;
                 real s = transmissionDistance * C * transmission.thickness;
                 real s = transmissionDistance * C * transmission.thickness;
-                
+
                 // Use scattering color to weight thin object transmission color
                 // Use scattering color to weight thin object transmission color
                 const real3 invScattering = rcp(max(transmission.scatterDistance, real(0.00001)));
                 const real3 invScattering = rcp(max(transmission.scatterDistance, real(0.00001)));
-                
+
 #if !USE_HUMAN_SKIN_PROFILE
 #if !USE_HUMAN_SKIN_PROFILE
                 // Generic profile based on scatter color
                 // Generic profile based on scatter color
                 result = TransmissionKernel(s, invScattering) * lightIntensity * E * transmission.GetScale();
                 result = TransmissionKernel(s, invScattering) * lightIntensity * E * transmission.GetScale();
@@ -106,4 +106,3 @@ real3 GetBackLighting(Surface surface, LightingData lightingData, real3 lightInt
 
 
     return result;
     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/LightCulling/LightCullingTileIterator.azsli>
 #include <Atom/Features/PBR/LightingUtils.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
 class LightingData
 {
 {
     LightCullingTileIterator tileIterator;
     LightCullingTileIterator tileIterator;
-        
+
     uint lightingChannels;
     uint lightingChannels;
 
 
     // Lighting outputs
     // Lighting outputs
     real3 diffuseLighting;
     real3 diffuseLighting;
-    real3 specularLighting;
+    real3 specularLighting[MAX_SHADING_VIEWS];
     real3 translucentBackLighting;
     real3 translucentBackLighting;
 
 
     // Factors for the amount of diffuse and specular lighting applied
     // Factors for the amount of diffuse and specular lighting applied
@@ -29,17 +37,17 @@ class LightingData
     real3 specularResponse;
     real3 specularResponse;
 
 
     // (N . L) to accept below (N . L = 0) in scattering through thin objects
     // (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
     // Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
     real shrinkFactor;
     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;
     real distanceAttenuation;
 
 
     // Normalized direction from surface to camera
     // Normalized direction from surface to camera
-    real3 dirToCamera;
-    
+    real3 dirToCamera[MAX_SHADING_VIEWS];
+
     // Scaling term to approximate multiscattering contribution in specular BRDF
     // Scaling term to approximate multiscattering contribution in specular BRDF
     real3 multiScatterCompensation;
     real3 multiScatterCompensation;
 
 
@@ -47,58 +55,69 @@ class LightingData
     real3 emissiveLighting;
     real3 emissiveLighting;
 
 
     // BRDF texture values
     // BRDF texture values
-    real2 brdf;
+    real2 brdf[MAX_SHADING_VIEWS];
 
 
     // Normal . View
     // Normal . View
-    real NdotV;
+    real NdotV[MAX_SHADING_VIEWS];
 
 
     // Occlusion factors
     // Occlusion factors
     // 0 = dark, 1 = light
     // 0 = dark, 1 = light
     real diffuseAmbientOcclusion;
     real diffuseAmbientOcclusion;
     real specularOcclusion;
     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 CalculateMultiscatterCompensation(real3 specularF0, bool enabled);
     void FinalizeLighting();
     void FinalizeLighting();
     void FinalizeLighting(real3 transmissionTint);
     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);
     diffuseLighting = real3(0.0, 0.0, 0.0);
-    specularLighting = real3(0.0, 0.0, 0.0);
     translucentBackLighting = real3(0.0, 0.0, 0.0);
     translucentBackLighting = real3(0.0, 0.0, 0.0);
     multiScatterCompensation = real3(1.0, 1.0, 1.0);
     multiScatterCompensation = real3(1.0, 1.0, 1.0);
     emissiveLighting = real3(0.0, 0.0, 0.0);
     emissiveLighting = real3(0.0, 0.0, 0.0);
     diffuseAmbientOcclusion = 1.0;
     diffuseAmbientOcclusion = 1.0;
     specularOcclusion = 1.0;
     specularOcclusion = 1.0;
-    
+
     lightingChannels = 0xFFFFFFFF;
     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)
 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()
 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())
     if(!IsDiffuseLightingEnabled())
     {
     {
         diffuseLighting = real3(0, 0, 0);
         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/LightingUtils.azsli>
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
 
 
+
 // Then define the Diffuse and Specular lighting functions
 // Then define the Diffuse and Specular lighting functions
 real3 GetDiffuseLighting(Surface surface, LightingData lightingData, real3 lightIntensity, real3 dirToLight)
 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)
     if(o_clearCoat_feature_enabled)
     {
     {
         // Attenuate diffuse term by clear coat's fresnel term to account for energy loss
         // 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);
         diffuse *= 1.0 - (FresnelSchlick(HdotV, 0.04) * surface.clearCoat.factor);
     }
     }
 #endif
 #endif
@@ -38,14 +39,19 @@ real3 GetDiffuseLighting(Surface surface, LightingData lightingData, real3 light
     return diffuse;
     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 ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     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
 #endif
 
 
@@ -72,7 +78,7 @@ PbrLightingOutput GetPbrLightingOutput(Surface surface, LightingData lightingDat
     PbrLightingOutput lightingOutput;
     PbrLightingOutput lightingOutput;
 
 
     lightingOutput.m_diffuseColor = real4(lightingData.diffuseLighting, alpha);
     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)
     // albedo, specularF0, roughness, and normals for later passes (specular IBL, Diffuse GI, SSR, AO, etc)
     lightingOutput.m_specularF0 = real4(surface.GetSpecularF0(), surface.roughnessLinear);
     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>
 #include <Atom/Features/PBR/Lights/PointLight.azsli>
 
 
 #if ENABLE_CAPSULE_LIGHTS
 #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)
 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 startPoint = light.m_startPoint;
     float3 startToEnd = light.m_direction * lightLength;
     float3 startToEnd = light.m_direction * lightLength;
     float3 endPoint = startPoint + startToEnd;
     float3 endPoint = startPoint + startToEnd;
-    
+
     // Do simple distance check to line for falloff and tight culling.
     // Do simple distance check to line for falloff and tight culling.
     float3 surfaceToStart = startPoint - surface.position;
     float3 surfaceToStart = startPoint - surface.position;
     float closestPointDistance = dot(-surfaceToStart, light.m_direction);
     float closestPointDistance = dot(-surfaceToStart, light.m_direction);
@@ -113,7 +72,7 @@ void ApplyCapsuleLight(CapsuleLight light, Surface surface, inout LightingData l
         float distanceToStart = length(surfaceToStart);
         float distanceToStart = length(surfaceToStart);
         float3 surfaceToEnd = endPoint - surface.position;
         float3 surfaceToEnd = endPoint - surface.position;
         float distanceToEnd = length(surfaceToEnd);
         float distanceToEnd = length(surfaceToEnd);
-        
+
         // Integration of lambert reflectance of a line segment to get diffuse intensity
         // Integration of lambert reflectance of a line segment to get diffuse intensity
         // See https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
         // See https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
         float NdotStart = dot(surface.GetDiffuseNormal(), surfaceToStart) / distanceToStart;
         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;
         float3 lightIntensity = (intensity * radiusAttenuation * ratioVisible) * light.m_rgbIntensityCandelas;
         lightingData.diffuseLighting += max(0.0, surface.albedo * lightIntensity);
         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
         // Transmission contribution
         // We cannot compute the actual transmission distance so we want to:
         // 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 thick object -> use transmission thickness parameter instead
         // - If transmission mode is thin object -> ignore back lighting
         // - 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;
         const float transmissionDistance = -1.0f;
         // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
         // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
         const float attenuationDistance = 0.0f;
         const float attenuationDistance = 0.0f;
 
 
 #if ENABLE_TRANSMISSION
 #if ENABLE_TRANSMISSION
-        lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), transmissionDistance, attenuationDistance);
+        lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(dirToClosestPoint), transmissionDistance, attenuationDistance);
 #endif
 #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 capArea =  4.0 * PI * light.m_radius * light.m_radius;
     float cylinderArea = 2.0 * PI * light.m_radius * light.m_length;
     float cylinderArea = 2.0 * PI * light.m_radius * light.m_length;
     float capToCylinderAreaRatio = capArea / (capArea + cylinderArea);
     float capToCylinderAreaRatio = capArea / (capArea + cylinderArea);
-    
+
     // Construct local to world matrix for transforming sample points.
     // Construct local to world matrix for transforming sample points.
     float3x3 localToWorld;
     float3x3 localToWorld;
     localToWorld[0] = light.m_direction;
     localToWorld[0] = light.m_direction;
@@ -202,9 +201,13 @@ void ValidateCapsuleLight(CapsuleLight light, Surface surface, inout LightingDat
     }
     }
     localToWorld[1]= cross(localToWorld[0], localToWorld[2]);
     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)
     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
     // Lighting value is in Candela, convert to Lumen for total light output of the light
     float3 intensityLumens = light.m_rgbIntensityCandelas * 4.0 * PI;
     float3 intensityLumens = light.m_rgbIntensityCandelas * 4.0 * PI;
+
     // Each of the N samples will contribute intensity / N lumens. However it will radiate in
     // 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
     // equal directions across the hemisphere, so we need to account for that
     float3 intensity = intensityLumens * INV_PI;
     float3 intensity = intensityLumens * INV_PI;
-    
+
     lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * intensity;
     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
 #if ENABLE_TRANSMISSION
     lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensity;
     lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensity;
 #endif
 #endif
@@ -252,15 +260,15 @@ void ApplyCapsuleLightInternal(uint lightIndex, Surface surface, inout LightingD
 }
 }
 
 
 void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
 void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
-{    
+{
 #if ENABLE_LIGHT_CULLING
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
     lightingData.tileIterator.LoadAdvance();
-                
+
     while(!lightingData.tileIterator.IsDone())
     while(!lightingData.tileIterator.IsDone())
     {
     {
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
         lightingData.tileIterator.LoadAdvance();
-        
+
         ApplyCapsuleLightInternal(currLightIndex, surface, lightingData);
         ApplyCapsuleLightInternal(currLightIndex, surface, lightingData);
     }
     }
 #else
 #else
@@ -270,10 +278,12 @@ void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
     }
     }
 #endif
 #endif
 }
 }
-#else
+
+#else   // ENABLE_CAPSULE_LIGHTS
+
 void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
 void ApplyCapsuleLights(Surface surface, inout LightingData lightingData)
-{    
+{
     //Not Enabled
     //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 ENABLE_SHADOWS
     if (o_enableShadows && shadowIndex <  SceneSrg::m_directionalLightCount)
     if (o_enableShadows && shadowIndex <  SceneSrg::m_directionalLightCount)
-    {           
+    {
 #if ENABLE_TRANSMISSION
 #if ENABLE_TRANSMISSION
         if (o_transmission_mode == TransmissionMode::ThickObject)
         if (o_transmission_mode == TransmissionMode::ThickObject)
         {
         {
             real2 visibilityAndThickness = DirectionalLightShadow::GetVisibilityThickTransmission(shadowIndex, real3(surface.position), surface.vertexNormal, screenUv);
             real2 visibilityAndThickness = DirectionalLightShadow::GetVisibilityThickTransmission(shadowIndex, real3(surface.position), surface.vertexNormal, screenUv);
             litRatio = visibilityAndThickness.x;
             litRatio = visibilityAndThickness.x;
             transmissionDistance = visibilityAndThickness.y;
             transmissionDistance = visibilityAndThickness.y;
-        } 
-        else if (o_transmission_mode == TransmissionMode::ThinObject) 
+        }
+        else if (o_transmission_mode == TransmissionMode::ThinObject)
         {
         {
             real2 visibilityAndThickness = DirectionalLightShadow::GetVisibilityThinTransmission(
             real2 visibilityAndThickness = DirectionalLightShadow::GetVisibilityThinTransmission(
                                                 shadowIndex, real3(surface.position), surface.vertexNormal, screenUv, surface.transmission.GetShrinkFactor());
                                                 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);
             litRatio = DirectionalLightShadow::GetVisibility(shadowIndex, real3(surface.position), surface.vertexNormal, screenUv);
         }
         }
-#else 
+#else
         litRatio = DirectionalLightShadow::GetVisibility(shadowIndex, surface.position, surface.vertexNormal, screenUv);
         litRatio = DirectionalLightShadow::GetVisibility(shadowIndex, surface.position, surface.vertexNormal, screenUv);
 #endif // ENABLE_TRANSMISSION
 #endif // ENABLE_TRANSMISSION
 
 
@@ -63,15 +63,6 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
         {
         {
             continue;
             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
         // [GFX TODO][ATOM-2012] care of multiple directional light
         // Currently shadow check is done only for index == shadowIndex.
         // Currently shadow check is done only for index == shadowIndex.
@@ -89,11 +80,28 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
 
 
         // Transmission contribution
         // Transmission contribution
 #if ENABLE_TRANSMISSION
 #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
 #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.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 ENABLE_SHADER_DEBUGGING
         if(IsDebuggingEnabled_PLACEHOLDER() && GetRenderDebugViewMode() == RenderDebugViewMode::CascadeShadows)
         if(IsDebuggingEnabled_PLACEHOLDER() && GetRenderDebugViewMode() == RenderDebugViewMode::CascadeShadows)
@@ -103,11 +111,16 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData, fl
 #endif
 #endif
     }
     }
 
 
-#if ENABLE_SHADOWS    
+#if ENABLE_SHADOWS
     // Add debug coloring for directional light shadow
     // Add debug coloring for directional light shadow
     if (o_enableShadows && shadowIndex <  SceneSrg::m_directionalLightCount)
     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
 #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
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         float radiusAttenuation = 1.0 - (falloff * falloff);
         float radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Find the distance to the closest point on the disk
         // Find the distance to the closest point on the disk
         float distanceToPlane = dot(posToLight, -light.m_direction);
         float distanceToPlane = dot(posToLight, -light.m_direction);
         float distanceToPlane2 = distanceToPlane * distanceToPlane;
         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;
         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.
         // 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,
                 surface.position,
                 -dirToConeTip,
                 -dirToConeTip,
                 surface.vertexNormal);
                 surface.vertexNormal);
-             
+
 
 
             // o_transmission_mode == NONE is not taken into account because GetBackLighting already ignores this case
             // 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)
             if (o_transmission_mode == TransmissionMode::ThickObject)
             {
             {
                 transmissionDistance = ProjectedShadow::GetThickness(light.m_shadowIndex, surface.position);
                 transmissionDistance = ProjectedShadow::GetThickness(light.m_shadowIndex, surface.position);
@@ -108,13 +108,13 @@ void ApplyDiskLight(DiskLight light, Surface surface, inout LightingData lightin
 #endif
 #endif
 
 
         if (useConeAngle && dotWithDirection < light.m_cosInnerConeAngle) // in penumbra
         if (useConeAngle && dotWithDirection < light.m_cosInnerConeAngle) // in penumbra
-        {   
+        {
             // Normalize into 0.0 - 1.0 space.
             // Normalize into 0.0 - 1.0 space.
             float penumbraMask = (dotWithDirection - light.m_cosOuterConeAngle) / (light.m_cosInnerConeAngle - light.m_cosOuterConeAngle);
             float penumbraMask = (dotWithDirection - light.m_cosOuterConeAngle) / (light.m_cosInnerConeAngle - light.m_cosOuterConeAngle);
-            
+
             // Apply smoothstep
             // Apply smoothstep
             penumbraMask = penumbraMask * penumbraMask * (3.0 - 2.0 * penumbraMask);
             penumbraMask = penumbraMask * penumbraMask * (3.0 - 2.0 * penumbraMask);
-            
+
             lightIntensity *= 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);
         lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, posToLightDir, transmissionDistance, distanceToLight2);
 #endif
 #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;
     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)
     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);
         float3 samplePoint = SampleDisk(randomPoint, light);
         AddSampleContribution(surface, lightingData, samplePoint, light.m_direction, 0.0, diffuseAcc, specularAcc, translucentAcc);
         AddSampleContribution(surface, lightingData, samplePoint, light.m_direction, 0.0, diffuseAcc, specularAcc, translucentAcc);
     }
     }
-    
+
     lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * light.m_rgbIntensityCandelas;
     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)
 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)
 void ApplyDiskLights(Surface surface, inout LightingData lightingData)
-{    
+{
 #if ENABLE_LIGHT_CULLING
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
     lightingData.tileIterator.LoadAdvance();
 
 
@@ -242,7 +261,7 @@ void ApplyDiskLights(Surface surface, inout LightingData lightingData)
     {
     {
         uint currLightIndex = lightingData.tileIterator.GetValue();
         uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
         lightingData.tileIterator.LoadAdvance();
-        
+
         ApplyDiskLightInternal(currLightIndex, surface, lightingData);
         ApplyDiskLightInternal(currLightIndex, surface, lightingData);
     }
     }
 #else
 #else
@@ -256,7 +275,7 @@ void ApplyDiskLights(Surface surface, inout LightingData lightingData)
 #else
 #else
 
 
 void ApplyDiskLights(Surface surface, inout LightingData lightingData)
 void ApplyDiskLights(Surface surface, inout LightingData lightingData)
-{    
+{
     //Not Enabled
     //Not Enabled
 }
 }
 
 

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

@@ -32,8 +32,8 @@
 #endif
 #endif
 
 
 real3 GetIblDiffuse(
 real3 GetIblDiffuse(
-    real3 normal, 
-    real3 albedo, 
+    real3 normal,
+    real3 albedo,
     real3 diffuseResponse)
     real3 diffuseResponse)
 {
 {
     real3 irradianceDir = MultiplyVectorQuaternion(normal, real4(SceneSrg::m_iblOrientation));
     real3 irradianceDir = MultiplyVectorQuaternion(normal, real4(SceneSrg::m_iblOrientation));
@@ -59,22 +59,22 @@ real3 EnvBRDFApprox( real3 specularF0, real roughnessLinear, real NdotV )
 }
 }
 
 
 real3 GetIblSpecular(
 real3 GetIblSpecular(
-    float3 position, 
-    real3 normal, 
-    real3 specularF0, 
-    real roughnessLinear, 
-    real3 dirToCamera, 
+    float3 position,
+    real3 normal,
+    real3 specularF0,
+    real roughnessLinear,
+    real3 dirToCamera,
     real2 brdf,
     real2 brdf,
     ReflectionProbeData reflectionProbe,
     ReflectionProbeData reflectionProbe,
     TextureCube reflectionProbeCubemap)
     TextureCube reflectionProbeCubemap)
 {
 {
     real3 reflectDir = reflect(-dirToCamera, normal);
     real3 reflectDir = reflect(-dirToCamera, normal);
-    reflectDir = MultiplyVectorQuaternion(reflectDir, real4(SceneSrg::m_iblOrientation));    
+    reflectDir = MultiplyVectorQuaternion(reflectDir, real4(SceneSrg::m_iblOrientation));
 
 
     // global
     // global
     real3 outSpecular = real3(SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb);
     real3 outSpecular = real3(SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb);
     outSpecular *= (specularF0 * brdf.x + brdf.y);
     outSpecular *= (specularF0 * brdf.x + brdf.y);
-    
+
     // reflection probe
     // reflection probe
     if (reflectionProbe.m_useReflectionProbe)
     if (reflectionProbe.m_useReflectionProbe)
     {
     {
@@ -101,9 +101,9 @@ real3 GetIblSpecular(
         outSpecular = lerp(outSpecular, probeSpecular, blendAmount);
         outSpecular = lerp(outSpecular, probeSpecular, blendAmount);
 #else
 #else
         outSpecular = probeSpecular;
         outSpecular = probeSpecular;
-#endif 
+#endif
     }
     }
-    
+
     return outSpecular;
     return outSpecular;
 }
 }
 
 
@@ -114,7 +114,7 @@ void ApplyIBL(Surface surface, inout LightingData lightingData, bool useDiffuseI
     if(useIbl)
     if(useIbl)
     {
     {
         real globalIblExposure = pow(2.0, real(SceneSrg::m_iblExposure));
         real globalIblExposure = pow(2.0, real(SceneSrg::m_iblExposure));
-        
+
         if(useDiffuseIbl)
         if(useDiffuseIbl)
         {
         {
             real3 iblDiffuse = GetIblDiffuse(surface.GetDiffuseNormal(), surface.albedo, lightingData.diffuseResponse);
             real3 iblDiffuse = GetIblDiffuse(surface.GetDiffuseNormal(), surface.albedo, lightingData.diffuseResponse);
@@ -123,48 +123,53 @@ void ApplyIBL(Surface surface, inout LightingData lightingData, bool useDiffuseI
 
 
         if(useSpecularIbl)
         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
 #endif
 
 
 #if ENABLE_DUAL_SPECULAR
 #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
 #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 real3 lightSampleDirection,
     in real bothDirectionsFactor,
     in real bothDirectionsFactor,
     inout real3 diffuseAcc,
     inout real3 diffuseAcc,
-    inout real3 specularAcc,
+    inout real3 specularAcc[MAX_SHADING_VIEWS],
     inout real3 translucentAcc)
     inout real3 translucentAcc)
 {
 {
     real3 posToLightSample = lightSamplePoint - real3(surface.position);
     real3 posToLightSample = lightSamplePoint - real3(surface.position);
@@ -38,8 +38,10 @@ void AddSampleContribution(
 
 
     // Lambertian emitter
     // Lambertian emitter
     real intensity = dot(-lightSampleDirection, posToLightSampleDir);
     real intensity = dot(-lightSampleDirection, posToLightSampleDir);
+
     // Handle if the light emits from both sides
     // Handle if the light emits from both sides
     intensity = abs(clamp(intensity, bothDirectionsFactor, 1.0));
     intensity = abs(clamp(intensity, bothDirectionsFactor, 1.0));
+
     // Attenuate with distance
     // Attenuate with distance
     intensity /= distanceToLight2;
     intensity /= distanceToLight2;
 
 
@@ -51,13 +53,19 @@ void AddSampleContribution(
     // We cannot compute the actual transmission distance so we want to:
     // 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 thick object -> use transmission thickness parameter instead
     // - If transmission mode is thin object -> ignore back lighting
     // - 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;
     const real transmissionDistance = -1.0;
+
     // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
     // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
     const real attenuationDistance = 0.0;
     const real attenuationDistance = 0.0;
     translucentAcc += GetBackLighting(surface, lightingData, intensityRgb, posToLightSampleDir, transmissionDistance, attenuationDistance);
     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)
 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 lightIntensity = real3(SceneSrg::m_debugLightingIntensity);
         real3 lightDirection = real3(SceneSrg::m_debugLightingDirection);
         real3 lightDirection = real3(SceneSrg::m_debugLightingDirection);
 
 
+        // Diffuse lighting is view-independent
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, lightDirection);
         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:
 /* begin-license-attribution:
 
 
 This code is based in part on third-party work(s), see below for applicable
 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.
 Inc. or its affiliates or licensors.
 
 
 begin-original-license-text:
 begin-original-license-text:
@@ -12,7 +12,7 @@ All rights reserved.
 Redistribution and use in source and binary forms, with or without
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 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:
 reference to the paper:
 
 
 Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
 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.
 //   ltcMat - The LTC matrix for specular, or identity for diffuse.
 //   p[4] - The 4 light positions relative to the surface position.
 //   p[4] - The 4 light positions relative to the surface position.
 //   doubleSided - If the quad emits light from both sides
 //   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
 //   diffuse - The output diffuse response for the quad light
 //   specular - The output specular response for the quad light
 //   specular - The output specular response for the quad light
 void LtcQuadEvaluate(
 void LtcQuadEvaluate(
@@ -385,6 +386,7 @@ void LtcQuadEvaluate(
     in Texture2D<float2> ltcAmpMatrix,
     in Texture2D<float2> ltcAmpMatrix,
     in float3 p[4],
     in float3 p[4],
     in bool doubleSided,
     in bool doubleSided,
+    in uint viewIndex,
     out float diffuseOut,
     out float diffuseOut,
     out float3 specularOut)
     out float3 specularOut)
 {
 {
@@ -395,7 +397,7 @@ void LtcQuadEvaluate(
     float3 polygon[5];
     float3 polygon[5];
 
 
     // Transform the points of the light into the space of the normal's hemisphere and clip to the hemisphere
     // 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)
     if (vertexCount == 0)
     {
     {
         // Entire light is below the horizon.
         // Entire light is below the horizon.
@@ -405,7 +407,7 @@ void LtcQuadEvaluate(
     // IntegrateQuadDiffuse is a cheap approximation compared to specular.
     // IntegrateQuadDiffuse is a cheap approximation compared to specular.
     float diffuse = IntegrateQuadDiffuse(polygon, vertexCount, doubleSided);
     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);
     float specular = LtcEvaluateSpecularUnscaled(ltcCoords, ltcMatrix, polygon, vertexCount, doubleSided);
 
 
     // Apply BRDF scale terms (BRDF magnitude and Schlick Fresnel)
     // Apply BRDF scale terms (BRDF magnitude and Schlick Fresnel)
@@ -415,14 +417,14 @@ void LtcQuadEvaluate(
     float2 schlick = ltcAmpMatrix.Sample(SceneSrg::LtcSampler, ltcCoords).xy;
     float2 schlick = ltcAmpMatrix.Sample(SceneSrg::LtcSampler, ltcCoords).xy;
     #endif
     #endif
     float3 specularRgb = specular * (schlick.x * surface.GetSpecularF0() + (1.0 - surface.GetSpecularF0()) * schlick.y);
     float3 specularRgb = specular * (schlick.x * surface.GetSpecularF0() + (1.0 - surface.GetSpecularF0()) * schlick.y);
-    
+
 #if ENABLE_CLEAR_COAT
 #if ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     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)
         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);
             float clearCoatSpecular = LtcEvaluateSpecularUnscaled(ltcCoordsCc, ltcMatrix, polygon, vertexCountCc, doubleSided);
 
 
             // Apply BRDF scale terms (BRDF magnitude and Schlick Fresnel)
             // 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);
         float3 clipPoint = ClipEdge(p1, p0);
         diffuse += IntegrateEdgeDiffuse(normalize(prevClipPoint), normalize(clipPoint));
         diffuse += IntegrateEdgeDiffuse(normalize(prevClipPoint), normalize(clipPoint));
         diffuse += IntegrateEdgeDiffuse(normalize(clipPoint), normalize(p1));
         diffuse += IntegrateEdgeDiffuse(normalize(clipPoint), normalize(p1));
-        
+
         clipPoint = mul(ltcMat, clipPoint);
         clipPoint = mul(ltcMat, clipPoint);
         specular += IntegrateEdge(normalize(mul(ltcMat, prevClipPoint)), normalize(clipPoint));
         specular += IntegrateEdge(normalize(mul(ltcMat, prevClipPoint)), normalize(clipPoint));
         specular += IntegrateEdge(normalize(clipPoint), normalize(mul(ltcMat, p1)));
         specular += IntegrateEdge(normalize(clipPoint), normalize(mul(ltcMat, p1)));
@@ -540,10 +542,10 @@ void LtcPolygonEvaluateInitialPoints(
 {
 {
     // Prepare initial values
     // Prepare initial values
     p0 = mul(orthonormalMat, positions[startIdx].xyz - surfacePosition); // First point in polygon
     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.
     prevClipPoint = float3(0.0, 0.0, 0.0); // Used to hold previous clip point when polygon dips below horizon.
     closePoint = p0;
     closePoint = p0;
-    
+
     // Handle if the first point is below the horizon.
     // Handle if the first point is below the horizon.
     if (p0.z < 0.0)
     if (p0.z < 0.0)
     {
     {
@@ -565,23 +567,24 @@ void LtcPolygonEvaluateInitialPoints(
 
 
         p0 = firstPoint; // Restore the original p0
         p0 = firstPoint; // Restore the original p0
     }
     }
-    
+
 }
 }
 
 
 // Evaluates the LTC result of an arbitrary polygon lighting a surface position.
 // 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
 //   positions - The buffer where the polygon positions are
 //   startIdx - The index of the first polygon position
 //   startIdx - The index of the first polygon position
 //   endIdx - The index of the point directly after the last 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
 // 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
 // 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
 // EvaluatePolyEdge() later. During this search it also adjusts the end point index as necessary to avoid processing
 // those points that are below the horizon.
 // those points that are below the horizon.
 void LtcPolygonEvaluate(
 void LtcPolygonEvaluate(
@@ -592,6 +595,7 @@ void LtcPolygonEvaluate(
     in StructuredBuffer<float4> positions,
     in StructuredBuffer<float4> positions,
     in uint startIdx,
     in uint startIdx,
     in uint endIdx,
     in uint endIdx,
+    in uint viewIndex,
     out float diffuseOut,
     out float diffuseOut,
     out float3 specularRgbOut
     out float3 specularRgbOut
 )
 )
@@ -608,7 +612,7 @@ void LtcPolygonEvaluate(
     uint originalEndIdx = endIdx; // Original endIdx may be needed for clearcoat
     uint originalEndIdx = endIdx; // Original endIdx may be needed for clearcoat
 
 
     // Rotate ltc matrix
     // 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
     // Evaluate the starting point (p0), previous point, and point used to close the polygon
     float3 p0, prevClipPoint, closePoint;
     float3 p0, prevClipPoint, closePoint;
@@ -619,15 +623,15 @@ void LtcPolygonEvaluate(
     {
     {
         diffuseOut = 0.0f;
         diffuseOut = 0.0f;
         specularRgbOut = float3(0.0f, 0.0f, 0.0f);
         specularRgbOut = float3(0.0f, 0.0f, 0.0f);
-        return; 
+        return;
     }
     }
-    
+
     float diffuse = 0.0;
     float diffuse = 0.0;
     float specular = 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);
     float3x3 ltcMat = LtcMatrix(ltcMatrix, ltcCoords);
-    
+
     // Evaluate all the points
     // Evaluate all the points
     for (uint curIdx = startIdx + 1; curIdx < endIdx; ++curIdx)
     for (uint curIdx = startIdx + 1; curIdx < endIdx; ++curIdx)
     {
     {
@@ -635,7 +639,7 @@ void LtcPolygonEvaluate(
         EvaluatePolyEdge(p0, p1, ltcMat, prevClipPoint, diffuse, specular);
         EvaluatePolyEdge(p0, p1, ltcMat, prevClipPoint, diffuse, specular);
         p0 = p1;
         p0 = p1;
     }
     }
-    
+
     EvaluatePolyEdge(p0, closePoint, ltcMat, prevClipPoint, diffuse, specular);
     EvaluatePolyEdge(p0, closePoint, ltcMat, prevClipPoint, diffuse, specular);
 
 
     // Note: negated due to winding order
     // Note: negated due to winding order
@@ -654,7 +658,7 @@ void LtcPolygonEvaluate(
     if(o_clearCoat_feature_enabled)
     if(o_clearCoat_feature_enabled)
     {
     {
         // Rotate ltc matrix
         // 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.
         // restore original endIdx and re-evaluate initial points with matrix based on the clearcoat normal.
         endIdx = originalEndIdx;
         endIdx = originalEndIdx;
@@ -665,9 +669,9 @@ void LtcPolygonEvaluate(
         {
         {
             float specularCc = 0.0;
             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);
             float3x3 ltcMatCc = LtcMatrix(ltcMatrix, ltcCoordsCc);
-            
+
             // Evaluate all the points
             // Evaluate all the points
             for (uint curIdx = startIdx + 1; curIdx < endIdx; ++curIdx)
             for (uint curIdx = startIdx + 1; curIdx < endIdx; ++curIdx)
             {
             {
@@ -675,7 +679,7 @@ void LtcPolygonEvaluate(
                 EvaluatePolyEdgeSpecularOnly(p0, p1, ltcMatCc, prevClipPoint, specularCc);
                 EvaluatePolyEdgeSpecularOnly(p0, p1, ltcMatCc, prevClipPoint, specularCc);
                 p0 = p1;
                 p0 = p1;
             }
             }
-            
+
             EvaluatePolyEdgeSpecularOnly(p0, closePoint, ltcMatCc, prevClipPoint, specularCc);
             EvaluatePolyEdgeSpecularOnly(p0, closePoint, ltcMatCc, prevClipPoint, specularCc);
 
 
             // Note: negated due to winding order
             // Note: negated due to winding order
@@ -699,5 +703,4 @@ void LtcPolygonEvaluate(
 
 
     diffuseOut = diffuse;
     diffuseOut = diffuse;
     specularRgbOut = specularRgb;
     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)
 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)));
     const float maxElement = max(abs(toPoint.z), max(abs(toPoint.x), abs(toPoint.y)));
     if (toPoint.x == -maxElement)
     if (toPoint.x == -maxElement)
     {
     {
@@ -29,7 +29,7 @@ int GetPointLightShadowCubemapFace(const float3 targetPos, const float3 lightPos
     else if (toPoint.x == maxElement)
     else if (toPoint.x == maxElement)
     {
     {
         return 1;
         return 1;
-    }   
+    }
     else if (toPoint.y == -maxElement)
     else if (toPoint.y == -maxElement)
     {
     {
         return 2;
         return 2;
@@ -42,31 +42,31 @@ int GetPointLightShadowCubemapFace(const float3 targetPos, const float3 lightPos
     {
     {
         return 4;
         return 4;
     }
     }
-    else 
+    else
     {
     {
         return 5;
         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
 // 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)
 int UnpackPointLightShadowIndex(const PointLight light, const int face)
 {
 {
     const int index = face >> 1;
     const int index = face >> 1;
     const int shiftAmount = (face & 1) * 16;
     const int shiftAmount = (face & 1) * 16;
     return (light.m_shadowIndices[index] >> shiftAmount) & 0xFFFF;
     return (light.m_shadowIndices[index] >> shiftAmount) & 0xFFFF;
-} 
+}
 
 
 uint ComputeShadowIndex(const PointLight light, const Surface surface)
 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
     // 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 uint lightIndex0 = UnpackPointLightShadowIndex(light, 0);
     const float shadowmapSize = ViewSrg::m_projectedFilterParams[lightIndex0].m_shadowmapSize;
     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.
     // 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 float normalBias = ViewSrg::m_projectedShadows[lightIndex0].m_normalShadowBias;
     const float3 biasedPosition = surface.position + ComputeNormalShadowOffset(normalBias, surface.vertexNormal, shadowmapSize);
     const float3 biasedPosition = surface.position + ComputeNormalShadowOffset(normalBias, surface.vertexNormal, shadowmapSize);
-    
+
     const int shadowCubemapFace = GetPointLightShadowCubemapFace(biasedPosition, light.m_position);
     const int shadowCubemapFace = GetPointLightShadowCubemapFace(biasedPosition, light.m_position);
     return UnpackPointLightShadowIndex(light, shadowCubemapFace);
     return UnpackPointLightShadowIndex(light, shadowCubemapFace);
 }
 }
@@ -79,14 +79,14 @@ void ApplyPointLight(PointLight light, Surface surface, inout LightingData light
     real posToLightDist = length(posToLight);
     real posToLightDist = length(posToLight);
     real d2 = dot(posToLight, posToLight); // light distance squared
     real d2 = dot(posToLight, posToLight); // light distance squared
     real falloff = d2 * real(light.m_invAttenuationRadiusSquared);
     real falloff = d2 * real(light.m_invAttenuationRadiusSquared);
-    
+
     // Only calculate shading if light is in range
     // Only calculate shading if light is in range
     if (falloff < 1.0)
     if (falloff < 1.0)
     {
     {
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         real radiusAttenuation = 1.0 - (falloff * falloff);
         real radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Standard quadratic falloff
         // Standard quadratic falloff
         d2 = max(0.001 * 0.001, d2); // clamp the light to at least 1mm away to avoid extreme values.
         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;
         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);
         lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, lightIntensity, normalize(posToLight), transmissionDistance, posToLightDist);
 #endif
 #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;
     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)
     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
     // Lighting value is in Candela, convert to Lumen for total light output of the light
     real3 intensityLumens = real3(light.m_rgbIntensityCandelas) * 4.0 * PI;
     real3 intensityLumens = real3(light.m_rgbIntensityCandelas) * 4.0 * PI;
+
     // Each of the N samples will contribute intensity / N lumens. However it will radiate in
     // 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
     // equal directions across the hemisphere, so we need to account for that
     real3 intensity = intensityLumens * INV_PI;
     real3 intensity = intensityLumens * INV_PI;
 
 
     lightingData.diffuseLighting += (diffuseAcc / real(sampleCount)) * intensity;
     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
 #if ENABLE_TRANSMISSION
     lightingData.translucentBackLighting += (translucentAcc / real(sampleCount)) * intensity;
     lightingData.translucentBackLighting += (translucentAcc / real(sampleCount)) * intensity;
@@ -216,12 +231,12 @@ void ApplyPointLights(Surface surface, inout LightingData lightingData)
 {
 {
 #if ENABLE_LIGHT_CULLING
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
     lightingData.tileIterator.LoadAdvance();
-    
-    while( !lightingData.tileIterator.IsDone() ) 
-    { 
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+
+    while( !lightingData.tileIterator.IsDone() )
+    {
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
         lightingData.tileIterator.LoadAdvance();
-    
+
         ApplyPointLightInternal(currLightIndex, surface, lightingData);
         ApplyPointLightInternal(currLightIndex, surface, lightingData);
     }
     }
 #else
 #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;
     float3 posToLight = light.m_position - surface.position;
     float distanceToLight2 = dot(posToLight, posToLight); // light distance squared
     float distanceToLight2 = dot(posToLight, posToLight); // light distance squared
     float falloff = distanceToLight2 * abs(light.m_invAttenuationRadiusSquared);
     float falloff = distanceToLight2 * abs(light.m_invAttenuationRadiusSquared);
-    
+
     if (falloff > 1.0f)
     if (falloff > 1.0f)
     {
     {
         return; // light out of range
         return; // light out of range
@@ -43,19 +43,30 @@ void ApplyPoylgonLight(PolygonLight light, Surface surface, inout LightingData l
     float radiusAttenuation = 1.0 - (falloff * falloff);
     float radiusAttenuation = 1.0 - (falloff * falloff);
     radiusAttenuation = radiusAttenuation * radiusAttenuation;
     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)
 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))
             if(!IsSameLightChannel(lightingData.lightingChannels, light.m_lightingChannelMask))
             {
             {
                 continue;
                 continue;
-            }            
+            }
             ApplyPoylgonLight(light, surface, lightingData);
             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 n1 = normalize(cross(v1, v2));
     float3 n2 = normalize(cross(v2, v3));
     float3 n2 = normalize(cross(v2, v3));
     float3 n3 = normalize(cross(v3, v0));
     float3 n3 = normalize(cross(v3, v0));
-    
+
     float g0 = acos(dot (-n0, n1));
     float g0 = acos(dot (-n0, n1));
     float g1 = acos(dot (-n1, n2));
     float g1 = acos(dot (-n1, n2));
     float g2 = acos(dot (-n2, n3));
     float g2 = acos(dot (-n2, n3));
@@ -36,7 +36,7 @@ float3 RayLightPlaneIntersection(in float3 pos, in float3 rayDirection, in float
 {
 {
     float3 lightToPos = pos - lightOrigin;
     float3 lightToPos = pos - lightOrigin;
     float reflectionDotLight = dot(rayDirection, -lightDirection);
     float reflectionDotLight = dot(rayDirection, -lightDirection);
-    
+
     float distanceToPlane = dot(lightToPos, lightDirection);
     float distanceToPlane = dot(lightToPos, lightDirection);
     if (reflectionDotLight >= 0.0001)
     if (reflectionDotLight >= 0.0001)
     {
     {
@@ -106,16 +106,30 @@ void ApplyQuadLight(QuadLight light, Surface surface, inout LightingData lightin
         if (!useFastApproximation && o_enableQuadLightLTC)
         if (!useFastApproximation && o_enableQuadLightLTC)
         {
         {
             float3 p[4] = {p0, p1, p2, p3};
             float3 p[4] = {p0, p1, p2, p3};
-            
+
             float diffuse = 0.0;
             float diffuse = 0.0;
             float3 specular = float3(0.0, 0.0, 0.0); // specularF0 used in LtcQuadEvaluate which is a float3
             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)
         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)
             // Each position contributes 1/5 of the light (4 corners + center)
             float3 intensity = solidAngle * 0.2 * radiusAttenuation * light.m_rgbIntensityNits;
             float3 intensity = solidAngle * 0.2 * radiusAttenuation * light.m_rgbIntensityNits;
 
 
-            lightingData.diffuseLighting += 
+            lightingData.diffuseLighting +=
             (
             (
                 GetDiffuseLighting(surface, lightingData, intensity, p0) +
                 GetDiffuseLighting(surface, lightingData, intensity, p0) +
                 GetDiffuseLighting(surface, lightingData, intensity, p1) +
                 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, p3) +
                 GetDiffuseLighting(surface, lightingData, intensity, dirToLightCenter)
                 GetDiffuseLighting(surface, lightingData, intensity, dirToLightCenter)
             );
             );
-            
+
             // Transmission contribution
             // Transmission contribution
             // We cannot compute the actual transmission distance so we want to:
             // 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 thick object -> use transmission thickness parameter instead
             // - If transmission mode is thin object -> ignore back lighting
             // - 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;
             const float transmissionDistance = -1.0f;
             // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
             // If the transmissionDistance is ignored then the attenuation distance (only used on thin objects) does not have any influence
             const float attenuationDistance = 0.0f;
             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)
                 GetBackLighting(surface, lightingData, intensity, dirToLightCenter, transmissionDistance, attenuationDistance)
             );
             );
 #endif
 #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;
     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;
     bool emitsBothDirections = (light.m_flags & EmitsBothDirections) > 0;
     float bothDirectionsFactor = emitsBothDirections ? -1.0 : 0.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;
     float3 intensityCandelas = light.m_rgbIntensityNits * area;
 
 
     lightingData.diffuseLighting += (diffuseAcc / float(sampleCount)) * intensityCandelas;
     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
 #if ENABLE_TRANSMISSION
     lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensityCandelas;
     lightingData.translucentBackLighting += (translucentAcc / float(sampleCount)) * intensityCandelas;
@@ -272,10 +287,10 @@ void ApplyQuadLights(Surface surface, inout LightingData lightingData)
 {
 {
 #if ENABLE_LIGHT_CULLING
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
     lightingData.tileIterator.LoadAdvance();
-                
-    while( !lightingData.tileIterator.IsDone() ) 
-    { 
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+
+    while( !lightingData.tileIterator.IsDone() )
+    {
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
         lightingData.tileIterator.LoadAdvance();
 
 
         ApplyQuadLightInternal(currLightIndex, surface, lightingData);
         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);
     real3 posToLight = real3(light.m_position - surface.position);
     real d2 = dot(posToLight, posToLight); // light distance squared
     real d2 = dot(posToLight, posToLight); // light distance squared
     real falloff = d2 * real(light.m_invAttenuationRadiusSquared);
     real falloff = d2 * real(light.m_invAttenuationRadiusSquared);
-    
+
     // Only calculate shading if light is in range
     // Only calculate shading if light is in range
     if (falloff < 1.0f)
     if (falloff < 1.0f)
     {
     {
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         real radiusAttenuation = 1.0 - (falloff * falloff);
         real radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Standard quadratic falloff
         // Standard quadratic falloff
         d2 = max(0.001 * 0.001, d2); // clamp the light to at least 1mm away to avoid extreme values.
         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;
         real3 lightIntensity = (real3(light.m_rgbIntensityCandelas) / d2) * radiusAttenuation;
@@ -31,8 +31,13 @@ void ApplySimplePointLight(SimplePointLight light, Surface surface, inout Lighti
         // Diffuse contribution
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, posToLightDir);
         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)
 void ApplySimplePointLights(Surface surface, inout LightingData lightingData)
 {
 {
 #if ENABLE_LIGHT_CULLING
 #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();
         lightingData.tileIterator.LoadAdvance();
-    
+
         ApplySimplePointLightInternal(currLightIndex, surface, lightingData);
         ApplySimplePointLightInternal(currLightIndex, surface, lightingData);
     }
     }
 #else
 #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
     // 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)
     // This is only applicable if ENABLE_LIGHT_CULLING is disabled (i.e no gpu culling)
     #ifndef ENABLE_SIMPLE_POINTLIGHTS_CAP
     #ifndef ENABLE_SIMPLE_POINTLIGHTS_CAP
-        #define ENABLE_SIMPLE_POINTLIGHTS_CAP 20 
+        #define ENABLE_SIMPLE_POINTLIGHTS_CAP 20
     #endif
     #endif
 
 
     // Since there's no GPU culling for simple point lights, we rely on culling done by CPU
     // 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)
 void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout LightingData lightingData)
 {
 {
     real3 posToLight = real3(light.m_position - surface.position);
     real3 posToLight = real3(light.m_position - surface.position);
-    
+
     real3 dirToLight = normalize(posToLight);
     real3 dirToLight = normalize(posToLight);
     real dotWithDirection = dot(dirToLight, -real3(light.m_direction));
     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
         // Smoothly adjusts the light intensity so it reaches 0 at light.m_attenuationRadius distance
         real radiusAttenuation = 1.0 - (falloff * falloff);
         real radiusAttenuation = 1.0 - (falloff * falloff);
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
         radiusAttenuation = radiusAttenuation * radiusAttenuation;
-        
+
         // Standard quadratic falloff
         // Standard quadratic falloff
         d2 = max(0.001 * 0.001, d2); // clamp the light to at least 1mm away to avoid extreme values.
         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;
         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
         if (dotWithDirection < cosInnerConeAngle) // in penumbra
-        {   
+        {
             // Normalize into 0.0 - 1.0 space.
             // Normalize into 0.0 - 1.0 space.
             real penumbraMask = (dotWithDirection - real(light.m_cosOuterConeAngle)) / (cosInnerConeAngle - real(light.m_cosOuterConeAngle));
             real penumbraMask = (dotWithDirection - real(light.m_cosOuterConeAngle)) / (cosInnerConeAngle - real(light.m_cosOuterConeAngle));
-            
+
             // Apply smoothstep
             // Apply smoothstep
             penumbraMask = penumbraMask * penumbraMask * (3.0 - 2.0 * penumbraMask);
             penumbraMask = penumbraMask * penumbraMask * (3.0 - 2.0 * penumbraMask);
-            
+
             lightIntensity *= penumbraMask;
             lightIntensity *= penumbraMask;
         }
         }
 
 
         // Diffuse contribution
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, posToLightDir) * litRatio;
         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
 #if ENABLE_LIGHT_CULLING
     lightingData.tileIterator.LoadAdvance();
     lightingData.tileIterator.LoadAdvance();
-                
-    while( !lightingData.tileIterator.IsDone() ) 
-    { 
-        uint currLightIndex = lightingData.tileIterator.GetValue(); 
+
+    while( !lightingData.tileIterator.IsDone() )
+    {
+        uint currLightIndex = lightingData.tileIterator.GetValue();
         lightingData.tileIterator.LoadAdvance();
         lightingData.tileIterator.LoadAdvance();
-    
+
         ApplySimpleSpotLightInternal(currLightIndex, surface, lightingData);
         ApplySimpleSpotLightInternal(currLightIndex, surface, lightingData);
     }
     }
 #else
 #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
     // 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)
     // This is only applicable if ENABLE_LIGHT_CULLING is disabled (i.e no gpu culling)
     #ifndef ENABLE_SIMPLE_SPOTLIGHTS_CAP
     #ifndef ENABLE_SIMPLE_SPOTLIGHTS_CAP
-        #define ENABLE_SIMPLE_SPOTLIGHTS_CAP 20 
+        #define ENABLE_SIMPLE_SPOTLIGHTS_CAP 20
     #endif
     #endif
 
 
     // Since there's no GPU culling for simple spot lights, we rely on culling done by CPU
     // 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);
     G = max(0.0, G);
 
 
     // the common denominator 4 * NdotL * NdotV is included in the simplified G term
     // 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)
 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
 #endif
 
 
 #ifndef GetSpecularLighting
 #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
 #endif
 
 
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
@@ -36,12 +36,12 @@ real3 GetDiffuseLighting_BasePBR(Surface surface, LightingData lightingData, rea
     return diffuse;
     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
 #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
 #endif
     specular *= lightIntensity;
     specular *= lightIntensity;
     return specular;
     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
 // 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
 // used in your shader. Simply #define LightingData to your custom definition before including this file
-// 
+//
 #ifndef LightingData
 #ifndef LightingData
 #define LightingData        LightingData_BasePBR
 #define LightingData        LightingData_BasePBR
 #endif
 #endif
@@ -21,25 +21,33 @@
 #include <Atom/Features/PBR/LightingUtils.azsli>
 #include <Atom/Features/PBR/LightingUtils.azsli>
 #include <Atom/Features/PBR/LightingOptions.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
 class LightingData_BasePBR
 {
 {
     LightCullingTileIterator tileIterator;
     LightCullingTileIterator tileIterator;
-    
+
     uint lightingChannels;
     uint lightingChannels;
-    
+
     // Lighting outputs
     // Lighting outputs
     real3 diffuseLighting;
     real3 diffuseLighting;
-    real3 specularLighting;
+    real3 specularLighting[MAX_SHADING_VIEWS];
 
 
     // Factors for the amount of diffuse and specular lighting applied
     // Factors for the amount of diffuse and specular lighting applied
     real3 diffuseResponse;
     real3 diffuseResponse;
-   
+
     // Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
     // Shrink (absolute) offset towards the normal opposite direction to ensure correct shadow map projection
     real shrinkFactor;
     real shrinkFactor;
 
 
     // Normalized direction from surface to camera
     // Normalized direction from surface to camera
-    float3 dirToCamera;
-    
+    float3 dirToCamera[MAX_SHADING_VIEWS];
+
     // Scaling term to approximate multiscattering contribution in specular BRDF
     // Scaling term to approximate multiscattering contribution in specular BRDF
     real3 multiScatterCompensation;
     real3 multiScatterCompensation;
 
 
@@ -47,10 +55,10 @@ class LightingData_BasePBR
     real3 emissiveLighting;
     real3 emissiveLighting;
 
 
     // BRDF texture values
     // BRDF texture values
-    real2 brdf;
+    real2 brdf[MAX_SHADING_VIEWS];
 
 
     // Normal . View
     // Normal . View
-    float NdotV;
+    float NdotV[MAX_SHADING_VIEWS];
 
 
 #if ENABLE_TRANSMISSION
 #if ENABLE_TRANSMISSION
     real3 translucentBackLighting;
     real3 translucentBackLighting;
@@ -60,16 +68,16 @@ class LightingData_BasePBR
     real diffuseAmbientOcclusion;
     real diffuseAmbientOcclusion;
     real specularOcclusion;
     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 CalculateMultiscatterCompensation(real3 specularF0, bool enabled);
     void FinalizeLighting(Surface surface);
     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);
     diffuseLighting = real3(0.0, 0.0, 0.0);
-    specularLighting = real3(0.0, 0.0, 0.0);
     multiScatterCompensation = real3(1.0, 1.0, 1.0);
     multiScatterCompensation = real3(1.0, 1.0, 1.0);
     emissiveLighting = real3(0.0, 0.0, 0.0);
     emissiveLighting = real3(0.0, 0.0, 0.0);
 #if ENABLE_TRANSMISSION
 #if ENABLE_TRANSMISSION
@@ -77,35 +85,46 @@ void LightingData_BasePBR::Init(float3 positionWS, real3 specularNormal, real ro
 #endif
 #endif
     diffuseAmbientOcclusion = 1.0;
     diffuseAmbientOcclusion = 1.0;
     specularOcclusion = 1.0;
     specularOcclusion = 1.0;
-        
+
     lightingChannels = 0xFFFFFFFF;
     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
     #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
     #else
-    brdf = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg);
+        brdf[i] = real2(PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, brdfUV).rg);
     #endif
     #endif
+    }
 }
 }
 
 
 void LightingData_BasePBR::CalculateMultiscatterCompensation(real3 specularF0, bool enabled)
 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)
 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())
     if(!IsDiffuseLightingEnabled())
     {
     {
         diffuseLighting = real3(0, 0, 0);
         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/Lights/IblForward.azsli>
 #include <Atom/Features/PBR/Decals.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
     // Light iterator
 #if ENABLE_LIGHT_CULLING
 #if ENABLE_LIGHT_CULLING
@@ -28,10 +28,10 @@ void InitializeLightingData_BasePBR(inout Surface surface, float4 screenPosition
     lightingData.tileIterator.InitNoCulling();
     lightingData.tileIterator.InitNoCulling();
 #endif
 #endif
 
 
-    lightingData.Init(surface.position, surface.GetSpecularNormal(), surface.roughnessLinear, viewPosition);
+    lightingData.Init(surface.position, surface.GetSpecularNormal(), surface.roughnessLinear, viewPositions);
 
 
     lightingData.lightingChannels = ObjectSrg::m_lightingChannelMask;
     lightingData.lightingChannels = ObjectSrg::m_lightingChannelMask;
-    
+
     // Diffuse and Specular response (used in IBL calculations)
     // Diffuse and Specular response (used in IBL calculations)
     real3 specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.GetSpecularF0(), surface.roughnessLinear);
     real3 specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.GetSpecularF0(), surface.roughnessLinear);
     lightingData.diffuseResponse = 1.0 - specularResponse;
     lightingData.diffuseResponse = 1.0 - specularResponse;
@@ -55,10 +55,10 @@ void CalculateLighting_BasePBR(inout Surface surface, float4 screenPosition, ino
     lightingData.FinalizeLighting(surface);
     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;
     LightingData lightingData;
-    InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
+    InitializeLightingData_BasePBR(surface, screenPosition, viewPositions, lightingData);
     CalculateLighting_BasePBR(surface, screenPosition, lightingData);
     CalculateLighting_BasePBR(surface, screenPosition, lightingData);
     return 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
 // used in your shader. Simply #define GetDiffuseLighting & GetSpecularLighting to your custom definition before including this file
 //
 //
 #ifndef GetDiffuseLighting
 #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
 #endif
 
 
 #ifndef GetSpecularLighting
 #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
 #endif
 
 
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
@@ -37,7 +37,7 @@ real3 GetDiffuseLighting_EnhancedPBR(Surface surface, LightingData lightingData,
     if(o_enableSubsurfaceScattering)
     if(o_enableSubsurfaceScattering)
     {
     {
         // Use diffuse brdf contains double Fresnel (enter/exit surface) terms if subsurface scattering is enabled
         // 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
     else
     {
     {
@@ -48,7 +48,7 @@ real3 GetDiffuseLighting_EnhancedPBR(Surface surface, LightingData lightingData,
     if(o_clearCoat_feature_enabled)
     if(o_clearCoat_feature_enabled)
     {
     {
         // Attenuate diffuse term by clear coat's fresnel term to account for energy loss
         // 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);
         diffuse *= 1.0 - (FresnelSchlick(HdotV, 0.04) * surface.clearCoat.factor);
     }
     }
 #endif
 #endif
@@ -57,30 +57,30 @@ real3 GetDiffuseLighting_EnhancedPBR(Surface surface, LightingData lightingData,
     return diffuse;
     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;
     real3 specular;
     if (o_enableAnisotropy)
     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
     else
     {
     {
 #if ENABLE_MOBILEBRDF
 #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
 #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
 #endif
     }
     }
 
 
 #if ENABLE_CLEAR_COAT
 #if ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     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
 #endif
-    
+
     specular *= lightIntensity;
     specular *= lightIntensity;
     return specular;
     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
 // used in your shader. Simply #define GetDiffuseLighting & GetSpecularLighting to your custom definition before including this file
 //
 //
 #ifndef GetDiffuseLighting
 #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
 #endif
 
 
 #ifndef GetSpecularLighting
 #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
 #endif
 
 
 #include "../BasePBR/BasePBR_LightingBrdf.azsli"
 #include "../BasePBR/BasePBR_LightingBrdf.azsli"
@@ -32,19 +32,19 @@
 // Then define the Diffuse and Specular lighting functions
 // Then define the Diffuse and Specular lighting functions
 real3 GetDiffuseLighting_Skin(Surface surface, LightingData lightingData, real3 lightIntensity, real3 dirToLight)
 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;
     diffuse *= lightIntensity;
     return diffuse;
     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)
     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);
         specular = lerp(specular, dualSpecular, surface.dualSpecFactor);
     }
     }
 
 
@@ -52,4 +52,3 @@ real3 GetSpecularLighting_Skin(Surface surface, LightingData lightingData, const
 
 
     return specular;
     return specular;
 }
 }
-

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

@@ -17,7 +17,7 @@
 
 
 #include "../BasePBR/BasePBR_LightingEval.azsli"
 #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 ---
     // --- Base PBR ---
     InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
     InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
@@ -27,7 +27,7 @@ void InitializeLightingData_SkinPBR(inout Surface surface, float4 screenPosition
     lightingData.specularOcclusion = surface.specularOcclusion;
     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;
     LightingData lightingData;
     InitializeLightingData_SkinPBR(surface, screenPosition, viewPosition, 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
 // used in your shader. Simply #define GetDiffuseLighting & GetSpecularLighting to your custom definition before including this file
 //
 //
 #ifndef GetDiffuseLighting
 #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
 #endif
 
 
 #ifndef GetSpecularLighting
 #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
 #endif
 
 
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
 #include <Atom/Features/PBR/Microfacet/Brdf.azsli>
@@ -39,7 +39,7 @@ real3 GetDiffuseLighting_StandardPBR(Surface surface, LightingData lightingData,
     if(o_clearCoat_feature_enabled)
     if(o_clearCoat_feature_enabled)
     {
     {
         // Attenuate diffuse term by clear coat's fresnel term to account for energy loss
         // 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);
         diffuse *= 1.0 - (FresnelSchlick(HdotV, 0.04) * surface.clearCoat.factor);
     }
     }
 #endif
 #endif
@@ -48,18 +48,18 @@ real3 GetDiffuseLighting_StandardPBR(Surface surface, LightingData lightingData,
     return diffuse;
     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
 #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
 #endif
 
 
 #if ENABLE_CLEAR_COAT
 #if ENABLE_CLEAR_COAT
     if(o_clearCoat_feature_enabled)
     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
 #endif
 
 

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

@@ -17,7 +17,7 @@
 
 
 #include "../BasePBR/BasePBR_LightingEval.azsli"
 #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 ---
     // --- Base PBR ---
     InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
     InitializeLightingData_BasePBR(surface, screenPosition, viewPosition, lightingData);
@@ -37,7 +37,7 @@ void InitializeLightingData_StandardPBR(inout Surface surface, float4 screenPosi
 #endif
 #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;
     LightingData lightingData;
     InitializeLightingData_StandardPBR(surface, screenPosition, viewPosition, 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)
 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));
     real4x4 objectToWorld = real4x4(GetObjectToWorldMatrix(IN.m_instanceId));
     real3x3 objectToWorldIT = real3x3(GetObjectToWorldMatrixInverseTranspose(IN.m_instanceId));
     real3x3 objectToWorldIT = real3x3(GetObjectToWorldMatrixInverseTranspose(IN.m_instanceId));
 
 
@@ -202,13 +205,13 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN)
 
 
     // Light iterator
     // Light iterator
     lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
     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
     // Shadow
     lightingData.diffuseAmbientOcclusion = 1.0f - surfaceDepth * AutoBrickSrg::m_aoFactor;
     lightingData.diffuseAmbientOcclusion = 1.0f - surfaceDepth * AutoBrickSrg::m_aoFactor;
 
 
     // Diffuse and Specular response
     // 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;
     lightingData.diffuseResponse = 1.0f - lightingData.specularResponse;
 
 
     const float alpha = 1.0f;
     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)
 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));
     real4x4 objectToWorld = real4x4(GetObjectToWorldMatrix(IN.m_instanceId));
     real3x3 objectToWorldIT = real3x3(GetObjectToWorldMatrixInverseTranspose(IN.m_instanceId));
     real3x3 objectToWorldIT = real3x3(GetObjectToWorldMatrixInverseTranspose(IN.m_instanceId));
 
 
@@ -93,11 +96,11 @@ ForwardPassOutput MinimalPBR_MainPassPS(VSOutput IN)
 
 
     // Light iterator
     // Light iterator
     lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
     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
     // 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;
     lightingData.diffuseResponse = 1.0f - lightingData.specularResponse;
 
 
     const float alpha = 1.0f;
     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;
 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
     float3 specular = float3(1, 0, 1);  // purple - error color
     if (o_hairLightingModel == HairLightingModel::GGX)
     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)
     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
     // [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(
 void UpdateLightingParameters(
     inout LightingData lightingData, 
     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(
 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 Surface surface, 
     inout LightingData lightingData)
     inout LightingData lightingData)
 {
 {
     float3 biNormal;
     float3 biNormal;
     if (o_hairLightingModel == HairLightingModel::GGX)
     if (o_hairLightingModel == HairLightingModel::GGX)
     {   // Towards half vector but never cross fully (more weight to camera direction)
     {   // 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));
         biNormal = normalize(cross(tangent, halfDir));
     }
     }
     else
     else
     {   // Face forward towards the camera
     {   // Face forward towards the camera
-        biNormal = normalize(cross(tangent, lightingData.dirToCamera));   
+        biNormal = normalize(cross(tangent, lightingData.dirToCamera[0]));   
     }
     }
 
 
     float3 projectedNormal = cross(biNormal, tangent);
     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.
     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
     // 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
     // Diffuse and Specular response
     lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.GetSpecularF0(), surface.roughnessLinear);
     lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.GetSpecularNdotV(), surface.GetSpecularF0(), surface.roughnessLinear);
@@ -154,7 +158,7 @@ void SetNormalAndUpdateLightingParams(
 //==============================================================================
 //==============================================================================
 //                         Simple Point Light 
 //                         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;
     float3 posToLight = light.m_position - surface.position;
     float d2 = dot(posToLight, posToLight); // light distance squared
     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 lightIntensity = (light.m_rgbIntensityCandelas / d2) * radiusAttenuation;
 
 
         float3 dirToLight = normalize(posToLight);
         float3 dirToLight = normalize(posToLight);
-        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, surface, lightingData);
+        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, views, surface, lightingData);
 
 
         // Diffuse contribution
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight);
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight);
 
 
         // Specular contribution
         // 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. 
 // 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 
 // 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)
 // 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();
     lightingData.tileIterator.LoadAdvance();
 
 
@@ -198,24 +204,24 @@ void ApplyCulledSimplePointLights(Surface surface, inout LightingData lightingDa
         lightingData.tileIterator.LoadAdvance();
         lightingData.tileIterator.LoadAdvance();
 
 
         SimplePointLight light = ViewSrg::m_simplePointLights[currLightIndex];
         SimplePointLight light = ViewSrg::m_simplePointLights[currLightIndex];
-        ApplySimplePointLight(light, surface, lightingData);
+        ApplySimplePointLight(light, surface, lightingData, views);
         ++lightCount;
         ++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)
     for (int l = 0; l < ViewSrg::m_simplePointLightCount; ++l)
     {
     {
         SimplePointLight light = ViewSrg::m_simplePointLights[l];
         SimplePointLight light = ViewSrg::m_simplePointLights[l];
-        ApplySimplePointLight(light, surface, lightingData);
+        ApplySimplePointLight(light, surface, lightingData, views);
     }
     }
 }
 }
 
 
 //==============================================================================
 //==============================================================================
 //                           Simple Spot Light 
 //                           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 posToLight = light.m_position - surface.position;
     float3 dirToLight = normalize(posToLight);
     float3 dirToLight = normalize(posToLight);
@@ -252,7 +258,7 @@ void ApplySimpleSpotLight(SimpleSpotLight light, Surface surface, inout Lighting
             lightIntensity *= penumbraMask;
             lightIntensity *= penumbraMask;
         }
         }
 
 
-        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, surface, lightingData);
+        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, views, surface, lightingData);
 
 
         // Tranmission contribution
         // Tranmission contribution
         lightingData.translucentBackLighting += GetHairBackLighting(surface, lightingData, lightIntensity, dirToLight, 1.0);
         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);
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight);
 
 
         // Specular contribution
         // 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)
     for (int l = 0; l < ViewSrg::m_simpleSpotLightCount; ++l)
     {
     {
         SimpleSpotLight light = ViewSrg::m_simpleSpotLights[l];
         SimpleSpotLight light = ViewSrg::m_simpleSpotLights[l];
-        ApplySimpleSpotLight(light, surface, lightingData);
+        ApplySimpleSpotLight(light, surface, lightingData, views);
     }
     }
 }
 }
 //==============================================================================
 //==============================================================================
 //                              Point Light 
 //                              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;
     float3 posToLight = light.m_position - surface.position;
     float d2 = dot(posToLight, posToLight); // light distance squared
     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 lightIntensity = (light.m_rgbIntensityCandelas / d2) * radiusAttenuation;
 
 
         float3 dirToLight = normalize(posToLight);
         float3 dirToLight = normalize(posToLight);
-        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, surface, lightingData);
+        SetNormalAndUpdateLightingParams(surface.tangent, dirToLight, views, surface, lightingData);
 
 
         // Diffuse contribution
         // Diffuse contribution
         lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, lightIntensity, dirToLight);
         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
         // Adjust the light direcion for specular based on bulb size
 
 
         // Calculate the reflection off the normal from the view direction
         // 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
         // Calculate a vector from the reflection vector to the light
         float3 reflectionPosToLight = posToLight - dot(posToLight, reflectionDir) * reflectionDir;
         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);
         float sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_bulbRadius, d2);
 
 
         // Specular contribution
         // 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)
     for (int l = 0; l < ViewSrg::m_pointLightCount; ++l)
     {
     {
         PointLight light = ViewSrg::m_pointLights[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>
 #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)
     for (int l = 0; l < ViewSrg::m_diskLightCount; ++l)
     {
     {
         DiskLight light = ViewSrg::m_diskLights[l];
         DiskLight light = ViewSrg::m_diskLights[l];
@@ -348,14 +358,14 @@ void ApplyAllDiskLights(Surface surface, inout LightingData lightingData)
 //==============================================================================
 //==============================================================================
 //                            Directional Lights
 //                            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
     // Shadowed check
     const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
     const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
     float litRatio = 1.0f;
     float litRatio = 1.0f;
     float backShadowRatio = 0.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)
     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);
         float3 dirToLight = normalize(-light.m_direction);
 
 
         // Adjust the direction of the light based on its angular diameter.
         // 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;
         float3 lightDirToReflectionDir = reflectionDir - dirToLight;
         float lightDirToReflectionDirLen = length(lightDirToReflectionDir);
         float lightDirToReflectionDirLen = length(lightDirToReflectionDir);
         lightDirToReflectionDir = lightDirToReflectionDir / lightDirToReflectionDirLen; // normalize the length
         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.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);
         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 irradianceDir = MultiplyVectorQuaternion(surface.GetDiffuseNormal(), SceneSrg::m_iblOrientation);
     float3 diffuseSample = SceneSrg::m_diffuseEnvMap.Sample(SceneSrg::m_samplerEnv, GetCubemapCoords(irradianceDir)).rgb;
     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());
 //    float3 diffuseLighting = GetDiffuseLighting(surface, lightingData, diffuseSample, surface.GetDiffuseNormal());
 
 
     // Notice the multiplication with inverse thickness used as a measure of occlusion
     // Notice the multiplication with inverse thickness used as a measure of occlusion
     return lightingData.diffuseResponse * surface.albedo * diffuseSample * (1.0f - surface.thickness);
     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);    
     reflectDir = MultiplyVectorQuaternion(reflectDir, SceneSrg::m_iblOrientation);    
 
 
     // global
     // global
@@ -432,24 +445,29 @@ float3 ApplyIblSpecular(Surface surface, LightingData lightingData)
         SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), 
         SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), 
         GetRoughnessMip(surface.roughnessLinear)).rgb;
         GetRoughnessMip(surface.roughnessLinear)).rgb;
 
 
-    float3 specularLighting = GetSpecularLighting(surface, lightingData, specularSample, reflectDir);
+    float3 specularLighting = GetSpecularLighting(surface, lightingData, specularSample, reflectDir, viewIndex);
     return specularLighting;
     return specularLighting;
 }
 }
 
 
 // Remark: IBL is still WIP and this part will change in the near future
 // 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));
 //    float3 normal = normalize(float3(surface.tangent.z, -surface.tangent.x, surface.tangent.y));
 //    SetNormalAndUpdateLightingParams(surface.tangent, normal, surface, lightingData);
 //    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 iblDiffuse = ApplyIblDiffuse(surface, lightingData);
-    float3 iblSpecular = ApplyIblSpecular(surface, lightingData);
 
 
     // Adjust IBL lighting by exposure.
     // Adjust IBL lighting by exposure.
     float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure);
     float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure);
     lightingData.diffuseLighting += (iblDiffuse * iblExposureFactor * lightingData.diffuseAmbientOcclusion);
     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
 //                       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
     // Shadow coordinates generation for the directional light
     const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
     const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
 
 
-
     // Light loops application.
     // Light loops application.
     // If culling is used, the order of the calls must match the light types list order 
     // If culling is used, the order of the calls must match the light types list order 
 //    ApplyDecals(lightingData.tileIterator, surface);
 //    ApplyDecals(lightingData.tileIterator, surface);
 
 
     if (o_enableDirectionalLights)
     if (o_enableDirectionalLights)
     {
     {
-        ApplyDirectionalLights(surface, lightingData, screenCoords);
+        ApplyDirectionalLights(surface, lightingData, screenCoords, views);
     }
     }
 
 
     if (o_enablePunctualLights)
     if (o_enablePunctualLights)
     {
     {
-        ApplyAllSimplePointLights(surface, lightingData);
-        ApplyAllSimpleSpotLights(surface, lightingData);
+        ApplyAllSimplePointLights(surface, lightingData, views);
+        ApplyAllSimpleSpotLights(surface, lightingData, views);
     }
     }
 
 
     if (o_enableAreaLights)
     if (o_enableAreaLights)
     {
     {
-        ApplyAllPointLights(surface, lightingData);
-        ApplyAllDiskLights(surface, lightingData);
+        ApplyAllPointLights(surface, lightingData, views);
+        ApplyAllDiskLights(surface, lightingData, views);
     }
     }
 
 
     if (o_enableIBL)
     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, 
     float3 vPositionWS, float3 vViewDirWS, float3 vTangent, 
     float thickness, in HairShadeParams material )
     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 init --------
     Surface surface;
     Surface surface;
 
 
@@ -226,11 +229,18 @@ float3 CalculateLighting(
     lightingData.tileIterator.Init(screenCoords, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
     lightingData.tileIterator.Init(screenCoords, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
 
 
     // The normal assignment will be overriden afterwards per light
     // 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)
 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
 //                    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 ---------------
     //-------------- Lighting Parameters Calculations ---------------
     // Incoming and outgoing light directions
     // Incoming and outgoing light directions
     float3 Wi = normalize(dirToLight);                 // Incident light direction
     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
     float3 T = normalize(surface.tangent);             // Hair tangent
 
 
     // Incident light and reflection direction projected along the 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)
 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;
     Surface surface;
     surface.position = input.m_worldPosition.xyz;
     surface.position = input.m_worldPosition.xyz;
@@ -162,13 +165,13 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput input)
 
 
     // Light iterator
     // Light iterator
     lightingData.tileIterator.Init(input.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
     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
     // Shadow, Occlusion
     lightingData.diffuseAmbientOcclusion = detailSurface.m_occlusion;
     lightingData.diffuseAmbientOcclusion = detailSurface.m_occlusion;
 
 
     // Diffuse and Specular response
     // 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;
     lightingData.diffuseResponse = 1.0f - lightingData.specularResponse;
 
 
     const float alpha = 1.0f;
     const float alpha = 1.0f;