mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2025-02-21 11:21:32 +00:00

- added the foundation for a GPU particle system - reworked volumetric particle injection
398 lines
13 KiB
HLSL
398 lines
13 KiB
HLSL
/*
|
|
===========================================================================
|
|
Copyright (C) 2024 Gian 'myT' Schellenbaum
|
|
|
|
This file is part of Challenge Quake 3 (CNQ3).
|
|
|
|
Challenge Quake 3 is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Challenge Quake 3 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
// volumetric lighting: inject a NanoVDB volume into the material textures
|
|
|
|
|
|
//#define PREVIEW_MODE 1
|
|
// preview mode is a faster and less complex version of the shader
|
|
// that doesn't crash the AMD/Intel shader compilers
|
|
|
|
// this shader considers smoke to be fully isotropic (g = 0)
|
|
// in "Creating the Atmospheric World of Red Dead Redemption 2" by Fabian Bauer,
|
|
// g is 0.1 with strong backscatter for smoke
|
|
// RDR2 uses a phase function with multiple octaves of HG
|
|
// it fakes backscattering for a specific extinction range
|
|
// the phase function returns the max. of the sum of octaves and the backscatter
|
|
|
|
|
|
#include "common.hlsli"
|
|
#include "scene_view.h.hlsli"
|
|
#include "light_grid.h.hlsli"
|
|
#include "vl_common.h.hlsli"
|
|
#include "vl_nanovdb.hlsli"
|
|
|
|
|
|
cbuffer RootConstants
|
|
{
|
|
float4 packedTransform[4];
|
|
float2 packedTransform2;
|
|
uint nanovdbBufferIndex;
|
|
uint blackbodyTextureIndex;
|
|
LightGridRC lightGridRC;
|
|
uint materialTextureAIndex;
|
|
uint materialTextureBIndex;
|
|
uint materialTextureCIndex;
|
|
uint scatterExtTextureIndex;
|
|
uint frustumVisTextureIndex;
|
|
uint densityGridByteOffset;
|
|
uint flamesGridByteOffset;
|
|
uint densityGridByteOffset2;
|
|
uint flamesGridByteOffset2;
|
|
uint linearInterpolation;
|
|
uint accurateOverlapTest;
|
|
uint ambientAngularCoverage;
|
|
float densityExtinctionScale;
|
|
float densityAlbedo;
|
|
float flamesEmissionScale;
|
|
float flamesTemperatureScale;
|
|
float stepScale; // for super-sampling froxel average
|
|
float transStepScale; // for under-sampling ambient transmittance
|
|
float t;
|
|
}
|
|
|
|
static const float SqrtOneThird = sqrt(1.0 / 3.0);
|
|
#if PREVIEW_MODE
|
|
static const float3 Dirs[4] =
|
|
{
|
|
float3(-SqrtOneThird, -SqrtOneThird, SqrtOneThird),
|
|
float3(SqrtOneThird, SqrtOneThird, SqrtOneThird),
|
|
float3(-SqrtOneThird, SqrtOneThird, SqrtOneThird),
|
|
float3(SqrtOneThird, -SqrtOneThird, SqrtOneThird)
|
|
};
|
|
#else
|
|
static const float3 Dirs[6 + 8] =
|
|
{
|
|
float3(1, 0, 0),
|
|
float3(-1, 0, 0),
|
|
float3(0, 1, 0),
|
|
float3(0, -1, 0),
|
|
float3(0, 0, 1),
|
|
float3(0, 0, -1),
|
|
float3(-SqrtOneThird, -SqrtOneThird, -SqrtOneThird),
|
|
float3(-SqrtOneThird, -SqrtOneThird, SqrtOneThird),
|
|
float3(-SqrtOneThird, SqrtOneThird, -SqrtOneThird),
|
|
float3(-SqrtOneThird, SqrtOneThird, SqrtOneThird),
|
|
float3(SqrtOneThird, -SqrtOneThird, -SqrtOneThird),
|
|
float3(SqrtOneThird, -SqrtOneThird, SqrtOneThird),
|
|
float3(SqrtOneThird, SqrtOneThird, -SqrtOneThird),
|
|
float3(SqrtOneThird, SqrtOneThird, SqrtOneThird)
|
|
};
|
|
#endif
|
|
|
|
float3 BlackbodyColor(float temperatureK, Texture2D blackbodyTexture, SamplerState blackbodySampler)
|
|
{
|
|
const float minT = 800;
|
|
const float maxT = 12000;
|
|
float t = saturate((temperatureK - minT) / (maxT - minT));
|
|
float3 emission = blackbodyTexture.SampleLevel(blackbodySampler, float2(t, 0.5), 0.0).rgb;
|
|
|
|
return emission;
|
|
}
|
|
|
|
// Stefan-Boltzmann law
|
|
float BlackbodyRadiation(float temperatureK)
|
|
{
|
|
const float sigma = 5.670373e-8;
|
|
float T = temperatureK;
|
|
float T2 = T * T;
|
|
float T4 = T2 * T2;
|
|
float radiation = (T4 * sigma) / PI;
|
|
|
|
return radiation;
|
|
}
|
|
|
|
float3 BlackbodyEmission(float temperatureK, Texture2D blackbodyTexture, SamplerState blackbodySampler)
|
|
{
|
|
const float scale = 1.0e-6;
|
|
float3 color = BlackbodyColor(temperatureK, blackbodyTexture, blackbodySampler);
|
|
float radiation = BlackbodyRadiation(temperatureK);
|
|
float colorBrightness = Brightness(color);
|
|
float3 result = (color * radiation * scale) / colorBrightness;
|
|
|
|
return result;
|
|
}
|
|
|
|
struct SampleRequest
|
|
{
|
|
pnanovdb_buf_t buffer;
|
|
uint gridByteOffset;
|
|
float3 froxelPosition;
|
|
int3 froxelIndex;
|
|
float3 textureSize;
|
|
float3 froxelSize;
|
|
Transform transform;
|
|
SceneView scene;
|
|
bool ambientLight;
|
|
int3 froxelId;
|
|
LightGrid lightGrid;
|
|
};
|
|
|
|
#if PREVIEW_MODE
|
|
SampleResult SampleFroxel(SampleRequest request)
|
|
{
|
|
SampleResult result = CreateSampleResult();
|
|
if(request.gridByteOffset == 0)
|
|
{
|
|
// no grid requested
|
|
return result;
|
|
}
|
|
|
|
Grid grid = GetGrid(request.buffer, request.gridByteOffset);
|
|
float3 indexF = grid.WorldToIndex(request.froxelPosition, request.transform);
|
|
bool overlaps = IsInRange(indexF, grid.bboxMin, grid.bboxMax);
|
|
if(!overlaps)
|
|
{
|
|
// no grid/froxel intersection
|
|
return result;
|
|
}
|
|
|
|
result = grid.GetFroxelAverage(request.scene, request.froxelPosition, request.froxelSize, request.transform);
|
|
if(!request.ambientLight)
|
|
{
|
|
// no ambient light requested
|
|
return result;
|
|
}
|
|
|
|
const float inScatterScale = 2.0;
|
|
const float stepScale = 8.0;
|
|
const uint dirCount = 4;
|
|
float jitterScale = 0.5 * Hash3To1(request.froxelId);
|
|
float extScale = densityExtinctionScale;
|
|
float trans = 0.0;
|
|
[unroll]
|
|
for(uint i = 0; i < dirCount; i++)
|
|
{
|
|
float3 dir = Dirs[i];
|
|
float3 step = dir * stepScale;
|
|
trans += grid.RaymarchTransmittance(request.froxelPosition + step * jitterScale, step, request.transform, extScale);
|
|
}
|
|
result.inScatteredLight = request.scene.ambientColor * (inScatterScale * trans * request.scene.ambientIntensity);
|
|
|
|
return result;
|
|
}
|
|
#else
|
|
SampleResult SampleFroxel(SampleRequest request)
|
|
{
|
|
SampleResult result = CreateSampleResult();
|
|
if(request.gridByteOffset == 0)
|
|
{
|
|
// no grid requested
|
|
return result;
|
|
}
|
|
|
|
Grid grid = GetGrid(request.buffer, request.gridByteOffset);
|
|
bool overlaps;
|
|
if(accurateOverlapTest != 0u)
|
|
{
|
|
overlaps = grid.OverlapsFroxel(request.scene, request.froxelIndex, request.textureSize, request.transform);
|
|
}
|
|
else
|
|
{
|
|
float3 indexF = grid.WorldToIndex(request.froxelPosition, request.transform);
|
|
overlaps = IsInRange(indexF, grid.bboxMin, grid.bboxMax);
|
|
}
|
|
if(!overlaps)
|
|
{
|
|
// no grid/froxel intersection
|
|
return result;
|
|
}
|
|
|
|
result = grid.GetFroxelAverage(request.scene, request.froxelPosition, request.froxelSize, request.transform);
|
|
if(!request.ambientLight)
|
|
{
|
|
// no ambient light requested
|
|
return result;
|
|
}
|
|
|
|
float3x3 rotation = RandomRotationMatrix3x3(float3(request.froxelId));
|
|
float jitterScale = 0.5 * Hash3To1(request.froxelId);
|
|
float stepScale = transStepScale;
|
|
uint dirCount = ambientAngularCoverage != 0 ? 14 : 6;
|
|
float localWeight = ambientAngularCoverage != 0 ? 1.75 : 0.75;
|
|
if(lightGridRC.isAvailable)
|
|
{
|
|
LightGridSample ambient = request.lightGrid.SampleAtPosition(request.froxelPosition);
|
|
float extScale = densityExtinctionScale;
|
|
float3 lightDir = ambient.GetLightDirection();
|
|
float3 dirTransStep = lightDir * stepScale;
|
|
float dirTrans = grid.RaymarchTransmittance(request.froxelPosition + dirTransStep * jitterScale, dirTransStep, request.transform, extScale);
|
|
float3 globalColor = float3(0, 0, 0);
|
|
for(uint i = 0; i < dirCount; i++)
|
|
{
|
|
float3 dir = mul(rotation, Dirs[i]);
|
|
float scale = stepScale;
|
|
float3 step = dir * stepScale;
|
|
LightGridSample sample = request.lightGrid.SampleAtPosition(request.froxelPosition + dir * scale * 0.5);
|
|
float trans = grid.RaymarchTransmittance(request.froxelPosition + step * jitterScale, step, request.transform, extScale);
|
|
float3 color = ColorAtBrightness(sample.GetGlobalColor(), 0.5);
|
|
globalColor += color * trans;
|
|
}
|
|
float3 cameraDir = normalize(request.froxelPosition - request.scene.cameraPosition);
|
|
float localScale = dot(-cameraDir, lightDir) * 0.5 + 0.5; // wraps around
|
|
float3 localColor = ColorAtBrightness(ambient.GetLocalColor(), 0.5) * localScale * dirTrans;
|
|
float3 color = (globalColor + localColor * localWeight) / (float(dirCount) + localWeight);
|
|
result.inScatteredLight = color * request.scene.ambientIntensity;
|
|
}
|
|
else
|
|
{
|
|
float extScale = densityExtinctionScale;
|
|
float trans = 0.0;
|
|
for(uint i = 0; i < dirCount; i++)
|
|
{
|
|
float3 dir = Dirs[i];
|
|
float3 step = dir * stepScale;
|
|
trans += grid.RaymarchTransmittance(request.froxelPosition + step * jitterScale, step, request.transform, extScale);
|
|
}
|
|
trans /= float(dirCount);
|
|
result.inScatteredLight = request.scene.ambientColor * (trans * request.scene.ambientIntensity);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
[numthreads(4, 4, 4)]
|
|
void cs(uint3 id : SV_DispatchThreadID)
|
|
{
|
|
RWTexture3D<float4> materialTextureA = ResourceDescriptorHeap[materialTextureAIndex];
|
|
uint3 textureSize = GetTextureSize(materialTextureA);
|
|
if(any(id >= textureSize))
|
|
{
|
|
return;
|
|
}
|
|
|
|
RWTexture2D<uint> frustumVisTexture = ResourceDescriptorHeap[frustumVisTextureIndex];
|
|
uint furthestVisibleFroxelZIndex = frustumVisTexture[id.xy];
|
|
if(id.z > furthestVisibleFroxelZIndex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SceneView scene = GetSceneView();
|
|
LightGrid lightGrid;
|
|
if(lightGridRC.isAvailable)
|
|
{
|
|
lightGrid = GetLightGrid(lightGridRC);
|
|
}
|
|
pnanovdb_buf_t nanovdbBuffer = ResourceDescriptorHeap[nanovdbBufferIndex];
|
|
Texture2D blackbodyTexture = ResourceDescriptorHeap[blackbodyTextureIndex];
|
|
SamplerState blackbodySampler = SamplerDescriptorHeap[scene.linearClampSamplerIndex];
|
|
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
|
|
RWTexture3D<float> materialTextureC = ResourceDescriptorHeap[materialTextureCIndex];
|
|
RWTexture3D<float4> scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex];
|
|
|
|
float3 textureSizeF = float3(textureSize);
|
|
float3 froxelPosition = scene.FroxelIndexToWorldSpace(int3(id), textureSizeF);
|
|
float3 froxelSize = scene.FroxelMaxDimensions(id, textureSizeF);
|
|
Transform transform = DecodeTransform(packedTransform, packedTransform2);
|
|
transform.stepSize *= stepScale;
|
|
|
|
#if !PREVIEW_MODE
|
|
if(linearInterpolation != 0u)
|
|
{
|
|
SampleRequest r;
|
|
r.buffer = nanovdbBuffer;
|
|
r.froxelIndex = id;
|
|
r.froxelPosition = froxelPosition;
|
|
r.froxelSize = froxelSize;
|
|
r.gridByteOffset = densityGridByteOffset;
|
|
r.scene = scene;
|
|
r.textureSize = textureSizeF;
|
|
r.transform = transform;
|
|
r.lightGrid = lightGrid;
|
|
r.froxelId = id;
|
|
r.ambientLight = true;
|
|
SampleResult extResult1 = SampleFroxel(r);
|
|
r.gridByteOffset = densityGridByteOffset2;
|
|
SampleResult extResult2 = SampleFroxel(r);
|
|
|
|
r.ambientLight = false;
|
|
r.gridByteOffset = flamesGridByteOffset;
|
|
SampleResult emResult1 = SampleFroxel(r);
|
|
r.gridByteOffset = flamesGridByteOffset2;
|
|
SampleResult emResult2 = SampleFroxel(r);
|
|
|
|
if(extResult1.sum > 0.0 || extResult2.sum > 0.0)
|
|
{
|
|
float extinction1 = (extResult1.sum / float(extResult1.maxSampleCount));
|
|
float extinction2 = (extResult2.sum / float(extResult2.maxSampleCount));
|
|
float extinction = lerp(extinction1, extinction2, t) * densityExtinctionScale;
|
|
float scatter = extinction * densityAlbedo;
|
|
float absorption = extinction - scatter;
|
|
float coverage1 = float(extResult1.sampleCount) / float(extResult1.maxSampleCount);
|
|
float coverage2 = float(extResult2.sampleCount) / float(extResult2.maxSampleCount);
|
|
float coverage = lerp(coverage1, coverage2, t);
|
|
float3 inScattered = lerp(extResult1.inScatteredLight, extResult2.inScatteredLight, t);
|
|
materialTextureA[id] += float4(scatter.xxx, absorption);
|
|
materialTextureC[id] += coverage;
|
|
scatterExtTexture[id] += float4(scatter * inScattered, 0);
|
|
}
|
|
|
|
if(emResult1.sum > 0.0 || emResult2.sum > 0.0)
|
|
{
|
|
float Tnorm1 = emResult1.sum / float(emResult1.maxSampleCount);
|
|
float Tnorm2 = emResult2.sum / float(emResult2.maxSampleCount);
|
|
float T = lerp(Tnorm1, Tnorm2, t) * flamesTemperatureScale;
|
|
float3 emission = BlackbodyEmission(T, blackbodyTexture, blackbodySampler) * flamesEmissionScale;
|
|
materialTextureB[id] += float4(emission, 0.0);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
SampleRequest r;
|
|
r.buffer = nanovdbBuffer;
|
|
r.froxelIndex = id;
|
|
r.froxelPosition = froxelPosition;
|
|
r.froxelSize = froxelSize;
|
|
r.gridByteOffset = densityGridByteOffset;
|
|
r.scene = scene;
|
|
r.textureSize = textureSizeF;
|
|
r.transform = transform;
|
|
r.lightGrid = lightGrid;
|
|
r.froxelId = id;
|
|
r.ambientLight = true;
|
|
SampleResult extResult = SampleFroxel(r);
|
|
|
|
r.gridByteOffset = flamesGridByteOffset;
|
|
r.ambientLight = false;
|
|
SampleResult emResult = SampleFroxel(r);
|
|
|
|
if(extResult.sum > 0.0)
|
|
{
|
|
float extinction = (extResult.sum / float(extResult.maxSampleCount)) * densityExtinctionScale;
|
|
float scatter = extinction * densityAlbedo;
|
|
float absorption = extinction - scatter;
|
|
float coverage = float(extResult.sampleCount) / float(extResult.maxSampleCount);
|
|
materialTextureA[id] += float4(scatter.xxx, absorption);
|
|
materialTextureC[id] += coverage;
|
|
scatterExtTexture[id] += float4(scatter * extResult.inScatteredLight, 0);
|
|
}
|
|
|
|
if(emResult.sum > 0.0)
|
|
{
|
|
float Tnorm = emResult.sum / float(emResult.maxSampleCount);
|
|
float T = Tnorm * flamesTemperatureScale;
|
|
float3 emission = BlackbodyEmission(T, blackbodyTexture, blackbodySampler) * flamesEmissionScale;
|
|
materialTextureB[id] += float4(emission, 0.0);
|
|
}
|
|
}
|
|
}
|