Add Volumetric Fog support for particles to modify local fog
This commit is contained in:
246
Content/Editor/MaterialTemplates/VolumeParticle.shader
Normal file
246
Content/Editor/MaterialTemplates/VolumeParticle.shader
Normal 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
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,9 @@ namespace FlaxEditor.Viewport.Previews
|
||||
usePreviewActor = false;
|
||||
deformableMaterial = _material;
|
||||
break;
|
||||
case MaterialDomain.VolumeParticle:
|
||||
usePreviewActor = false;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ private:
|
||||
Cache _cacheSprite;
|
||||
Cache _cacheModel;
|
||||
Cache _cacheRibbon;
|
||||
PipelineStateCache _cacheVolumetricFog;
|
||||
DrawPass _drawModes = DrawPass::None;
|
||||
|
||||
public:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -218,6 +218,13 @@ struct DrawCall
|
||||
uint32 SegmentCount;
|
||||
GPUBuffer* SegmentDistances;
|
||||
} Ribbon;
|
||||
|
||||
struct
|
||||
{
|
||||
Vector3 Position;
|
||||
float Radius;
|
||||
int32 ParticleIndex;
|
||||
} VolumetricFog;
|
||||
} Particle;
|
||||
|
||||
struct
|
||||
|
||||
@@ -350,6 +350,7 @@ void RenderList::Clear()
|
||||
DirectionalLights.Clear();
|
||||
EnvironmentProbes.Clear();
|
||||
Decals.Clear();
|
||||
VolumetricFogParticles.Clear();
|
||||
Sky = nullptr;
|
||||
AtmosphericFog = nullptr;
|
||||
Fog = nullptr;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user