diff --git a/Content/Editor/MaterialTemplates/VolumeParticle.shader b/Content/Editor/MaterialTemplates/VolumeParticle.shader
new file mode 100644
index 000000000..c0fc57e54
--- /dev/null
+++ b/Content/Editor/MaterialTemplates/VolumeParticle.shader
@@ -0,0 +1,246 @@
+// File generated by Flax Materials Editor
+// Version: @0
+
+#define MATERIAL 1
+@3
+
+#include "./Flax/Common.hlsl"
+#include "./Flax/MaterialCommon.hlsl"
+#include "./Flax/GBufferCommon.hlsl"
+@7
+
+// Primary constant buffer (with additional material parameters)
+META_CB_BEGIN(0, Data)
+float4x4 ViewProjectionMatrix;
+float4x4 InverseViewProjectionMatrix;
+float4x4 ViewMatrix;
+float4x4 WorldMatrix;
+float4x4 WorldMatrixInverseTransposed;
+float3 ViewPos;
+float ViewFar;
+float3 ViewDir;
+float TimeParam;
+float4 ViewInfo;
+float4 ScreenSize;
+float3 GridSize;
+float PerInstanceRandom;
+float Dummy0;
+float VolumetricFogMaxDistance;
+int ParticleStride;
+int ParticleIndex;
+@1META_CB_END
+
+// Particles attributes buffer
+ByteAddressBuffer ParticlesData : register(t0);
+
+// Shader resources
+@2
+// Material properties generation input
+struct MaterialInput
+{
+ float3 WorldPosition;
+ float TwoSidedSign;
+ float2 TexCoord;
+ uint ParticleIndex;
+#if USE_VERTEX_COLOR
+ half4 VertexColor;
+#endif
+ float3x3 TBN;
+ float4 SvPosition;
+ float3 PreSkinnedPosition;
+ float3 PreSkinnedNormal;
+ float3 InstanceOrigin;
+ float InstanceParams;
+#if USE_CUSTOM_VERTEX_INTERPOLATORS
+ float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT];
+#endif
+};
+
+#define GetInstanceTransform(input) WorldMatrix;
+
+// Removes the scale vector from the local to world transformation matrix (supports instancing)
+float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld)
+{
+ // Extract per axis scales from localToWorld transform
+ float scaleX = length(localToWorld[0]);
+ float scaleY = length(localToWorld[1]);
+ float scaleZ = length(localToWorld[2]);
+ float3 invScale = float3(
+ scaleX > 0.00001f ? 1.0f / scaleX : 0.0f,
+ scaleY > 0.00001f ? 1.0f / scaleY : 0.0f,
+ scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f);
+ localToWorld[0] *= invScale.x;
+ localToWorld[1] *= invScale.y;
+ localToWorld[2] *= invScale.z;
+ return localToWorld;
+}
+
+// Transforms a vector from tangent space to world space
+float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector)
+{
+ return mul(tangentVector, input.TBN);
+}
+
+// Transforms a vector from world space to tangent space
+float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector)
+{
+ return mul(input.TBN, worldVector);
+}
+
+// Transforms a vector from world space to view space
+float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector)
+{
+ return mul(worldVector, (float3x3)ViewMatrix);
+}
+
+// Transforms a vector from view space to world space
+float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector)
+{
+ return mul((float3x3)ViewMatrix, viewVector);
+}
+
+// Transforms a vector from local space to world space
+float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector)
+{
+ float3x3 localToWorld = (float3x3)GetInstanceTransform(input);
+ //localToWorld = RemoveScaleFromLocalToWorld(localToWorld);
+ return mul(localVector, localToWorld);
+}
+
+// Transforms a vector from local space to world space
+float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector)
+{
+ float3x3 localToWorld = (float3x3)GetInstanceTransform(input);
+ //localToWorld = RemoveScaleFromLocalToWorld(localToWorld);
+ return mul(localToWorld, worldVector);
+}
+
+// Gets the current object position (supports instancing)
+float3 GetObjectPosition(MaterialInput input)
+{
+ return input.InstanceOrigin.xyz;
+}
+
+// Gets the current object size
+float3 GetObjectSize(MaterialInput input)
+{
+ return float3(1, 1, 1);
+}
+
+// Get the current object random value (supports instancing)
+float GetPerInstanceRandom(MaterialInput input)
+{
+ return input.InstanceParams;
+}
+
+// Get the current object LOD transition dither factor (supports instancing)
+float GetLODDitherFactor(MaterialInput input)
+{
+ return 0;
+}
+
+// Gets the interpolated vertex color (in linear space)
+float4 GetVertexColor(MaterialInput input)
+{
+ return 1;
+}
+
+uint GetParticleUint(uint particleIndex, int offset)
+{
+ return ParticlesData.Load(particleIndex * ParticleStride + offset);
+}
+
+int GetParticleInt(uint particleIndex, int offset)
+{
+ return asint(ParticlesData.Load(particleIndex * ParticleStride + offset));
+}
+
+float GetParticleFloat(uint particleIndex, int offset)
+{
+ return asfloat(ParticlesData.Load(particleIndex * ParticleStride + offset));
+}
+
+float2 GetParticleVec2(uint particleIndex, int offset)
+{
+ return asfloat(ParticlesData.Load2(particleIndex * ParticleStride + offset));
+}
+
+float3 GetParticleVec3(uint particleIndex, int offset)
+{
+ return asfloat(ParticlesData.Load3(particleIndex * ParticleStride + offset));
+}
+
+float4 GetParticleVec4(uint particleIndex, int offset)
+{
+ return asfloat(ParticlesData.Load4(particleIndex * ParticleStride + offset));
+}
+
+float3 TransformParticlePosition(float3 input)
+{
+ return mul(float4(input, 1.0f), WorldMatrix).xyz;
+}
+
+float3 TransformParticleVector(float3 input)
+{
+ return mul(float4(input, 0.0f), WorldMatrixInverseTransposed).xyz;
+}
+
+@8
+
+// Get material properties function (for vertex shader)
+Material GetMaterialVS(MaterialInput input)
+{
+@5
+}
+
+// Get material properties function (for domain shader)
+Material GetMaterialDS(MaterialInput input)
+{
+@6
+}
+
+// Get material properties function (for pixel shader)
+Material GetMaterialPS(MaterialInput input)
+{
+@4
+}
+
+// Pixel Shader function for Volumetric Fog material injection (local fog)
+META_PS(true, FEATURE_LEVEL_SM5)
+void PS_VolumetricFog(Quad_GS2PS input, out float4 VBufferA : SV_Target0, out float4 VBufferB : SV_Target1)
+{
+ uint3 gridCoordinate = uint3(input.Vertex.Position.xy, input.LayerIndex);
+ float3 cellOffset = 0.5f;
+ float2 volumeUV = (gridCoordinate.xy + cellOffset.xy) / GridSize.xy;
+ float zSlice = gridCoordinate.z + cellOffset.z;
+ float sceneDepth = (zSlice / GridSize.z) * VolumetricFogMaxDistance / ViewFar;
+ float deviceDepth = (ViewInfo.w / sceneDepth) + ViewInfo.z;
+ float4 clipPos = float4(volumeUV * float2(2.0, -2.0) + float2(-1.0, 1.0), deviceDepth, 1.0);
+ float4 wsPos = mul(clipPos, InverseViewProjectionMatrix);
+ float3 positionWS = wsPos.xyz / wsPos.w;
+
+ // Get material parameters
+ MaterialInput materialInput = (MaterialInput)0;
+ materialInput.WorldPosition = positionWS;
+ materialInput.TexCoord = input.Vertex.TexCoord;
+ materialInput.ParticleIndex = ParticleIndex;
+ materialInput.TBN = float3x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1));
+ materialInput.TwoSidedSign = 1.0f;
+ materialInput.InstanceOrigin = WorldMatrix[3].xyz;
+ materialInput.InstanceParams = PerInstanceRandom;
+ materialInput.SvPosition = clipPos;
+ Material material = GetMaterialPS(materialInput);
+
+ // Compute fog properties
+ float3 albedo = material.Color;
+ float extinction = material.Opacity * material.Mask * 0.001f;
+ float3 emission = material.Emissive;
+ float3 scattering = albedo * extinction;
+ float absorption = max(0.0f, extinction - Luminance(scattering));
+
+ // Write fog properties
+ VBufferA = float4(scattering, absorption);
+ VBufferB = float4(emission, 0);
+}
+
+@9
diff --git a/Source/Editor/CustomEditors/Editors/ListEditor.cs b/Source/Editor/CustomEditors/Editors/ListEditor.cs
index 07963a33f..0c6efaea4 100644
--- a/Source/Editor/CustomEditors/Editors/ListEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ListEditor.cs
@@ -1,6 +1,5 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
-using System;
using System.Collections;
using System.Collections.Generic;
using FlaxEngine;
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index a89f62274..38ce19df6 100644
--- a/Source/Editor/Surface/Archetypes/Material.cs
+++ b/Source/Editor/Surface/Archetypes/Material.cs
@@ -234,7 +234,25 @@ namespace FlaxEditor.Surface.Archetypes
GetBox(MaterialNodeBoxes.SubsurfaceColor).Enabled = false;
break;
}
- default: throw new ArgumentOutOfRangeException();
+ case MaterialDomain.VolumeParticle:
+ {
+ GetBox(MaterialNodeBoxes.Color).Enabled = true;
+ GetBox(MaterialNodeBoxes.Mask).Enabled = true;
+ GetBox(MaterialNodeBoxes.Emissive).Enabled = true;
+ GetBox(MaterialNodeBoxes.Metalness).Enabled = false;
+ GetBox(MaterialNodeBoxes.Specular).Enabled = false;
+ GetBox(MaterialNodeBoxes.Roughness).Enabled = false;
+ GetBox(MaterialNodeBoxes.AmbientOcclusion).Enabled = false;
+ GetBox(MaterialNodeBoxes.Normal).Enabled = false;
+ GetBox(MaterialNodeBoxes.Opacity).Enabled = true;
+ GetBox(MaterialNodeBoxes.Refraction).Enabled = false;
+ GetBox(MaterialNodeBoxes.PositionOffset).Enabled = false;
+ GetBox(MaterialNodeBoxes.TessellationMultiplier).Enabled = false;
+ GetBox(MaterialNodeBoxes.WorldDisplacement).Enabled = false;
+ GetBox(MaterialNodeBoxes.SubsurfaceColor).Enabled = false;
+ break;
+ }
+ default: throw new ArgumentOutOfRangeException();
}
}
diff --git a/Source/Editor/Surface/Archetypes/ParticleModules.cs b/Source/Editor/Surface/Archetypes/ParticleModules.cs
index 64adc828d..49a5d031d 100644
--- a/Source/Editor/Surface/Archetypes/ParticleModules.cs
+++ b/Source/Editor/Surface/Archetypes/ParticleModules.cs
@@ -1483,7 +1483,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 404,
Create = CreateParticleModuleNode,
Title = "Ribbon Rendering",
- Description = "Draws a a ribbon connecting all particles in order by particle age.",
+ Description = "Draws a ribbon connecting all particles in order by particle age.",
Flags = DefaultModuleFlags,
Size = new Vector2(200, 170),
DefaultValues = new object[]
@@ -1517,6 +1517,27 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Enum(100.0f, 6.0f * Surface.Constants.LayoutOffsetY, 140, 6, typeof(DrawPass)),
},
},
+ new NodeArchetype
+ {
+ TypeID = 405,
+ Create = CreateParticleModuleNode,
+ Title = "Volumetric Fog Rendering",
+ Description = "Draws the particle into the volumetric fog (material color, opacity and emission are used for local fog properties).",
+ Flags = DefaultModuleFlags,
+ Size = new Vector2(200, 70),
+ DefaultValues = new object[]
+ {
+ true,
+ (int)ModuleType.Render,
+ Guid.Empty, // Material
+ },
+ Elements = new[]
+ {
+ // Material
+ NodeElementArchetype.Factory.Text(0, -10, "Material", 80.0f, 16.0f, "The material used for volumetric fog rendering. It must have Domain set to Particle."),
+ NodeElementArchetype.Factory.Asset(80, -10, 2, typeof(MaterialBase)),
+ },
+ },
};
}
}
diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs
index 4d03e51f8..c4f2c8131 100644
--- a/Source/Editor/Viewport/Previews/MaterialPreview.cs
+++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs
@@ -179,6 +179,9 @@ namespace FlaxEditor.Viewport.Previews
usePreviewActor = false;
deformableMaterial = _material;
break;
+ case MaterialDomain.VolumeParticle:
+ usePreviewActor = false;
+ break;
default: throw new ArgumentOutOfRangeException();
}
}
diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h
index 862f8f221..7edf7da54 100644
--- a/Source/Engine/Graphics/Materials/MaterialInfo.h
+++ b/Source/Engine/Graphics/Materials/MaterialInfo.h
@@ -44,6 +44,11 @@ API_ENUM() enum class MaterialDomain : byte
/// The deformable shader. Can be used only with objects that can be deformed (spline models).
///
Deformable = 6,
+
+ ///
+ /// The particle shader used for volumetric effects rendering such as Volumetric Fog.
+ ///
+ VolumeParticle = 7,
};
///
diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp
index caac3b5f2..95a6f556e 100644
--- a/Source/Engine/Graphics/Materials/MaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp
@@ -15,6 +15,7 @@
#include "TerrainMaterialShader.h"
#include "ParticleMaterialShader.h"
#include "DeformableMaterialShader.h"
+#include "VolumeParticleMaterialShader.h"
GPUPipelineState* MaterialShader::PipelineStateCache::InitPS(CullMode mode, bool wireframe)
{
@@ -65,8 +66,11 @@ MaterialShader* MaterialShader::Create(const String& name, MemoryReadStream& sha
case MaterialDomain::Deformable:
material = New(name);
break;
+ case MaterialDomain::VolumeParticle:
+ material = New(name);
+ break;
default:
- LOG(Fatal, "Unknown material type.");
+ LOG(Error, "Unknown material type.");
return nullptr;
}
if (material->Load(shaderCacheStream, info))
diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp
index 7a718ceb7..23c7f81a4 100644
--- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp
@@ -15,8 +15,6 @@
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
#include "Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h"
-#define MAX_LOCAL_LIGHTS 4
-
PACK_STRUCT(struct ParticleMaterialShaderData {
Matrix ViewProjectionMatrix;
Matrix WorldMatrix;
@@ -98,53 +96,6 @@ void ParticleMaterialShader::Bind(BindParameters& params)
}
}
- // Select pipeline state based on current pass and render mode
- const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe;
- CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode;
- PipelineStateCache* psCache = nullptr;
- switch (drawCall.Particle.Module->TypeID)
- {
- // Sprite Rendering
- case 400:
- {
- psCache = (PipelineStateCache*)_cacheSprite.GetPS(view.Pass);
- break;
- }
- // Model Rendering
- case 403:
- {
- psCache = (PipelineStateCache*)_cacheModel.GetPS(view.Pass);
- break;
- }
- // Ribbon Rendering
- case 404:
- {
- psCache = (PipelineStateCache*)_cacheRibbon.GetPS(view.Pass);
-
- static StringView ParticleRibbonWidth(TEXT("RibbonWidth"));
- static StringView ParticleRibbonTwist(TEXT("RibbonTwist"));
- static StringView ParticleRibbonFacingVector(TEXT("RibbonFacingVector"));
-
- materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1);
- materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1);
- materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1);
-
- materialData->RibbonUVTilingDistance = drawCall.Particle.Ribbon.UVTilingDistance;
- materialData->RibbonUVScale.X = drawCall.Particle.Ribbon.UVScaleX;
- materialData->RibbonUVScale.Y = drawCall.Particle.Ribbon.UVScaleY;
- materialData->RibbonUVOffset.X = drawCall.Particle.Ribbon.UVOffsetX;
- materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY;
- materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount;
-
- if (drawCall.Particle.Ribbon.SegmentDistances)
- context->BindSR(1, drawCall.Particle.Ribbon.SegmentDistances->View());
-
- break;
- }
- }
- ASSERT(psCache);
- GPUPipelineState* state = psCache->GetPS(cullMode, wireframe);
-
// Setup material constants
{
static StringView ParticlePosition(TEXT("Position"));
@@ -179,6 +130,53 @@ void ParticleMaterialShader::Bind(BindParameters& params)
Matrix::Invert(drawCall.World, materialData->WorldMatrixInverseTransposed);
}
+ // Select pipeline state based on current pass and render mode
+ bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe;
+ CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode;
+ PipelineStateCache* psCache = nullptr;
+ switch (drawCall.Particle.Module->TypeID)
+ {
+ // Sprite Rendering
+ case 400:
+ {
+ psCache = _cacheSprite.GetPS(view.Pass);
+ break;
+ }
+ // Model Rendering
+ case 403:
+ {
+ psCache = _cacheModel.GetPS(view.Pass);
+ break;
+ }
+ // Ribbon Rendering
+ case 404:
+ {
+ psCache = _cacheRibbon.GetPS(view.Pass);
+
+ static StringView ParticleRibbonWidth(TEXT("RibbonWidth"));
+ static StringView ParticleRibbonTwist(TEXT("RibbonTwist"));
+ static StringView ParticleRibbonFacingVector(TEXT("RibbonFacingVector"));
+
+ materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1);
+ materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1);
+ materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1);
+
+ materialData->RibbonUVTilingDistance = drawCall.Particle.Ribbon.UVTilingDistance;
+ materialData->RibbonUVScale.X = drawCall.Particle.Ribbon.UVScaleX;
+ materialData->RibbonUVScale.Y = drawCall.Particle.Ribbon.UVScaleY;
+ materialData->RibbonUVOffset.X = drawCall.Particle.Ribbon.UVOffsetX;
+ materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY;
+ materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount;
+
+ if (drawCall.Particle.Ribbon.SegmentDistances)
+ context->BindSR(1, drawCall.Particle.Ribbon.SegmentDistances->View());
+
+ break;
+ }
+ }
+ ASSERT(psCache);
+ GPUPipelineState* state = psCache->GetPS(cullMode, wireframe);
+
// Bind constants
if (_cb)
{
@@ -198,6 +196,7 @@ void ParticleMaterialShader::Unload()
_cacheSprite.Release();
_cacheModel.Release();
_cacheRibbon.Release();
+ _cacheVolumetricFog.Release();
}
bool ParticleMaterialShader::Load()
@@ -263,5 +262,8 @@ bool ParticleMaterialShader::Load()
psDesc.VS = vsRibbon;
_cacheRibbon.Depth.Init(psDesc);
+ // Lazy initialization
+ _cacheVolumetricFog.Desc.PS = nullptr;
+
return false;
}
diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.h b/Source/Engine/Graphics/Materials/ParticleMaterialShader.h
index 3a8794183..a38bf56f0 100644
--- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.h
+++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.h
@@ -45,6 +45,7 @@ private:
Cache _cacheSprite;
Cache _cacheModel;
Cache _cacheRibbon;
+ PipelineStateCache _cacheVolumetricFog;
DrawPass _drawModes = DrawPass::None;
public:
diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp
new file mode 100644
index 000000000..5a536e926
--- /dev/null
+++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#include "VolumeParticleMaterialShader.h"
+#include "MaterialShaderFeatures.h"
+#include "MaterialParams.h"
+#include "Engine/Engine/Time.h"
+#include "Engine/Renderer/DrawCall.h"
+#include "Engine/Renderer/VolumetricFogPass.h"
+#include "Engine/Renderer/RenderList.h"
+#include "Engine/Graphics/RenderView.h"
+#include "Engine/Graphics/GPUContext.h"
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Graphics/Shaders/GPUShader.h"
+#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
+#include "Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h"
+
+PACK_STRUCT(struct VolumeParticleMaterialShaderData {
+ Matrix ViewProjectionMatrix;
+ Matrix InverseViewProjectionMatrix;
+ Matrix ViewMatrix;
+ Matrix WorldMatrix;
+ Matrix WorldMatrixInverseTransposed;
+ Vector3 ViewPos;
+ float ViewFar;
+ Vector3 ViewDir;
+ float TimeParam;
+ Vector4 ViewInfo;
+ Vector4 ScreenSize;
+ Vector3 GridSize;
+ float PerInstanceRandom;
+ float Dummy0;
+ float VolumetricFogMaxDistance;
+ int ParticleStride;
+ int ParticleIndex;
+ });
+
+DrawPass VolumeParticleMaterialShader::GetDrawModes() const
+{
+ return DrawPass::None;
+}
+
+void VolumeParticleMaterialShader::Bind(BindParameters& params)
+{
+ // Prepare
+ auto context = params.GPUContext;
+ auto& view = params.RenderContext.View;
+ auto& drawCall = *params.FirstDrawCall;
+ byte* cb = _cbData.Get();
+ auto materialData = reinterpret_cast(cb);
+ cb += sizeof(VolumeParticleMaterialShaderData);
+ int32 srv = 1;
+ auto customData = (VolumetricFogPass::CustomData*)params.CustomData;
+
+ // Setup parameters
+ MaterialParameter::BindMeta bindMeta;
+ bindMeta.Context = context;
+ bindMeta.Constants = cb;
+ bindMeta.Input = nullptr;
+ bindMeta.Buffers = params.RenderContext.Buffers;
+ bindMeta.CanSampleDepth = true;
+ bindMeta.CanSampleGBuffer = true;
+ MaterialParams::Bind(params.ParamsLink, bindMeta);
+
+ // Setup particles data
+ context->BindSR(0, drawCall.Particle.Particles->GPU.Buffer->View());
+
+ // Setup particles attributes binding info
+ {
+ const auto& p = *params.ParamsLink->This;
+ for (int32 i = 0; i < p.Count(); i++)
+ {
+ const auto& param = p.At(i);
+ if (param.GetParameterType() == MaterialParameterType::Integer && param.GetName().StartsWith(TEXT("Particle.")))
+ {
+ const StringView name(param.GetName().Get() + 9, param.GetName().Length() - 9);
+ const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name);
+ *((int32*)(bindMeta.Constants + param.GetBindOffset())) = offset;
+ }
+ }
+ }
+
+ // Setup material constants
+ {
+ Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix);
+ Matrix::Transpose(view.IVP, materialData->InverseViewProjectionMatrix);
+ Matrix::Transpose(view.View, materialData->ViewMatrix);
+ Matrix::Transpose(drawCall.World, materialData->WorldMatrix);
+ Matrix::Invert(drawCall.World, materialData->WorldMatrixInverseTransposed);
+ materialData->ViewPos = view.Position;
+ materialData->ViewFar = view.Far;
+ materialData->ViewDir = view.Direction;
+ materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds();
+ materialData->ViewInfo = view.ViewInfo;
+ materialData->ScreenSize = view.ScreenSize;
+ materialData->GridSize = customData->GridSize;
+ materialData->PerInstanceRandom = drawCall.PerInstanceRandom;
+ materialData->VolumetricFogMaxDistance = customData->VolumetricFogMaxDistance;
+ materialData->ParticleStride = drawCall.Particle.Particles->Stride;
+ materialData->ParticleIndex = customData->ParticleIndex;
+ }
+
+ // Bind constants
+ if (_cb)
+ {
+ context->UpdateCB(_cb, _cbData.Get());
+ context->BindCB(0, _cb);
+ }
+
+ // Bind pipeline
+ if (_psVolumetricFog == nullptr)
+ {
+ auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
+ psDesc.BlendMode = BlendingMode::Add;
+ psDesc.VS = customData->Shader->GetVS("VS_WriteToSlice");
+ psDesc.GS = customData->Shader->GetGS("GS_WriteToSlice");
+ psDesc.PS = _shader->GetPS("PS_VolumetricFog");
+ _psVolumetricFog = GPUDevice::Instance->CreatePipelineState();
+ _psVolumetricFog->Init(psDesc);
+ }
+ context->SetState(_psVolumetricFog);
+}
+
+void VolumeParticleMaterialShader::Unload()
+{
+ // Base
+ MaterialShader::Unload();
+
+ SAFE_DELETE_GPU_RESOURCE(_psVolumetricFog);
+}
+
+bool VolumeParticleMaterialShader::Load()
+{
+ return false;
+}
diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h
new file mode 100644
index 000000000..e38944342
--- /dev/null
+++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "MaterialShader.h"
+
+///
+/// Represents material that can be used to render particles.
+///
+class VolumeParticleMaterialShader : public MaterialShader
+{
+private:
+
+ GPUPipelineState* _psVolumetricFog = nullptr;
+
+public:
+
+ ///
+ /// Init
+ ///
+ /// Material resource name
+ VolumeParticleMaterialShader(const String& name)
+ : MaterialShader(name)
+ {
+ }
+
+public:
+
+ // [MaterialShader]
+ DrawPass GetDrawModes() const override;
+ void Bind(BindParameters& params) override;
+ void Unload() override;
+
+protected:
+
+ // [MaterialShader]
+ bool Load() override;
+};
diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
index 76af84011..d65d8339a 100644
--- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
+++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
@@ -99,6 +99,7 @@ protected:
int32 _attrMass;
int32 _attrRibbonWidth;
int32 _attrColor;
+ int32 _attrRadius;
public:
@@ -169,6 +170,8 @@ public:
///
Array> RibbonRenderingModules;
+ bool UsesVolumetricFogRendering = false;
+
protected:
virtual void InitializeNode(NodeType* node)
@@ -505,6 +508,14 @@ protected:
// TODO: add support for custom sorting key - not only by age
USE_ATTRIBUTE(Age, Float, 1);
break;
+ }
+ // Volumetric Fog Rendering
+ case GRAPH_NODE_MAKE_TYPE(15, 405):
+ {
+ node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]);
+ USE_ATTRIBUTE(Position, Vector3, 0);
+ USE_ATTRIBUTE(Radius, Float, 1);
+ break;
}
}
@@ -544,6 +555,7 @@ public:
LightModules.Clear();
SortModules.Clear();
RibbonRenderingModules.Clear();
+ UsesVolumetricFogRendering = false;
// Base
Base::Clear();
@@ -607,6 +619,7 @@ public:
SETUP_ATTRIBUTE(Mass, Float, Variant(1.0f));
SETUP_ATTRIBUTE(RibbonWidth, Float, Variant(10.0f));
SETUP_ATTRIBUTE(Color, Vector4, Variant(Vector4(0.0f, 0.0f, 0.0f, 1.0f)));
+ SETUP_ATTRIBUTE(Radius, Float, Variant(100.0f));
#undef SETUP_ATTRIBUTE
return false;
@@ -649,6 +662,9 @@ public:
case 404:
RibbonRenderingModules.Add(n);
break;
+ case 405:
+ UsesVolumetricFogRendering = true;
+ break;
}
break;
}
diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp
index 716fdccea..777c2f0f5 100644
--- a/Source/Engine/Particles/ParticleEmitter.cpp
+++ b/Source/Engine/Particles/ParticleEmitter.cpp
@@ -112,12 +112,8 @@ Asset::LoadResult ParticleEmitter::load()
}
if (SimulationMode == ParticlesSimulationMode::GPU)
{
- if (IsUsingLights)
- {
- // Downgrade to CPU simulation for light particles (no GPU support for that)
- SimulationMode = ParticlesSimulationMode::CPU;
- }
- else if (Graph.RibbonRenderingModules.HasItems())
+ // Downgrade to CPU simulation if no GPU support for that
+ if (IsUsingLights || Graph.RibbonRenderingModules.HasItems() || Graph.UsesVolumetricFogRendering)
{
// Downgrade to CPU simulation for ribbons (no GPU support for that)
SimulationMode = ParticlesSimulationMode::CPU;
diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/ParticleManager.cpp
index 0ad96c74a..99ce64b73 100644
--- a/Source/Engine/Particles/ParticleManager.cpp
+++ b/Source/Engine/Particles/ParticleManager.cpp
@@ -524,6 +524,34 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa
break;
}
+ // Volumetric Fog Rendering
+ case 405:
+ {
+ const auto material = (MaterialBase*)module->Assets[0].Get();
+ drawCall.Material = material;
+ drawCall.InstanceCount = 1;
+
+ auto positionOffset = emitter->Graph.Layout.GetAttributeOffset(module->Attributes[0]);
+ int32 count = buffer->CPU.Count;
+ if (positionOffset == -1 || count < 0)
+ break;
+ auto radiusOffset = emitter->Graph.Layout.GetAttributeOffset(module->Attributes[1]);
+ ParticleBufferCPUDataAccessor positionData(buffer, positionOffset);
+ ParticleBufferCPUDataAccessor radiusData(buffer, radiusOffset);
+ const bool hasRadius = radiusOffset != -1;
+ for (int32 i = 0; i < count; i++)
+ {
+ // Submit draw call
+ // TODO: use instancing for volumetric fog particles (combine it with instanced circle rasterization into 3d texture)
+ drawCall.Particle.VolumetricFog.Position = positionData[i];
+ if (emitter->SimulationSpace == ParticlesSimulationSpace::Local)
+ Vector3::Transform(drawCall.Particle.VolumetricFog.Position, drawCall.World, drawCall.Particle.VolumetricFog.Position);
+ drawCall.Particle.VolumetricFog.Radius = hasRadius ? radiusData[i] : 100.0f;
+ drawCall.Particle.VolumetricFog.ParticleIndex = i;
+ renderContext.List->VolumetricFogParticles.Add(drawCall);
+ }
+ break;
+ }
}
}
}
@@ -713,7 +741,12 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa
case 404:
{
// Not supported
- CRASH;
+ break;
+ }
+ // Volumetric Fog Rendering
+ case 405:
+ {
+ // Not supported
break;
}
}
@@ -775,7 +808,12 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa
case 404:
{
// Not supported
- CRASH;
+ break;
+ }
+ // Volumetric Fog Rendering
+ case 405:
+ {
+ // Not supported
break;
}
}
@@ -841,7 +879,12 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa
case 404:
{
// Not supported
- CRASH;
+ break;
+ }
+ // Volumetric Fog Rendering
+ case 405:
+ {
+ // Not supported
break;
}
}
@@ -909,7 +952,6 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect
(view.Pass & material->GetDrawModes() & moduleDrawModes) == 0
)
break;
-
renderModulesIndices.Add(moduleIndex);
break;
}
@@ -929,7 +971,6 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect
(view.Pass & material->GetDrawModes() & moduleDrawModes) == 0
)
break;
-
renderModulesIndices.Add(moduleIndex);
break;
}
@@ -944,7 +985,19 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect
(view.Pass & material->GetDrawModes() & moduleDrawModes) == 0
)
break;
-
+ renderModulesIndices.Add(moduleIndex);
+ break;
+ }
+ // Volumetric Fog Rendering
+ case 405:
+ {
+ const auto material = (MaterialBase*)module->Assets[0].Get();
+ if (!material ||
+ !material->IsReady() ||
+ material->GetInfo().Domain != MaterialDomain::VolumeParticle ||
+ (view.Flags & ViewFlags::Fog) == 0
+ )
+ break;
renderModulesIndices.Add(moduleIndex);
break;
}
diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h
index 036ba26bd..28acd72ff 100644
--- a/Source/Engine/Renderer/DrawCall.h
+++ b/Source/Engine/Renderer/DrawCall.h
@@ -218,6 +218,13 @@ struct DrawCall
uint32 SegmentCount;
GPUBuffer* SegmentDistances;
} Ribbon;
+
+ struct
+ {
+ Vector3 Position;
+ float Radius;
+ int32 ParticleIndex;
+ } VolumetricFog;
} Particle;
struct
diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp
index 1dbf1b663..8f9be5e74 100644
--- a/Source/Engine/Renderer/RenderList.cpp
+++ b/Source/Engine/Renderer/RenderList.cpp
@@ -350,6 +350,7 @@ void RenderList::Clear()
DirectionalLights.Clear();
EnvironmentProbes.Clear();
Decals.Clear();
+ VolumetricFogParticles.Clear();
Sky = nullptr;
AtmosphericFog = nullptr;
Fog = nullptr;
diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h
index 69ef0c33d..bf0f25d0e 100644
--- a/Source/Engine/Renderer/RenderList.h
+++ b/Source/Engine/Renderer/RenderList.h
@@ -303,6 +303,11 @@ public:
///
Array Decals;
+ ///
+ /// Local volumetric fog particles registered for the rendering.
+ ///
+ Array VolumetricFogParticles;
+
///
/// Sky/skybox renderer proxy to use (only one per frame)
///
diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp
index ff8f469ae..6dbc6e5ac 100644
--- a/Source/Engine/Renderer/VolumetricFogPass.cpp
+++ b/Source/Engine/Renderer/VolumetricFogPass.cpp
@@ -253,13 +253,9 @@ GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext
if (renderContext.Buffers->LocalShadowedLightScattering == nullptr)
{
ASSERT(renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount);
-
const GPUTextureDescription volumeDescRGB = GPUTextureDescription::New3D(_cache.GridSize, PixelFormat::R11G11B10_Float, GPUTextureFlags::RenderTarget | GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
-
const auto texture = RenderTargetPool::Get(volumeDescRGB);
-
renderContext.Buffers->LocalShadowedLightScattering = texture;
-
context->Clear(texture->ViewVolume(), Color::Transparent);
}
@@ -299,7 +295,7 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte
// Bind the output
context->SetRenderTarget(localShadowedLightScattering);
- context->SetViewportAndScissors((float)_cache.Data.GridSizeIntX, (float)_cache.Data.GridSizeIntY);
+ context->SetViewportAndScissors(_cache.Data.GridSize.X, _cache.Data.GridSize.Y);
// Setup data
perLight.MinZ = volumeZBoundsMin;
@@ -481,8 +477,8 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
&& Vector3::NearEqual(renderContext.Buffers->VolumetricFogHistory->Size3(), cache.GridSize);
// Allocate buffers
- const GPUTextureDescription volumeDesc = GPUTextureDescription::New3D(cache.GridSize, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
- const GPUTextureDescription volumeDescRGB = GPUTextureDescription::New3D(cache.GridSize, PixelFormat::R11G11B10_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
+ const GPUTextureDescription volumeDesc = GPUTextureDescription::New3D(cache.GridSize, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::RenderTarget | GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
+ const GPUTextureDescription volumeDescRGB = GPUTextureDescription::New3D(cache.GridSize, PixelFormat::R11G11B10_Float, GPUTextureFlags::RenderTarget | GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
auto vBufferA = RenderTargetPool::Get(volumeDesc);
auto vBufferB = RenderTargetPool::Get(volumeDescRGB);
const auto lightScattering = RenderTargetPool::Get(volumeDesc);
@@ -500,15 +496,78 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
context->BindUA(1, vBufferB->ViewVolume());
context->Dispatch(_csInitialize, groupCountX, groupCountY, groupCountZ);
+
+ context->UnBindUA(0);
+ context->UnBindUA(1);
+ context->FlushState();
}
- // Voxelize local fog particles
- // TODO: support rendering volume particles with custom Albedo/Extinction/Emissive
+ // Render local fog particles
+ if (renderContext.List->VolumetricFogParticles.HasItems())
+ {
+ PROFILE_GPU_CPU("Local Fog");
- // Unbind fog attributes tables
- context->UnBindUA(0);
- context->UnBindUA(1);
- context->FlushState();
+ // Bind the output
+ GPUTextureView* rt[] = { vBufferA->ViewVolume(), vBufferB->ViewVolume() };
+ context->SetRenderTarget(nullptr, Span(rt, 2));
+ context->SetViewportAndScissors((float)volumeDesc.Width, (float)volumeDesc.Height);
+
+ // Ensure to have valid buffers created
+ if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr)
+ InitCircleBuffer();
+
+ MaterialBase::BindParameters bindParams(context, renderContext);
+ bindParams.DrawCallsCount = 1;
+ CustomData customData;
+ customData.Shader = _shader->GetShader();
+ customData.GridSize = cache.GridSize;
+ customData.VolumetricFogMaxDistance = cache.Data.VolumetricFogMaxDistance;
+ bindParams.CustomData = &customData;
+
+ for (auto& drawCall : renderContext.List->VolumetricFogParticles)
+ {
+ const BoundingSphere bounds(drawCall.Particle.VolumetricFog.Position, drawCall.Particle.VolumetricFog.Radius);
+ ASSERT(!bounds.Center.IsNanOrInfinity() && !isnan(bounds.Radius) && !isinf(bounds.Radius));
+
+ // Calculate light volume bounds in camera frustum depth range (min and max)
+ const Vector3 viewSpaceLightBoundsOrigin = Vector3::Transform(bounds.Center, view.View);
+ const float furthestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z + bounds.Radius, options, cache.GridSizeZ);
+ const float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - bounds.Radius, options, cache.GridSizeZ);
+ const int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f);
+ const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f);
+
+ // Culling
+ if ((view.Position - bounds.Center).LengthSquared() >= (options.Distance + bounds.Radius) * (options.Distance + bounds.Radius) || volumeZBoundsMin >= volumeZBoundsMax)
+ continue;
+
+ // Setup material shader data
+ customData.ParticleIndex = drawCall.Particle.VolumetricFog.ParticleIndex;
+ bindParams.FirstDrawCall = &drawCall;
+ drawCall.Material->Bind(bindParams);
+
+ // Setup volumetric shader data
+ PerLight perLight;
+ auto cb1 = _shader->GetShader()->GetCB(1);
+ perLight.SliceToDepth.X = cache.Data.GridSize.Z;
+ perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance;
+ perLight.MinZ = volumeZBoundsMin;
+ perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius);
+ Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip);
+
+ // Upload data
+ context->UpdateCB(cb1, &perLight);
+ context->BindCB(1, cb1);
+
+ // Call rendering to the volume
+ const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin;
+ const int32 indexCount = _ibCircleRasterize->GetElementsCount();
+ context->BindVB(ToSpan(&_vbCircleRasterize, 1));
+ context->BindIB(_ibCircleRasterize);
+ context->DrawIndexedInstanced(indexCount, instanceCount, 0);
+ }
+
+ context->ResetRenderTarget();
+ }
// Render Lights
GPUTextureView* localShadowedLightScattering = nullptr;
@@ -542,13 +601,15 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
// Skip if no lights to render
if (pointLights.Count() + spotLights.Count())
{
- PROFILE_GPU("Render Lights");
+ PROFILE_GPU_CPU("Lights Injection");
// Allocate temporary buffer for light scattering injection
localShadowedLightScattering = GetLocalShadowedLightScattering(renderContext, context, options);
// Prepare
PerLight perLight;
+ perLight.SliceToDepth.X = cache.Data.GridSize.Z;
+ perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance;
auto cb1 = _shader->GetShader()->GetCB(1);
// Bind the output
diff --git a/Source/Engine/Renderer/VolumetricFogPass.h b/Source/Engine/Renderer/VolumetricFogPass.h
index 8a1f42730..52fc84a77 100644
--- a/Source/Engine/Renderer/VolumetricFogPass.h
+++ b/Source/Engine/Renderer/VolumetricFogPass.h
@@ -11,6 +11,16 @@
///
class VolumetricFogPass : public RendererPass
{
+public:
+
+ struct CustomData
+ {
+ GPUShader* Shader;
+ Vector3 GridSize;
+ float VolumetricFogMaxDistance;
+ int32 ParticleIndex;
+ };
+
private:
PACK_STRUCT(struct SkyLightData {
@@ -53,7 +63,7 @@ private:
});
PACK_STRUCT(struct PerLight {
- Vector2 Dummy1;
+ Vector2 SliceToDepth;
int32 MinZ;
float LocalLightScatteringIntensity;
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp
index f000cb015..2f3394932 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Particles.cpp
@@ -75,7 +75,7 @@ MaterialValue MaterialGenerator::AccessParticleAttribute(Node* caller, const Str
void MaterialGenerator::ProcessGroupParticles(Box* box, Node* node, Value& value)
{
// Only particle shaders can access particles data
- if (GetRootLayer()->Domain != MaterialDomain::Particle)
+ if (GetRootLayer()->Domain != MaterialDomain::Particle && GetRootLayer()->Domain != MaterialDomain::VolumeParticle)
{
value = MaterialValue::Zero;
return;
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp
index 23779e3e1..b6b5306e2 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp
@@ -296,6 +296,21 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo
eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::Refraction);
eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::SubsurfaceColor);
}
+ else if (baseLayer->Domain == MaterialDomain::VolumeParticle)
+ {
+ eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Emissive);
+ eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Opacity);
+ eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Mask);
+ eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Color);
+
+ eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::Normal);
+ eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::Metalness);
+ eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::Specular);
+ eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::AmbientOcclusion);
+ eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::Roughness);
+ eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::Refraction);
+ eatMaterialGraphBoxWithDefault(baseLayer, MaterialGraphBoxes::SubsurfaceColor);
+ }
else
{
CRASH;
@@ -422,6 +437,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo
case MaterialDomain::Deformable:
srv = 1; // Mesh deformation buffer
break;
+ case MaterialDomain::VolumeParticle:
+ srv = 1; // Particles data
+ break;
}
for (auto f : features)
{
@@ -504,6 +522,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo
case MaterialDomain::Deformable:
path /= TEXT("Deformable.shader");
break;
+ case MaterialDomain::VolumeParticle:
+ path /= TEXT("VolumeParticle.shader");
+ break;
default:
LOG(Warning, "Unknown material domain.");
return true;
diff --git a/Source/Shaders/VolumetricFog.shader b/Source/Shaders/VolumetricFog.shader
index 44aa71254..79b055732 100644
--- a/Source/Shaders/VolumetricFog.shader
+++ b/Source/Shaders/VolumetricFog.shader
@@ -60,7 +60,7 @@ META_CB_END
META_CB_BEGIN(1, PerLight)
-float2 Dummy1;
+float2 SliceToDepth;
int MinZ;
float LocalLightScatteringIntensity;
@@ -119,7 +119,7 @@ Quad_VS2GS VS_WriteToSlice(float2 TexCoord : TEXCOORD0, uint LayerIndex : SV_Ins
Quad_VS2GS output;
uint slice = LayerIndex + MinZ;
- float depth = GetSliceDepth(slice);
+ float depth = (slice / SliceToDepth.x) * SliceToDepth.y;
float depthOffset = abs(depth - ViewSpaceBoundingSphere.z);
if (depthOffset < ViewSpaceBoundingSphere.w)
@@ -133,7 +133,7 @@ Quad_VS2GS VS_WriteToSlice(float2 TexCoord : TEXCOORD0, uint LayerIndex : SV_Ins
output.Vertex.Position = float4(0, 0, 0, 0);
}
- output.Vertex.TexCoord = 0;
+ output.Vertex.TexCoord = TexCoord;
output.LayerIndex = slice;
return output;