cnq3/code/renderer/shaders/crp/vl_nanovdb.hlsli
myT afc81437c3 added NanoVDB support
- added the foundation for a GPU particle system
- reworked volumetric particle injection
2024-07-02 02:06:15 +02:00

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