mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-10 06:31:48 +00:00
afc81437c3
- added the foundation for a GPU particle system - reworked volumetric particle injection
279 lines
8.8 KiB
HLSL
279 lines
8.8 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: sampling NanoVDB volumes over oriented froxels and axis-aligned boxes
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
#include "common.hlsli"
|
|
#include "scene_view.h.hlsli"
|
|
#define PNANOVDB_HLSL
|
|
#include "PNanoVDB.h"
|
|
|
|
|
|
struct Transform
|
|
{
|
|
float3x3 worldToIndex;
|
|
float3 originOffset;
|
|
float3 translation;
|
|
float3 stepSize;
|
|
|
|
void DecodeTransform(float4 transform[4], float2 transform2)
|
|
{
|
|
worldToIndex = float3x3(
|
|
transform[0].x, transform[0].y, transform[0].z,
|
|
transform[0].w, transform[1].x, transform[1].y,
|
|
transform[1].z, transform[1].w, transform[2].x);
|
|
originOffset = float3(transform[2].yzw);
|
|
translation = float3(transform[3].xyz);
|
|
stepSize = float3(transform[3].w, transform2.x, transform2.y);
|
|
}
|
|
};
|
|
|
|
Transform DecodeTransform(float4 packed[4], float2 packed2)
|
|
{
|
|
Transform transform;
|
|
transform.DecodeTransform(packed, packed2);
|
|
|
|
return transform;
|
|
}
|
|
|
|
struct SampleResult
|
|
{
|
|
float sum;
|
|
int maxSampleCount;
|
|
int sampleCount;
|
|
float transmittance;
|
|
float3 inScatteredLight;
|
|
|
|
void Clear()
|
|
{
|
|
sum = 0.0;
|
|
maxSampleCount = 1;
|
|
sampleCount = 0;
|
|
transmittance = 1.0;
|
|
inScatteredLight = float3(0, 0, 0);
|
|
}
|
|
};
|
|
|
|
SampleResult CreateSampleResult()
|
|
{
|
|
SampleResult result;
|
|
result.Clear();
|
|
|
|
return result;
|
|
}
|
|
|
|
struct Grid
|
|
{
|
|
pnanovdb_buf_t buffer;
|
|
pnanovdb_grid_handle_t grid;
|
|
pnanovdb_tree_handle_t tree;
|
|
pnanovdb_root_handle_t root;
|
|
pnanovdb_vec3_t bboxMin;
|
|
pnanovdb_vec3_t bboxMax;
|
|
pnanovdb_uint32_t gridType;
|
|
pnanovdb_readaccessor_t accessor;
|
|
|
|
void Init(pnanovdb_buf_t gridBuffer, uint gridByteOffset)
|
|
{
|
|
buffer = gridBuffer;
|
|
grid.address.byte_offset = gridByteOffset;
|
|
pnanovdb_vec3_t extBboxMin;
|
|
pnanovdb_vec3_t extBboxMax;
|
|
pnanovdb_tree_handle_t tree = pnanovdb_grid_get_tree(buffer, grid);
|
|
pnanovdb_root_handle_t root = pnanovdb_tree_get_root(buffer, tree);
|
|
bboxMin = pnanovdb_coord_to_vec3(pnanovdb_root_get_bbox_min(buffer, root));
|
|
bboxMax = pnanovdb_coord_to_vec3(pnanovdb_root_get_bbox_max(buffer, root));
|
|
gridType = pnanovdb_grid_get_grid_type(buffer, grid);
|
|
pnanovdb_readaccessor_init(accessor, root);
|
|
}
|
|
|
|
float3 WorldToIndex(float3 worldPosition, Transform transform)
|
|
{
|
|
float3 index = mul(transform.worldToIndex, worldPosition - transform.translation) - transform.originOffset;
|
|
|
|
return index;
|
|
}
|
|
|
|
float ReadFloat(pnanovdb_coord_t coords)
|
|
{
|
|
pnanovdb_uint32_t level;
|
|
pnanovdb_address_t address = pnanovdb_readaccessor_get_value_address_and_level(gridType, buffer, accessor, coords, level);
|
|
float result;
|
|
if(level == 0u && gridType != PNANOVDB_GRID_TYPE_FLOAT)
|
|
{
|
|
if(gridType == PNANOVDB_GRID_TYPE_FPN)
|
|
{
|
|
result = pnanovdb_leaf_fpn_read_float(buffer, address, coords);
|
|
}
|
|
else
|
|
{
|
|
result = pnanovdb_leaf_fp_read_float(buffer, address, coords, gridType - PNANOVDB_GRID_TYPE_FP4 + 2u);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = pnanovdb_read_float(buffer, address);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
SampleResult GetFroxelAverage(SceneView scene, float3 froxelPosition, float3 froxelSize, Transform transform)
|
|
{
|
|
int3 steps = int3(ceil(froxelSize / (2.0 * transform.stepSize)));
|
|
|
|
SampleResult result;
|
|
result.maxSampleCount = (2 * steps.x + 1) * (2 * steps.y + 1) * (2 * steps.z + 1);
|
|
result.sampleCount = 0;
|
|
result.sum = 0.0;
|
|
for(int z = -steps.z; z <= steps.z; z++)
|
|
{
|
|
float3 offZ = float(z) * transform.stepSize.z * scene.cameraForward;
|
|
for(int y = -steps.y; y <= steps.y; y++)
|
|
{
|
|
float3 offY = float(y) * transform.stepSize.y * scene.cameraUp;
|
|
for(int x = -steps.x; x <= steps.x; x++)
|
|
{
|
|
float3 offX = float(x) * transform.stepSize.x * scene.cameraLeft;
|
|
float3 worldPosition = froxelPosition + offX + offY + offZ;
|
|
float3 indexF = WorldToIndex(worldPosition, transform);
|
|
if(IsInRange(indexF, bboxMin, bboxMax))
|
|
{
|
|
int3 index = int3(indexF);
|
|
float value = ReadFloat(index);
|
|
result.sum += value;
|
|
result.sampleCount += value > 0.0 ? 1.0 : 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
SampleResult GetAxisAlignedBoxAverage(SceneView scene, float3 voxelPosition, float3 voxelSize, Transform transform)
|
|
{
|
|
int3 steps = int3(voxelSize / (2.0 * transform.stepSize));
|
|
|
|
SampleResult result;
|
|
result.maxSampleCount = (2 * steps.x + 1) * (2 * steps.y + 1) * (2 * steps.z + 1);
|
|
result.sampleCount = 0;
|
|
result.sum = 0.0;
|
|
for(int z = -steps.z; z <= steps.z; z++)
|
|
{
|
|
for(int y = -steps.y; y <= steps.y; y++)
|
|
{
|
|
for(int x = -steps.x; x <= steps.x; x++)
|
|
{
|
|
float3 worldPosition = voxelPosition + float3(x, y, z) * transform.stepSize;
|
|
float3 indexF = WorldToIndex(worldPosition, transform);
|
|
int3 index = int3(indexF);
|
|
float value = ReadFloat(index);
|
|
result.sum += value;
|
|
result.sampleCount += value > 0.0 ? 1.0 : 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void ExpandFroxelBoundingBox(inout float3 boxMin, inout float3 boxMax, SceneView scene, float3 tc, float3 textureSize, Transform transform)
|
|
{
|
|
float3 pointWS = scene.FroxelTCToWorldSpace(tc, textureSize);
|
|
float3 indexF = WorldToIndex(pointWS, transform);
|
|
ExpandBoundingBox(boxMin, boxMax, indexF);
|
|
}
|
|
|
|
bool OverlapsFroxel(SceneView scene, int3 index, float3 textureSize, Transform transform)
|
|
{
|
|
float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
|
|
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
|
|
float3 froxelMin, froxelMax;
|
|
ClearBoundingBox(froxelMin, froxelMax);
|
|
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(-halfTexel.x, 0, 0), textureSize, transform);
|
|
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(halfTexel.x, 0, 0), textureSize, transform);
|
|
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, -halfTexel.y, 0), textureSize, transform);
|
|
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, halfTexel.y, 0), textureSize, transform);
|
|
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, 0, -halfTexel.z), textureSize, transform);
|
|
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, 0, halfTexel.z), textureSize, transform);
|
|
bool overlaps = all(froxelMax >= bboxMin) && all(froxelMin <= bboxMax);
|
|
|
|
return overlaps;
|
|
}
|
|
|
|
void ExpandAABB(inout float3 boxMin, inout float3 boxMax, SceneView scene, float3 tc, float3 textureSize, float worldScale, Transform transform)
|
|
{
|
|
float3 pointWS = scene.ExtinctionTCToWorldSpace(tc, textureSize, worldScale);
|
|
float3 indexF = WorldToIndex(pointWS, transform);
|
|
ExpandBoundingBox(boxMin, boxMax, indexF);
|
|
}
|
|
|
|
bool OverlapsAxisAlignedBox(SceneView scene, int3 index, float3 textureSize, float worldScale, Transform transform)
|
|
{
|
|
float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
|
|
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
|
|
float3 voxelMin, voxelMax;
|
|
ClearBoundingBox(voxelMin, voxelMax);
|
|
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(-halfTexel.x, 0, 0), textureSize, worldScale, transform);
|
|
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(halfTexel.x, 0, 0), textureSize, worldScale, transform);
|
|
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, -halfTexel.y, 0), textureSize, worldScale, transform);
|
|
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, halfTexel.y, 0), textureSize, worldScale, transform);
|
|
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, 0, -halfTexel.z), textureSize, worldScale, transform);
|
|
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, 0, halfTexel.z), textureSize, worldScale, transform);
|
|
bool overlaps = all(voxelMax >= bboxMin) && all(voxelMin <= bboxMax);
|
|
|
|
return overlaps;
|
|
}
|
|
|
|
float RaymarchTransmittance(float3 position, float3 step, Transform transform, float extinctionScale)
|
|
{
|
|
float stepDist = length(step);
|
|
float accumTrans = 1.0;
|
|
while(true)
|
|
{
|
|
position += step;
|
|
float3 indexF = WorldToIndex(position, transform);
|
|
if(!IsInRange(indexF, bboxMin, bboxMax))
|
|
{
|
|
break;
|
|
}
|
|
|
|
int3 index = int3(indexF);
|
|
float ext = ReadFloat(index) * extinctionScale;
|
|
float sampleTrans = Transmittance(stepDist, ext);
|
|
accumTrans *= saturate(sampleTrans);
|
|
}
|
|
|
|
return accumTrans;
|
|
}
|
|
};
|
|
|
|
Grid GetGrid(pnanovdb_buf_t buffer, uint gridByteOffset)
|
|
{
|
|
Grid grid;
|
|
grid.Init(buffer, gridByteOffset);
|
|
|
|
return grid;
|
|
}
|