Add Volumetric Fog support for particles to modify local fog

This commit is contained in:
Wojtek Figat
2021-03-05 13:56:07 +01:00
parent dfb502621d
commit 1dee615d6e
22 changed files with 726 additions and 84 deletions

View File

@@ -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

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using FlaxEngine;

View File

@@ -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();
}
}

View File

@@ -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)),
},
},
};
}
}

View File

@@ -179,6 +179,9 @@ namespace FlaxEditor.Viewport.Previews
usePreviewActor = false;
deformableMaterial = _material;
break;
case MaterialDomain.VolumeParticle:
usePreviewActor = false;
break;
default: throw new ArgumentOutOfRangeException();
}
}

View File

@@ -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).
/// </summary>
Deformable = 6,
/// <summary>
/// The particle shader used for volumetric effects rendering such as Volumetric Fog.
/// </summary>
VolumeParticle = 7,
};
/// <summary>

View File

@@ -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<DeformableMaterialShader>(name);
break;
case MaterialDomain::VolumeParticle:
material = New<VolumeParticleMaterialShader>(name);
break;
default:
LOG(Fatal, "Unknown material type.");
LOG(Error, "Unknown material type.");
return nullptr;
}
if (material->Load(shaderCacheStream, info))

View File

@@ -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;
}

View File

@@ -45,6 +45,7 @@ private:
Cache _cacheSprite;
Cache _cacheModel;
Cache _cacheRibbon;
PipelineStateCache _cacheVolumetricFog;
DrawPass _drawModes = DrawPass::None;
public:

View File

@@ -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<VolumeParticleMaterialShaderData*>(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;
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "MaterialShader.h"
/// <summary>
/// Represents material that can be used to render particles.
/// </summary>
class VolumeParticleMaterialShader : public MaterialShader
{
private:
GPUPipelineState* _psVolumetricFog = nullptr;
public:
/// <summary>
/// Init
/// </summary>
/// <param name="name">Material resource name</param>
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;
};

View File

@@ -99,6 +99,7 @@ protected:
int32 _attrMass;
int32 _attrRibbonWidth;
int32 _attrColor;
int32 _attrRadius;
public:
@@ -169,6 +170,8 @@ public:
/// </summary>
Array<NodeType*, FixedAllocation<PARTICLE_EMITTER_MAX_MODULES>> 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<Asset>((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;
}

View File

@@ -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;

View File

@@ -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<Vector3> positionData(buffer, positionOffset);
ParticleBufferCPUDataAccessor<float> 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;
}

View File

@@ -218,6 +218,13 @@ struct DrawCall
uint32 SegmentCount;
GPUBuffer* SegmentDistances;
} Ribbon;
struct
{
Vector3 Position;
float Radius;
int32 ParticleIndex;
} VolumetricFog;
} Particle;
struct

View File

@@ -350,6 +350,7 @@ void RenderList::Clear()
DirectionalLights.Clear();
EnvironmentProbes.Clear();
Decals.Clear();
VolumetricFogParticles.Clear();
Sky = nullptr;
AtmosphericFog = nullptr;
Fog = nullptr;

View File

@@ -303,6 +303,11 @@ public:
/// </summary>
Array<Decal*> Decals;
/// <summary>
/// Local volumetric fog particles registered for the rendering.
/// </summary>
Array<DrawCall> VolumetricFogParticles;
/// <summary>
/// Sky/skybox renderer proxy to use (only one per frame)
/// </summary>

View File

@@ -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<GPUTextureView*>(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

View File

@@ -11,6 +11,16 @@
/// </summary>
class VolumetricFogPass : public RendererPass<VolumetricFogPass>
{
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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;