mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
9473 lines
302 KiB
C++
9473 lines
302 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
IceTech GPL Source Code
|
|
Copyright (C) 2026 Justin Marshall
|
|
|
|
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
|
|
|
|
IceTech Source Code 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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
IceTech Source Code 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 IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "opengl.h"
|
|
|
|
#include <windows.h>
|
|
#include <d3d12.h>
|
|
#include <dxgi1_6.h>
|
|
#include <d3dcompiler.h>
|
|
#include <dxcapi.h>
|
|
#include <wrl/client.h>
|
|
|
|
#include <stdint.h>
|
|
#include <vector>
|
|
#include <thread>
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <wchar.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
#pragma comment(lib, "dxcompiler.lib")
|
|
#pragma comment(lib, "d3dcompiler.lib")
|
|
|
|
using Microsoft::WRL::ComPtr;
|
|
|
|
extern float com_pathTracingGpuMsec;
|
|
|
|
// ============================================================
|
|
// Logging / checks
|
|
// ============================================================
|
|
|
|
static void glRaytracingLog(const char* fmt, ...)
|
|
{
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
va_end(args);
|
|
OutputDebugStringA(buffer);
|
|
OutputDebugStringA("\n");
|
|
}
|
|
|
|
static void glRaytracingFatal(const char* fmt, ...)
|
|
{
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
va_end(args);
|
|
OutputDebugStringA(buffer);
|
|
OutputDebugStringA("\n");
|
|
MessageBoxA(nullptr, buffer, "glRaytracing Fatal", MB_OK | MB_ICONERROR);
|
|
DebugBreak();
|
|
}
|
|
|
|
#define GLR_CHECK(x) \
|
|
do { HRESULT _hr = (x); if (FAILED(_hr)) { glRaytracingFatal("HRESULT 0x%08X failed at %s:%d", (unsigned)_hr, __FILE__, __LINE__); return 0; } } while (0)
|
|
|
|
#define GLR_CHECKV(x) \
|
|
do { HRESULT _hr = (x); if (FAILED(_hr)) { glRaytracingFatal("HRESULT 0x%08X failed at %s:%d", (unsigned)_hr, __FILE__, __LINE__); return; } } while (0)
|
|
|
|
static void glRaytracingBeginPixEvent(ID3D12GraphicsCommandList* cl, const wchar_t* name)
|
|
{
|
|
if (!cl || !name)
|
|
return;
|
|
|
|
cl->BeginEvent(0, name, (UINT)((wcslen(name) + 1) * sizeof(wchar_t)));
|
|
}
|
|
|
|
static void glRaytracingEndPixEvent(ID3D12GraphicsCommandList* cl)
|
|
{
|
|
if (cl)
|
|
cl->EndEvent();
|
|
}
|
|
|
|
static void glRaytracingSetPixMarker(ID3D12GraphicsCommandList* cl, const wchar_t* name)
|
|
{
|
|
if (!cl || !name)
|
|
return;
|
|
|
|
cl->SetMarker(0, name, (UINT)((wcslen(name) + 1) * sizeof(wchar_t)));
|
|
}
|
|
|
|
static void glRaytracingSetObjectName(ID3D12Object* object, const wchar_t* name)
|
|
{
|
|
if (object && name)
|
|
object->SetName(name);
|
|
}
|
|
|
|
class glRaytracingScopedPixEvent
|
|
{
|
|
public:
|
|
glRaytracingScopedPixEvent(ID3D12GraphicsCommandList* commandList, const wchar_t* name)
|
|
: cl(commandList)
|
|
{
|
|
glRaytracingBeginPixEvent(cl, name);
|
|
}
|
|
|
|
~glRaytracingScopedPixEvent()
|
|
{
|
|
glRaytracingEndPixEvent(cl);
|
|
}
|
|
|
|
private:
|
|
ID3D12GraphicsCommandList* cl;
|
|
};
|
|
|
|
#define GLR_CONCAT_INNER(a, b) a##b
|
|
#define GLR_CONCAT(a, b) GLR_CONCAT_INNER(a, b)
|
|
#define GLR_PIX_EVENT(cl, name) glRaytracingScopedPixEvent GLR_CONCAT(glrPixEvent_, __LINE__)(cl, name)
|
|
#define GLR_PIX_MARKER(cl, name) glRaytracingSetPixMarker(cl, name)
|
|
|
|
// ============================================================
|
|
// Helpers
|
|
// ============================================================
|
|
|
|
static UINT64 glRaytracingAlignUp(UINT64 v, UINT64 a)
|
|
{
|
|
return (v + (a - 1)) & ~(a - 1);
|
|
}
|
|
|
|
template<typename T>
|
|
static T glRaytracingClamp(T v, T lo, T hi)
|
|
{
|
|
return (v < lo) ? lo : ((v > hi) ? hi : v);
|
|
}
|
|
|
|
static void glRaytracingSetIdentity4x4(float* m)
|
|
{
|
|
if (!m)
|
|
return;
|
|
|
|
memset(m, 0, sizeof(float) * 16);
|
|
m[0] = 1.0f;
|
|
m[5] = 1.0f;
|
|
m[10] = 1.0f;
|
|
m[15] = 1.0f;
|
|
}
|
|
|
|
static int glRaytracingInvertMatrix4x4(const float* m, float* out)
|
|
{
|
|
if (!m || !out)
|
|
return 0;
|
|
|
|
float a[4][8];
|
|
|
|
for (int r = 0; r < 4; ++r)
|
|
{
|
|
for (int c = 0; c < 4; ++c)
|
|
a[r][c] = m[r * 4 + c];
|
|
|
|
for (int c = 0; c < 4; ++c)
|
|
a[r][4 + c] = (r == c) ? 1.0f : 0.0f;
|
|
}
|
|
|
|
for (int col = 0; col < 4; ++col)
|
|
{
|
|
int pivot = col;
|
|
float best = a[col][col] < 0.0f ? -a[col][col] : a[col][col];
|
|
|
|
for (int r = col + 1; r < 4; ++r)
|
|
{
|
|
const float v = a[r][col] < 0.0f ? -a[r][col] : a[r][col];
|
|
if (v > best)
|
|
{
|
|
best = v;
|
|
pivot = r;
|
|
}
|
|
}
|
|
|
|
if (best <= 1.0e-8f)
|
|
return 0;
|
|
|
|
if (pivot != col)
|
|
{
|
|
for (int c = 0; c < 8; ++c)
|
|
{
|
|
const float tmp = a[col][c];
|
|
a[col][c] = a[pivot][c];
|
|
a[pivot][c] = tmp;
|
|
}
|
|
}
|
|
|
|
const float invPivot = 1.0f / a[col][col];
|
|
for (int c = 0; c < 8; ++c)
|
|
a[col][c] *= invPivot;
|
|
|
|
for (int r = 0; r < 4; ++r)
|
|
{
|
|
if (r == col)
|
|
continue;
|
|
|
|
const float f = a[r][col];
|
|
if (f == 0.0f)
|
|
continue;
|
|
|
|
for (int c = 0; c < 8; ++c)
|
|
a[r][c] -= f * a[col][c];
|
|
}
|
|
}
|
|
|
|
for (int r = 0; r < 4; ++r)
|
|
{
|
|
for (int c = 0; c < 4; ++c)
|
|
out[r * 4 + c] = a[r][4 + c];
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static DXGI_FORMAT glRaytracingGetSrvFormatForDepth(DXGI_FORMAT fmt)
|
|
{
|
|
switch (fmt)
|
|
{
|
|
case DXGI_FORMAT_D32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
|
|
case DXGI_FORMAT_D24_UNORM_S8_UINT: return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
|
|
case DXGI_FORMAT_D16_UNORM: return DXGI_FORMAT_R16_UNORM;
|
|
default: return fmt;
|
|
}
|
|
}
|
|
|
|
struct glRaytracingBuffer_t
|
|
{
|
|
ComPtr<ID3D12Resource> resource;
|
|
UINT64 size;
|
|
D3D12_GPU_VIRTUAL_ADDRESS gpuVA;
|
|
|
|
glRaytracingBuffer_t()
|
|
{
|
|
size = 0;
|
|
gpuVA = 0;
|
|
}
|
|
};
|
|
|
|
static glRaytracingBuffer_t glRaytracingCreateBuffer(
|
|
ID3D12Device* device,
|
|
UINT64 size,
|
|
D3D12_HEAP_TYPE heapType,
|
|
D3D12_RESOURCE_STATES initialState,
|
|
D3D12_RESOURCE_FLAGS flags)
|
|
{
|
|
glRaytracingBuffer_t out;
|
|
|
|
D3D12_HEAP_PROPERTIES hp = {};
|
|
hp.Type = heapType;
|
|
|
|
D3D12_RESOURCE_DESC rd = {};
|
|
rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
|
rd.Width = size;
|
|
rd.Height = 1;
|
|
rd.DepthOrArraySize = 1;
|
|
rd.MipLevels = 1;
|
|
rd.Format = DXGI_FORMAT_UNKNOWN;
|
|
rd.SampleDesc.Count = 1;
|
|
rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
|
rd.Flags = flags;
|
|
|
|
HRESULT hr = device->CreateCommittedResource(
|
|
&hp,
|
|
D3D12_HEAP_FLAG_NONE,
|
|
&rd,
|
|
initialState,
|
|
nullptr,
|
|
IID_PPV_ARGS(&out.resource));
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("CreateCommittedResource failed 0x%08X", (unsigned)hr);
|
|
return out;
|
|
}
|
|
|
|
out.size = size;
|
|
out.gpuVA = out.resource->GetGPUVirtualAddress();
|
|
return out;
|
|
}
|
|
|
|
struct glRaytracingTexture_t
|
|
{
|
|
ComPtr<ID3D12Resource> resource;
|
|
UINT width;
|
|
UINT height;
|
|
DXGI_FORMAT format;
|
|
D3D12_RESOURCE_STATES state;
|
|
|
|
glRaytracingTexture_t()
|
|
{
|
|
width = 0;
|
|
height = 0;
|
|
format = DXGI_FORMAT_UNKNOWN;
|
|
state = D3D12_RESOURCE_STATE_COMMON;
|
|
}
|
|
};
|
|
|
|
static glRaytracingTexture_t glRaytracingCreateTexture2D(
|
|
ID3D12Device* device,
|
|
UINT width,
|
|
UINT height,
|
|
DXGI_FORMAT format,
|
|
D3D12_RESOURCE_STATES initialState,
|
|
D3D12_RESOURCE_FLAGS flags)
|
|
{
|
|
glRaytracingTexture_t out;
|
|
|
|
D3D12_HEAP_PROPERTIES hp = {};
|
|
hp.Type = D3D12_HEAP_TYPE_DEFAULT;
|
|
|
|
D3D12_RESOURCE_DESC rd = {};
|
|
rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
rd.Width = width;
|
|
rd.Height = height;
|
|
rd.DepthOrArraySize = 1;
|
|
rd.MipLevels = 1;
|
|
rd.Format = format;
|
|
rd.SampleDesc.Count = 1;
|
|
rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
|
rd.Flags = flags;
|
|
|
|
HRESULT hr = device->CreateCommittedResource(
|
|
&hp,
|
|
D3D12_HEAP_FLAG_NONE,
|
|
&rd,
|
|
initialState,
|
|
nullptr,
|
|
IID_PPV_ARGS(&out.resource));
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("CreateCommittedResource texture failed 0x%08X", (unsigned)hr);
|
|
return out;
|
|
}
|
|
|
|
out.width = width;
|
|
out.height = height;
|
|
out.format = format;
|
|
out.state = initialState;
|
|
return out;
|
|
}
|
|
|
|
static void glRaytracingMapCopy(ID3D12Resource* res, const void* src, size_t bytes)
|
|
{
|
|
void* dst = nullptr;
|
|
HRESULT hr = res->Map(0, nullptr, &dst);
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("Map failed 0x%08X", (unsigned)hr);
|
|
return;
|
|
}
|
|
|
|
memcpy(dst, src, bytes);
|
|
res->Unmap(0, nullptr);
|
|
}
|
|
|
|
static void glRaytracingTransition(
|
|
ID3D12GraphicsCommandList* cmd,
|
|
ID3D12Resource* res,
|
|
D3D12_RESOURCE_STATES before,
|
|
D3D12_RESOURCE_STATES after)
|
|
{
|
|
if (!res || before == after)
|
|
return;
|
|
|
|
D3D12_RESOURCE_BARRIER b = {};
|
|
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
b.Transition.pResource = res;
|
|
b.Transition.StateBefore = before;
|
|
b.Transition.StateAfter = after;
|
|
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
cmd->ResourceBarrier(1, &b);
|
|
}
|
|
|
|
static D3D12_CPU_DESCRIPTOR_HANDLE glRaytracingOffsetCpu(D3D12_CPU_DESCRIPTOR_HANDLE h, UINT stride, UINT idx)
|
|
{
|
|
h.ptr += UINT64(stride) * UINT64(idx);
|
|
return h;
|
|
}
|
|
|
|
static D3D12_GPU_DESCRIPTOR_HANDLE glRaytracingOffsetGpu(D3D12_GPU_DESCRIPTOR_HANDLE h, UINT stride, UINT idx)
|
|
{
|
|
h.ptr += UINT64(stride) * UINT64(idx);
|
|
return h;
|
|
}
|
|
|
|
// ============================================================
|
|
// Shared command context
|
|
// ============================================================
|
|
|
|
#ifndef GL_RAYTRACING_CMD_RING_SIZE
|
|
#define GL_RAYTRACING_CMD_RING_SIZE 4
|
|
#endif
|
|
|
|
#if GL_RAYTRACING_CMD_RING_SIZE < 2
|
|
#error GL_RAYTRACING_CMD_RING_SIZE must be at least 2
|
|
#endif
|
|
|
|
#ifndef GL_RAYTRACING_FORCE_CPU_SYNC
|
|
#define GL_RAYTRACING_FORCE_CPU_SYNC 0
|
|
#endif
|
|
|
|
struct glRaytracingCmdContext_t
|
|
{
|
|
ComPtr<ID3D12Device5> device;
|
|
ComPtr<ID3D12CommandQueue> queue;
|
|
|
|
// Active command allocator/list for the command currently being recorded.
|
|
// The ring below avoids the old every-frame CPU/GPU fence wait.
|
|
ComPtr<ID3D12CommandAllocator> cmdAlloc;
|
|
ComPtr<ID3D12GraphicsCommandList4> cmdList;
|
|
UINT64 cmdLastFenceValue;
|
|
ComPtr<ID3D12CommandAllocator> cmdAllocRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
ComPtr<ID3D12GraphicsCommandList4> cmdListRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT64 cmdFenceValueRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT cmdRingIndex;
|
|
UINT cmdCurrentSlot;
|
|
|
|
ComPtr<ID3D12CommandAllocator> blasCmdAlloc;
|
|
ComPtr<ID3D12GraphicsCommandList4> blasCmdList;
|
|
UINT64 blasLastFenceValue;
|
|
ComPtr<ID3D12CommandAllocator> blasCmdAllocRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
ComPtr<ID3D12GraphicsCommandList4> blasCmdListRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT64 blasFenceValueRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT blasRingIndex;
|
|
UINT blasCurrentSlot;
|
|
|
|
ComPtr<ID3D12CommandAllocator> tlasCmdAlloc;
|
|
ComPtr<ID3D12GraphicsCommandList4> tlasCmdList;
|
|
UINT64 tlasLastFenceValue;
|
|
ComPtr<ID3D12CommandAllocator> tlasCmdAllocRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
ComPtr<ID3D12GraphicsCommandList4> tlasCmdListRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT64 tlasFenceValueRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT tlasRingIndex;
|
|
UINT tlasCurrentSlot;
|
|
|
|
ComPtr<ID3D12Fence> fence;
|
|
HANDLE fenceEvent;
|
|
UINT64 nextFenceValue;
|
|
ComPtr<ID3D12QueryHeap> pathTracingTimestampHeap;
|
|
glRaytracingBuffer_t pathTracingTimestampReadback;
|
|
UINT64 pathTracingTimestampFrequency;
|
|
UINT64 pathTracingTimestampFenceRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
bool initialized;
|
|
|
|
glRaytracingCmdContext_t()
|
|
{
|
|
cmdLastFenceValue = 0;
|
|
blasLastFenceValue = 0;
|
|
tlasLastFenceValue = 0;
|
|
cmdRingIndex = GL_RAYTRACING_CMD_RING_SIZE - 1;
|
|
blasRingIndex = GL_RAYTRACING_CMD_RING_SIZE - 1;
|
|
tlasRingIndex = GL_RAYTRACING_CMD_RING_SIZE - 1;
|
|
cmdCurrentSlot = 0;
|
|
blasCurrentSlot = 0;
|
|
tlasCurrentSlot = 0;
|
|
for (UINT i = 0; i < GL_RAYTRACING_CMD_RING_SIZE; ++i)
|
|
{
|
|
cmdFenceValueRing[i] = 0;
|
|
blasFenceValueRing[i] = 0;
|
|
tlasFenceValueRing[i] = 0;
|
|
pathTracingTimestampFenceRing[i] = 0;
|
|
}
|
|
fenceEvent = nullptr;
|
|
nextFenceValue = 0;
|
|
pathTracingTimestampFrequency = 0;
|
|
initialized = false;
|
|
}
|
|
};
|
|
|
|
static glRaytracingCmdContext_t g_glRaytracingCmd;
|
|
static std::mutex g_glRaytracingMutex;
|
|
|
|
static void glRaytracingWaitFenceValue(UINT64 value)
|
|
{
|
|
if (!value || !g_glRaytracingCmd.fence)
|
|
return;
|
|
|
|
if (g_glRaytracingCmd.fence->GetCompletedValue() >= value)
|
|
return;
|
|
|
|
g_glRaytracingCmd.fence->SetEventOnCompletion(value, g_glRaytracingCmd.fenceEvent);
|
|
WaitForSingleObject(g_glRaytracingCmd.fenceEvent, INFINITE);
|
|
}
|
|
|
|
static UINT64 glRaytracingSignalQueue(void)
|
|
{
|
|
if (!g_glRaytracingCmd.queue || !g_glRaytracingCmd.fence)
|
|
return 0;
|
|
|
|
const UINT64 value = ++g_glRaytracingCmd.nextFenceValue;
|
|
g_glRaytracingCmd.queue->Signal(g_glRaytracingCmd.fence.Get(), value);
|
|
return value;
|
|
}
|
|
|
|
static void glRaytracingWaitIdle(void)
|
|
{
|
|
const UINT64 value = glRaytracingSignalQueue();
|
|
glRaytracingWaitFenceValue(value);
|
|
}
|
|
|
|
static void glRaytracingPollPathTracingTimestamp(UINT slot)
|
|
{
|
|
if (!g_glRaytracingCmd.pathTracingTimestampReadback.resource ||
|
|
!g_glRaytracingCmd.pathTracingTimestampFrequency ||
|
|
slot >= GL_RAYTRACING_CMD_RING_SIZE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UINT64 fenceValue = g_glRaytracingCmd.pathTracingTimestampFenceRing[slot];
|
|
if (!fenceValue || !g_glRaytracingCmd.fence || g_glRaytracingCmd.fence->GetCompletedValue() < fenceValue)
|
|
return;
|
|
|
|
const UINT64 offset = sizeof(UINT64) * 2ull * slot;
|
|
D3D12_RANGE readRange = { offset, offset + sizeof(UINT64) * 2ull };
|
|
UINT64* timestamps = nullptr;
|
|
if (SUCCEEDED(g_glRaytracingCmd.pathTracingTimestampReadback.resource->Map(0, &readRange, reinterpret_cast<void**>(×tamps))) && timestamps != nullptr)
|
|
{
|
|
const UINT64 begin = timestamps[slot * 2 + 0];
|
|
const UINT64 end = timestamps[slot * 2 + 1];
|
|
if (end >= begin)
|
|
{
|
|
com_pathTracingGpuMsec = (float)((double)(end - begin) * 1000.0 / (double)g_glRaytracingCmd.pathTracingTimestampFrequency);
|
|
}
|
|
|
|
D3D12_RANGE writeRange = { 0, 0 };
|
|
g_glRaytracingCmd.pathTracingTimestampReadback.resource->Unmap(0, &writeRange);
|
|
}
|
|
g_glRaytracingCmd.pathTracingTimestampFenceRing[slot] = 0;
|
|
}
|
|
|
|
static int glRaytracingCreatePathTracingTimestamps(void)
|
|
{
|
|
D3D12_QUERY_HEAP_DESC qh = {};
|
|
qh.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
|
|
qh.Count = GL_RAYTRACING_CMD_RING_SIZE * 2;
|
|
if (FAILED(g_glRaytracingCmd.device->CreateQueryHeap(&qh, IID_PPV_ARGS(&g_glRaytracingCmd.pathTracingTimestampHeap))))
|
|
return 0;
|
|
|
|
g_glRaytracingCmd.pathTracingTimestampReadback = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
sizeof(UINT64) * GL_RAYTRACING_CMD_RING_SIZE * 2ull,
|
|
D3D12_HEAP_TYPE_READBACK,
|
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
if (!g_glRaytracingCmd.pathTracingTimestampReadback.resource)
|
|
return 0;
|
|
|
|
g_glRaytracingCmd.pathTracingTimestampFrequency = 0;
|
|
if (FAILED(g_glRaytracingCmd.queue->GetTimestampFrequency(&g_glRaytracingCmd.pathTracingTimestampFrequency)) ||
|
|
g_glRaytracingCmd.pathTracingTimestampFrequency == 0)
|
|
{
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Reset();
|
|
g_glRaytracingCmd.pathTracingTimestampReadback = glRaytracingBuffer_t();
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingCreateDirectCommandListPair(
|
|
ID3D12Device5* device,
|
|
ComPtr<ID3D12CommandAllocator>& allocator,
|
|
ComPtr<ID3D12GraphicsCommandList4>& list)
|
|
{
|
|
GLR_CHECK(device->CreateCommandAllocator(
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
IID_PPV_ARGS(&allocator)));
|
|
|
|
GLR_CHECK(device->CreateCommandList(
|
|
0,
|
|
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
allocator.Get(),
|
|
nullptr,
|
|
IID_PPV_ARGS(&list)));
|
|
|
|
GLR_CHECK(list->Close());
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingInitCmdContext(void)
|
|
{
|
|
ID3D12Device* baseDevice = QD3D12_GetDevice();
|
|
ID3D12CommandQueue* baseQueue = QD3D12_GetQueue();
|
|
|
|
if (g_glRaytracingCmd.initialized)
|
|
{
|
|
if (!baseDevice || !baseQueue)
|
|
{
|
|
glRaytracingFatal("glRaytracingInitCmdContext: missing device or queue");
|
|
return 0;
|
|
}
|
|
|
|
ComPtr<ID3D12Device5> currentDevice;
|
|
HRESULT hr = baseDevice->QueryInterface(IID_PPV_ARGS(¤tDevice));
|
|
if (FAILED(hr) || currentDevice.Get() != g_glRaytracingCmd.device.Get() || baseQueue != g_glRaytracingCmd.queue.Get())
|
|
{
|
|
glRaytracingFatal("glRaytracingInitCmdContext: D3D12 device/queue changed. DXR state is device-local; create/use all windows with the same D3D12 device and queue, or fully shut down raytracing before switching devices.");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (!baseDevice || !baseQueue)
|
|
{
|
|
glRaytracingFatal("glRaytracingInitCmdContext: missing device or queue");
|
|
return 0;
|
|
}
|
|
|
|
GLR_CHECK(baseDevice->QueryInterface(IID_PPV_ARGS(&g_glRaytracingCmd.device)));
|
|
g_glRaytracingCmd.queue = baseQueue;
|
|
|
|
for (UINT i = 0; i < GL_RAYTRACING_CMD_RING_SIZE; ++i)
|
|
{
|
|
if (!glRaytracingCreateDirectCommandListPair(
|
|
g_glRaytracingCmd.device.Get(),
|
|
g_glRaytracingCmd.cmdAllocRing[i],
|
|
g_glRaytracingCmd.cmdListRing[i]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingCreateDirectCommandListPair(
|
|
g_glRaytracingCmd.device.Get(),
|
|
g_glRaytracingCmd.blasCmdAllocRing[i],
|
|
g_glRaytracingCmd.blasCmdListRing[i]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingCreateDirectCommandListPair(
|
|
g_glRaytracingCmd.device.Get(),
|
|
g_glRaytracingCmd.tlasCmdAllocRing[i],
|
|
g_glRaytracingCmd.tlasCmdListRing[i]))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
g_glRaytracingCmd.cmdAlloc = g_glRaytracingCmd.cmdAllocRing[0];
|
|
g_glRaytracingCmd.cmdList = g_glRaytracingCmd.cmdListRing[0];
|
|
g_glRaytracingCmd.blasCmdAlloc = g_glRaytracingCmd.blasCmdAllocRing[0];
|
|
g_glRaytracingCmd.blasCmdList = g_glRaytracingCmd.blasCmdListRing[0];
|
|
g_glRaytracingCmd.tlasCmdAlloc = g_glRaytracingCmd.tlasCmdAllocRing[0];
|
|
g_glRaytracingCmd.tlasCmdList = g_glRaytracingCmd.tlasCmdListRing[0];
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateFence(
|
|
0,
|
|
D3D12_FENCE_FLAG_NONE,
|
|
IID_PPV_ARGS(&g_glRaytracingCmd.fence)));
|
|
|
|
g_glRaytracingCmd.fenceEvent = CreateEventA(nullptr, FALSE, FALSE, nullptr);
|
|
if (!g_glRaytracingCmd.fenceEvent)
|
|
{
|
|
glRaytracingFatal("CreateEventA failed");
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingCreatePathTracingTimestamps())
|
|
{
|
|
glRaytracingLog("path tracing timestamp queries unavailable");
|
|
}
|
|
|
|
g_glRaytracingCmd.initialized = true;
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingShutdownCmdContext(void)
|
|
{
|
|
if (!g_glRaytracingCmd.initialized)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
|
|
if (g_glRaytracingCmd.fenceEvent)
|
|
{
|
|
CloseHandle(g_glRaytracingCmd.fenceEvent);
|
|
g_glRaytracingCmd.fenceEvent = nullptr;
|
|
}
|
|
|
|
g_glRaytracingCmd = glRaytracingCmdContext_t();
|
|
}
|
|
|
|
static int glRaytracingBeginCmd(void)
|
|
{
|
|
const UINT slot = (g_glRaytracingCmd.cmdRingIndex + 1u) % GL_RAYTRACING_CMD_RING_SIZE;
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.cmdFenceValueRing[slot]);
|
|
glRaytracingPollPathTracingTimestamp(slot);
|
|
|
|
g_glRaytracingCmd.cmdRingIndex = slot;
|
|
g_glRaytracingCmd.cmdCurrentSlot = slot;
|
|
g_glRaytracingCmd.cmdAlloc = g_glRaytracingCmd.cmdAllocRing[slot];
|
|
g_glRaytracingCmd.cmdList = g_glRaytracingCmd.cmdListRing[slot];
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.cmdAlloc->Reset());
|
|
GLR_CHECK(g_glRaytracingCmd.cmdList->Reset(g_glRaytracingCmd.cmdAlloc.Get(), nullptr));
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingEndCmd(void)
|
|
{
|
|
GLR_CHECK(g_glRaytracingCmd.cmdList->Close());
|
|
|
|
ID3D12CommandList* lists[] = { g_glRaytracingCmd.cmdList.Get() };
|
|
g_glRaytracingCmd.queue->ExecuteCommandLists(1, lists);
|
|
|
|
g_glRaytracingCmd.cmdLastFenceValue = glRaytracingSignalQueue();
|
|
g_glRaytracingCmd.cmdFenceValueRing[g_glRaytracingCmd.cmdCurrentSlot] = g_glRaytracingCmd.cmdLastFenceValue;
|
|
|
|
#if GL_RAYTRACING_FORCE_CPU_SYNC
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.cmdLastFenceValue);
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingBeginBlasCmd(void)
|
|
{
|
|
const UINT slot = (g_glRaytracingCmd.blasRingIndex + 1u) % GL_RAYTRACING_CMD_RING_SIZE;
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.blasFenceValueRing[slot]);
|
|
|
|
g_glRaytracingCmd.blasRingIndex = slot;
|
|
g_glRaytracingCmd.blasCurrentSlot = slot;
|
|
g_glRaytracingCmd.blasCmdAlloc = g_glRaytracingCmd.blasCmdAllocRing[slot];
|
|
g_glRaytracingCmd.blasCmdList = g_glRaytracingCmd.blasCmdListRing[slot];
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.blasCmdAlloc->Reset());
|
|
GLR_CHECK(g_glRaytracingCmd.blasCmdList->Reset(g_glRaytracingCmd.blasCmdAlloc.Get(), nullptr));
|
|
return 1;
|
|
}
|
|
|
|
static UINT64 glRaytracingEndBlasCmd(void)
|
|
{
|
|
GLR_CHECK(g_glRaytracingCmd.blasCmdList->Close());
|
|
|
|
ID3D12CommandList* lists[] = { g_glRaytracingCmd.blasCmdList.Get() };
|
|
g_glRaytracingCmd.queue->ExecuteCommandLists(1, lists);
|
|
|
|
g_glRaytracingCmd.blasLastFenceValue = glRaytracingSignalQueue();
|
|
g_glRaytracingCmd.blasFenceValueRing[g_glRaytracingCmd.blasCurrentSlot] = g_glRaytracingCmd.blasLastFenceValue;
|
|
|
|
#if GL_RAYTRACING_FORCE_CPU_SYNC
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.blasLastFenceValue);
|
|
#endif
|
|
|
|
return g_glRaytracingCmd.blasLastFenceValue;
|
|
}
|
|
|
|
static int glRaytracingBeginTlasCmd(void)
|
|
{
|
|
const UINT slot = (g_glRaytracingCmd.tlasRingIndex + 1u) % GL_RAYTRACING_CMD_RING_SIZE;
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.tlasFenceValueRing[slot]);
|
|
|
|
g_glRaytracingCmd.tlasRingIndex = slot;
|
|
g_glRaytracingCmd.tlasCurrentSlot = slot;
|
|
g_glRaytracingCmd.tlasCmdAlloc = g_glRaytracingCmd.tlasCmdAllocRing[slot];
|
|
g_glRaytracingCmd.tlasCmdList = g_glRaytracingCmd.tlasCmdListRing[slot];
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.tlasCmdAlloc->Reset());
|
|
GLR_CHECK(g_glRaytracingCmd.tlasCmdList->Reset(g_glRaytracingCmd.tlasCmdAlloc.Get(), nullptr));
|
|
return 1;
|
|
}
|
|
|
|
static UINT64 glRaytracingEndTlasCmd(void)
|
|
{
|
|
GLR_CHECK(g_glRaytracingCmd.tlasCmdList->Close());
|
|
|
|
ID3D12CommandList* lists[] = { g_glRaytracingCmd.tlasCmdList.Get() };
|
|
g_glRaytracingCmd.queue->ExecuteCommandLists(1, lists);
|
|
|
|
g_glRaytracingCmd.tlasLastFenceValue = glRaytracingSignalQueue();
|
|
g_glRaytracingCmd.tlasFenceValueRing[g_glRaytracingCmd.tlasCurrentSlot] = g_glRaytracingCmd.tlasLastFenceValue;
|
|
|
|
#if GL_RAYTRACING_FORCE_CPU_SYNC
|
|
glRaytracingWaitFenceValue(g_glRaytracingCmd.tlasLastFenceValue);
|
|
#endif
|
|
|
|
return g_glRaytracingCmd.tlasLastFenceValue;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Scene builder state
|
|
// ============================================================
|
|
|
|
#ifndef GL_RAYTRACING_MAX_RENDER_WORLDS
|
|
#define GL_RAYTRACING_MAX_RENDER_WORLDS 24
|
|
#endif
|
|
|
|
#ifndef GL_RAYTRACING_SCENE_HANDLE_T_DEFINED
|
|
typedef uint32_t glRaytracingSceneHandle_t;
|
|
#define GL_RAYTRACING_SCENE_HANDLE_T_DEFINED
|
|
#endif
|
|
|
|
// Internal material metadata encoded into D3D12's 24-bit shader-visible
|
|
// InstanceID. Lower 16 bits remain caller/user ID; bits 16-23 are material flags.
|
|
static const uint32_t GL_RAYTRACING_INSTANCE_USER_ID_MASK = 0x0000FFFFu;
|
|
static const uint32_t GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT = 16u;
|
|
static const uint32_t GL_RAYTRACING_INSTANCE_MATERIAL_MASK = 0x000000FFu;
|
|
|
|
struct glRaytracingMeshRecord_t
|
|
{
|
|
uint32_t handle;
|
|
int alive;
|
|
|
|
glRaytracingMeshDesc_t descCpu;
|
|
|
|
std::vector<glRaytracingVertex_t> verticesCpu;
|
|
std::vector<uint32_t> indicesCpu;
|
|
|
|
glRaytracingBuffer_t vertexBuffer;
|
|
glRaytracingBuffer_t indexBuffer;
|
|
|
|
glRaytracingBuffer_t blasScratch;
|
|
glRaytracingBuffer_t blasResult[2];
|
|
|
|
UINT64 blasScratchSize;
|
|
UINT64 blasResultSize;
|
|
|
|
int blasBuilt;
|
|
int dirty;
|
|
UINT64 blasBuildFenceValue;
|
|
int currentBlasIndex;
|
|
uint32_t materialFlags;
|
|
uint32_t procFlags;
|
|
int32_t areaNum;
|
|
uint32_t materialVertexOffset;
|
|
uint32_t materialIndexOffset;
|
|
uint32_t materialVertexCount;
|
|
uint32_t materialIndexCount;
|
|
float averageColor[4];
|
|
float emissiveColor[4];
|
|
|
|
glRaytracingMeshRecord_t()
|
|
{
|
|
handle = 0;
|
|
alive = 0;
|
|
memset(&descCpu, 0, sizeof(descCpu));
|
|
blasScratchSize = 0;
|
|
blasResultSize = 0;
|
|
blasBuilt = 0;
|
|
dirty = 0;
|
|
blasBuildFenceValue = 0;
|
|
currentBlasIndex = 0;
|
|
materialFlags = 0;
|
|
procFlags = 0;
|
|
areaNum = -1;
|
|
materialVertexOffset = 0;
|
|
materialIndexOffset = 0;
|
|
materialVertexCount = 0;
|
|
materialIndexCount = 0;
|
|
averageColor[0] = averageColor[1] = averageColor[2] = averageColor[3] = 1.0f;
|
|
emissiveColor[0] = emissiveColor[1] = emissiveColor[2] = emissiveColor[3] = 0.0f;
|
|
}
|
|
};
|
|
|
|
struct glRaytracingInstanceRecord_t
|
|
{
|
|
uint32_t handle;
|
|
int alive;
|
|
glRaytracingInstanceDesc_t descCpu;
|
|
int dirty;
|
|
int cachedActive;
|
|
D3D12_GPU_VIRTUAL_ADDRESS cachedBlasGpuVA;
|
|
D3D12_RAYTRACING_INSTANCE_DESC cachedDescCpu;
|
|
|
|
glRaytracingInstanceRecord_t()
|
|
{
|
|
handle = 0;
|
|
alive = 0;
|
|
memset(&descCpu, 0, sizeof(descCpu));
|
|
dirty = 0;
|
|
cachedActive = 0;
|
|
cachedBlasGpuVA = 0;
|
|
memset(&cachedDescCpu, 0, sizeof(cachedDescCpu));
|
|
}
|
|
};
|
|
|
|
struct glRaytracingSceneUploadBuffer_t
|
|
{
|
|
glRaytracingBuffer_t buffer;
|
|
UINT64 capacityBytes;
|
|
D3D12_RAYTRACING_INSTANCE_DESC* mapped;
|
|
|
|
glRaytracingSceneUploadBuffer_t()
|
|
{
|
|
capacityBytes = 0;
|
|
mapped = nullptr;
|
|
}
|
|
};
|
|
|
|
// One render world owns exactly one TLAS pair and its own list of geometry
|
|
// instances. Mesh/BLAS resources stay shared across all worlds.
|
|
struct glRaytracingRenderWorld_t
|
|
{
|
|
uint32_t handle;
|
|
int alive;
|
|
|
|
std::vector<glRaytracingInstanceRecord_t> instances;
|
|
std::vector<int> activeInstanceIndices;
|
|
std::vector<D3D12_RAYTRACING_INSTANCE_DESC> cpuInstanceDescs;
|
|
std::vector<int> instanceHandleToIndex;
|
|
|
|
uint32_t nextInstanceHandle;
|
|
|
|
glRaytracingSceneUploadBuffer_t instanceDescUpload[2];
|
|
UINT64 instanceDescUploadFenceValue[2];
|
|
glRaytracingBuffer_t tlasScratch;
|
|
glRaytracingBuffer_t tlasResult[2];
|
|
|
|
UINT64 tlasScratchSize;
|
|
UINT64 tlasResultSize;
|
|
|
|
UINT activeInstanceCount;
|
|
UINT builtInstanceCount;
|
|
|
|
int tlasBuilt;
|
|
int tlasNeedsRebuild;
|
|
int tlasNeedsUpdate;
|
|
int currentTLASIndex;
|
|
|
|
glRaytracingRenderWorld_t()
|
|
{
|
|
handle = 0;
|
|
alive = 0;
|
|
nextInstanceHandle = 1;
|
|
instanceDescUploadFenceValue[0] = 0;
|
|
instanceDescUploadFenceValue[1] = 0;
|
|
tlasScratchSize = 0;
|
|
tlasResultSize = 0;
|
|
activeInstanceCount = 0;
|
|
builtInstanceCount = 0;
|
|
tlasBuilt = 0;
|
|
tlasNeedsRebuild = 1;
|
|
tlasNeedsUpdate = 1;
|
|
currentTLASIndex = 0;
|
|
}
|
|
};
|
|
|
|
struct glRaytracingSceneState_t
|
|
{
|
|
std::vector<glRaytracingMeshRecord_t> meshes;
|
|
std::vector<int> meshHandleToIndex;
|
|
|
|
uint32_t nextMeshHandle;
|
|
|
|
glRaytracingRenderWorld_t worlds[GL_RAYTRACING_MAX_RENDER_WORLDS];
|
|
|
|
int initialized;
|
|
|
|
glRaytracingSceneState_t()
|
|
{
|
|
nextMeshHandle = 1;
|
|
initialized = 0;
|
|
}
|
|
};
|
|
|
|
static glRaytracingSceneState_t g_glRaytracingScene;
|
|
|
|
void glRaytracingClear(void);
|
|
static void glRaytracingLightingResetDenoiseHistory(void);
|
|
|
|
static void glRaytracingReleaseWorldResources(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world)
|
|
return;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
if (world->instanceDescUpload[i].buffer.resource && world->instanceDescUpload[i].mapped)
|
|
world->instanceDescUpload[i].buffer.resource->Unmap(0, nullptr);
|
|
|
|
world->instanceDescUpload[i] = glRaytracingSceneUploadBuffer_t();
|
|
world->tlasResult[i].resource.Reset();
|
|
}
|
|
|
|
world->instanceDescUploadFenceValue[0] = 0;
|
|
world->instanceDescUploadFenceValue[1] = 0;
|
|
world->tlasScratch.resource.Reset();
|
|
world->tlasScratchSize = 0;
|
|
world->tlasResultSize = 0;
|
|
}
|
|
|
|
static void glRaytracingResetWorldSlot(glRaytracingRenderWorld_t* world, uint32_t handle, int alive)
|
|
{
|
|
if (!world)
|
|
return;
|
|
|
|
glRaytracingReleaseWorldResources(world);
|
|
*world = glRaytracingRenderWorld_t();
|
|
world->handle = handle;
|
|
world->alive = alive ? 1 : 0;
|
|
world->nextInstanceHandle = 1;
|
|
}
|
|
|
|
static int glRaytracingWorldHandleToSlot(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
if (worldHandle == 0 || worldHandle > GL_RAYTRACING_MAX_RENDER_WORLDS)
|
|
return -1;
|
|
return (int)(worldHandle - 1);
|
|
}
|
|
|
|
static glRaytracingRenderWorld_t* glRaytracingFindWorld(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
const int slot = glRaytracingWorldHandleToSlot(worldHandle);
|
|
if (slot < 0)
|
|
return nullptr;
|
|
|
|
glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[slot];
|
|
if (!world.alive || world.handle != worldHandle)
|
|
return nullptr;
|
|
|
|
return &world;
|
|
}
|
|
|
|
static const glRaytracingRenderWorld_t* glRaytracingFindWorldConst(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
const int slot = glRaytracingWorldHandleToSlot(worldHandle);
|
|
if (slot < 0)
|
|
return nullptr;
|
|
|
|
const glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[slot];
|
|
if (!world.alive || world.handle != worldHandle)
|
|
return nullptr;
|
|
|
|
return &world;
|
|
}
|
|
|
|
static void glRaytracingClearWorldContents(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world)
|
|
return;
|
|
|
|
const uint32_t handle = world->handle;
|
|
const int alive = world->alive;
|
|
|
|
glRaytracingReleaseWorldResources(world);
|
|
|
|
world->instances.clear();
|
|
world->activeInstanceIndices.clear();
|
|
world->cpuInstanceDescs.clear();
|
|
world->instanceHandleToIndex.clear();
|
|
|
|
world->handle = handle;
|
|
world->alive = alive;
|
|
world->nextInstanceHandle = 1;
|
|
world->instanceDescUploadFenceValue[0] = 0;
|
|
world->instanceDescUploadFenceValue[1] = 0;
|
|
world->tlasScratchSize = 0;
|
|
world->tlasResultSize = 0;
|
|
world->activeInstanceCount = 0;
|
|
world->builtInstanceCount = 0;
|
|
world->tlasBuilt = 0;
|
|
world->tlasNeedsRebuild = 1;
|
|
world->tlasNeedsUpdate = 1;
|
|
world->currentTLASIndex = 0;
|
|
}
|
|
|
|
static void glRaytracingClearAllSceneStateInternal(void)
|
|
{
|
|
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
glRaytracingReleaseWorldResources(&g_glRaytracingScene.worlds[i]);
|
|
|
|
const int wasInitialized = g_glRaytracingScene.initialized;
|
|
g_glRaytracingScene = glRaytracingSceneState_t();
|
|
g_glRaytracingScene.initialized = wasInitialized;
|
|
}
|
|
|
|
static void glRaytracingMarkWorldNeedsRebuild(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world || !world->alive)
|
|
return;
|
|
|
|
world->tlasNeedsRebuild = 1;
|
|
world->tlasNeedsUpdate = 0;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
static void glRaytracingMarkWorldNeedsUpdate(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world || !world->alive)
|
|
return;
|
|
|
|
if (!world->tlasNeedsRebuild)
|
|
world->tlasNeedsUpdate = 1;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
static void glRaytracingMarkAllWorldsNeedRebuild(void)
|
|
{
|
|
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
{
|
|
if (g_glRaytracingScene.worlds[i].alive)
|
|
glRaytracingMarkWorldNeedsRebuild(&g_glRaytracingScene.worlds[i]);
|
|
}
|
|
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
static void glRaytracingCopyMeshAverageColor(float outAverageColor[4], const glRaytracingMeshDesc_t* desc)
|
|
{
|
|
if (!outAverageColor)
|
|
return;
|
|
|
|
outAverageColor[0] = outAverageColor[1] = outAverageColor[2] = outAverageColor[3] = 1.0f;
|
|
if (!desc)
|
|
return;
|
|
|
|
const bool hasExplicitAverage =
|
|
desc->averageColor[0] != 0.0f ||
|
|
desc->averageColor[1] != 0.0f ||
|
|
desc->averageColor[2] != 0.0f ||
|
|
desc->averageColor[3] != 0.0f;
|
|
if (!hasExplicitAverage)
|
|
return;
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
outAverageColor[i] = glRaytracingClamp<float>(desc->averageColor[i], 0.0f, 1.0f);
|
|
}
|
|
|
|
static uint32_t glRaytracingCountAliveInstances(const glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world)
|
|
return 0;
|
|
|
|
uint32_t count = 0;
|
|
for (size_t i = 0; i < world->instances.size(); ++i)
|
|
{
|
|
if (world->instances[i].alive)
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void glRaytracingEnsureMeshHandleTable(uint32_t handle)
|
|
{
|
|
if (handle >= g_glRaytracingScene.meshHandleToIndex.size())
|
|
g_glRaytracingScene.meshHandleToIndex.resize((size_t)handle + 1, -1);
|
|
}
|
|
|
|
static void glRaytracingEnsureInstanceHandleTable(glRaytracingRenderWorld_t* world, uint32_t handle)
|
|
{
|
|
if (!world)
|
|
return;
|
|
|
|
if (handle >= world->instanceHandleToIndex.size())
|
|
world->instanceHandleToIndex.resize((size_t)handle + 1, -1);
|
|
}
|
|
|
|
static glRaytracingBuffer_t* glRaytracingGetMeshCurrentBLAS(glRaytracingMeshRecord_t* mesh)
|
|
{
|
|
if (!mesh)
|
|
return nullptr;
|
|
return &mesh->blasResult[mesh->currentBlasIndex & 1];
|
|
}
|
|
|
|
static const glRaytracingBuffer_t* glRaytracingGetMeshCurrentBLASConst(const glRaytracingMeshRecord_t* mesh)
|
|
{
|
|
if (!mesh)
|
|
return nullptr;
|
|
return &mesh->blasResult[mesh->currentBlasIndex & 1];
|
|
}
|
|
|
|
static int glRaytracingGetInactiveTLASIndex(const glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world)
|
|
return 0;
|
|
return world->currentTLASIndex ^ 1;
|
|
}
|
|
|
|
static glRaytracingSceneUploadBuffer_t* glRaytracingGetBuildInstanceUpload(glRaytracingRenderWorld_t* world)
|
|
{
|
|
return &world->instanceDescUpload[glRaytracingGetInactiveTLASIndex(world)];
|
|
}
|
|
|
|
static glRaytracingBuffer_t* glRaytracingGetCurrentTLASBuffer(glRaytracingRenderWorld_t* world)
|
|
{
|
|
return &world->tlasResult[world->currentTLASIndex & 1];
|
|
}
|
|
|
|
static const glRaytracingBuffer_t* glRaytracingGetCurrentTLASBufferConst(const glRaytracingRenderWorld_t* world)
|
|
{
|
|
return &world->tlasResult[world->currentTLASIndex & 1];
|
|
}
|
|
|
|
static glRaytracingBuffer_t* glRaytracingGetBuildTLASBuffer(glRaytracingRenderWorld_t* world)
|
|
{
|
|
return &world->tlasResult[glRaytracingGetInactiveTLASIndex(world)];
|
|
}
|
|
|
|
static int glRaytracingEnsureTLASBuffers(
|
|
glRaytracingRenderWorld_t* world,
|
|
const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS* inputs)
|
|
{
|
|
if (!world)
|
|
return 0;
|
|
|
|
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO prebuild = {};
|
|
g_glRaytracingCmd.device->GetRaytracingAccelerationStructurePrebuildInfo(inputs, &prebuild);
|
|
|
|
if (prebuild.ResultDataMaxSizeInBytes == 0)
|
|
{
|
|
glRaytracingFatal("TLAS prebuild size is zero");
|
|
return 0;
|
|
}
|
|
|
|
const UINT64 requiredScratch = glRaytracingAlignUp(
|
|
prebuild.ScratchDataSizeInBytes,
|
|
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT);
|
|
|
|
const UINT64 requiredResult = glRaytracingAlignUp(
|
|
prebuild.ResultDataMaxSizeInBytes,
|
|
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT);
|
|
|
|
const bool resizingScratch = world->tlasScratch.resource &&
|
|
world->tlasScratchSize < requiredScratch;
|
|
const bool resizingResult = (world->tlasResult[0].resource || world->tlasResult[1].resource) &&
|
|
world->tlasResultSize < requiredResult;
|
|
if (resizingScratch || resizingResult)
|
|
{
|
|
// Releasing/reallocating a TLAS resource can invalidate an in-flight ray
|
|
// dispatch that still references the old resource. This resize path is rare,
|
|
// so prefer correctness over complex deferred destruction.
|
|
glRaytracingWaitIdle();
|
|
}
|
|
|
|
if (!world->tlasScratch.resource ||
|
|
world->tlasScratchSize < requiredScratch)
|
|
{
|
|
world->tlasScratch.resource.Reset();
|
|
world->tlasScratch = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
requiredScratch,
|
|
D3D12_HEAP_TYPE_DEFAULT,
|
|
D3D12_RESOURCE_STATE_COMMON,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
|
|
if (!world->tlasScratch.resource)
|
|
return 0;
|
|
|
|
world->tlasScratchSize = requiredScratch;
|
|
}
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
if (!world->tlasResult[i].resource ||
|
|
world->tlasResultSize < requiredResult)
|
|
{
|
|
world->tlasResult[i].resource.Reset();
|
|
world->tlasResult[i] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
requiredResult,
|
|
D3D12_HEAP_TYPE_DEFAULT,
|
|
D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
|
|
if (!world->tlasResult[i].resource)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
world->tlasResultSize = requiredResult;
|
|
return 1;
|
|
}
|
|
|
|
static glRaytracingMeshRecord_t* glRaytracingFindMesh(uint32_t handle)
|
|
{
|
|
if (handle == 0 || handle >= g_glRaytracingScene.meshHandleToIndex.size())
|
|
return nullptr;
|
|
|
|
const int index = g_glRaytracingScene.meshHandleToIndex[handle];
|
|
if (index < 0 || (size_t)index >= g_glRaytracingScene.meshes.size())
|
|
return nullptr;
|
|
|
|
glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[(size_t)index];
|
|
if (!mesh.alive || mesh.handle != handle)
|
|
return nullptr;
|
|
|
|
return &mesh;
|
|
}
|
|
|
|
static const glRaytracingMeshRecord_t* glRaytracingFindMeshConst(uint32_t handle)
|
|
{
|
|
if (handle == 0 || handle >= g_glRaytracingScene.meshHandleToIndex.size())
|
|
return nullptr;
|
|
|
|
const int index = g_glRaytracingScene.meshHandleToIndex[handle];
|
|
if (index < 0 || (size_t)index >= g_glRaytracingScene.meshes.size())
|
|
return nullptr;
|
|
|
|
const glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[(size_t)index];
|
|
if (!mesh.alive || mesh.handle != handle)
|
|
return nullptr;
|
|
|
|
return &mesh;
|
|
}
|
|
|
|
static glRaytracingInstanceRecord_t* glRaytracingFindInstance(glRaytracingRenderWorld_t* world, uint32_t handle)
|
|
{
|
|
if (!world || handle == 0 || handle >= world->instanceHandleToIndex.size())
|
|
return nullptr;
|
|
|
|
const int index = world->instanceHandleToIndex[handle];
|
|
if (index < 0 || (size_t)index >= world->instances.size())
|
|
return nullptr;
|
|
|
|
glRaytracingInstanceRecord_t& inst = world->instances[(size_t)index];
|
|
if (!inst.alive || inst.handle != handle)
|
|
return nullptr;
|
|
|
|
return &inst;
|
|
}
|
|
|
|
static const glRaytracingInstanceRecord_t* glRaytracingFindInstanceConst(const glRaytracingRenderWorld_t* world, uint32_t handle)
|
|
{
|
|
if (!world || handle == 0 || handle >= world->instanceHandleToIndex.size())
|
|
return nullptr;
|
|
|
|
const int index = world->instanceHandleToIndex[handle];
|
|
if (index < 0 || (size_t)index >= world->instances.size())
|
|
return nullptr;
|
|
|
|
const glRaytracingInstanceRecord_t& inst = world->instances[(size_t)index];
|
|
if (!inst.alive || inst.handle != handle)
|
|
return nullptr;
|
|
|
|
return &inst;
|
|
}
|
|
|
|
static int glRaytracingEnsureMeshScratch(glRaytracingMeshRecord_t* mesh, UINT64 requiredScratch)
|
|
{
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
requiredScratch = glRaytracingAlignUp(requiredScratch, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT);
|
|
|
|
if (!mesh->blasScratch.resource || mesh->blasScratchSize < requiredScratch)
|
|
{
|
|
glRaytracingWaitFenceValue(mesh->blasBuildFenceValue);
|
|
mesh->blasScratch.resource.Reset();
|
|
mesh->blasScratch = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
requiredScratch,
|
|
D3D12_HEAP_TYPE_DEFAULT,
|
|
D3D12_RESOURCE_STATE_COMMON,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
|
|
if (!mesh->blasScratch.resource)
|
|
return 0;
|
|
|
|
mesh->blasScratchSize = requiredScratch;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingEnsureMeshResultBuffers(glRaytracingMeshRecord_t* mesh, UINT64 requiredResult)
|
|
{
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
requiredResult = glRaytracingAlignUp(requiredResult, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT);
|
|
|
|
const int resultCount = mesh->descCpu.allowUpdate ? 2 : 1;
|
|
for (int i = 0; i < resultCount; ++i)
|
|
{
|
|
if (!mesh->blasResult[i].resource || mesh->blasResultSize < requiredResult)
|
|
{
|
|
glRaytracingWaitFenceValue(mesh->blasBuildFenceValue);
|
|
mesh->blasResult[i].resource.Reset();
|
|
mesh->blasResult[i] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
requiredResult,
|
|
D3D12_HEAP_TYPE_DEFAULT,
|
|
D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
|
|
if (!mesh->blasResult[i].resource)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!mesh->descCpu.allowUpdate && mesh->blasResult[1].resource)
|
|
{
|
|
glRaytracingWaitFenceValue(mesh->blasBuildFenceValue);
|
|
mesh->blasResult[1].resource.Reset();
|
|
}
|
|
|
|
mesh->blasResultSize = requiredResult;
|
|
return 1;
|
|
}
|
|
|
|
static inline void glRaytracingBuildInstanceDesc(
|
|
D3D12_RAYTRACING_INSTANCE_DESC* outDesc,
|
|
const glRaytracingInstanceRecord_t& inst,
|
|
uint32_t meshHandle,
|
|
uint32_t meshMaterialFlags,
|
|
D3D12_GPU_VIRTUAL_ADDRESS blasGpuVA)
|
|
{
|
|
memcpy(outDesc->Transform, inst.descCpu.transform, sizeof(float) * 12);
|
|
|
|
// InstanceID is 24-bit in D3D12_RAYTRACING_INSTANCE_DESC. The lower 16 bits
|
|
// carry the mesh metadata index, and bits 16-23 carry material flags so the
|
|
// any-hit shader can decide whether visibility rays pass through the hit.
|
|
//
|
|
// Accept both sources:
|
|
// - meshMaterialFlags set by glRaytracingSetMeshMaterialFlags()/shim mesh tags
|
|
// - already-encoded high InstanceID bits for direct low-level callers
|
|
const uint32_t userInstanceId = meshHandle & GL_RAYTRACING_INSTANCE_USER_ID_MASK;
|
|
const uint32_t instanceMaterialFlags =
|
|
(inst.descCpu.instanceID >> GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT) & GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
const uint32_t combinedMaterialFlags =
|
|
(meshMaterialFlags | instanceMaterialFlags) & GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
const uint32_t materialBits = combinedMaterialFlags << GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT;
|
|
outDesc->InstanceID = userInstanceId | materialBits;
|
|
outDesc->InstanceMask = (UINT8)(inst.descCpu.mask & 0xFFu);
|
|
outDesc->InstanceContributionToHitGroupIndex = 0;
|
|
outDesc->Flags = D3D12_RAYTRACING_INSTANCE_FLAG_NONE;
|
|
outDesc->AccelerationStructure = blasGpuVA;
|
|
}
|
|
|
|
static void glRaytracingInvalidateInstanceCache(glRaytracingInstanceRecord_t* inst)
|
|
{
|
|
if (!inst)
|
|
return;
|
|
|
|
inst->cachedActive = 0;
|
|
inst->cachedBlasGpuVA = 0;
|
|
memset(&inst->cachedDescCpu, 0, sizeof(inst->cachedDescCpu));
|
|
}
|
|
|
|
static int glRaytracingResolveInstanceDesc(
|
|
glRaytracingInstanceRecord_t* inst,
|
|
D3D12_RAYTRACING_INSTANCE_DESC* outDesc,
|
|
D3D12_GPU_VIRTUAL_ADDRESS* outBlasGpuVA)
|
|
{
|
|
if (!inst || !inst->alive)
|
|
return 0;
|
|
|
|
const glRaytracingMeshRecord_t* mesh = glRaytracingFindMeshConst(inst->descCpu.meshHandle);
|
|
if (!mesh || !mesh->blasBuilt)
|
|
return 0;
|
|
|
|
const glRaytracingBuffer_t* blas = glRaytracingGetMeshCurrentBLASConst(mesh);
|
|
if (!blas || !blas->resource || blas->gpuVA == 0)
|
|
return 0;
|
|
|
|
if (outDesc)
|
|
glRaytracingBuildInstanceDesc(outDesc, *inst, mesh->handle, mesh->materialFlags, blas->gpuVA);
|
|
if (outBlasGpuVA)
|
|
*outBlasGpuVA = blas->gpuVA;
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingRebuildActiveInstanceCache(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world)
|
|
return 0;
|
|
|
|
world->activeInstanceIndices.clear();
|
|
world->cpuInstanceDescs.clear();
|
|
world->activeInstanceIndices.reserve(world->instances.size());
|
|
world->cpuInstanceDescs.reserve(world->instances.size());
|
|
|
|
for (size_t i = 0; i < world->instances.size(); ++i)
|
|
{
|
|
glRaytracingInstanceRecord_t& inst = world->instances[i];
|
|
glRaytracingInvalidateInstanceCache(&inst);
|
|
|
|
if (!inst.alive)
|
|
continue;
|
|
|
|
D3D12_RAYTRACING_INSTANCE_DESC desc = {};
|
|
D3D12_GPU_VIRTUAL_ADDRESS blasGpuVA = 0;
|
|
if (!glRaytracingResolveInstanceDesc(&inst, &desc, &blasGpuVA))
|
|
continue;
|
|
|
|
inst.cachedActive = 1;
|
|
inst.cachedBlasGpuVA = blasGpuVA;
|
|
inst.cachedDescCpu = desc;
|
|
inst.dirty = 0;
|
|
|
|
world->activeInstanceIndices.push_back((int)i);
|
|
world->cpuInstanceDescs.push_back(desc);
|
|
}
|
|
|
|
world->activeInstanceCount = (UINT)world->cpuInstanceDescs.size();
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingRefreshDirtyInstanceCache(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world)
|
|
return 0;
|
|
|
|
for (size_t listIndex = 0; listIndex < world->activeInstanceIndices.size(); ++listIndex)
|
|
{
|
|
const int instIndex = world->activeInstanceIndices[listIndex];
|
|
if (instIndex < 0 || (size_t)instIndex >= world->instances.size())
|
|
return 0;
|
|
|
|
glRaytracingInstanceRecord_t& inst = world->instances[(size_t)instIndex];
|
|
if (!inst.alive)
|
|
return 0;
|
|
|
|
D3D12_RAYTRACING_INSTANCE_DESC desc = {};
|
|
D3D12_GPU_VIRTUAL_ADDRESS blasGpuVA = 0;
|
|
if (!glRaytracingResolveInstanceDesc(&inst, &desc, &blasGpuVA))
|
|
return 0;
|
|
|
|
if (inst.dirty || !inst.cachedActive || inst.cachedBlasGpuVA != blasGpuVA)
|
|
{
|
|
inst.cachedActive = 1;
|
|
inst.cachedBlasGpuVA = blasGpuVA;
|
|
inst.cachedDescCpu = desc;
|
|
world->cpuInstanceDescs[listIndex] = desc;
|
|
}
|
|
|
|
inst.dirty = 0;
|
|
}
|
|
|
|
world->activeInstanceCount = (UINT)world->cpuInstanceDescs.size();
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingEnsureSceneUploadBuffer(glRaytracingRenderWorld_t* world, UINT64 requiredBytes);
|
|
|
|
static int glRaytracingUploadCachedInstanceDescs(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world)
|
|
return 0;
|
|
|
|
const UINT activeCount = (UINT)world->cpuInstanceDescs.size();
|
|
const UINT64 instBytes = glRaytracingAlignUp(
|
|
(UINT64)activeCount * (UINT64)sizeof(D3D12_RAYTRACING_INSTANCE_DESC),
|
|
D3D12_RAYTRACING_INSTANCE_DESCS_BYTE_ALIGNMENT);
|
|
|
|
const int uploadIndex = glRaytracingGetInactiveTLASIndex(world);
|
|
glRaytracingWaitFenceValue(world->instanceDescUploadFenceValue[uploadIndex]);
|
|
|
|
if (!glRaytracingEnsureSceneUploadBuffer(world, instBytes))
|
|
return 0;
|
|
|
|
glRaytracingSceneUploadBuffer_t* upload = glRaytracingGetBuildInstanceUpload(world);
|
|
if (!upload->mapped)
|
|
return 0;
|
|
|
|
if (activeCount > 0)
|
|
memcpy(upload->mapped, world->cpuInstanceDescs.data(), (size_t)activeCount * sizeof(D3D12_RAYTRACING_INSTANCE_DESC));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingUploadMeshBuffers(glRaytracingMeshRecord_t* mesh);
|
|
|
|
static int glRaytracingBuildDirtyMeshesInternal(void)
|
|
{
|
|
static thread_local std::vector<glRaytracingMeshRecord_t*> dirtyMeshes;
|
|
dirtyMeshes.clear();
|
|
dirtyMeshes.reserve(g_glRaytracingScene.meshes.size());
|
|
|
|
for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i)
|
|
{
|
|
glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[i];
|
|
if (!mesh.alive)
|
|
continue;
|
|
|
|
if (!mesh.blasBuilt || mesh.dirty)
|
|
dirtyMeshes.push_back(&mesh);
|
|
}
|
|
|
|
if (dirtyMeshes.empty())
|
|
return 1;
|
|
|
|
struct glRaytracingMeshBuildInfo_t
|
|
{
|
|
glRaytracingMeshRecord_t* mesh;
|
|
D3D12_RAYTRACING_GEOMETRY_DESC geomDesc;
|
|
D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS inputs;
|
|
D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildDesc;
|
|
ID3D12Resource* barrierResource;
|
|
int newBlasIndex;
|
|
};
|
|
|
|
static thread_local std::vector<glRaytracingMeshBuildInfo_t> builds;
|
|
builds.clear();
|
|
builds.resize(dirtyMeshes.size());
|
|
|
|
for (size_t i = 0; i < dirtyMeshes.size(); ++i)
|
|
{
|
|
glRaytracingMeshRecord_t* mesh = dirtyMeshes[i];
|
|
if (!mesh->vertexBuffer.resource || !mesh->indexBuffer.resource)
|
|
{
|
|
if (!glRaytracingUploadMeshBuffers(mesh))
|
|
return 0;
|
|
}
|
|
|
|
glRaytracingMeshBuildInfo_t& info = builds[i];
|
|
memset(&info, 0, sizeof(info));
|
|
info.mesh = mesh;
|
|
|
|
info.geomDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES;
|
|
const bool meshIsGlass = (mesh->materialFlags & GL_RAYTRACING_MATERIAL_FLAG_GLASS) != 0u;
|
|
info.geomDesc.Flags = (mesh->descCpu.opaque && !meshIsGlass)
|
|
? D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE
|
|
: D3D12_RAYTRACING_GEOMETRY_FLAG_NONE;
|
|
info.geomDesc.Triangles.Transform3x4 = 0;
|
|
info.geomDesc.Triangles.IndexFormat = DXGI_FORMAT_R32_UINT;
|
|
info.geomDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT;
|
|
info.geomDesc.Triangles.IndexCount = (UINT)mesh->indicesCpu.size();
|
|
info.geomDesc.Triangles.VertexCount = (UINT)mesh->verticesCpu.size();
|
|
info.geomDesc.Triangles.IndexBuffer = mesh->indexBuffer.gpuVA;
|
|
info.geomDesc.Triangles.VertexBuffer.StartAddress = mesh->vertexBuffer.gpuVA;
|
|
info.geomDesc.Triangles.VertexBuffer.StrideInBytes = sizeof(glRaytracingVertex_t);
|
|
|
|
info.inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL;
|
|
info.inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
|
|
info.inputs.NumDescs = 1;
|
|
info.inputs.pGeometryDescs = &info.geomDesc;
|
|
info.inputs.Flags = mesh->descCpu.allowUpdate
|
|
? (D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE |
|
|
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_ALLOW_UPDATE)
|
|
: D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE;
|
|
|
|
const bool canUpdateInPlace = (mesh->blasBuilt != 0) && (mesh->descCpu.allowUpdate != 0);
|
|
if (canUpdateInPlace)
|
|
info.inputs.Flags |= D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PERFORM_UPDATE;
|
|
|
|
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO prebuild = {};
|
|
g_glRaytracingCmd.device->GetRaytracingAccelerationStructurePrebuildInfo(&info.inputs, &prebuild);
|
|
if (prebuild.ResultDataMaxSizeInBytes == 0)
|
|
{
|
|
glRaytracingFatal("BLAS prebuild size is zero");
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingEnsureMeshScratch(mesh, prebuild.ScratchDataSizeInBytes))
|
|
return 0;
|
|
if (!glRaytracingEnsureMeshResultBuffers(mesh, prebuild.ResultDataMaxSizeInBytes))
|
|
return 0;
|
|
|
|
const int oldIndex = mesh->currentBlasIndex & 1;
|
|
info.newBlasIndex = (mesh->descCpu.allowUpdate && mesh->blasBuilt) ? (oldIndex ^ 1) : oldIndex;
|
|
|
|
info.buildDesc.Inputs = info.inputs;
|
|
info.buildDesc.ScratchAccelerationStructureData = mesh->blasScratch.gpuVA;
|
|
info.buildDesc.DestAccelerationStructureData = mesh->blasResult[info.newBlasIndex].gpuVA;
|
|
info.buildDesc.SourceAccelerationStructureData = 0;
|
|
|
|
if (canUpdateInPlace)
|
|
info.buildDesc.SourceAccelerationStructureData = mesh->blasResult[oldIndex].gpuVA;
|
|
|
|
info.barrierResource = mesh->blasResult[info.newBlasIndex].resource.Get();
|
|
}
|
|
|
|
if (!glRaytracingBeginBlasCmd())
|
|
return 0;
|
|
|
|
glRaytracingBeginPixEvent(g_glRaytracingCmd.blasCmdList.Get(), L"GLR Build Dirty Mesh BLAS Batch");
|
|
for (size_t i = 0; i < builds.size(); ++i)
|
|
{
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.blasCmdList.Get(), L"GLR Build BLAS");
|
|
g_glRaytracingCmd.blasCmdList->BuildRaytracingAccelerationStructure(&builds[i].buildDesc, 0, nullptr);
|
|
}
|
|
|
|
D3D12_RESOURCE_BARRIER uav = {};
|
|
uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
uav.UAV.pResource = builds[i].barrierResource;
|
|
g_glRaytracingCmd.blasCmdList->ResourceBarrier(1, &uav);
|
|
}
|
|
glRaytracingEndPixEvent(g_glRaytracingCmd.blasCmdList.Get());
|
|
|
|
const UINT64 blasFenceValue = glRaytracingEndBlasCmd();
|
|
if (!blasFenceValue)
|
|
return 0;
|
|
|
|
// Do not block the CPU here. The BLAS build was submitted before any TLAS
|
|
// build/lighting work that consumes it, and the shared D3D12 queue preserves
|
|
// that order. Command allocator reuse is protected by the ring fence in
|
|
// glRaytracingBeginBlasCmd().
|
|
for (size_t i = 0; i < builds.size(); ++i)
|
|
{
|
|
glRaytracingMeshRecord_t* mesh = builds[i].mesh;
|
|
mesh->currentBlasIndex = builds[i].newBlasIndex;
|
|
mesh->blasBuildFenceValue = blasFenceValue;
|
|
mesh->blasBuilt = 1;
|
|
mesh->dirty = 0;
|
|
}
|
|
|
|
glRaytracingMarkAllWorldsNeedRebuild();
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingUploadMeshBuffers(glRaytracingMeshRecord_t* mesh)
|
|
{
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
if (mesh->verticesCpu.empty() || mesh->indicesCpu.empty())
|
|
return 0;
|
|
|
|
const UINT64 vbBytes = UINT64(mesh->verticesCpu.size()) * sizeof(glRaytracingVertex_t);
|
|
const UINT64 ibBytes = UINT64(mesh->indicesCpu.size()) * sizeof(uint32_t);
|
|
|
|
mesh->vertexBuffer = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
vbBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
mesh->indexBuffer = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
ibBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
if (!mesh->vertexBuffer.resource || !mesh->indexBuffer.resource)
|
|
return 0;
|
|
|
|
glRaytracingMapCopy(mesh->vertexBuffer.resource.Get(), mesh->verticesCpu.data(), (size_t)vbBytes);
|
|
glRaytracingMapCopy(mesh->indexBuffer.resource.Get(), mesh->indicesCpu.data(), (size_t)ibBytes);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingBuildMeshInternal(glRaytracingMeshRecord_t* mesh)
|
|
{
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
const int oldDirty = mesh->dirty;
|
|
mesh->dirty = 1;
|
|
const int ok = glRaytracingBuildDirtyMeshesInternal();
|
|
if (!ok)
|
|
mesh->dirty = oldDirty;
|
|
return ok;
|
|
}
|
|
|
|
static int glRaytracingEnsureSceneUploadBuffer(glRaytracingRenderWorld_t* world, UINT64 requiredBytes)
|
|
{
|
|
if (!world)
|
|
return 0;
|
|
|
|
glRaytracingSceneUploadBuffer_t* upload = glRaytracingGetBuildInstanceUpload(world);
|
|
|
|
if (requiredBytes == 0)
|
|
requiredBytes = D3D12_RAYTRACING_INSTANCE_DESCS_BYTE_ALIGNMENT;
|
|
|
|
requiredBytes = glRaytracingAlignUp(
|
|
requiredBytes,
|
|
D3D12_RAYTRACING_INSTANCE_DESCS_BYTE_ALIGNMENT);
|
|
|
|
if (upload->buffer.resource &&
|
|
upload->capacityBytes >= requiredBytes &&
|
|
upload->mapped)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (upload->buffer.resource && upload->mapped)
|
|
upload->buffer.resource->Unmap(0, nullptr);
|
|
|
|
upload->mapped = nullptr;
|
|
upload->buffer.resource.Reset();
|
|
upload->capacityBytes = 0;
|
|
|
|
upload->buffer = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
requiredBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
if (!upload->buffer.resource)
|
|
return 0;
|
|
|
|
void* mapped = nullptr;
|
|
D3D12_RANGE readRange = {};
|
|
if (FAILED(upload->buffer.resource->Map(0, &readRange, &mapped)) || !mapped)
|
|
{
|
|
upload->buffer.resource.Reset();
|
|
return 0;
|
|
}
|
|
|
|
upload->mapped = (D3D12_RAYTRACING_INSTANCE_DESC*)mapped;
|
|
upload->capacityBytes = requiredBytes;
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingBuildSceneInternal(glRaytracingRenderWorld_t* world)
|
|
{
|
|
if (!world || !world->alive)
|
|
return 0;
|
|
|
|
UINT aliveCount = 0;
|
|
int anyDirty = 0;
|
|
int needsRebuild = world->tlasNeedsRebuild;
|
|
int needsUpdate = world->tlasNeedsUpdate;
|
|
|
|
for (size_t i = 0; i < world->instances.size(); ++i)
|
|
{
|
|
const glRaytracingInstanceRecord_t& inst = world->instances[i];
|
|
if (!inst.alive)
|
|
continue;
|
|
|
|
++aliveCount;
|
|
if (inst.dirty)
|
|
anyDirty = 1;
|
|
}
|
|
|
|
if (aliveCount == 0)
|
|
{
|
|
world->activeInstanceIndices.clear();
|
|
world->cpuInstanceDescs.clear();
|
|
world->activeInstanceCount = 0;
|
|
world->builtInstanceCount = 0;
|
|
world->tlasBuilt = 0;
|
|
world->tlasNeedsRebuild = 0;
|
|
world->tlasNeedsUpdate = 0;
|
|
return 1;
|
|
}
|
|
|
|
if (!world->tlasBuilt)
|
|
needsRebuild = 1;
|
|
|
|
if ((UINT)world->activeInstanceIndices.size() != world->builtInstanceCount)
|
|
needsRebuild = 1;
|
|
|
|
if (needsRebuild)
|
|
{
|
|
if (!glRaytracingRebuildActiveInstanceCache(world))
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (!needsUpdate && !anyDirty)
|
|
{
|
|
world->activeInstanceCount = (UINT)world->cpuInstanceDescs.size();
|
|
return 1;
|
|
}
|
|
|
|
if (!glRaytracingRefreshDirtyInstanceCache(world))
|
|
{
|
|
world->tlasNeedsRebuild = 1;
|
|
if (!glRaytracingRebuildActiveInstanceCache(world))
|
|
return 0;
|
|
needsRebuild = 1;
|
|
}
|
|
}
|
|
|
|
const UINT activeCount = (UINT)world->cpuInstanceDescs.size();
|
|
if (activeCount == 0)
|
|
{
|
|
world->activeInstanceCount = 0;
|
|
world->builtInstanceCount = 0;
|
|
world->tlasBuilt = 0;
|
|
world->tlasNeedsRebuild = 0;
|
|
world->tlasNeedsUpdate = 0;
|
|
return 1;
|
|
}
|
|
|
|
if (!world->tlasBuilt || activeCount != world->builtInstanceCount)
|
|
needsRebuild = 1;
|
|
|
|
if (!glRaytracingUploadCachedInstanceDescs(world))
|
|
return 0;
|
|
|
|
glRaytracingSceneUploadBuffer_t* upload = glRaytracingGetBuildInstanceUpload(world);
|
|
|
|
D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS inputs = {};
|
|
inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL;
|
|
inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
|
|
inputs.NumDescs = activeCount;
|
|
inputs.InstanceDescs = upload->buffer.gpuVA;
|
|
inputs.Flags =
|
|
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE |
|
|
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_ALLOW_UPDATE;
|
|
|
|
if (!glRaytracingEnsureTLASBuffers(world, &inputs))
|
|
return 0;
|
|
|
|
const int buildTLASIndex = glRaytracingGetInactiveTLASIndex(world);
|
|
glRaytracingBuffer_t* dstTLAS = glRaytracingGetBuildTLASBuffer(world);
|
|
const glRaytracingBuffer_t* srcTLAS = glRaytracingGetCurrentTLASBufferConst(world);
|
|
|
|
// The TLAS build is queued after any pending BLAS builds on the same D3D12
|
|
// queue, so GPU ordering is sufficient and a CPU wait would stall the frame.
|
|
if (!glRaytracingBeginTlasCmd())
|
|
return 0;
|
|
glRaytracingBeginPixEvent(g_glRaytracingCmd.tlasCmdList.Get(), needsRebuild ? L"GLR Build TLAS" : L"GLR Update TLAS");
|
|
|
|
D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildDesc = {};
|
|
buildDesc.Inputs = inputs;
|
|
buildDesc.ScratchAccelerationStructureData = world->tlasScratch.gpuVA;
|
|
buildDesc.DestAccelerationStructureData = dstTLAS->gpuVA;
|
|
buildDesc.SourceAccelerationStructureData = 0;
|
|
|
|
if (!needsRebuild && world->tlasBuilt)
|
|
{
|
|
buildDesc.Inputs.Flags |= D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PERFORM_UPDATE;
|
|
buildDesc.SourceAccelerationStructureData = srcTLAS->gpuVA;
|
|
}
|
|
|
|
g_glRaytracingCmd.tlasCmdList->BuildRaytracingAccelerationStructure(&buildDesc, 0, nullptr);
|
|
|
|
D3D12_RESOURCE_BARRIER uav = {};
|
|
uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
uav.UAV.pResource = dstTLAS->resource.Get();
|
|
g_glRaytracingCmd.tlasCmdList->ResourceBarrier(1, &uav);
|
|
glRaytracingEndPixEvent(g_glRaytracingCmd.tlasCmdList.Get());
|
|
|
|
const UINT64 tlasFenceValue = glRaytracingEndTlasCmd();
|
|
if (!tlasFenceValue)
|
|
return 0;
|
|
|
|
// Keep TLAS builds asynchronous. Later ray dispatches are submitted to the
|
|
// same queue after this command list, so the GPU sees a complete TLAS before
|
|
// tracing without forcing the CPU to wait every update. The upload buffer is
|
|
// fence-tagged so the CPU does not overwrite instance descriptors still being
|
|
// consumed by an in-flight TLAS build.
|
|
world->instanceDescUploadFenceValue[buildTLASIndex] = tlasFenceValue;
|
|
world->currentTLASIndex = buildTLASIndex;
|
|
world->activeInstanceCount = activeCount;
|
|
world->builtInstanceCount = activeCount;
|
|
world->tlasBuilt = 1;
|
|
world->tlasNeedsRebuild = 0;
|
|
world->tlasNeedsUpdate = 0;
|
|
|
|
for (size_t i = 0; i < world->instances.size(); ++i)
|
|
{
|
|
if (world->instances[i].alive)
|
|
world->instances[i].dirty = 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingInvalidateInstancesForMesh(uint32_t meshHandle, int deleteInstances)
|
|
{
|
|
for (int w = 0; w < GL_RAYTRACING_MAX_RENDER_WORLDS; ++w)
|
|
{
|
|
glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[w];
|
|
if (!world.alive)
|
|
continue;
|
|
|
|
int touched = 0;
|
|
for (size_t i = 0; i < world.instances.size(); ++i)
|
|
{
|
|
glRaytracingInstanceRecord_t& inst = world.instances[i];
|
|
if (inst.alive && inst.descCpu.meshHandle == meshHandle)
|
|
{
|
|
glRaytracingInvalidateInstanceCache(&inst);
|
|
inst.dirty = 1;
|
|
touched = 1;
|
|
|
|
if (deleteInstances)
|
|
{
|
|
inst.alive = 0;
|
|
if (inst.handle < world.instanceHandleToIndex.size())
|
|
world.instanceHandleToIndex[inst.handle] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (touched)
|
|
glRaytracingMarkWorldNeedsRebuild(&world);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Scene public API
|
|
// ============================================================
|
|
|
|
int glRaytracingInit(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingScene.initialized)
|
|
return 1;
|
|
|
|
if (!glRaytracingInitCmdContext())
|
|
return 0;
|
|
|
|
g_glRaytracingScene.initialized = 1;
|
|
|
|
glRaytracingLog("glRaytracingInit ok");
|
|
return 1;
|
|
}
|
|
|
|
void glRaytracingShutdown(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingClearAllSceneStateInternal();
|
|
g_glRaytracingScene = glRaytracingSceneState_t();
|
|
glRaytracingShutdownCmdContext();
|
|
}
|
|
|
|
void glRaytracingClear(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingClearAllSceneStateInternal();
|
|
}
|
|
|
|
glRaytracingSceneHandle_t glRaytracingCreateScene(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
for (uint32_t i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
{
|
|
glRaytracingRenderWorld_t& world = g_glRaytracingScene.worlds[i];
|
|
if (!world.alive)
|
|
{
|
|
const uint32_t handle = i + 1;
|
|
glRaytracingResetWorldSlot(&world, handle, 1);
|
|
return handle;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void glRaytracingClearScene(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingClearWorldContents(world);
|
|
}
|
|
|
|
void glRaytracingDeleteScene(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingReleaseWorldResources(world);
|
|
*world = glRaytracingRenderWorld_t();
|
|
}
|
|
|
|
uint32_t glRaytracingGetSceneCount(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
uint32_t count = 0;
|
|
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
{
|
|
if (g_glRaytracingScene.worlds[i].alive)
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
glRaytracingMeshHandle_t glRaytracingCreateMesh(const glRaytracingMeshDesc_t* desc)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized || !desc)
|
|
return 0;
|
|
|
|
if (!desc->vertices || !desc->indices || desc->vertexCount == 0 || desc->indexCount == 0)
|
|
return 0;
|
|
|
|
glRaytracingMeshRecord_t mesh;
|
|
mesh.handle = g_glRaytracingScene.nextMeshHandle++;
|
|
mesh.alive = 1;
|
|
mesh.descCpu = *desc;
|
|
mesh.verticesCpu.assign(desc->vertices, desc->vertices + desc->vertexCount);
|
|
mesh.indicesCpu.assign(desc->indices, desc->indices + desc->indexCount);
|
|
mesh.descCpu.vertices = nullptr;
|
|
mesh.descCpu.indices = nullptr;
|
|
glRaytracingCopyMeshAverageColor(mesh.averageColor, desc);
|
|
mesh.dirty = 1;
|
|
|
|
g_glRaytracingScene.meshes.push_back(mesh);
|
|
const size_t newIndex = g_glRaytracingScene.meshes.size() - 1;
|
|
glRaytracingEnsureMeshHandleTable(mesh.handle);
|
|
g_glRaytracingScene.meshHandleToIndex[mesh.handle] = (int)newIndex;
|
|
|
|
return mesh.handle;
|
|
}
|
|
|
|
int glRaytracingUpdateMesh(glRaytracingMeshHandle_t meshHandle, const glRaytracingMeshDesc_t* desc)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized || !desc)
|
|
return 0;
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
if (!desc->vertices || !desc->indices || desc->vertexCount == 0 || desc->indexCount == 0)
|
|
return 0;
|
|
|
|
const UINT64 vbBytes = UINT64(desc->vertexCount) * sizeof(glRaytracingVertex_t);
|
|
const UINT64 ibBytes = UINT64(desc->indexCount) * sizeof(uint32_t);
|
|
const bool sameVertexData =
|
|
mesh->verticesCpu.size() == desc->vertexCount &&
|
|
memcmp(mesh->verticesCpu.data(), desc->vertices, (size_t)vbBytes) == 0;
|
|
const bool sameIndexData =
|
|
mesh->indicesCpu.size() == desc->indexCount &&
|
|
memcmp(mesh->indicesCpu.data(), desc->indices, (size_t)ibBytes) == 0;
|
|
const bool sameDesc =
|
|
mesh->descCpu.vertexCount == desc->vertexCount &&
|
|
mesh->descCpu.indexCount == desc->indexCount &&
|
|
mesh->descCpu.allowUpdate == desc->allowUpdate &&
|
|
mesh->descCpu.opaque == desc->opaque;
|
|
float descAverageColor[4];
|
|
glRaytracingCopyMeshAverageColor(descAverageColor, desc);
|
|
const bool sameAverageColor = memcmp(mesh->averageColor, descAverageColor, sizeof(mesh->averageColor)) == 0;
|
|
|
|
if (sameDesc && sameVertexData && sameIndexData)
|
|
{
|
|
if (!sameAverageColor)
|
|
{
|
|
memcpy(mesh->averageColor, descAverageColor, sizeof(mesh->averageColor));
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const bool canUpdateExistingBuffers =
|
|
desc->allowUpdate != 0 &&
|
|
mesh->descCpu.allowUpdate != 0 &&
|
|
mesh->descCpu.opaque == desc->opaque &&
|
|
mesh->blasBuilt != 0 &&
|
|
mesh->vertexBuffer.resource &&
|
|
mesh->indexBuffer.resource &&
|
|
mesh->vertexBuffer.size >= vbBytes &&
|
|
mesh->indexBuffer.size >= ibBytes &&
|
|
mesh->verticesCpu.size() == desc->vertexCount &&
|
|
mesh->indicesCpu.size() == desc->indexCount;
|
|
|
|
mesh->descCpu = *desc;
|
|
mesh->verticesCpu.assign(desc->vertices, desc->vertices + desc->vertexCount);
|
|
mesh->indicesCpu.assign(desc->indices, desc->indices + desc->indexCount);
|
|
mesh->descCpu.vertices = nullptr;
|
|
mesh->descCpu.indices = nullptr;
|
|
memcpy(mesh->averageColor, descAverageColor, sizeof(mesh->averageColor));
|
|
|
|
if (canUpdateExistingBuffers)
|
|
{
|
|
glRaytracingMapCopy(mesh->vertexBuffer.resource.Get(), mesh->verticesCpu.data(), (size_t)vbBytes);
|
|
if (!sameIndexData)
|
|
glRaytracingMapCopy(mesh->indexBuffer.resource.Get(), mesh->indicesCpu.data(), (size_t)ibBytes);
|
|
|
|
mesh->dirty = 1;
|
|
glRaytracingInvalidateInstancesForMesh(meshHandle, 0);
|
|
return 1;
|
|
}
|
|
|
|
// Updating a mesh destroys/replaces resources that an already submitted frame
|
|
// may still reference. Wait only for this destructive path; steady-state
|
|
// rendering remains asynchronous.
|
|
glRaytracingWaitIdle();
|
|
|
|
mesh->vertexBuffer.resource.Reset();
|
|
mesh->indexBuffer.resource.Reset();
|
|
mesh->blasScratch.resource.Reset();
|
|
mesh->blasResult[0].resource.Reset();
|
|
mesh->blasResult[1].resource.Reset();
|
|
mesh->blasScratchSize = 0;
|
|
mesh->blasResultSize = 0;
|
|
mesh->blasBuildFenceValue = 0;
|
|
mesh->blasBuilt = 0;
|
|
mesh->dirty = 1;
|
|
mesh->currentBlasIndex = 0;
|
|
|
|
glRaytracingInvalidateInstancesForMesh(meshHandle, 0);
|
|
glRaytracingMarkAllWorldsNeedRebuild();
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t glRaytracingGetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
const glRaytracingMeshRecord_t* mesh = glRaytracingFindMeshConst(meshHandle);
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
return mesh->materialFlags;
|
|
}
|
|
|
|
void glRaytracingSetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle, uint32_t materialFlags)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return;
|
|
|
|
materialFlags &= GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
if (mesh->materialFlags == materialFlags)
|
|
return;
|
|
|
|
mesh->materialFlags = materialFlags;
|
|
|
|
// The glass bit also represents shim-auto-tagged alpha-blended surfaces.
|
|
// It changes whether BLAS geometry is opaque, so force a full BLAS rebuild.
|
|
// The TLAS is also rebuilt so InstanceID carries the material bit.
|
|
mesh->blasBuilt = 0;
|
|
mesh->dirty = 1;
|
|
glRaytracingInvalidateInstancesForMesh(meshHandle, 0);
|
|
glRaytracingMarkAllWorldsNeedRebuild();
|
|
}
|
|
|
|
void glRaytracingSetMeshGlass(glRaytracingMeshHandle_t meshHandle, int isGlass)
|
|
{
|
|
uint32_t flags = glRaytracingGetMeshMaterialFlags(meshHandle);
|
|
if (isGlass)
|
|
flags |= GL_RAYTRACING_MATERIAL_FLAG_GLASS;
|
|
else
|
|
flags &= ~GL_RAYTRACING_MATERIAL_FLAG_GLASS;
|
|
glRaytracingSetMeshMaterialFlags(meshHandle, flags);
|
|
}
|
|
|
|
void glRaytracingSetMeshAverageColor(glRaytracingMeshHandle_t meshHandle, float r, float g, float b, float a)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return;
|
|
|
|
const float averageColor[4] =
|
|
{
|
|
glRaytracingClamp<float>(r, 0.0f, 1.0f),
|
|
glRaytracingClamp<float>(g, 0.0f, 1.0f),
|
|
glRaytracingClamp<float>(b, 0.0f, 1.0f),
|
|
glRaytracingClamp<float>(a, 0.0f, 1.0f)
|
|
};
|
|
|
|
if (memcmp(mesh->averageColor, averageColor, sizeof(averageColor)) == 0)
|
|
return;
|
|
|
|
memcpy(mesh->averageColor, averageColor, sizeof(mesh->averageColor));
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
void glRaytracingSetMeshProcMetadata(glRaytracingMeshHandle_t meshHandle, uint32_t procFlags, int areaNum, float emissiveR, float emissiveG, float emissiveB, float emissivePower)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return;
|
|
|
|
const float emissiveColor[4] =
|
|
{
|
|
glRaytracingClamp<float>(emissiveR, 0.0f, 64.0f),
|
|
glRaytracingClamp<float>(emissiveG, 0.0f, 64.0f),
|
|
glRaytracingClamp<float>(emissiveB, 0.0f, 64.0f),
|
|
glRaytracingClamp<float>(emissivePower, 0.0f, 64.0f)
|
|
};
|
|
|
|
const int32_t clampedAreaNum = (areaNum >= 0) ? (int32_t)areaNum : -1;
|
|
if (mesh->procFlags == procFlags &&
|
|
mesh->areaNum == clampedAreaNum &&
|
|
memcmp(mesh->emissiveColor, emissiveColor, sizeof(emissiveColor)) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mesh->procFlags = procFlags;
|
|
mesh->areaNum = clampedAreaNum;
|
|
memcpy(mesh->emissiveColor, emissiveColor, sizeof(mesh->emissiveColor));
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
void glRaytracingDeleteMesh(glRaytracingMeshHandle_t meshHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingInvalidateInstancesForMesh(meshHandle, 1);
|
|
|
|
mesh->alive = 0;
|
|
mesh->vertexBuffer.resource.Reset();
|
|
mesh->indexBuffer.resource.Reset();
|
|
mesh->blasScratch.resource.Reset();
|
|
mesh->blasResult[0].resource.Reset();
|
|
mesh->blasResult[1].resource.Reset();
|
|
mesh->blasScratchSize = 0;
|
|
mesh->blasResultSize = 0;
|
|
mesh->blasBuildFenceValue = 0;
|
|
mesh->blasBuilt = 0;
|
|
mesh->dirty = 0;
|
|
|
|
if (meshHandle < g_glRaytracingScene.meshHandleToIndex.size())
|
|
g_glRaytracingScene.meshHandleToIndex[meshHandle] = -1;
|
|
|
|
glRaytracingMarkAllWorldsNeedRebuild();
|
|
}
|
|
|
|
static inline uint32_t glRaytracingNormalizeVisibleInstanceMask(uint32_t mask)
|
|
{
|
|
mask &= 0xFFu;
|
|
return mask ? mask : 0xFFu;
|
|
}
|
|
|
|
glRaytracingInstanceHandle_t glRaytracingCreateInstanceInScene(glRaytracingSceneHandle_t worldHandle, const glRaytracingInstanceDesc_t* desc)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized || !desc)
|
|
return 0;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return 0;
|
|
|
|
if (!glRaytracingFindMeshConst(desc->meshHandle))
|
|
return 0;
|
|
|
|
glRaytracingInstanceRecord_t inst;
|
|
inst.handle = world->nextInstanceHandle++;
|
|
inst.alive = 1;
|
|
inst.descCpu = *desc;
|
|
inst.descCpu.mask = glRaytracingNormalizeVisibleInstanceMask(inst.descCpu.mask);
|
|
inst.dirty = 1;
|
|
|
|
world->instances.push_back(inst);
|
|
const size_t newIndex = world->instances.size() - 1;
|
|
glRaytracingEnsureInstanceHandleTable(world, inst.handle);
|
|
world->instanceHandleToIndex[inst.handle] = (int)newIndex;
|
|
glRaytracingMarkWorldNeedsRebuild(world);
|
|
return inst.handle;
|
|
}
|
|
|
|
int glRaytracingUpdateInstanceInScene(glRaytracingSceneHandle_t worldHandle, glRaytracingInstanceHandle_t instanceHandle, const glRaytracingInstanceDesc_t* desc)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized || !desc)
|
|
return 0;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return 0;
|
|
|
|
if (!glRaytracingFindMeshConst(desc->meshHandle))
|
|
return 0;
|
|
|
|
glRaytracingInstanceRecord_t* inst = glRaytracingFindInstance(world, instanceHandle);
|
|
if (!inst)
|
|
return 0;
|
|
|
|
const uint32_t oldMeshHandle = inst->descCpu.meshHandle;
|
|
const uint32_t oldMask = ((uint32_t)inst->descCpu.mask) & 0xFFu;
|
|
const int wasHidden = (oldMask == 0u);
|
|
|
|
glRaytracingInstanceDesc_t newDesc = *desc;
|
|
|
|
// If this instance is hidden, keep it hidden even if the caller's
|
|
// transform-update helper sends mask = 0xFF again.
|
|
newDesc.mask = wasHidden
|
|
? 0u
|
|
: glRaytracingNormalizeVisibleInstanceMask(newDesc.mask);
|
|
|
|
if (memcmp(&inst->descCpu, &newDesc, sizeof(newDesc)) == 0)
|
|
return 1;
|
|
|
|
inst->descCpu = newDesc;
|
|
inst->dirty = 1;
|
|
|
|
if (oldMeshHandle != desc->meshHandle)
|
|
{
|
|
glRaytracingInvalidateInstanceCache(inst);
|
|
glRaytracingMarkWorldNeedsRebuild(world);
|
|
}
|
|
else
|
|
{
|
|
glRaytracingMarkWorldNeedsUpdate(world);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void glRaytracingDeleteInstanceInScene(glRaytracingSceneHandle_t worldHandle, glRaytracingInstanceHandle_t instanceHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return;
|
|
|
|
glRaytracingInstanceRecord_t* inst = glRaytracingFindInstance(world, instanceHandle);
|
|
if (!inst)
|
|
return;
|
|
|
|
glRaytracingInvalidateInstanceCache(inst);
|
|
inst->alive = 0;
|
|
if (instanceHandle < world->instanceHandleToIndex.size())
|
|
world->instanceHandleToIndex[instanceHandle] = -1;
|
|
glRaytracingMarkWorldNeedsRebuild(world);
|
|
}
|
|
|
|
int glRaytracingBuildMesh(glRaytracingMeshHandle_t meshHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle);
|
|
if (!mesh)
|
|
return 0;
|
|
|
|
return glRaytracingBuildMeshInternal(mesh);
|
|
}
|
|
|
|
int glRaytracingBuildAllMeshes(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
return glRaytracingBuildDirtyMeshesInternal();
|
|
}
|
|
|
|
int glRaytracingBuildSceneForHandle(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return 0;
|
|
|
|
if (!glRaytracingBuildDirtyMeshesInternal())
|
|
return 0;
|
|
|
|
return glRaytracingBuildSceneInternal(world);
|
|
}
|
|
|
|
ID3D12Resource* glRaytracingGetTopLevelASForScene(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return nullptr;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return nullptr;
|
|
|
|
if (!glRaytracingBuildDirtyMeshesInternal())
|
|
return nullptr;
|
|
|
|
if (!glRaytracingBuildSceneInternal(world))
|
|
return nullptr;
|
|
|
|
if (!world->tlasBuilt)
|
|
return nullptr;
|
|
|
|
return glRaytracingGetCurrentTLASBuffer(world)->resource.Get();
|
|
}
|
|
|
|
uint32_t glRaytracingGetMeshCount(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
uint32_t count = 0;
|
|
for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i)
|
|
{
|
|
if (g_glRaytracingScene.meshes[i].alive)
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
uint32_t glRaytracingGetInstanceCountForScene(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
const glRaytracingRenderWorld_t* world = glRaytracingFindWorldConst(worldHandle);
|
|
return glRaytracingCountAliveInstances(world);
|
|
}
|
|
|
|
// ============================================================
|
|
// Lighting state
|
|
// ============================================================
|
|
|
|
struct glRaytracingLightingConstants_t
|
|
{
|
|
float invViewProj[16];
|
|
float invViewMatrix[16];
|
|
float viewProj[16];
|
|
float cameraPos[4];
|
|
float ambientColor[4];
|
|
float screenSize[4];
|
|
|
|
// Keep this CPU layout 16-byte aligned with the HLSL cbuffer.
|
|
float normalReconstructZ;
|
|
uint32_t lightCount;
|
|
uint32_t enableSpecular;
|
|
uint32_t enableHalfLambert;
|
|
|
|
float shadowBias;
|
|
uint32_t frameIndex;
|
|
uint32_t samplesPerPixel;
|
|
uint32_t maxBounces;
|
|
|
|
uint32_t enableDenoiser;
|
|
uint32_t denoisePassIndex;
|
|
float denoiseStepWidth;
|
|
float denoiseStrength;
|
|
|
|
float denoisePhiColor;
|
|
float denoisePhiNormal;
|
|
float denoisePhiPosition;
|
|
float bumpStrength;
|
|
|
|
int32_t secondaryLightBudget;
|
|
uint32_t secondaryCorrectionBudget;
|
|
float secondaryLightCompensation;
|
|
uint32_t historyReset;
|
|
|
|
uint32_t externalDenoiser;
|
|
float giAmount;
|
|
float worldGrimeAmount;
|
|
uint32_t pad2;
|
|
};
|
|
|
|
struct glRaytracingMeshMetadata_t
|
|
{
|
|
float averageColor[4];
|
|
float emissiveColor[4];
|
|
uint32_t procFlags;
|
|
int32_t areaNum;
|
|
uint32_t vertexOffset;
|
|
uint32_t indexOffset;
|
|
uint32_t vertexCount;
|
|
uint32_t indexCount;
|
|
uint32_t pad[2];
|
|
};
|
|
|
|
static const uint32_t GL_RAYTRACING_MAX_IES_TEXTURES = 8;
|
|
|
|
struct glRaytracingLightingState_t
|
|
{
|
|
std::vector<glRaytracingLight_t> cpuLights;
|
|
std::vector<glRaytracingLight_t> uploadLights;
|
|
glRaytracingLightingConstants_t constants;
|
|
|
|
ComPtr<ID3D12DescriptorHeap> descriptorHeap;
|
|
ComPtr<ID3D12DescriptorHeap> descriptorHeapRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
UINT descriptorStride;
|
|
|
|
glRaytracingBuffer_t constantBuffer;
|
|
glRaytracingBuffer_t lightBuffer;
|
|
glRaytracingBuffer_t meshMetadataBuffer;
|
|
glRaytracingBuffer_t materialVertexBuffer;
|
|
glRaytracingBuffer_t materialIndexBuffer;
|
|
void* constantBufferMapped;
|
|
void* lightBufferMapped;
|
|
void* meshMetadataBufferMapped;
|
|
glRaytracingBuffer_t constantBufferRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
glRaytracingBuffer_t lightBufferRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
glRaytracingBuffer_t meshMetadataBufferRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
glRaytracingBuffer_t materialVertexBufferRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
glRaytracingBuffer_t materialIndexBufferRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
uint32_t materialVertexCapacityRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
uint32_t materialIndexCapacityRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
uint32_t materialVertexCount;
|
|
uint32_t materialIndexCount;
|
|
void* constantBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
void* lightBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
void* meshMetadataBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE];
|
|
|
|
ComPtr<ID3D12RootSignature> globalRootSig;
|
|
ComPtr<ID3D12RootSignature> localRootSig;
|
|
|
|
ComPtr<ID3D12StateObject> rtStateObject;
|
|
ComPtr<ID3D12StateObjectProperties> rtStateProps;
|
|
|
|
glRaytracingBuffer_t raygenTable;
|
|
glRaytracingBuffer_t missTable;
|
|
glRaytracingBuffer_t hitTable;
|
|
|
|
ComPtr<ID3D12PipelineState> denoisePSO;
|
|
ComPtr<ID3D12PipelineState> temporalPSO;
|
|
glRaytracingTexture_t pathTraceTexture[2];
|
|
glRaytracingTexture_t temporalTexture;
|
|
glRaytracingTexture_t historyTexture[2];
|
|
glRaytracingTexture_t denoiseTemp[2];
|
|
uint32_t currentHistoryIndex;
|
|
uint32_t currentPathTraceIndex;
|
|
glRaytracingBuffer_t denoiseConstantBuffer[3];
|
|
glRaytracingBuffer_t denoiseConstantBufferRing[GL_RAYTRACING_CMD_RING_SIZE][3];
|
|
void* denoiseConstantBufferMapped[3];
|
|
void* denoiseConstantBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE][3];
|
|
UINT denoiseWidth;
|
|
UINT denoiseHeight;
|
|
DXGI_FORMAT denoiseFormat;
|
|
uint32_t frameCounter;
|
|
bool externalDenoiser;
|
|
ID3D12Resource* emissiveTexture;
|
|
DXGI_FORMAT emissiveFormat;
|
|
ID3D12Resource* specularTexture;
|
|
DXGI_FORMAT specularFormat;
|
|
uint32_t iesTextureIds[GL_RAYTRACING_MAX_IES_TEXTURES];
|
|
ID3D12Resource* iesTextures[GL_RAYTRACING_MAX_IES_TEXTURES];
|
|
DXGI_FORMAT iesFormats[GL_RAYTRACING_MAX_IES_TEXTURES];
|
|
bool uploadToCurrentFrameResource;
|
|
bool initialized;
|
|
glRaytracingSceneHandle_t lastProfileSceneHandle;
|
|
uint32_t lastProfileInstanceCount;
|
|
uint32_t lastProfileWidth;
|
|
uint32_t lastProfileHeight;
|
|
|
|
glRaytracingLightingState_t()
|
|
{
|
|
memset(&constants, 0, sizeof(constants));
|
|
descriptorStride = 0;
|
|
constantBufferMapped = nullptr;
|
|
lightBufferMapped = nullptr;
|
|
meshMetadataBufferMapped = nullptr;
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
constantBufferMappedRing[frame] = nullptr;
|
|
lightBufferMappedRing[frame] = nullptr;
|
|
meshMetadataBufferMappedRing[frame] = nullptr;
|
|
materialVertexCapacityRing[frame] = 0;
|
|
materialIndexCapacityRing[frame] = 0;
|
|
for (int i = 0; i < 3; ++i)
|
|
denoiseConstantBufferMappedRing[frame][i] = nullptr;
|
|
}
|
|
materialVertexCount = 0;
|
|
materialIndexCount = 0;
|
|
for (int i = 0; i < 3; ++i)
|
|
denoiseConstantBufferMapped[i] = nullptr;
|
|
denoiseWidth = 0;
|
|
denoiseHeight = 0;
|
|
denoiseFormat = DXGI_FORMAT_UNKNOWN;
|
|
currentHistoryIndex = 0;
|
|
currentPathTraceIndex = 0;
|
|
frameCounter = 0;
|
|
externalDenoiser = false;
|
|
emissiveTexture = nullptr;
|
|
emissiveFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
|
specularTexture = nullptr;
|
|
specularFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
for (uint32_t i = 0; i < GL_RAYTRACING_MAX_IES_TEXTURES; ++i)
|
|
{
|
|
iesTextureIds[i] = 0;
|
|
iesTextures[i] = nullptr;
|
|
iesFormats[i] = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
}
|
|
uploadToCurrentFrameResource = false;
|
|
initialized = false;
|
|
lastProfileSceneHandle = 0;
|
|
lastProfileInstanceCount = 0;
|
|
lastProfileWidth = 0;
|
|
lastProfileHeight = 0;
|
|
}
|
|
};
|
|
|
|
static glRaytracingLightingState_t g_glRaytracingLighting;
|
|
|
|
static const DXGI_FORMAT GL_RAYTRACING_DENOISE_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
|
enum glRaytracingLightingDescriptorIndex_t
|
|
{
|
|
GLR_DESC_LIGHTS_SRV = 0,
|
|
GLR_DESC_ALBEDO_SRV = 1,
|
|
GLR_DESC_DEPTH_SRV = 2,
|
|
GLR_DESC_NORMAL_SRV = 3,
|
|
GLR_DESC_POSITION_SRV = 4,
|
|
GLR_DESC_TLAS_SRV = 5,
|
|
GLR_DESC_PATHTRACE_SRV = 6,
|
|
GLR_DESC_DENOISE_A_SRV = 7,
|
|
GLR_DESC_DENOISE_B_SRV = 8,
|
|
GLR_DESC_HISTORY_SRV = 9,
|
|
GLR_DESC_TEMPORAL_SRV = 10,
|
|
GLR_DESC_EMISSIVE_SRV = 11,
|
|
GLR_DESC_SPECULAR_SRV = 12,
|
|
GLR_DESC_MESH_METADATA_SRV = 13,
|
|
GLR_DESC_IES_TEXTURES_SRV = 14,
|
|
GLR_DESC_VELOCITY_SRV = GLR_DESC_IES_TEXTURES_SRV + GL_RAYTRACING_MAX_IES_TEXTURES,
|
|
GLR_DESC_MATERIAL_VERTICES_SRV = GLR_DESC_VELOCITY_SRV + 1,
|
|
GLR_DESC_MATERIAL_INDICES_SRV = GLR_DESC_VELOCITY_SRV + 2,
|
|
GLR_DESC_PATHTRACE_UAV = GLR_DESC_VELOCITY_SRV + 3,
|
|
GLR_DESC_DENOISE_A_UAV = GLR_DESC_PATHTRACE_UAV + 1,
|
|
GLR_DESC_DENOISE_B_UAV = GLR_DESC_PATHTRACE_UAV + 2,
|
|
GLR_DESC_OUTPUT_UAV = GLR_DESC_PATHTRACE_UAV + 3,
|
|
GLR_DESC_TEMPORAL_UAV = GLR_DESC_PATHTRACE_UAV + 4,
|
|
GLR_DESC_HISTORY_UAV = GLR_DESC_PATHTRACE_UAV + 5,
|
|
GLR_DESC_COUNT = GLR_DESC_PATHTRACE_UAV + 6,
|
|
GLR_DESC_SRV_COUNT = GLR_DESC_PATHTRACE_UAV,
|
|
GLR_DESC_UAV_COUNT = 6
|
|
};
|
|
|
|
static void glRaytracingLightingResetDenoiseHistory(void)
|
|
{
|
|
// Kept under the old name so existing call sites keep compiling. This now
|
|
// invalidates the temporal GI accumulator as well as restarting stochastic
|
|
// sample indexing after material/camera/light/scene changes.
|
|
g_glRaytracingLighting.frameCounter = 0;
|
|
g_glRaytracingLighting.constants.frameIndex = 0;
|
|
}
|
|
|
|
static const char* g_glRaytracingLightingHlsl = R"(
|
|
struct Light
|
|
{
|
|
float3 position;
|
|
float radius;
|
|
|
|
float3 color;
|
|
float intensity;
|
|
|
|
float3 normal;
|
|
uint type;
|
|
|
|
float3 axisU;
|
|
float halfWidth;
|
|
|
|
float3 axisV;
|
|
float halfHeight;
|
|
|
|
uint samples;
|
|
uint twoSided;
|
|
float persistant;
|
|
|
|
// Reuses the old pad1 slot in glRaytracingLight_t. Keeping this in the
|
|
// same 16-byte lane preserves the CPU StructuredBuffer stride while giving
|
|
// point/spot lights an explicit volumetric scattering control.
|
|
// <= 0 disables the effect. Values around 0.25-1.0 are useful in Doom 3 units.
|
|
float volumetricScattering;
|
|
|
|
// For point lights, this is the axis-aligned XYZ attenuation radius.
|
|
// For spot lights, pointRadius.x stores the near clip plane.
|
|
// The scalar radius above is still kept as a max/fallback range for point lights,
|
|
// as the influence range for rect lights, and as the far clip distance for spot lights.
|
|
float3 pointRadius;
|
|
float pointRadiusPad;
|
|
|
|
uint iesTextureIndex; // 0 = none, 1..8 = gIesTextures index + 1
|
|
float iesStrength;
|
|
int areaNum;
|
|
float iesPad;
|
|
};
|
|
|
|
struct MeshMetadata
|
|
{
|
|
float4 averageColor;
|
|
float4 emissiveColor;
|
|
uint procFlags;
|
|
int areaNum;
|
|
uint vertexOffset;
|
|
uint indexOffset;
|
|
uint vertexCount;
|
|
uint indexCount;
|
|
uint2 pad;
|
|
};
|
|
|
|
struct RayMaterialVertex
|
|
{
|
|
float3 position;
|
|
float3 normal;
|
|
float2 texCoord;
|
|
};
|
|
|
|
struct ShadowPayload
|
|
{
|
|
uint hit;
|
|
};
|
|
|
|
struct BouncePayload
|
|
{
|
|
uint hit;
|
|
float hitT;
|
|
uint materialFlags;
|
|
uint meshIndex;
|
|
float3 hitNormal;
|
|
float2 hitTexCoord;
|
|
};
|
|
|
|
cbuffer LightingCB : register(b0)
|
|
{
|
|
float4x4 gInvViewProj;
|
|
float4x4 gInvViewMatrix;
|
|
float4x4 gViewProj;
|
|
float4 gCameraPos;
|
|
float4 gAmbientColor;
|
|
float4 gScreenSize;
|
|
float gNormalReconstructZ;
|
|
uint gLightCount;
|
|
uint gEnableSpecular;
|
|
uint gEnableHalfLambert;
|
|
float gShadowBias;
|
|
uint gFrameIndex;
|
|
uint gSamplesPerPixel;
|
|
uint gMaxBounces;
|
|
uint gEnableDenoiser;
|
|
uint gDenoisePassIndex;
|
|
float gDenoiseStepWidth;
|
|
float gDenoiseStrength;
|
|
float gDenoisePhiColor;
|
|
float gDenoisePhiNormal;
|
|
float gDenoisePhiPosition;
|
|
float gBumpStrength;
|
|
int gSecondaryLightBudget;
|
|
uint gSecondaryCorrectionBudget;
|
|
float gSecondaryLightCompensation;
|
|
uint gHistoryReset;
|
|
uint gExternalDenoiser;
|
|
float gGiAmount;
|
|
float gWorldGrimeAmount;
|
|
uint gPadding2;
|
|
};
|
|
|
|
StructuredBuffer<Light> gLights : register(t0);
|
|
Texture2D<float4> gAlbedoTex : register(t1);
|
|
Texture2D<float> gDepthTex : register(t2);
|
|
Texture2D<float4> gNormalTex : register(t3);
|
|
Texture2D<float4> gPositionTex : register(t4);
|
|
RaytracingAccelerationStructure gSceneBVH : register(t5);
|
|
Texture2D<float4> gEmissiveTex : register(t11);
|
|
Texture2D<float4> gSpecularTex : register(t12);
|
|
StructuredBuffer<MeshMetadata> gMeshMetadata : register(t13);
|
|
Texture2D<float4> gIesTextures[8] : register(t14);
|
|
Texture2D<float4> gVelocityTex : register(t22);
|
|
StructuredBuffer<RayMaterialVertex> gMaterialVertices : register(t23);
|
|
StructuredBuffer<uint> gMaterialIndices : register(t24);
|
|
RWTexture2D<float4> gOutputTex : register(u0);
|
|
|
|
static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0;
|
|
static const uint GL_RAYTRACING_LIGHT_TYPE_RECT = 1;
|
|
static const uint GL_RAYTRACING_LIGHT_TYPE_SPOT = 2;
|
|
|
|
static const uint GEOMETRY_FLAG_NONE = 0;
|
|
static const uint GEOMETRY_FLAG_SKELETAL = 1;
|
|
static const uint GEOMETRY_FLAG_UNLIT = 2;
|
|
static const uint GEOMETRY_FLAG_GLASS = 4;
|
|
|
|
static const uint GL_RAYTRACING_PROC_SURFACE_EMISSIVE = 0x00000001u;
|
|
static const uint GL_RAYTRACING_PROC_SURFACE_UNLIT = 0x00000002u;
|
|
static const uint GL_RAYTRACING_PROC_SURFACE_SKY = 0x00000004u;
|
|
static const uint GL_RAYTRACING_PROC_SURFACE_ALPHA_TESTED = 0x00000008u;
|
|
static const uint GL_RAYTRACING_PROC_SURFACE_TWO_SIDED = 0x00000010u;
|
|
|
|
// The shim encodes per-instance material flags into the upper bits of
|
|
// D3D12_RAYTRACING_INSTANCE_DESC::InstanceID so any-hit shaders can make
|
|
// visibility decisions without binding a separate material table.
|
|
static const uint GL_RAYTRACING_INSTANCE_USER_ID_MASK = 0x0000FFFFu;
|
|
static const uint GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT = 16u;
|
|
static const uint GL_RAYTRACING_INSTANCE_MATERIAL_MASK = 0x000000FFu;
|
|
static const uint GL_RAYTRACING_MATERIAL_FLAG_GLASS = 0x00000001u;
|
|
|
|
uint DecodeInstanceMaterialFlags()
|
|
{
|
|
return (InstanceID() >> GL_RAYTRACING_INSTANCE_MATERIAL_SHIFT) &
|
|
GL_RAYTRACING_INSTANCE_MATERIAL_MASK;
|
|
}
|
|
|
|
bool CurrentRayHitIsGlass()
|
|
{
|
|
return (DecodeInstanceMaterialFlags() & GL_RAYTRACING_MATERIAL_FLAG_GLASS) != 0u;
|
|
}
|
|
|
|
float3 GetMeshAverageColor(uint meshIndex)
|
|
{
|
|
float3 c = gMeshMetadata[meshIndex].averageColor.rgb;
|
|
return max(saturate(c), float3(0.04, 0.04, 0.04));
|
|
}
|
|
|
|
MeshMetadata GetMeshMetadataSafe(uint meshIndex)
|
|
{
|
|
return gMeshMetadata[meshIndex];
|
|
}
|
|
|
|
uint MeshMetadataGeometryFlags(MeshMetadata meta, uint materialFlags)
|
|
{
|
|
uint geoFlags = GEOMETRY_FLAG_NONE;
|
|
if ((meta.procFlags & (GL_RAYTRACING_PROC_SURFACE_UNLIT | GL_RAYTRACING_PROC_SURFACE_SKY)) != 0u)
|
|
geoFlags |= GEOMETRY_FLAG_UNLIT;
|
|
if ((materialFlags & GL_RAYTRACING_MATERIAL_FLAG_GLASS) != 0u)
|
|
geoFlags |= GEOMETRY_FLAG_GLASS;
|
|
return geoFlags;
|
|
}
|
|
|
|
float3 GetMeshEmissiveRadiance(MeshMetadata meta)
|
|
{
|
|
if ((meta.procFlags & GL_RAYTRACING_PROC_SURFACE_EMISSIVE) == 0u)
|
|
return 0.0;
|
|
|
|
float power = max(meta.emissiveColor.a, 1.0);
|
|
float3 e = max(meta.emissiveColor.rgb * power, 0.0);
|
|
float peak = max(max(e.r, e.g), e.b);
|
|
if (peak > 8.0 && peak > 1e-5)
|
|
e *= 8.0 / peak;
|
|
return e;
|
|
}
|
|
|
|
uint DecodeGeometryFlag(float geoFlag)
|
|
{
|
|
// position.w comes from a render target / buffer path, so do not require exact
|
|
// float equality. Values like 0.999, 1.001, 1.99, 2.02 should decode correctly.
|
|
//
|
|
// Clamp negative garbage to 0, then round to nearest integer flag.
|
|
float f = max(geoFlag, 0.0);
|
|
return (uint)floor(f + 0.5);
|
|
}
|
|
|
|
bool GeometryFlagEquals(float geoFlag, uint expectedFlag)
|
|
{
|
|
return DecodeGeometryFlag(geoFlag) == expectedFlag;
|
|
}
|
|
|
|
bool GeometryFlagHas(float geoFlag, uint expectedFlag)
|
|
{
|
|
// Supports both current single-value usage and future bitmask usage.
|
|
uint decoded = DecodeGeometryFlag(geoFlag);
|
|
return (decoded & expectedFlag) != 0u;
|
|
}
|
|
|
|
float3 LoadScenePosition(uint2 pixel)
|
|
{
|
|
float4 p = gPositionTex.Load(int3(pixel, 0));
|
|
return p.xyz;
|
|
}
|
|
|
|
float4 LoadSceneNormal(uint2 pixel)
|
|
{
|
|
float4 nSample = gNormalTex.Load(int3(pixel, 0));
|
|
return nSample;
|
|
}
|
|
|
|
float LoadSceneSurfaceRoughness(float4 normalSample)
|
|
{
|
|
// The raster G-buffer stores authored/material roughness in normal.w.
|
|
// Keep a small floor so glossy materials do not become perfect mirrors.
|
|
return clamp(normalSample.w, 0.05, 1.0);
|
|
}
|
|
|
|
float SafeLengthSq(float3 v)
|
|
{
|
|
return max(dot(v, v), 1e-8);
|
|
}
|
|
|
|
float3 SafeNormalizeLocal(float3 v, float3 fallback)
|
|
{
|
|
float lenSq = dot(v, v);
|
|
return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallback;
|
|
}
|
|
|
|
float BumpLuminance(float3 c)
|
|
{
|
|
return dot(c, float3(0.299, 0.587, 0.114));
|
|
}
|
|
|
|
float3 EnhanceBumpNormal(uint2 pixel, float3 worldPos, float3 baseAlbedo, bool isSkeletal)
|
|
{
|
|
float3 N = SafeNormalizeLocal(LoadSceneNormal(pixel).xyz, float3(0.0, 0.0, 1.0));
|
|
|
|
// Character albedo has skin, cloth, and painted detail that should not be
|
|
// interpreted as height. The synthetic bump pass looks good on old walls,
|
|
// but it makes animated models crawl and self-shadow like noisy relief maps.
|
|
if (isSkeletal)
|
|
return N;
|
|
|
|
float strength = max(gBumpStrength, 0.0);
|
|
if (strength <= 0.001)
|
|
return N;
|
|
|
|
int2 p = int2(pixel);
|
|
int2 maxP = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
|
|
int2 pxm = clamp(p + int2(-1, 0), int2(0, 0), maxP);
|
|
int2 pxp = clamp(p + int2( 1, 0), int2(0, 0), maxP);
|
|
int2 pym = clamp(p + int2( 0, -1), int2(0, 0), maxP);
|
|
int2 pyp = clamp(p + int2( 0, 1), int2(0, 0), maxP);
|
|
|
|
float3 posL = gPositionTex.Load(int3(pxm, 0)).xyz;
|
|
float3 posR = gPositionTex.Load(int3(pxp, 0)).xyz;
|
|
float3 posU = gPositionTex.Load(int3(pym, 0)).xyz;
|
|
float3 posD = gPositionTex.Load(int3(pyp, 0)).xyz;
|
|
|
|
float3 T = posR - posL;
|
|
float3 B = posD - posU;
|
|
|
|
// Fall back to a stable tangent basis when the position buffer is flat or invalid.
|
|
if (dot(T, T) <= 1e-8 || dot(B, B) <= 1e-8)
|
|
{
|
|
float3 up = (abs(N.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(0.0, 1.0, 0.0);
|
|
T = SafeNormalizeLocal(cross(up, N), float3(1.0, 0.0, 0.0));
|
|
B = cross(N, T);
|
|
}
|
|
else
|
|
{
|
|
T = SafeNormalizeLocal(T - N * dot(N, T), float3(1.0, 0.0, 0.0));
|
|
B = SafeNormalizeLocal(B - N * dot(N, B), float3(0.0, 1.0, 0.0));
|
|
}
|
|
|
|
float hL = BumpLuminance(saturate(gAlbedoTex.Load(int3(pxm, 0)).rgb));
|
|
float hR = BumpLuminance(saturate(gAlbedoTex.Load(int3(pxp, 0)).rgb));
|
|
float hU = BumpLuminance(saturate(gAlbedoTex.Load(int3(pym, 0)).rgb));
|
|
float hD = BumpLuminance(saturate(gAlbedoTex.Load(int3(pyp, 0)).rgb));
|
|
|
|
// Height-gradient bump from the diffuse texture. The scale is intentionally
|
|
// aggressive because the current renderer has no dedicated height/normal map
|
|
// slot here, and old idTech/Build textures need the fake relief to read.
|
|
float dhdx = (hR - hL);
|
|
float dhdy = (hD - hU);
|
|
float3 heightNormal = SafeNormalizeLocal(N - (T * dhdx + B * dhdy) * (strength * 4.25), N);
|
|
|
|
float3 nL = SafeNormalizeLocal(gNormalTex.Load(int3(pxm, 0)).xyz, N);
|
|
float3 nR = SafeNormalizeLocal(gNormalTex.Load(int3(pxp, 0)).xyz, N);
|
|
float3 nU = SafeNormalizeLocal(gNormalTex.Load(int3(pym, 0)).xyz, N);
|
|
float3 nD = SafeNormalizeLocal(gNormalTex.Load(int3(pyp, 0)).xyz, N);
|
|
float3 avgN = SafeNormalizeLocal((nL + nR + nU + nD) * 0.25, N);
|
|
|
|
// Amplify real G-buffer normal-map variation as well.
|
|
float3 detailN = SafeNormalizeLocal(N + (N - avgN) * (strength * 1.75), N);
|
|
|
|
float3 outN = SafeNormalizeLocal(lerp(detailN, heightNormal, 0.65), N);
|
|
|
|
// Avoid flipping normals so far that shadows/specular explode.
|
|
if (dot(outN, N) < 0.25)
|
|
outN = SafeNormalizeLocal(lerp(N, outN, 0.45), N);
|
|
|
|
return outN;
|
|
}
|
|
|
|
float3 StabilizeSkeletalNormal(uint2 pixel, float3 worldPos, float3 N)
|
|
{
|
|
int2 p = int2(pixel);
|
|
int2 maxP = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
|
|
float3 accum = N * 3.0;
|
|
float weightSum = 3.0;
|
|
|
|
[unroll]
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
[unroll]
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
if (x == 0 && y == 0)
|
|
continue;
|
|
|
|
int2 sp = clamp(p + int2(x, y), int2(0, 0), maxP);
|
|
float4 samplePos = gPositionTex.Load(int3(sp, 0));
|
|
uint sampleGeoFlag = DecodeGeometryFlag(samplePos.w);
|
|
if ((sampleGeoFlag & GEOMETRY_FLAG_SKELETAL) == 0u)
|
|
continue;
|
|
|
|
float3 sampleDelta = samplePos.xyz - worldPos;
|
|
float distSq = dot(sampleDelta, sampleDelta);
|
|
if (distSq > 16.0)
|
|
continue;
|
|
|
|
float3 sampleN = SafeNormalizeLocal(gNormalTex.Load(int3(sp, 0)).xyz, N);
|
|
if (dot(sampleN, N) < 0.0)
|
|
sampleN = -sampleN;
|
|
|
|
float nd = saturate(dot(sampleN, N));
|
|
if (nd < 0.20)
|
|
continue;
|
|
|
|
float w = nd * rcp(1.0 + distSq * 0.35);
|
|
accum += sampleN * w;
|
|
weightSum += w;
|
|
}
|
|
}
|
|
|
|
float3 smoothed = SafeNormalizeLocal(accum / max(weightSum, 1.0e-4), N);
|
|
return SafeNormalizeLocal(lerp(N, smoothed, 0.70), N);
|
|
}
|
|
|
|
float3 TransformObjectNormalToWorld(float3 n)
|
|
{
|
|
float3x4 objectToWorld = ObjectToWorld3x4();
|
|
float3 worldN = float3(
|
|
dot(objectToWorld[0].xyz, n),
|
|
dot(objectToWorld[1].xyz, n),
|
|
dot(objectToWorld[2].xyz, n));
|
|
return SafeNormalizeLocal(worldN, n);
|
|
}
|
|
|
|
void DecodeRayMaterialAtHit(uint meshIndex, BuiltInTriangleIntersectionAttributes attr, out float3 hitNormal, out float2 hitTexCoord)
|
|
{
|
|
MeshMetadata meta = GetMeshMetadataSafe(meshIndex);
|
|
uint localTriangleIndex = PrimitiveIndex() * 3u;
|
|
|
|
hitNormal = float3(0.0, 0.0, 0.0);
|
|
hitTexCoord = float2(0.0, 0.0);
|
|
|
|
if (meta.indexCount < localTriangleIndex + 3u || meta.vertexCount == 0u)
|
|
return;
|
|
|
|
uint i0 = gMaterialIndices[meta.indexOffset + localTriangleIndex + 0u];
|
|
uint i1 = gMaterialIndices[meta.indexOffset + localTriangleIndex + 1u];
|
|
uint i2 = gMaterialIndices[meta.indexOffset + localTriangleIndex + 2u];
|
|
|
|
RayMaterialVertex v0 = gMaterialVertices[i0];
|
|
RayMaterialVertex v1 = gMaterialVertices[i1];
|
|
RayMaterialVertex v2 = gMaterialVertices[i2];
|
|
|
|
float3 bary = float3(1.0 - attr.barycentrics.x - attr.barycentrics.y, attr.barycentrics.x, attr.barycentrics.y);
|
|
float3 localN = SafeNormalizeLocal(v0.normal * bary.x + v1.normal * bary.y + v2.normal * bary.z, float3(0.0, 0.0, 1.0));
|
|
|
|
hitNormal = TransformObjectNormalToWorld(localN);
|
|
hitTexCoord = v0.texCoord * bary.x + v1.texCoord * bary.y + v2.texCoord * bary.z;
|
|
}
|
|
|
|
[shader("miss")]
|
|
void ShadowMiss(inout ShadowPayload payload)
|
|
{
|
|
payload.hit = 0;
|
|
}
|
|
|
|
[shader("anyhit")]
|
|
void ShadowAnyHit(inout ShadowPayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Glass/alpha-blended surfaces should participate in the primary/raster image,
|
|
// but visibility rays must continue through them. Mark the BLAS geometry
|
|
// non-opaque on the CPU side so this any-hit shader runs, then IgnoreHit()
|
|
// lets the ray keep going to whatever is behind the pane/sprite.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
IgnoreHit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
[shader("closesthit")]
|
|
void ShadowClosestHit(inout ShadowPayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Safety fallback for incorrectly-built glass geometry. Correct glass meshes
|
|
// are non-opaque and are ignored by ShadowAnyHit() above.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
payload.hit = 0;
|
|
return;
|
|
}
|
|
|
|
payload.hit = 1;
|
|
}
|
|
|
|
[shader("miss")]
|
|
void BounceMiss(inout BouncePayload payload)
|
|
{
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = 0;
|
|
payload.meshIndex = 0;
|
|
payload.hitNormal = 0.0;
|
|
payload.hitTexCoord = 0.0;
|
|
}
|
|
|
|
[shader("anyhit")]
|
|
void BounceAnyHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Secondary diffuse rays should see through the same transparent surfaces that
|
|
// shadow rays see through. This keeps a pane/sprite from killing all bounced
|
|
// light behind it.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
IgnoreHit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
[shader("closesthit")]
|
|
void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Fallback for transparent geometry that was accidentally built opaque.
|
|
// Correctly tagged transparent surfaces are ignored in BounceAnyHit() above.
|
|
if (CurrentRayHitIsGlass())
|
|
{
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = DecodeInstanceMaterialFlags();
|
|
payload.meshIndex = InstanceID() & GL_RAYTRACING_INSTANCE_USER_ID_MASK;
|
|
payload.hitNormal = 0.0;
|
|
payload.hitTexCoord = 0.0;
|
|
return;
|
|
}
|
|
|
|
payload.hit = 1;
|
|
payload.hitT = RayTCurrent();
|
|
payload.materialFlags = DecodeInstanceMaterialFlags();
|
|
payload.meshIndex = InstanceID() & GL_RAYTRACING_INSTANCE_USER_ID_MASK;
|
|
DecodeRayMaterialAtHit(payload.meshIndex, attr, payload.hitNormal, payload.hitTexCoord);
|
|
}
|
|
)"
|
|
R"(
|
|
[shader("closesthit")]
|
|
void ReflectionClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr)
|
|
{
|
|
// Specular rays are view rays, not diffuse/visibility rays. They should
|
|
// report the first reflected surface even when that surface was tagged as
|
|
// glass/alpha, otherwise glass panes/sprites vanish from mirror-like hits.
|
|
payload.hit = 1;
|
|
payload.hitT = RayTCurrent();
|
|
payload.materialFlags = DecodeInstanceMaterialFlags();
|
|
payload.meshIndex = InstanceID() & GL_RAYTRACING_INSTANCE_USER_ID_MASK;
|
|
DecodeRayMaterialAtHit(payload.meshIndex, attr, payload.hitNormal, payload.hitTexCoord);
|
|
}
|
|
)"
|
|
R"(
|
|
float TraceShadow(float3 origin, float3 dir, float maxT)
|
|
{
|
|
RayDesc ray;
|
|
ray.Origin = origin;
|
|
ray.Direction = dir;
|
|
ray.TMin = 5.0;
|
|
ray.TMax = maxT;
|
|
|
|
ShadowPayload payload;
|
|
payload.hit = 0;
|
|
|
|
TraceRay(
|
|
gSceneBVH,
|
|
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH,
|
|
0xFF,
|
|
0,
|
|
0,
|
|
0,
|
|
ray,
|
|
payload);
|
|
|
|
return (payload.hit != 0) ? 0.0 : 1.0;
|
|
}
|
|
|
|
bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags, out uint meshIndex, out float3 hitNormal, out float2 hitTexCoord)
|
|
{
|
|
RayDesc ray;
|
|
ray.Origin = origin;
|
|
ray.Direction = dir;
|
|
ray.TMin = 0.001;
|
|
ray.TMax = maxT;
|
|
|
|
BouncePayload payload;
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = 0;
|
|
payload.meshIndex = 0;
|
|
payload.hitNormal = 0.0;
|
|
payload.hitTexCoord = 0.0;
|
|
|
|
TraceRay(
|
|
gSceneBVH,
|
|
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH,
|
|
0xFF,
|
|
1,
|
|
0,
|
|
1,
|
|
ray,
|
|
payload);
|
|
|
|
hitT = payload.hitT;
|
|
materialFlags = payload.materialFlags;
|
|
meshIndex = payload.meshIndex;
|
|
hitNormal = payload.hitNormal;
|
|
hitTexCoord = payload.hitTexCoord;
|
|
return payload.hit != 0;
|
|
}
|
|
|
|
bool TraceSpecularReflection(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags, out uint meshIndex, out float3 hitNormal, out float2 hitTexCoord)
|
|
{
|
|
RayDesc ray;
|
|
ray.Origin = origin;
|
|
ray.Direction = dir;
|
|
ray.TMin = 0.001;
|
|
ray.TMax = maxT;
|
|
|
|
BouncePayload payload;
|
|
payload.hit = 0;
|
|
payload.hitT = 0.0;
|
|
payload.materialFlags = 0;
|
|
payload.meshIndex = 0;
|
|
payload.hitNormal = 0.0;
|
|
payload.hitTexCoord = 0.0;
|
|
|
|
TraceRay(
|
|
gSceneBVH,
|
|
RAY_FLAG_NONE,
|
|
0xFF,
|
|
2,
|
|
0,
|
|
1,
|
|
ray,
|
|
payload);
|
|
|
|
hitT = payload.hitT;
|
|
materialFlags = payload.materialFlags;
|
|
meshIndex = payload.meshIndex;
|
|
hitNormal = payload.hitNormal;
|
|
hitTexCoord = payload.hitTexCoord;
|
|
return payload.hit != 0;
|
|
}
|
|
)"
|
|
R"(
|
|
float Hash12(float2 p)
|
|
{
|
|
float3 p3 = frac(float3(p.xyx) * 0.1031);
|
|
p3 += dot(p3, p3.yzx + 33.33);
|
|
return frac((p3.x + p3.y) * p3.z);
|
|
}
|
|
|
|
float Hash13(float3 p)
|
|
{
|
|
p = frac(p * 0.1031);
|
|
p += dot(p, p.yzx + 33.33);
|
|
return frac((p.x + p.y) * p.z);
|
|
}
|
|
|
|
float ValueNoise3D(float3 p)
|
|
{
|
|
float3 i = floor(p);
|
|
float3 f = frac(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
|
|
float n000 = Hash13(i + float3(0, 0, 0));
|
|
float n100 = Hash13(i + float3(1, 0, 0));
|
|
float n010 = Hash13(i + float3(0, 1, 0));
|
|
float n110 = Hash13(i + float3(1, 1, 0));
|
|
float n001 = Hash13(i + float3(0, 0, 1));
|
|
float n101 = Hash13(i + float3(1, 0, 1));
|
|
float n011 = Hash13(i + float3(0, 1, 1));
|
|
float n111 = Hash13(i + float3(1, 1, 1));
|
|
|
|
float nx00 = lerp(n000, n100, f.x);
|
|
float nx10 = lerp(n010, n110, f.x);
|
|
float nx01 = lerp(n001, n101, f.x);
|
|
float nx11 = lerp(n011, n111, f.x);
|
|
float nxy0 = lerp(nx00, nx10, f.y);
|
|
float nxy1 = lerp(nx01, nx11, f.y);
|
|
return lerp(nxy0, nxy1, f.z);
|
|
}
|
|
|
|
void ApplyWorldGrime(float3 worldPos, float cavity, float ao, bool isSkeletal, inout float3 baseAlbedo, inout float3 specularAlbedo)
|
|
{
|
|
if (isSkeletal || gWorldGrimeAmount <= 0.0001)
|
|
return;
|
|
|
|
float crevice = saturate((1.0 - cavity) * 1.35 + (1.0 - ao) * 0.55);
|
|
float largeNoise = ValueNoise3D(worldPos * 0.025);
|
|
float fineNoise = ValueNoise3D(worldPos * 0.11 + float3(17.0, 41.0, 9.0));
|
|
float grimeMask = saturate(crevice * (0.65 + 0.55 * largeNoise) + fineNoise * 0.18);
|
|
float amount = saturate(grimeMask * gWorldGrimeAmount);
|
|
|
|
float3 dirtTint = float3(0.36, 0.31, 0.24);
|
|
baseAlbedo = lerp(baseAlbedo, baseAlbedo * dirtTint, amount);
|
|
specularAlbedo *= lerp(1.0, 0.55, amount);
|
|
}
|
|
|
|
float2 Hammersley2D(uint i, uint N, float rand)
|
|
{
|
|
float e1 = frac((float)i / (float)N + rand);
|
|
|
|
uint bits = i;
|
|
bits = (bits << 16) | (bits >> 16);
|
|
bits = ((bits & 0x55555555u) << 1) | ((bits & 0xAAAAAAAAu) >> 1);
|
|
bits = ((bits & 0x33333333u) << 2) | ((bits & 0xCCCCCCCCu) >> 2);
|
|
bits = ((bits & 0x0F0F0F0Fu) << 4) | ((bits & 0xF0F0F0F0u) >> 4);
|
|
bits = ((bits & 0x00FF00FFu) << 8) | ((bits & 0xFF00FF00u) >> 8);
|
|
|
|
float e2 = (float)bits * 2.3283064365386963e-10;
|
|
return float2(e1, e2);
|
|
}
|
|
|
|
float2 ConcentricSampleDisk(float2 u)
|
|
{
|
|
float2 uOffset = 2.0 * u - 1.0;
|
|
|
|
if (uOffset.x == 0.0 && uOffset.y == 0.0)
|
|
return float2(0.0, 0.0);
|
|
|
|
float r, theta;
|
|
if (abs(uOffset.x) > abs(uOffset.y))
|
|
{
|
|
r = uOffset.x;
|
|
theta = (3.14159265 / 4.0) * (uOffset.y / uOffset.x);
|
|
}
|
|
else
|
|
{
|
|
r = uOffset.y;
|
|
theta = (3.14159265 / 2.0) - (3.14159265 / 4.0) * (uOffset.x / uOffset.y);
|
|
}
|
|
|
|
return r * float2(cos(theta), sin(theta));
|
|
}
|
|
|
|
void BuildOrthonormalBasis(float3 n, out float3 t, out float3 b)
|
|
{
|
|
float3 up = (abs(n.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(0.0, 1.0, 0.0);
|
|
t = normalize(cross(up, n));
|
|
b = cross(n, t);
|
|
}
|
|
|
|
float3 CosineSampleHemisphere(float2 u)
|
|
{
|
|
float2 d = ConcentricSampleDisk(u);
|
|
float z = sqrt(saturate(1.0 - dot(d, d)));
|
|
return float3(d.x, d.y, z);
|
|
}
|
|
|
|
float3 GetPointLightRadius(Light Lgt)
|
|
{
|
|
float scalarRadius = max(abs(Lgt.radius), 1e-4);
|
|
float3 r = abs(Lgt.pointRadius);
|
|
|
|
// Allow older/zero-initialized light records to behave like the old scalar radius.
|
|
if (max(max(r.x, r.y), r.z) <= 1e-4)
|
|
{
|
|
r = float3(scalarRadius, scalarRadius, scalarRadius);
|
|
}
|
|
|
|
return max(r, float3(1e-4, 1e-4, 1e-4));
|
|
}
|
|
|
|
float GetPointLightMaxRadius(Light Lgt)
|
|
{
|
|
float3 r = GetPointLightRadius(Lgt);
|
|
return max(max(r.x, r.y), r.z);
|
|
}
|
|
)"
|
|
R"(
|
|
float3 Doom3SafeNormalizeOr(float3 v, float3 fallbackDir)
|
|
{
|
|
float lenSq = dot(v, v);
|
|
return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallbackDir;
|
|
}
|
|
|
|
)"
|
|
R"(
|
|
float Doom3QuadraticFalloffImage(float texCoord)
|
|
{
|
|
// Math version of Doom 3 BFG's built-in _quadratic lookup table.
|
|
// The source table is 32 texels wide, brightest at the center and clamped
|
|
// to black outside the light volume.
|
|
if (texCoord <= 0.0 || texCoord >= 1.0)
|
|
return 0.0;
|
|
|
|
const float QUADRATIC_WIDTH = 32.0;
|
|
|
|
// Convert a normalized lookup coordinate to the source generator's texel-space
|
|
// x value, then apply the same centered squared ramp used by R_QuadraticImage().
|
|
float x = texCoord * QUADRATIC_WIDTH - 0.5;
|
|
float d = x - (QUADRATIC_WIDTH * 0.5 - 0.5);
|
|
d = abs(d);
|
|
d -= 0.5;
|
|
d /= (QUADRATIC_WIDTH * 0.5);
|
|
d = 1.0 - d;
|
|
d = saturate(d);
|
|
return d * d;
|
|
}
|
|
)"
|
|
R"(
|
|
float Doom3QuadraticCentered(float centeredCoord)
|
|
{
|
|
// centeredCoord is -1 at one side of the light volume, 0 at the light center,
|
|
// and +1 at the opposite side.
|
|
return Doom3QuadraticFalloffImage(centeredCoord * 0.5 + 0.5);
|
|
}
|
|
|
|
float Doom3ProjectionTexture2D(float2 centeredCoord)
|
|
{
|
|
// Doom 3 multiplies a projected light image by a separate falloff image. This
|
|
// renderer does not bind Doom light materials/cookies, so use the same built-in
|
|
// quadratic shape on S/T as a neutral default projection texture approximation.
|
|
if (abs(centeredCoord.x) >= 1.0 || abs(centeredCoord.y) >= 1.0)
|
|
return 0.0;
|
|
|
|
return Doom3QuadraticCentered(centeredCoord.x) * Doom3QuadraticCentered(centeredCoord.y);
|
|
}
|
|
|
|
int WrapIESHorizontalCoord(int x)
|
|
{
|
|
x = x % 512;
|
|
return (x < 0) ? (x + 512) : x;
|
|
}
|
|
|
|
float SampleIESTexture(Light Lgt, float3 lightToSurface)
|
|
{
|
|
if (Lgt.iesTextureIndex == 0u || Lgt.iesStrength <= 0.0)
|
|
return 1.0;
|
|
|
|
float3 dir = Doom3SafeNormalizeOr(lightToSurface, Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0)));
|
|
float3 axisW = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
|
|
float3 axisU = Doom3SafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
|
|
float3 axisV = Doom3SafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
|
|
|
|
float localX = dot(dir, axisU);
|
|
float localY = dot(dir, axisV);
|
|
float localZ = dot(dir, axisW);
|
|
|
|
float horizontal = atan2(localY, localX) * (0.15915494309189535) + 0.5;
|
|
float vertical = acos(clamp(localZ, -1.0, 1.0)) * (0.3183098861837907);
|
|
|
|
uint texIndex = min(Lgt.iesTextureIndex - 1u, 7u);
|
|
float u = frac(horizontal) * 512.0 - 0.5;
|
|
float v = saturate(vertical) * 256.0 - 0.5;
|
|
int x0 = (int)floor(u);
|
|
int y0 = (int)floor(v);
|
|
float tx = frac(u);
|
|
float ty = frac(v);
|
|
int x1 = x0 + 1;
|
|
int y1 = y0 + 1;
|
|
|
|
x0 = WrapIESHorizontalCoord(x0);
|
|
x1 = WrapIESHorizontalCoord(x1);
|
|
y0 = clamp(y0, 0, 255);
|
|
y1 = clamp(y1, 0, 255);
|
|
|
|
float p00 = gIesTextures[texIndex].Load(int3(x0, y0, 0)).r;
|
|
float p10 = gIesTextures[texIndex].Load(int3(x1, y0, 0)).r;
|
|
float p01 = gIesTextures[texIndex].Load(int3(x0, y1, 0)).r;
|
|
float p11 = gIesTextures[texIndex].Load(int3(x1, y1, 0)).r;
|
|
float profile = lerp(lerp(p00, p10, tx), lerp(p01, p11, tx), ty);
|
|
return lerp(1.0, profile, saturate(Lgt.iesStrength));
|
|
}
|
|
|
|
float ApplyIESProfile(float attenuation, Light Lgt, float3 lightToSurface)
|
|
{
|
|
if (attenuation <= 0.0 || Lgt.iesTextureIndex == 0u || Lgt.iesStrength <= 0.0)
|
|
return attenuation;
|
|
|
|
return attenuation * SampleIESTexture(Lgt, lightToSurface);
|
|
}
|
|
)"
|
|
R"(
|
|
float Doom3ProjectedDepthFalloff(float depth, float nearClip, float farClip)
|
|
{
|
|
// For spot/projected lights, the renderer API supplies a conventional near/far
|
|
// range. Sample the bright-to-far half of Doom's centered falloff image so the
|
|
// light is strongest at the projector and fades out at the far plane.
|
|
float depth01 = saturate((depth - nearClip) / max(farClip - nearClip, 1e-4));
|
|
return Doom3QuadraticFalloffImage(0.5 + depth01 * 0.5);
|
|
}
|
|
|
|
float ComputePointLightAttenuation(float3 worldPos, Light Lgt)
|
|
{
|
|
float3 radii = GetPointLightRadius(Lgt);
|
|
float3 offset = worldPos - Lgt.position;
|
|
|
|
// Doom 3 point lights are box/projector lights, not inverse-square or
|
|
// ellipsoidal distance lights. S/T sample the projected light image and the
|
|
// third axis samples lightFalloffImage. Use the light axes when provided so
|
|
// rectangular radii behave like idTech4 light volumes.
|
|
float3 axisU = Doom3SafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
|
|
float3 axisV = Doom3SafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
|
|
float3 axisW = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
|
|
|
|
float u = dot(offset, axisU) / radii.x;
|
|
float v = dot(offset, axisV) / radii.y;
|
|
float w = dot(offset, axisW) / radii.z;
|
|
|
|
if (abs(u) >= 1.0 || abs(v) >= 1.0 || abs(w) >= 1.0)
|
|
return 0.0;
|
|
|
|
float projection = Doom3ProjectionTexture2D(float2(u, v));
|
|
float falloff = Doom3QuadraticCentered(w);
|
|
|
|
return projection * falloff;
|
|
}
|
|
)"
|
|
R"(
|
|
float ComputeSpotLightAttenuation(float3 worldPos, Light Lgt)
|
|
{
|
|
float3 lightToSurface = worldPos - Lgt.position;
|
|
|
|
float nearClip = max(Lgt.pointRadius.x, 0.0);
|
|
float farClip = max(Lgt.radius, nearClip + 1e-4);
|
|
|
|
float3 spotDir = Doom3SafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0));
|
|
|
|
float depth = dot(lightToSurface, spotDir);
|
|
|
|
if (Lgt.iesTextureIndex != 0u && Lgt.iesStrength > 0.0)
|
|
{
|
|
if (depth <= 1e-4 || depth >= farClip)
|
|
return 0.0;
|
|
|
|
float t = saturate(depth / max(farClip, 1e-4));
|
|
return 1.0 - smoothstep(0.985, 1.0, t);
|
|
}
|
|
|
|
if (depth <= nearClip || depth >= farClip)
|
|
return 0.0;
|
|
|
|
float3 axisU = Doom3SafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0));
|
|
float3 axisV = Doom3SafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0));
|
|
|
|
float invDepth = 1.0 / max(depth, 1e-4);
|
|
|
|
float halfU = max(abs(Lgt.halfWidth), 1e-4);
|
|
float halfV = max(abs(Lgt.halfHeight), 1e-4);
|
|
|
|
float signedU = (dot(lightToSurface, axisU) * invDepth) / halfU;
|
|
float signedV = (dot(lightToSurface, axisV) * invDepth) / halfV;
|
|
|
|
if (abs(signedU) >= 1.0 || abs(signedV) >= 1.0)
|
|
return 0.0;
|
|
|
|
float projection = Doom3ProjectionTexture2D(float2(signedU, signedV));
|
|
|
|
float range = max(farClip - nearClip, 1e-4);
|
|
float t = saturate((depth - nearClip) / range);
|
|
|
|
/*
|
|
Stronger Doom 3-style projected-light behavior:
|
|
almost no depth attenuation, only a tiny fade at the very end.
|
|
*/
|
|
float falloff = 1.0 - smoothstep(0.985, 1.0, t);
|
|
|
|
/*
|
|
Slight projected-light boost.
|
|
This helps match Doom 3's aggressive light volumes without changing shape.
|
|
*/
|
|
float boost = 1.35;
|
|
|
|
return saturate(projection * falloff * boost);
|
|
}
|
|
|
|
float TraceSpotShadow(float3 worldPos, float3 N, float3 toLight, float dist)
|
|
{
|
|
float3 L = toLight / max(dist, 1e-6);
|
|
|
|
float NdotLRaw = saturate(dot(N, L));
|
|
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NdotLRaw);
|
|
|
|
float3 shadowOrigin = worldPos + N * normalBias + L * (gShadowBias * 0.5);
|
|
float shadowTMax = max(dist - gShadowBias * 0.5, 0.001);
|
|
|
|
return TraceShadow(shadowOrigin, L, shadowTMax);
|
|
}
|
|
|
|
float TraceSoftShadow(float3 worldPos, float3 N, Light Lgt, float3 toLight, float dist)
|
|
{
|
|
const uint SHADOW_SAMPLES = 4;
|
|
|
|
float3 L = toLight / max(dist, 1e-6);
|
|
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(L, tangent, bitangent);
|
|
|
|
float areaRadius = max(GetPointLightMaxRadius(Lgt) * 0.03, 0.12);
|
|
|
|
float shadowAccum = 0.0;
|
|
float rand = Hash12(worldPos.xy + float2(worldPos.z, dist));
|
|
|
|
[unroll]
|
|
for (uint s = 0; s < SHADOW_SAMPLES; ++s)
|
|
{
|
|
float2 xi = Hammersley2D(s, SHADOW_SAMPLES, rand);
|
|
float2 d = ConcentricSampleDisk(xi) * areaRadius;
|
|
|
|
float3 sampleLightPos = Lgt.position + tangent * d.x + bitangent * d.y;
|
|
float3 sampleVec = sampleLightPos - worldPos;
|
|
float sampleDist = length(sampleVec);
|
|
|
|
if (sampleDist <= 1e-4)
|
|
{
|
|
shadowAccum += 1.0;
|
|
continue;
|
|
}
|
|
|
|
float3 sampleDir = sampleVec / sampleDist;
|
|
|
|
float NdotLRaw = saturate(dot(N, sampleDir));
|
|
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NdotLRaw);
|
|
|
|
float3 shadowOrigin = worldPos + N * normalBias + sampleDir * (gShadowBias * 0.5);
|
|
float shadowTMax = max(sampleDist - gShadowBias * 0.5, 0.001);
|
|
|
|
shadowAccum += TraceShadow(shadowOrigin, sampleDir, shadowTMax);
|
|
}
|
|
|
|
return shadowAccum / (float)SHADOW_SAMPLES;
|
|
}
|
|
)"
|
|
R"(
|
|
float RectLightShadow(float3 worldPos, float3 N, Light Lgt, uint2 pixel)
|
|
{
|
|
uint sampleCount = max(Lgt.samples, 1u);
|
|
sampleCount = min(sampleCount, 4u);
|
|
|
|
float visibility = 0.0;
|
|
|
|
float rand = Hash12((float2)pixel + worldPos.xy + float2(worldPos.z, dot(N.xy, N.xy)));
|
|
|
|
float NoL_center = saturate(dot(N, normalize(Lgt.position - worldPos)));
|
|
float normalBias = lerp(gShadowBias * 4.0, gShadowBias * 0.75, NoL_center);
|
|
float3 baseOrigin = worldPos + N * normalBias;
|
|
|
|
[loop]
|
|
for (uint s = 0; s < sampleCount; ++s)
|
|
{
|
|
float2 xi = Hammersley2D(s, sampleCount, rand);
|
|
float2 uv = xi * 2.0 - 1.0;
|
|
|
|
float3 sampleLightPos =
|
|
Lgt.position +
|
|
Lgt.axisU * (uv.x * Lgt.halfWidth) +
|
|
Lgt.axisV * (uv.y * Lgt.halfHeight);
|
|
|
|
float3 toLight = sampleLightPos - baseOrigin;
|
|
float distToLight = length(toLight);
|
|
|
|
if (distToLight <= 1e-4)
|
|
{
|
|
visibility += 1.0;
|
|
continue;
|
|
}
|
|
|
|
float3 L = toLight / distToLight;
|
|
|
|
float NdotL = dot(N, L);
|
|
if (NdotL <= 0.0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float emitTerm = (Lgt.twoSided != 0)
|
|
? abs(dot(Lgt.normal, -L))
|
|
: dot(Lgt.normal, -L);
|
|
|
|
if (emitTerm <= 0.0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float3 shadowOrigin = baseOrigin + L * (gShadowBias * 0.5);
|
|
float shadowTMax = max(distToLight - gShadowBias, 0.001);
|
|
|
|
visibility += TraceShadow(shadowOrigin, L, shadowTMax);
|
|
}
|
|
|
|
return visibility / (float)sampleCount;
|
|
}
|
|
|
|
float ComputeAmbientOcclusion(float3 worldPos, float3 N, uint2 pixel, bool isSkeletal)
|
|
{
|
|
const uint AO_SAMPLES = 8;
|
|
float aoRadius = isSkeletal ? 18.0 : 32.0;
|
|
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(N, tangent, bitangent);
|
|
|
|
float rand = Hash12((float2)pixel + worldPos.xy + worldPos.zz);
|
|
|
|
float visibility = 0.0;
|
|
|
|
[unroll]
|
|
for (uint i = 0; i < AO_SAMPLES; ++i)
|
|
{
|
|
float2 xi = Hammersley2D(i, AO_SAMPLES, rand);
|
|
float3 h = CosineSampleHemisphere(xi);
|
|
|
|
float3 aoDir =
|
|
tangent * h.x +
|
|
bitangent * h.y +
|
|
N * h.z;
|
|
|
|
aoDir = normalize(aoDir);
|
|
|
|
float3 aoOrigin = worldPos + N * (gShadowBias * (isSkeletal ? 0.75 : 0.15));
|
|
|
|
visibility += TraceShadow(aoOrigin, aoDir, aoRadius);
|
|
}
|
|
|
|
visibility /= (float)AO_SAMPLES;
|
|
visibility = saturate(pow(visibility, 1.5));
|
|
|
|
if (isSkeletal)
|
|
visibility = lerp(visibility, 1.0, 0.45);
|
|
|
|
return visibility;
|
|
}
|
|
|
|
float3 GetSkyLightDirection10AM()
|
|
{
|
|
// Direction from the shaded point TO the sky/sun.
|
|
// 10:00 AM style: angled, not straight vertical.
|
|
//
|
|
// Flip X/Y signs if you want the shadows cast the opposite horizontal way.
|
|
return normalize(float3(-0.55, -0.25, 0.80));
|
|
}
|
|
)"
|
|
R"(
|
|
float ComputeSkyVisibility(float3 worldPos, float3 N, uint2 pixel)
|
|
{
|
|
const uint SKY_SAMPLES = 4;
|
|
const float SKY_TMAX = 1000000.0;
|
|
|
|
// Soft angular size of the sky/sun shadow cone.
|
|
// Larger = softer shadows, but more chance of light leaking.
|
|
const float SKY_SOFTNESS = 0.085;
|
|
|
|
float3 skyCenterDir = GetSkyLightDirection10AM();
|
|
|
|
float NoSky = dot(N, skyCenterDir);
|
|
|
|
// Mostly back-facing relative to the sky direction.
|
|
// Return black visibility instead of casting unstable grazing rays.
|
|
if (NoSky <= -0.35)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(skyCenterDir, tangent, bitangent);
|
|
|
|
float normalBias = lerp(gShadowBias * 4.0, gShadowBias * 1.0, saturate(NoSky));
|
|
|
|
float3 baseOrigin =
|
|
worldPos +
|
|
N * normalBias +
|
|
skyCenterDir * (gShadowBias * 2.0);
|
|
|
|
float visibility = 0.0;
|
|
|
|
// IMPORTANT:
|
|
// No per-pixel random rotation here.
|
|
// The old noise came from random hemisphere sky sampling.
|
|
// This keeps the soft shadow sampling pattern stable per pixel/frame.
|
|
[unroll]
|
|
for (uint i = 0; i < SKY_SAMPLES; ++i)
|
|
{
|
|
float2 xi = Hammersley2D(i, SKY_SAMPLES, 0.0);
|
|
float2 d = ConcentricSampleDisk(xi) * SKY_SOFTNESS;
|
|
|
|
float3 skyDir = normalize(
|
|
skyCenterDir +
|
|
tangent * d.x +
|
|
bitangent * d.y);
|
|
|
|
// Do not shoot rays below the world horizon.
|
|
if (skyDir.z <= 0.02)
|
|
{
|
|
visibility += 0.0;
|
|
continue;
|
|
}
|
|
|
|
float sampleFacing = dot(N, skyDir);
|
|
|
|
// Avoid very noisy grazing rays on back-facing surfaces.
|
|
if (sampleFacing <= -0.35)
|
|
{
|
|
visibility += 0.0;
|
|
continue;
|
|
}
|
|
|
|
float3 skyOrigin =
|
|
worldPos +
|
|
N * normalBias +
|
|
skyDir * (gShadowBias * 2.0);
|
|
|
|
visibility += TraceShadow(skyOrigin, skyDir, SKY_TMAX);
|
|
}
|
|
|
|
visibility /= (float)SKY_SAMPLES;
|
|
|
|
// Slightly smooth the binary ray result so it does not look harsh.
|
|
return saturate(visibility);
|
|
}
|
|
|
|
float ComputeCavity(uint2 pixel, float3 worldPos, float3 N)
|
|
{
|
|
static const int2 taps[12] =
|
|
{
|
|
int2(-2, 0), int2( 2, 0),
|
|
int2( 0, -2), int2( 0, 2),
|
|
int2(-2, -2), int2( 2, -2),
|
|
int2(-2, 2), int2( 2, 2),
|
|
int2(-4, 0), int2( 4, 0),
|
|
int2( 0, -4), int2( 0, 4)
|
|
};
|
|
|
|
float accum = 0.0;
|
|
float weightSum = 0.0;
|
|
|
|
[unroll]
|
|
for (int i = 0; i < 12; ++i)
|
|
{
|
|
int2 sp = int2(pixel) + taps[i];
|
|
|
|
if (sp.x < 0 || sp.y < 0 || sp.x >= (int)gScreenSize.x || sp.y >= (int)gScreenSize.y)
|
|
continue;
|
|
|
|
float3 samplePos = gPositionTex.Load(int3(sp, 0)).xyz;
|
|
float3 sampleN = normalize(gNormalTex.Load(int3(sp, 0)).xyz);
|
|
|
|
float3 d = samplePos - worldPos;
|
|
float distSq = dot(d, d);
|
|
|
|
if (distSq > (24.0 * 24.0))
|
|
continue;
|
|
|
|
float nd = dot(N, sampleN);
|
|
if (nd < 0.65)
|
|
continue;
|
|
|
|
float curvature = 1.0 - saturate(nd);
|
|
float w = 1.0 / (1.0 + distSq * 0.02);
|
|
|
|
accum += curvature * w;
|
|
weightSum += w;
|
|
}
|
|
|
|
float cavity = (weightSum > 0.0) ? (accum / weightSum) : 0.0;
|
|
cavity = saturate(cavity * 2.0);
|
|
|
|
return 1.0 - cavity * 0.18;
|
|
}
|
|
|
|
float Doom3SpecularLookup(float x)
|
|
{
|
|
// Doom 3 used a lookup table for specular falloff. A single high-power
|
|
// lobe is too binary with this G-buffer path: small normal-map/grazing
|
|
// differences make some materials lose specular completely. Use a broad
|
|
// plastic lobe plus a tighter hot spot so highlights stay readable without
|
|
// turning into a flat additive wash.
|
|
x = saturate(x);
|
|
|
|
float broad = pow(x, 12.0);
|
|
float tight = pow(x, 48.0);
|
|
|
|
return saturate(broad * 0.55 + tight * 0.85);
|
|
}
|
|
|
|
float SpecularPeak3(float3 c)
|
|
{
|
|
return max(max(c.r, c.g), c.b);
|
|
}
|
|
|
|
bool LooksLikeAuthoredSpecularSample(float4 specSample, float3 baseAlbedo)
|
|
{
|
|
// Normal path: alpha is the raster G-buffer validity bit.
|
|
if (specSample.a > 0.5)
|
|
return true;
|
|
|
|
// Tolerant path: some raster paths/specular inputs write RGB but leave the
|
|
// validity alpha at zero. Do not treat the fallback albedo descriptor as a
|
|
// spec map; when no specular texture is bound, specSample.rgb == baseAlbedo.
|
|
float3 specRgb = saturate(specSample.rgb);
|
|
float3 baseRgb = saturate(baseAlbedo);
|
|
float rgbPeak = SpecularPeak3(specRgb);
|
|
float rgbDiff = length(specRgb - baseRgb);
|
|
|
|
return rgbPeak > 0.025 && rgbDiff > 0.035;
|
|
}
|
|
|
|
float EstimateSpecularNormalVariance(uint2 pixel, float3 centerN)
|
|
{
|
|
centerN = SafeNormalizeLocal(centerN, float3(0.0, 0.0, 1.0));
|
|
|
|
int2 p = int2(pixel);
|
|
int2 maxP = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
|
|
int2 pxp = clamp(p + int2( 1, 0), int2(0, 0), maxP);
|
|
int2 pyp = clamp(p + int2( 0, 1), int2(0, 0), maxP);
|
|
|
|
float3 nR = SafeNormalizeLocal(gNormalTex.Load(int3(pxp, 0)).xyz, centerN);
|
|
float3 nD = SafeNormalizeLocal(gNormalTex.Load(int3(pyp, 0)).xyz, centerN);
|
|
|
|
float v =
|
|
(1.0 - saturate(dot(centerN, nR))) +
|
|
(1.0 - saturate(dot(centerN, nD)));
|
|
|
|
return saturate(v * 1.70);
|
|
}
|
|
|
|
float StabilizeSpecularSurfaceRoughness(uint2 pixel, float3 N, float surfaceRoughness)
|
|
{
|
|
float normalVariance = EstimateSpecularNormalVariance(pixel, N);
|
|
|
|
// Doom 3 normal maps can be extremely high contrast. Let clean painted metal
|
|
// and wet floors stay glossy, but force rougher lobes on noisy pixels so GGX
|
|
// does not turn texel-scale normal detail into broken sparkles.
|
|
float varianceFloor = lerp(0.12, 0.46, smoothstep(0.08, 0.42, normalVariance));
|
|
return clamp(max(surfaceRoughness, varianceFloor), 0.05, 1.0);
|
|
}
|
|
|
|
float3 Doom3SyntheticSpecularMask(uint2 pixel, float3 baseAlbedo)
|
|
{
|
|
// Missing Doom 3 specular maps used to get a bright Phong-era fallback. Build
|
|
// a small dielectric F0 instead, then damp it with normal-map roughness/cavity
|
|
// so cracks, noisy concrete, and damaged surfaces do not become glossy plastic.
|
|
float3 albedo = saturate(baseAlbedo);
|
|
float lum = dot(albedo, float3(0.299, 0.587, 0.114));
|
|
float albedoMax = max(max(albedo.r, albedo.g), albedo.b);
|
|
float albedoMin = min(min(albedo.r, albedo.g), albedo.b);
|
|
float saturation = (albedoMax > 1.0e-4) ? ((albedoMax - albedoMin) / albedoMax) : 0.0;
|
|
|
|
float3 centerN = SafeNormalizeLocal(gNormalTex.Load(int3(pixel, 0)).xyz, float3(0.0, 0.0, 1.0));
|
|
float normalVariance = EstimateSpecularNormalVariance(pixel, centerN);
|
|
|
|
float cleanDiffuse = lerp(0.62, 1.08, smoothstep(0.05, 0.62, lum));
|
|
cleanDiffuse *= lerp(1.0, 0.78, saturate(saturation * 1.25));
|
|
|
|
float cavityDamping = lerp(1.0, 0.48, smoothstep(0.03, 0.32, normalVariance));
|
|
float roughSurfaceDamping = lerp(1.0, 0.58, smoothstep(0.08, 0.45, normalVariance));
|
|
|
|
float f0 = 0.045 * cleanDiffuse * cavityDamping * roughSurfaceDamping;
|
|
f0 = clamp(f0, 0.012, 0.070);
|
|
|
|
float tintAmount = 0.10 * saturate(saturation);
|
|
float3 neutralSpec = float3(f0, f0, f0);
|
|
float3 tintedSpec = lerp(neutralSpec, albedo * f0, tintAmount);
|
|
|
|
return max(tintedSpec, float3(0.006, 0.006, 0.006));
|
|
}
|
|
|
|
float3 ShapeAuthoredDoom3Specular(uint2 pixel, float3 baseAlbedo, float3 authoredSpecular)
|
|
{
|
|
float3 authored = saturate(authoredSpecular);
|
|
float authoredPeak = SpecularPeak3(authored);
|
|
|
|
// Authored black still means "no specular" for Doom materials.
|
|
if (authoredPeak <= 0.002)
|
|
return 0.0;
|
|
|
|
float3 synthetic = Doom3SyntheticSpecularMask(pixel, baseAlbedo);
|
|
|
|
// Doom/idTech spec maps were painted for Phong, often as broad intensity masks.
|
|
// Treat them as strength/tint hints over a physical dielectric baseline instead
|
|
// of feeding their RGB straight into F0.
|
|
float authoredStrength = smoothstep(0.018, 0.68, authoredPeak);
|
|
float strengthScale = lerp(0.85, 4.65, authoredStrength);
|
|
float3 shaped = synthetic * strengthScale;
|
|
|
|
float3 authoredTint = authored / max(authoredPeak, 1.0e-4);
|
|
float tintTrust = saturate((authoredPeak - 0.05) * 0.85);
|
|
shaped = lerp(shaped, authoredTint * SpecularPeak3(shaped), tintTrust);
|
|
|
|
float peak = SpecularPeak3(shaped);
|
|
if (peak > 0.30 && peak > 1.0e-5)
|
|
shaped *= 0.30 / peak;
|
|
|
|
return max(shaped, float3(0.004, 0.004, 0.004));
|
|
}
|
|
|
|
float3 LoadSceneSpecularAlbedo(uint2 pixel, float3 baseAlbedo)
|
|
{
|
|
float4 specSample = gSpecularTex.Load(int3(pixel, 0));
|
|
|
|
// The raster G-buffer writer stores alpha as a validity bit. This matters
|
|
// for black specular maps: black should mean zero specular, not "missing map".
|
|
// Also accept RGB-only specular inputs when they are clearly not the fallback
|
|
// albedo descriptor, which fixes materials whose specular buffer forgot to
|
|
// set the alpha-valid bit.
|
|
if (LooksLikeAuthoredSpecularSample(specSample, baseAlbedo))
|
|
return ShapeAuthoredDoom3Specular(pixel, baseAlbedo, specSample.rgb);
|
|
|
|
return Doom3SyntheticSpecularMask(pixel, baseAlbedo);
|
|
}
|
|
|
|
|
|
float ComputeDiffuseLightingTerm(float3 N, float3 L)
|
|
{
|
|
// Keep the original light volume/range attenuation exactly where it is, but
|
|
// make the surface response less gamey. The old 0.28-0.32 Half-Lambert wrap
|
|
// pushed too much light around silhouettes and into back-facing normal-map
|
|
// detail. This smaller squared wrap keeps Doom/idTech readability while
|
|
// giving a more Lambert-like, physically plausible rolloff.
|
|
float rawNoL = dot(normalize(N), normalize(L));
|
|
|
|
if (gEnableHalfLambert != 0u)
|
|
{
|
|
const float REALISTIC_WRAP = 0.12;
|
|
float wrapped = saturate((rawNoL + REALISTIC_WRAP) / (1.0 + REALISTIC_WRAP));
|
|
return wrapped * wrapped;
|
|
}
|
|
|
|
return saturate(rawNoL);
|
|
}
|
|
|
|
float EstimateSpecularRoughness(float3 specularAlbedo, float surfaceRoughness)
|
|
{
|
|
// Doom 3 spec maps are art-directed intensity/tint masks, not roughness maps.
|
|
// Use the authored/material roughness packed by the raster path, with spec
|
|
// strength only nudging the lobe sharper for bright painted highlights.
|
|
float peak = SpecularPeak3(saturate(specularAlbedo));
|
|
float strength = smoothstep(0.010, 0.145, peak);
|
|
float specDrivenRoughness = lerp(0.68, 0.22, strength);
|
|
float materialRoughness = clamp(surfaceRoughness, 0.05, 1.0);
|
|
float roughness = lerp(specDrivenRoughness, min(specDrivenRoughness, materialRoughness), 0.72);
|
|
return clamp(roughness, 0.18, 0.86);
|
|
}
|
|
|
|
float3 LimitSpecularPeak(float3 c, float peakLimit)
|
|
{
|
|
c = max(c, 0.0);
|
|
float peak = SpecularPeak3(c);
|
|
if (peak > peakLimit && peak > 1.0e-5)
|
|
c *= peakLimit / peak;
|
|
return c;
|
|
}
|
|
|
|
float3 ComputeSpecularShaped(
|
|
float3 N,
|
|
float3 V,
|
|
float3 L,
|
|
float3 lightColor,
|
|
float lightIntensity,
|
|
float atten,
|
|
float shadow,
|
|
float3 specularAlbedo,
|
|
float roughnessBias,
|
|
float specularWrap,
|
|
float f0Blend,
|
|
float fresnelToWhiteScale,
|
|
float energyScale,
|
|
float peakLimit,
|
|
float surfaceRoughness)
|
|
{
|
|
if (gEnableSpecular == 0)
|
|
return 0.0;
|
|
|
|
if (atten <= 0.0 || shadow <= 0.0)
|
|
return 0.0;
|
|
|
|
N = normalize(N);
|
|
V = normalize(V);
|
|
L = normalize(L);
|
|
|
|
float rawNoL = dot(N, L);
|
|
float NoL = (specularWrap > 0.0)
|
|
? saturate((rawNoL + specularWrap) / (1.0 + specularWrap))
|
|
: saturate(rawNoL);
|
|
float NoV = saturate(dot(N, V));
|
|
|
|
if (NoL <= 1.0e-4 || NoV <= 1.0e-4)
|
|
return 0.0;
|
|
|
|
float3 H = Doom3SafeNormalizeOr(L + V, N);
|
|
float NoH = saturate(dot(N, H));
|
|
float VoH = saturate(dot(V, H));
|
|
|
|
if (NoH <= 1.0e-4 || VoH <= 1.0e-4)
|
|
return 0.0;
|
|
|
|
float3 specMask = saturate(specularAlbedo);
|
|
float specPeak = SpecularPeak3(specMask);
|
|
if (specPeak <= 0.001)
|
|
return 0.0;
|
|
|
|
float roughness = clamp(EstimateSpecularRoughness(specMask, surfaceRoughness) + roughnessBias, 0.18, 0.92);
|
|
float a = roughness * roughness;
|
|
float a2 = max(a * a, 1.0e-4);
|
|
|
|
const float PI = 3.14159265;
|
|
|
|
// GGX/Trowbridge-Reitz distribution with Smith masking and Schlick Fresnel.
|
|
// This is still intentionally stylized for the existing idTech-style assets,
|
|
// but it produces more believable view-dependent highlights than the previous
|
|
// additive lookup lobe and does not alter light attenuation distance.
|
|
float dDenom = NoH * NoH * (a2 - 1.0) + 1.0;
|
|
float D = a2 / max(PI * dDenom * dDenom, 1.0e-4);
|
|
|
|
float k = ((roughness + 1.0) * (roughness + 1.0)) * 0.125;
|
|
float Gv = NoV / max(NoV * (1.0 - k) + k, 1.0e-4);
|
|
float Gl = NoL / max(NoL * (1.0 - k) + k, 1.0e-4);
|
|
float G = Gv * Gl;
|
|
|
|
float3 F0 = saturate(lerp(float3(0.025, 0.025, 0.025), specMask, saturate(f0Blend)));
|
|
float3 F = F0 + (1.0 - F0) * (pow(1.0 - VoH, 5.0) * saturate(fresnelToWhiteScale));
|
|
|
|
float specTerm = (D * G) / max(4.0 * NoL * NoV, 1.0e-4);
|
|
specTerm = min(specTerm, 2.85);
|
|
|
|
const float SPECULAR_ENERGY_SCALE = 3.15;
|
|
|
|
float3 specular =
|
|
lightColor *
|
|
lightIntensity *
|
|
atten *
|
|
shadow *
|
|
NoL *
|
|
F *
|
|
specTerm *
|
|
(SPECULAR_ENERGY_SCALE * max(energyScale, 0.0));
|
|
|
|
float legacyLobe = Doom3SpecularLookup(NoH) * saturate(1.0 - roughness * 0.55);
|
|
specular +=
|
|
lightColor *
|
|
lightIntensity *
|
|
atten *
|
|
shadow *
|
|
NoL *
|
|
specMask *
|
|
legacyLobe *
|
|
(0.72 * max(energyScale, 0.0));
|
|
|
|
float clearcoatMask = smoothstep(0.075, 0.22, specPeak) * saturate((0.58 - roughness) / 0.42);
|
|
if (clearcoatMask > 0.0)
|
|
{
|
|
float coatRoughness = clamp(roughness * 0.55, 0.14, 0.36);
|
|
float coatA = coatRoughness * coatRoughness;
|
|
float coatA2 = max(coatA * coatA, 1.0e-4);
|
|
float coatDenom = NoH * NoH * (coatA2 - 1.0) + 1.0;
|
|
float coatD = coatA2 / max(PI * coatDenom * coatDenom, 1.0e-4);
|
|
float coatK = ((coatRoughness + 1.0) * (coatRoughness + 1.0)) * 0.125;
|
|
float coatGv = NoV / max(NoV * (1.0 - coatK) + coatK, 1.0e-4);
|
|
float coatGl = NoL / max(NoL * (1.0 - coatK) + coatK, 1.0e-4);
|
|
float coatF = 0.04 + 0.96 * pow(1.0 - VoH, 5.0);
|
|
float coatTerm = min((coatD * coatGv * coatGl) / max(4.0 * NoL * NoV, 1.0e-4), 2.75);
|
|
|
|
specular +=
|
|
lightColor *
|
|
lightIntensity *
|
|
atten *
|
|
shadow *
|
|
NoL *
|
|
coatF *
|
|
coatTerm *
|
|
(0.30 * clearcoatMask * max(energyScale, 0.0));
|
|
}
|
|
|
|
return LimitSpecularPeak(specular, peakLimit);
|
|
}
|
|
|
|
float3 ComputeSpecular(
|
|
float3 N,
|
|
float3 V,
|
|
float3 L,
|
|
float3 lightColor,
|
|
float lightIntensity,
|
|
float atten,
|
|
float shadow,
|
|
float3 specularAlbedo,
|
|
float surfaceRoughness)
|
|
{
|
|
return ComputeSpecularShaped(
|
|
N,
|
|
V,
|
|
L,
|
|
lightColor,
|
|
lightIntensity,
|
|
atten,
|
|
shadow,
|
|
specularAlbedo,
|
|
-0.04,
|
|
0.12,
|
|
0.82,
|
|
1.0,
|
|
1.70,
|
|
6.5,
|
|
surfaceRoughness);
|
|
}
|
|
|
|
float EstimateSkeletalSubsurfaceMask(float3 baseAlbedo, float3 specularAlbedo)
|
|
{
|
|
// Doom 3 characters have no authored SSS/thickness maps. Use a conservative
|
|
// skin/flesh proxy from the visible albedo and damp it on shiny/metal-like
|
|
// pieces so armor and gear do not glow like wax.
|
|
baseAlbedo = saturate(baseAlbedo);
|
|
|
|
float luma = dot(baseAlbedo, float3(0.299, 0.587, 0.114));
|
|
float warm = saturate((baseAlbedo.r - baseAlbedo.b * 0.55) * 2.35);
|
|
float redBias = saturate((baseAlbedo.r - max(baseAlbedo.g, baseAlbedo.b)) * 3.25 + 0.18);
|
|
float fleshRange = smoothstep(0.035, 0.18, luma) * (1.0 - smoothstep(0.82, 1.0, luma));
|
|
float matte = 1.0 - saturate(SpecularPeak3(specularAlbedo) * 0.85);
|
|
|
|
return saturate((0.08 + warm * 0.42 + redBias * 0.38) * fleshRange * lerp(0.55, 1.0, matte));
|
|
}
|
|
)"
|
|
R"(
|
|
float3 ComputeSkeletalSpecularAlbedo(float3 baseAlbedo, float3 specularAlbedo)
|
|
{
|
|
float subsurfaceMask = EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo);
|
|
|
|
// Old character spec maps were authored for Doom 3's stylized interaction
|
|
// lights. In the path tracer they behave like wet plastic unless we make the
|
|
// BRDF much softer and cap the F0 on skin/flesh-like areas.
|
|
float3 spec = saturate(specularAlbedo);
|
|
float3 softSpec = spec * lerp(0.48, 0.24, subsurfaceMask);
|
|
float cap = lerp(0.16, 0.075, subsurfaceMask);
|
|
return min(softSpec, float3(cap, cap, cap));
|
|
}
|
|
)"
|
|
R"(
|
|
float3 ComputeSkeletalSpecular(
|
|
float3 N,
|
|
float3 V,
|
|
float3 L,
|
|
float3 baseAlbedo,
|
|
float3 lightColor,
|
|
float lightIntensity,
|
|
float atten,
|
|
float shadow,
|
|
float3 specularAlbedo,
|
|
float surfaceRoughness)
|
|
{
|
|
float subsurfaceMask = EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo);
|
|
float3 characterSpec = ComputeSkeletalSpecularAlbedo(baseAlbedo, specularAlbedo);
|
|
|
|
// Character MD5 specular maps often encode art-directed shine, not material
|
|
// F0. Shape the whole BRDF, including grazing Fresnel, so faces and bodies
|
|
// keep a soft oily skin response instead of reflecting like sealed plastic.
|
|
return ComputeSpecularShaped(
|
|
N,
|
|
V,
|
|
L,
|
|
lightColor,
|
|
lightIntensity,
|
|
atten,
|
|
shadow,
|
|
characterSpec,
|
|
lerp(0.10, 0.20, subsurfaceMask),
|
|
0.0,
|
|
0.58,
|
|
lerp(0.32, 0.14, subsurfaceMask),
|
|
lerp(0.40, 0.24, subsurfaceMask),
|
|
lerp(0.55, 0.18, subsurfaceMask),
|
|
max(surfaceRoughness, 0.48));
|
|
}
|
|
|
|
float ComputeSubsurfaceShape(float3 N, float3 V, float3 L)
|
|
{
|
|
N = normalize(N);
|
|
V = normalize(V);
|
|
L = normalize(L);
|
|
|
|
float NoL = dot(N, L);
|
|
float wrap = saturate((NoL + 0.58) / 1.58);
|
|
wrap = wrap * wrap;
|
|
|
|
// Transmission becomes visible around the terminator and when the light is
|
|
// behind the viewed surface, which is the bit missing most painfully on MD5s.
|
|
float back = saturate((-NoL + 0.22) / 1.22);
|
|
float forward = pow(saturate(dot(-L, V)), 2.25);
|
|
float rim = pow(saturate(1.0 - abs(dot(N, V))), 1.60);
|
|
|
|
return saturate(wrap * 0.42 + back * (0.55 + forward * 0.85) + rim * wrap * 0.18);
|
|
}
|
|
)"
|
|
R"(
|
|
float3 ComputeSkeletalSubsurfaceRadiance(
|
|
float3 N,
|
|
float3 V,
|
|
float3 L,
|
|
float3 baseAlbedo,
|
|
float3 lightColor,
|
|
float lightIntensity,
|
|
float atten,
|
|
float shadow,
|
|
float subsurfaceMask)
|
|
{
|
|
if (subsurfaceMask <= 0.001 || atten <= 0.0 || lightIntensity <= 0.0)
|
|
return 0.0;
|
|
|
|
float shape = ComputeSubsurfaceShape(N, V, L);
|
|
if (shape <= 0.001)
|
|
return 0.0;
|
|
|
|
float softShadow = lerp(shadow, 1.0, 0.18 * subsurfaceMask);
|
|
float3 scatterTint = saturate(lerp(
|
|
float3(1.0, 0.30, 0.18),
|
|
saturate(baseAlbedo) * float3(1.22, 0.68, 0.52) + float3(0.10, 0.03, 0.02),
|
|
0.58));
|
|
|
|
const float SUBSURFACE_DIRECT_SCALE = 0.34;
|
|
return clamp(
|
|
lightColor *
|
|
lightIntensity *
|
|
atten *
|
|
softShadow *
|
|
scatterTint *
|
|
shape *
|
|
(subsurfaceMask * SUBSURFACE_DIRECT_SCALE),
|
|
0.0,
|
|
2.25);
|
|
}
|
|
)"
|
|
R"(
|
|
float TraceStraightUpToSky(float3 worldPos, float3 N)
|
|
{
|
|
const float SKY_TMAX = 1000000.0;
|
|
float3 skyDir = float3(0.0, 0.0, 1.0);
|
|
float NoSky = dot(N, skyDir);
|
|
float normalBias = lerp(gShadowBias * 4.0, gShadowBias * 1.0, saturate(NoSky));
|
|
|
|
float3 skyOrigin =
|
|
worldPos +
|
|
N * normalBias +
|
|
skyDir * (gShadowBias * 2.0);
|
|
|
|
return TraceShadow(skyOrigin, skyDir, SKY_TMAX);
|
|
}
|
|
|
|
uint PcgHash(uint input)
|
|
{
|
|
uint state = input * 747796405u + 2891336453u;
|
|
uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
|
|
return (word >> 22u) ^ word;
|
|
}
|
|
|
|
uint InitRng(uint2 pixel, uint frameIndex, uint sampleIndex)
|
|
{
|
|
uint seed = pixel.x * 1973u;
|
|
seed ^= pixel.y * 9277u;
|
|
seed ^= frameIndex * 26699u;
|
|
seed ^= sampleIndex * 374761393u;
|
|
return PcgHash(seed) | 1u;
|
|
}
|
|
|
|
uint InitSpatialRng(uint2 pixel, float3 worldPos, uint salt)
|
|
{
|
|
uint seed = pixel.x * 1973u;
|
|
seed ^= pixel.y * 9277u;
|
|
seed ^= asuint(worldPos.x * 0.03125) * 1597334677u;
|
|
seed ^= asuint(worldPos.y * 0.03125) * 3812015801u;
|
|
seed ^= asuint(worldPos.z * 0.03125) * 2798796415u;
|
|
seed ^= salt * 374761393u;
|
|
return PcgHash(seed) | 1u;
|
|
}
|
|
|
|
uint InitSurfaceStableRng(uint2 pixel, float3 worldPos, float3 normal, uint sampleIndex, uint frameIndex)
|
|
{
|
|
if (gExternalDenoiser == 0u)
|
|
return InitRng(pixel, frameIndex, sampleIndex);
|
|
|
|
uint3 cell = (uint3)floor(abs(worldPos) * 16.0 + 0.5);
|
|
uint3 ncell = (uint3)floor(saturate(normal * 0.5 + 0.5) * 255.0 + 0.5);
|
|
uint seed =
|
|
cell.x * 73856093u ^
|
|
cell.y * 19349663u ^
|
|
cell.z * 83492791u ^
|
|
ncell.x * 1597334677u ^
|
|
ncell.y * 3812015801u ^
|
|
ncell.z * 2798796415u ^
|
|
sampleIndex * 0x9E3779B9u;
|
|
return PcgHash(seed) | 1u;
|
|
}
|
|
|
|
float Rand(inout uint rng)
|
|
{
|
|
rng = PcgHash(rng);
|
|
return (float)rng * 2.3283064365386963e-10;
|
|
}
|
|
|
|
float2 Rand2(inout uint rng)
|
|
{
|
|
return float2(Rand(rng), Rand(rng));
|
|
}
|
|
|
|
float3 SampleCosineWorld(float3 N, inout uint rng)
|
|
{
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(N, tangent, bitangent);
|
|
|
|
float3 localDir = CosineSampleHemisphere(Rand2(rng));
|
|
return normalize(
|
|
tangent * localDir.x +
|
|
bitangent * localDir.y +
|
|
N * localDir.z);
|
|
}
|
|
|
|
float3 SampleCosineWorldXi(float3 N, float2 xi)
|
|
{
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(N, tangent, bitangent);
|
|
|
|
float3 localDir = CosineSampleHemisphere(xi);
|
|
return normalize(
|
|
tangent * localDir.x +
|
|
bitangent * localDir.y +
|
|
N * localDir.z);
|
|
}
|
|
|
|
float3 SampleConeWorld(float3 centerDir, float coneRadius, inout uint rng)
|
|
{
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(centerDir, tangent, bitangent);
|
|
|
|
float2 d = ConcentricSampleDisk(Rand2(rng)) * coneRadius;
|
|
return normalize(centerDir + tangent * d.x + bitangent * d.y);
|
|
}
|
|
|
|
float3 GetSkyRadiance(float3 dir)
|
|
{
|
|
float upness = saturate(dir.z * 0.5 + 0.5);
|
|
|
|
float3 warmSky = float3(0.98, 0.55, 0.35);
|
|
float3 coolSky = float3(0.30, 0.40, 0.62);
|
|
float3 sky = lerp(warmSky * 0.22, coolSky * 0.55, upness);
|
|
|
|
float sunAmount = pow(saturate(dot(dir, GetSkyLightDirection10AM())), 96.0);
|
|
sky += warmSky * (sunAmount * 2.25);
|
|
|
|
return sky;
|
|
}
|
|
|
|
float TraceVisibilityBiased(float3 worldPos, float3 N, float3 dir, float maxT)
|
|
{
|
|
float NoD = saturate(dot(N, dir));
|
|
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoD);
|
|
float3 origin = worldPos + N * normalBias + dir * (gShadowBias * 0.5);
|
|
return TraceShadow(origin, dir, max(maxT - gShadowBias * 0.5, 0.001));
|
|
}
|
|
|
|
|
|
float3 CompressEmissiveRadiance(float3 e, float peakLimit)
|
|
{
|
|
e = max(e, 0.0);
|
|
float peak = max(max(e.r, e.g), e.b);
|
|
if (peak > peakLimit && peak > 1e-5)
|
|
e *= peakLimit / peak;
|
|
return e;
|
|
}
|
|
|
|
float3 LimitStochasticRadiance(float3 c, float lumaLimit, float peakLimit)
|
|
{
|
|
c = max(c, 0.0);
|
|
|
|
float luma = dot(c, float3(0.2126, 0.7152, 0.0722));
|
|
if (luma > lumaLimit && luma > 1.0e-5)
|
|
c *= lumaLimit / luma;
|
|
|
|
float peak = max(max(c.r, c.g), c.b);
|
|
if (peak > peakLimit && peak > 1.0e-5)
|
|
c *= peakLimit / peak;
|
|
|
|
return c;
|
|
}
|
|
|
|
float3 LoadEmissiveRadianceClamped(int2 p)
|
|
{
|
|
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
p = clamp(p, int2(0, 0), maxPixel);
|
|
float3 radiance = CompressEmissiveRadiance(gEmissiveTex.Load(int3(p, 0)).rgb, 18.0);
|
|
return min(radiance + sqrt(max(radiance, 0.0)) * 0.35, 22.0);
|
|
}
|
|
|
|
float3 EstimateEmissiveBloomAtPixel(uint2 pixel)
|
|
{
|
|
int2 p = int2(pixel);
|
|
|
|
// Strong threshold-free bloom in the ray output. Particle emissive lighting
|
|
// is injected separately below; this is the visible camera halo.
|
|
float3 bloom = 0.0;
|
|
bloom += LoadEmissiveRadianceClamped(p) * 0.320;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 1, 0)) * 0.420;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-1, 0)) * 0.420;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 1)) * 0.420;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -1)) * 0.420;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 2, 2)) * 0.260;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-2, 2)) * 0.260;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 2, -2)) * 0.260;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-2, -2)) * 0.260;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 4, 0)) * 0.180;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-4, 0)) * 0.180;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 4)) * 0.180;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -4)) * 0.180;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 8, 0)) * 0.120;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-8, 0)) * 0.120;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 8)) * 0.120;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -8)) * 0.120;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 14, 0)) * 0.075;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-14, 0)) * 0.075;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 14)) * 0.075;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -14)) * 0.075;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 24, 0)) * 0.045;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-24, 0)) * 0.045;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 24)) * 0.045;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -24)) * 0.045;
|
|
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 42, 0)) * 0.025;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2(-42, 0)) * 0.025;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 42)) * 0.025;
|
|
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -42)) * 0.025;
|
|
|
|
return min(bloom * 1.45, 28.0);
|
|
}
|
|
|
|
float3 SafeNormalizeOr(float3 v, float3 fallback)
|
|
{
|
|
float lenSq = dot(v, v);
|
|
if (lenSq <= 1e-8)
|
|
return fallback;
|
|
return v * rsqrt(lenSq);
|
|
}
|
|
|
|
bool TryScreenParticleWorldCandidate(float4 clip, float3 receiverPos, inout float bestDistSq, inout float3 bestWorld)
|
|
{
|
|
float4 worldA = mul(clip, gInvViewProj);
|
|
if (abs(worldA.w) > 1e-6)
|
|
{
|
|
float3 candidate = worldA.xyz / worldA.w;
|
|
float distSq = dot(candidate - receiverPos, candidate - receiverPos);
|
|
if (distSq < bestDistSq && distSq > 1e-4 && distSq < 147456.0)
|
|
{
|
|
bestDistSq = distSq;
|
|
bestWorld = candidate;
|
|
}
|
|
}
|
|
|
|
float4 worldB = mul(gInvViewProj, clip);
|
|
if (abs(worldB.w) > 1e-6)
|
|
{
|
|
float3 candidate = worldB.xyz / worldB.w;
|
|
float distSq = dot(candidate - receiverPos, candidate - receiverPos);
|
|
if (distSq < bestDistSq && distSq > 1e-4 && distSq < 147456.0)
|
|
{
|
|
bestDistSq = distSq;
|
|
bestWorld = candidate;
|
|
}
|
|
}
|
|
|
|
return bestDistSq < 147456.0;
|
|
}
|
|
|
|
bool TryReconstructScreenParticleWorld(uint2 particlePixel, float particleDepth, float3 receiverPos, out float3 particleWorld)
|
|
{
|
|
float2 uv = ((float2)particlePixel + 0.5) / max(gScreenSize.xy, float2(1.0, 1.0));
|
|
float x = uv.x * 2.0 - 1.0;
|
|
float yUp = 1.0 - uv.y * 2.0;
|
|
float yDown = uv.y * 2.0 - 1.0;
|
|
|
|
float bestDistSq = 1.0e30;
|
|
particleWorld = receiverPos;
|
|
TryScreenParticleWorldCandidate(float4(x, yUp, particleDepth, 1.0), receiverPos, bestDistSq, particleWorld);
|
|
TryScreenParticleWorldCandidate(float4(x, yDown, particleDepth, 1.0), receiverPos, bestDistSq, particleWorld);
|
|
return bestDistSq < 147456.0;
|
|
}
|
|
|
|
float3 EstimateScreenSpaceEmissiveParticleLighting(uint2 pixel, float3 worldPos, float3 N)
|
|
{
|
|
static const int2 kParticleLightTaps[17] =
|
|
{
|
|
int2( 0, 0),
|
|
int2( 7, 0), int2( -7, 0), int2( 0, 7), int2( 0, -7),
|
|
int2( 10, 10), int2(-10, 10), int2( 10, -10), int2(-10, -10),
|
|
int2( 18, 4), int2(-18, -4), int2( 4, 18), int2( -4, -18),
|
|
int2( 30, 0), int2(-30, 0), int2( 0, 30), int2( 0, -30)
|
|
};
|
|
|
|
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
float pixelScale = max(1.0, round(min(gScreenSize.x, gScreenSize.y) / 720.0));
|
|
float3 lighting = 0.0;
|
|
|
|
[unroll]
|
|
for (uint i = 0u; i < 17u; ++i)
|
|
{
|
|
int2 sp = int2(pixel) + int2(round((float2)kParticleLightTaps[i] * pixelScale));
|
|
sp = clamp(sp, int2(0, 0), maxPixel);
|
|
|
|
float4 particleEmission = gEmissiveTex.Load(int3(sp, 0));
|
|
if (particleEmission.a <= 1.5)
|
|
continue;
|
|
|
|
float particleDepth = saturate(particleEmission.a - 2.0);
|
|
float3 particleWorld;
|
|
if (!TryReconstructScreenParticleWorld(uint2(sp), particleDepth, worldPos, particleWorld))
|
|
continue;
|
|
|
|
float3 toParticle = particleWorld - worldPos;
|
|
float distSq = dot(toParticle, toParticle);
|
|
if (distSq <= 1e-4 || distSq >= 147456.0)
|
|
continue;
|
|
|
|
float dist = sqrt(distSq);
|
|
float3 L = toParticle / dist;
|
|
float NoL = saturate(dot(N, L));
|
|
if (NoL <= 0.01)
|
|
continue;
|
|
|
|
float visibility = TraceVisibilityBiased(worldPos, N, L, dist);
|
|
if (visibility <= 0.0)
|
|
continue;
|
|
|
|
float distanceFade = saturate(1.0 - dist / 640.0);
|
|
distanceFade *= distanceFade;
|
|
float attenuation = distanceFade / (1.0 + distSq * 0.000075);
|
|
lighting += CompressEmissiveRadiance(particleEmission.rgb, 24.0) * (NoL * visibility * attenuation);
|
|
}
|
|
|
|
return lighting * 9.0;
|
|
}
|
|
|
|
bool ProjectClipToGBufferCandidate(
|
|
float4 clipPos,
|
|
bool flipY,
|
|
float3 rayHitPos,
|
|
inout float bestDistSq,
|
|
inout uint2 bestPixel,
|
|
inout float3 bestPos,
|
|
inout float3 bestNormal,
|
|
inout float3 bestAlbedo,
|
|
inout uint bestGeoFlag)
|
|
{
|
|
if (abs(clipPos.w) <= 1e-6)
|
|
return false;
|
|
|
|
float3 ndc = clipPos.xyz / clipPos.w;
|
|
|
|
if (ndc.x < -1.0 || ndc.x > 1.0 ||
|
|
ndc.y < -1.0 || ndc.y > 1.0 ||
|
|
ndc.z < 0.0 || ndc.z > 1.0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
float2 uv;
|
|
uv.x = ndc.x * 0.5 + 0.5;
|
|
uv.y = flipY ? (0.5 - ndc.y * 0.5) : (ndc.y * 0.5 + 0.5);
|
|
|
|
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)
|
|
return false;
|
|
|
|
int2 ip = int2(uv * gScreenSize.xy);
|
|
ip = clamp(ip, int2(0, 0), int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1));
|
|
|
|
float depth = gDepthTex.Load(int3(ip, 0));
|
|
if (depth <= 0.0 || depth >= 1.0)
|
|
return false;
|
|
|
|
float4 posSample = gPositionTex.Load(int3(ip, 0));
|
|
float3 gbufPos = posSample.xyz;
|
|
float3 delta = gbufPos - rayHitPos;
|
|
float distSq = dot(delta, delta);
|
|
|
|
if (distSq >= bestDistSq)
|
|
return false;
|
|
|
|
float4 normalSample = LoadSceneNormal(uint2(ip));
|
|
float3 gbufNormal = SafeNormalizeOr(normalSample.xyz, float3(0.0, 0.0, 1.0));
|
|
float3 gbufAlbedo = saturate(gAlbedoTex.Load(int3(ip, 0)).rgb);
|
|
|
|
bestDistSq = distSq;
|
|
bestPixel = uint2(ip);
|
|
bestPos = gbufPos;
|
|
bestNormal = gbufNormal;
|
|
bestAlbedo = gbufAlbedo;
|
|
bestGeoFlag = DecodeGeometryFlag(posSample.w);
|
|
return true;
|
|
}
|
|
|
|
bool TryFetchGBufferAtRayHit(
|
|
float3 rayHitPos,
|
|
out uint2 hitPixel,
|
|
out float3 hitPos,
|
|
out float3 hitNormal,
|
|
out float3 hitAlbedo,
|
|
out uint hitGeoFlag)
|
|
{
|
|
float bestDistSq = 1.0e30;
|
|
uint2 bestPixel = uint2(0, 0);
|
|
float3 bestPos = rayHitPos;
|
|
float3 bestNormal = float3(0.0, 0.0, 1.0);
|
|
float3 bestAlbedo = float3(0.5, 0.5, 0.5);
|
|
uint bestGeoFlag = GEOMETRY_FLAG_NONE;
|
|
|
|
// CPU computes gViewProj from the inverse view-projection matrix. Try both
|
|
// matrix-vector orders and both Y conventions so this remains tolerant of
|
|
// row/column-major engine uploads and render-target origin conventions.
|
|
float4 wpos = float4(rayHitPos, 1.0);
|
|
float4 clipA = mul(wpos, gViewProj);
|
|
float4 clipB = mul(gViewProj, wpos);
|
|
|
|
bool found = false;
|
|
found = ProjectClipToGBufferCandidate(clipA, true, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found;
|
|
found = ProjectClipToGBufferCandidate(clipA, false, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found;
|
|
found = ProjectClipToGBufferCandidate(clipB, true, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found;
|
|
found = ProjectClipToGBufferCandidate(clipB, false, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found;
|
|
|
|
if (!found)
|
|
{
|
|
hitPixel = uint2(0, 0);
|
|
hitPos = rayHitPos;
|
|
hitNormal = bestNormal;
|
|
hitAlbedo = bestAlbedo;
|
|
hitGeoFlag = GEOMETRY_FLAG_NONE;
|
|
return false;
|
|
}
|
|
|
|
// The TLAS hit is exact, but the material data comes from the camera G-buffer.
|
|
// Reject projections that land on an unrelated visible surface.
|
|
float viewDist = length(rayHitPos - gCameraPos.xyz);
|
|
float maxPositionError = max(24.0, viewDist * 0.035);
|
|
if (bestDistSq > maxPositionError * maxPositionError)
|
|
{
|
|
hitPixel = bestPixel;
|
|
hitPos = bestPos;
|
|
hitNormal = bestNormal;
|
|
hitAlbedo = bestAlbedo;
|
|
hitGeoFlag = bestGeoFlag;
|
|
return false;
|
|
}
|
|
|
|
hitPixel = bestPixel;
|
|
hitPos = bestPos;
|
|
hitNormal = bestNormal;
|
|
hitAlbedo = bestAlbedo;
|
|
hitGeoFlag = bestGeoFlag;
|
|
return true;
|
|
}
|
|
|
|
float3 EstimatePathTracedSky(float3 worldPos, float3 N, inout uint rng)
|
|
{
|
|
const float SKY_TMAX = 1000000.0;
|
|
|
|
uint bounceSamples = max(gMaxBounces, 1u);
|
|
bounceSamples = min(bounceSamples, 4u);
|
|
|
|
float3 accum = 0.0;
|
|
|
|
[loop]
|
|
for (uint b = 0; b < bounceSamples; ++b)
|
|
{
|
|
float3 dir = SampleCosineWorld(N, rng);
|
|
float NoD = saturate(dot(N, dir));
|
|
float visibility = TraceVisibilityBiased(worldPos, N, dir, SKY_TMAX);
|
|
|
|
// This is a G-buffer path-traced approximation: secondary hits are used as
|
|
// occluders because this pass does not bind per-triangle material data yet.
|
|
accum += GetSkyRadiance(dir) * visibility * NoD;
|
|
}
|
|
|
|
accum /= (float)bounceSamples;
|
|
return accum * 0.55;
|
|
}
|
|
)"
|
|
R"(
|
|
float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, float surfaceRoughness, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut)
|
|
{
|
|
specularOut = 0.0;
|
|
subsurfaceOut = 0.0;
|
|
|
|
float3 toCenter = Lgt.position - worldPos;
|
|
float centerDist = length(toCenter);
|
|
if (centerDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 centerDir = toCenter / centerDist;
|
|
float3 tangent, bitangent;
|
|
BuildOrthonormalBasis(centerDir, tangent, bitangent);
|
|
|
|
float atten = ApplyIESProfile(ComputePointLightAttenuation(worldPos, Lgt), Lgt, worldPos - Lgt.position);
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
uint sampleCount = max(Lgt.samples, 1u);
|
|
sampleCount = min(sampleCount, 4u);
|
|
|
|
// One random area-light sample per frame was one of the visible noise sources.
|
|
// Use a deterministic low-discrepancy pattern instead. With a single sample,
|
|
// use the light center so default point lights are hard-shadowed and stable.
|
|
float areaRadius = (Lgt.samples > 1u) ? max(GetPointLightMaxRadius(Lgt) * 0.03, 0.12) : 0.0;
|
|
float rand = Hash12((float2)pixel + worldPos.xy + float2(worldPos.z, centerDist));
|
|
|
|
float3 diffuseAccum = 0.0;
|
|
float3 specAccum = 0.0;
|
|
float3 sssAccum = 0.0;
|
|
|
|
[loop]
|
|
for (uint s = 0u; s < sampleCount; ++s)
|
|
{
|
|
float2 disk = float2(0.0, 0.0);
|
|
if (areaRadius > 0.0)
|
|
disk = ConcentricSampleDisk(Hammersley2D(s, sampleCount, rand)) * areaRadius;
|
|
|
|
float3 sampleLightPos = Lgt.position + tangent * disk.x + bitangent * disk.y;
|
|
float3 toLight = sampleLightPos - worldPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
continue;
|
|
|
|
float3 L = toLight / dist;
|
|
|
|
float diffuseNoL = ComputeDiffuseLightingTerm(N, L);
|
|
|
|
float shadow = 1.0;
|
|
if (Lgt.samples != 0u && (diffuseNoL > 0.0001 || subsurfaceMask > 0.001))
|
|
shadow = TraceVisibilityBiased(worldPos, N, L, dist);
|
|
|
|
specAccum += isSkeletal
|
|
? ComputeSkeletalSpecular(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness)
|
|
: ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness);
|
|
|
|
sssAccum += ComputeSkeletalSubsurfaceRadiance(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, subsurfaceMask);
|
|
diffuseAccum += Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow);
|
|
}
|
|
|
|
float invSamples = 1.0 / (float)sampleCount;
|
|
specularOut = specAccum * invSamples;
|
|
subsurfaceOut = sssAccum * invSamples;
|
|
return diffuseAccum * invSamples;
|
|
}
|
|
)"
|
|
R"(
|
|
float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, float surfaceRoughness, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut)
|
|
{
|
|
specularOut = 0.0;
|
|
subsurfaceOut = 0.0;
|
|
|
|
float3 toLight = Lgt.position - worldPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 L = toLight / dist;
|
|
float atten = ApplyIESProfile(ComputeSpotLightAttenuation(worldPos, Lgt), Lgt, worldPos - Lgt.position);
|
|
|
|
float diffuseNoL = ComputeDiffuseLightingTerm(N, L);
|
|
|
|
float shadow = 1.0;
|
|
if (Lgt.samples != 0u && (diffuseNoL > 0.0001 || subsurfaceMask > 0.001) && atten > 0.0)
|
|
shadow = TraceVisibilityBiased(worldPos, N, L, dist);
|
|
|
|
specularOut = isSkeletal
|
|
? ComputeSkeletalSpecular(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness)
|
|
: ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness);
|
|
|
|
subsurfaceOut = ComputeSkeletalSubsurfaceRadiance(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, subsurfaceMask);
|
|
return Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow);
|
|
}
|
|
)"
|
|
R"(
|
|
float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, float surfaceRoughness, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut)
|
|
{
|
|
specularOut = 0.0;
|
|
subsurfaceOut = 0.0;
|
|
|
|
float3 toCenter = Lgt.position - worldPos;
|
|
float centerDist = length(toCenter);
|
|
if (centerDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float attenRadius = max(Lgt.radius, 1e-4);
|
|
float atten = saturate((attenRadius - centerDist) / attenRadius);
|
|
atten = atten * atten * atten * atten;
|
|
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
uint sampleCount = max(Lgt.samples, 1u);
|
|
sampleCount = min(sampleCount, 4u);
|
|
float rand = Hash12((float2)pixel + worldPos.xy + float2(centerDist, worldPos.z));
|
|
|
|
float3 diffuseAccum = 0.0;
|
|
float3 specAccum = 0.0;
|
|
float3 sssAccum = 0.0;
|
|
|
|
[loop]
|
|
for (uint s = 0u; s < sampleCount; ++s)
|
|
{
|
|
float2 uv = (sampleCount == 1u)
|
|
? float2(0.0, 0.0)
|
|
: (Hammersley2D(s, sampleCount, rand) * 2.0 - 1.0);
|
|
|
|
float3 sampleLightPos =
|
|
Lgt.position +
|
|
Lgt.axisU * (uv.x * Lgt.halfWidth) +
|
|
Lgt.axisV * (uv.y * Lgt.halfHeight);
|
|
|
|
float3 sampleVec = sampleLightPos - worldPos;
|
|
float sampleDist = length(sampleVec);
|
|
if (sampleDist <= 0.01)
|
|
continue;
|
|
|
|
float3 L = sampleVec / sampleDist;
|
|
float NdotL = ComputeDiffuseLightingTerm(N, L);
|
|
float sssShape = (subsurfaceMask > 0.001) ? ComputeSubsurfaceShape(N, V, L) : 0.0;
|
|
if (NdotL <= 0.0 && sssShape <= 0.001)
|
|
continue;
|
|
|
|
float faceTerm = (Lgt.twoSided != 0)
|
|
? abs(dot(-L, Lgt.normal))
|
|
: saturate(dot(-L, Lgt.normal));
|
|
|
|
if (faceTerm <= 0.0)
|
|
continue;
|
|
|
|
float shadow = 1.0;
|
|
if (Lgt.samples != 0u)
|
|
shadow = TraceVisibilityBiased(worldPos, N, L, sampleDist);
|
|
|
|
specAccum += (isSkeletal
|
|
? ComputeSkeletalSpecular(
|
|
N,
|
|
V,
|
|
L,
|
|
baseAlbedo,
|
|
Lgt.color,
|
|
Lgt.intensity * faceTerm,
|
|
1.0,
|
|
shadow,
|
|
specularAlbedo,
|
|
surfaceRoughness)
|
|
: ComputeSpecular(
|
|
N,
|
|
V,
|
|
L,
|
|
Lgt.color,
|
|
Lgt.intensity * faceTerm,
|
|
1.0,
|
|
shadow,
|
|
specularAlbedo,
|
|
surfaceRoughness)) * atten;
|
|
|
|
sssAccum += ComputeSkeletalSubsurfaceRadiance(
|
|
N,
|
|
V,
|
|
L,
|
|
baseAlbedo,
|
|
Lgt.color,
|
|
Lgt.intensity * faceTerm,
|
|
atten,
|
|
shadow,
|
|
subsurfaceMask);
|
|
|
|
diffuseAccum += clamp(Lgt.color * (Lgt.intensity * NdotL * faceTerm * atten * shadow), 0.0, 4.0);
|
|
}
|
|
|
|
float invSamples = 1.0 / (float)sampleCount;
|
|
specularOut = specAccum * invSamples;
|
|
subsurfaceOut = sssAccum * invSamples;
|
|
return diffuseAccum * invSamples;
|
|
}
|
|
|
|
float HenyeyGreensteinPhase(float cosTheta, float g)
|
|
{
|
|
g = clamp(g, -0.85, 0.85);
|
|
float g2 = g * g;
|
|
float denom = max(1.0 + g2 - 2.0 * g * cosTheta, 1e-3);
|
|
return (1.0 - g2) / max(4.0 * 3.14159265 * pow(denom, 1.5), 1e-3);
|
|
}
|
|
|
|
float ComputeLightVolumeAttenuation(float3 samplePos, Light Lgt)
|
|
{
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
return ComputePointLightAttenuation(samplePos, Lgt);
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
return ComputeSpotLightAttenuation(samplePos, Lgt);
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
float EstimateVolumeDensityFromLight(Light Lgt)
|
|
{
|
|
// Doom 3 world units are large. Tie the default participating-medium density
|
|
// to light range so the caller only needs one artist-facing attribute.
|
|
float range = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
? GetPointLightMaxRadius(Lgt)
|
|
: max(Lgt.radius, 1.0);
|
|
|
|
return clamp(2.25 / max(range, 32.0), 0.0015, 0.035);
|
|
}
|
|
)"
|
|
R"(
|
|
float3 EstimateSingleLightVolumetricScattering(uint2 pixel, float3 cameraPos, float3 worldPos, Light Lgt, inout uint rng)
|
|
{
|
|
if (Lgt.volumetricScattering <= 0.0)
|
|
return 0.0;
|
|
|
|
if (Lgt.type != GL_RAYTRACING_LIGHT_TYPE_POINT && Lgt.type != GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
return 0.0;
|
|
|
|
float3 cameraToSurface = worldPos - cameraPos;
|
|
float viewDist = length(cameraToSurface);
|
|
if (viewDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 viewDir = cameraToSurface / viewDist;
|
|
|
|
uint stepCount = min(max(Lgt.samples, 3u), 6u);
|
|
float stepLen = viewDist / (float)stepCount;
|
|
|
|
// Do not frame-jitter the march. This renderer has no temporal GI history,
|
|
// so varying the volume sample positions every frame creates visible sparkle.
|
|
// A centered deterministic slice is stable and the spatial denoiser can smooth it.
|
|
float jitter = 0.5;
|
|
|
|
float density = EstimateVolumeDensityFromLight(Lgt);
|
|
float anisotropy = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) ? 0.55 : 0.35;
|
|
|
|
float3 accum = 0.0;
|
|
|
|
[loop]
|
|
for (uint s = 0u; s < stepCount; ++s)
|
|
{
|
|
float t = ((float)s + jitter) * stepLen;
|
|
t = min(t, viewDist - 0.001);
|
|
|
|
float3 samplePos = cameraPos + viewDir * t;
|
|
float atten = ComputeLightVolumeAttenuation(samplePos, Lgt);
|
|
if (atten <= 0.0)
|
|
continue;
|
|
|
|
float3 toLight = Lgt.position - samplePos;
|
|
float lightDist = length(toLight);
|
|
if (lightDist <= 0.01)
|
|
continue;
|
|
|
|
float3 L = toLight / lightDist;
|
|
|
|
float3 shadowOrigin = samplePos + L * (gShadowBias * 0.75) + viewDir * (gShadowBias * 0.15);
|
|
float visibility = TraceShadow(shadowOrigin, L, max(lightDist - gShadowBias, 0.001));
|
|
if (visibility <= 0.0)
|
|
continue;
|
|
|
|
float phase = HenyeyGreensteinPhase(dot(L, -viewDir), anisotropy);
|
|
float transmittance = exp(-density * t);
|
|
float slice = density * stepLen;
|
|
|
|
accum += Lgt.color * (Lgt.intensity * atten * visibility * phase * transmittance * slice);
|
|
}
|
|
|
|
// Scale from normalized phase-function energy into a game-facing glow term.
|
|
// The user-facing light attribute still controls the final strength.
|
|
const float DOOM3_VOLUME_SCALE = 7.5;
|
|
return clamp(accum * max(Lgt.volumetricScattering, 0.0) * DOOM3_VOLUME_SCALE, 0.0, 12.0);
|
|
}
|
|
|
|
float3 EstimatePathTracedVolumetricScattering(uint2 pixel, float3 worldPos, inout uint rng)
|
|
{
|
|
float3 volume = 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
if (Lgt.volumetricScattering <= 0.0)
|
|
continue;
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT || Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
volume += EstimateSingleLightVolumetricScattering(pixel, gCameraPos.xyz, worldPos, Lgt, rng);
|
|
}
|
|
|
|
return volume;
|
|
}
|
|
)"
|
|
R"(
|
|
float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt)
|
|
{
|
|
// Cheap unshadowed estimate used as the all-lights baseline for secondary
|
|
// GI. A small shadowed subset below corrects this baseline so every light
|
|
// still bounces, but important occlusion is no longer missing from GI.
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
float3 toLight = Lgt.position - hitPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 L = toLight / dist;
|
|
float atten = ComputePointLightAttenuation(hitPos, Lgt);
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
float nDotL = ComputeDiffuseLightingTerm(hitN, L);
|
|
return clamp(Lgt.color * (Lgt.intensity * atten * nDotL), 0.0, 8.0);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
float3 toLight = Lgt.position - hitPos;
|
|
float dist = length(toLight);
|
|
if (dist <= 0.01)
|
|
return 0.0;
|
|
|
|
float3 L = toLight / dist;
|
|
float atten = ComputeSpotLightAttenuation(hitPos, Lgt);
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
float nDotL = ComputeDiffuseLightingTerm(hitN, L);
|
|
return clamp(Lgt.color * (Lgt.intensity * atten * nDotL), 0.0, 8.0);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
float3 toCenter = Lgt.position - hitPos;
|
|
float centerDist = length(toCenter);
|
|
if (centerDist <= 0.01)
|
|
return 0.0;
|
|
|
|
float attenRadius = max(Lgt.radius, 1e-4);
|
|
float atten = saturate((attenRadius - centerDist) / attenRadius);
|
|
atten = atten * atten;
|
|
if (atten <= 0.0)
|
|
return 0.0;
|
|
|
|
float3 L = toCenter / centerDist;
|
|
float nDotL = ComputeDiffuseLightingTerm(hitN, L);
|
|
if (nDotL <= 0.0)
|
|
return 0.0;
|
|
|
|
float faceTerm = (Lgt.twoSided != 0)
|
|
? abs(dot(-L, Lgt.normal))
|
|
: saturate(dot(-L, Lgt.normal));
|
|
|
|
if (faceTerm <= 0.0)
|
|
return 0.0;
|
|
|
|
return clamp(Lgt.color * (Lgt.intensity * nDotL * faceTerm * atten), 0.0, 8.0);
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
bool SecondaryLightMatchesArea(Light Lgt, int hitAreaNum)
|
|
{
|
|
// Unknown areas stay permissive so dynamic entities, particles, and legacy
|
|
// proc files do not disappear from GI. Known world/static hits prefer lights
|
|
// tagged to the same portal area, which reduces room-to-room bounce leaks.
|
|
return hitAreaNum < 0 || Lgt.areaNum < 0 || Lgt.areaNum == hitAreaNum;
|
|
}
|
|
|
|
uint CountSecondaryBounceAreaLights(int hitAreaNum)
|
|
{
|
|
uint count = 0u;
|
|
[loop]
|
|
for (uint i = 0u; i < gLightCount; ++i)
|
|
{
|
|
if (SecondaryLightMatchesArea(gLights[i], hitAreaNum))
|
|
++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
uint GetSecondaryBounceFastLightBudget(uint candidateCount)
|
|
{
|
|
if (candidateCount == 0u)
|
|
return 0u;
|
|
|
|
if (gSecondaryLightBudget < 0)
|
|
return candidateCount;
|
|
|
|
if (gSecondaryLightBudget > 0)
|
|
return min(candidateCount, (uint)gSecondaryLightBudget);
|
|
|
|
if (candidateCount > 16u)
|
|
return 8u;
|
|
if (candidateCount > 10u)
|
|
return 7u;
|
|
if (candidateCount > 6u)
|
|
return 6u;
|
|
return candidateCount;
|
|
}
|
|
|
|
uint GetSecondaryBounceCorrectionBudget(uint candidateCount)
|
|
{
|
|
if (candidateCount == 0u)
|
|
return 0u;
|
|
|
|
if (gSecondaryCorrectionBudget > 0u)
|
|
return min(candidateCount, gSecondaryCorrectionBudget);
|
|
|
|
uint correctionBudget = (candidateCount <= 2u) ? 1u : 0u;
|
|
if (gSamplesPerPixel >= 4u)
|
|
correctionBudget = min(candidateCount, max(correctionBudget, 1u));
|
|
if (gSamplesPerPixel >= 6u && candidateCount <= 4u)
|
|
correctionBudget = min(candidateCount, correctionBudget + 1u);
|
|
return correctionBudget;
|
|
}
|
|
|
|
float GetSecondaryBounceFastLightScale(uint fastBudget, uint candidateCount)
|
|
{
|
|
if (fastBudget == 0u || fastBudget >= candidateCount)
|
|
return 1.0;
|
|
|
|
// The uploaded light list is CPU-sorted by broad importance. Use a small
|
|
// energy compensation for skipped low-importance lights without pretending
|
|
// the top few lights represent the entire room perfectly.
|
|
float fullScale = (float)candidateCount / (float)fastBudget;
|
|
return lerp(1.0, fullScale, saturate(gSecondaryLightCompensation));
|
|
}
|
|
|
|
float3 EstimateUnresolvedBounceRadiance(float3 hitPos, float3 hitN, float3 albedo, int hitAreaNum)
|
|
{
|
|
hitN = SafeNormalizeOr(hitN, float3(0.0, 0.0, 1.0));
|
|
albedo = max(saturate(albedo), float3(0.03, 0.03, 0.03));
|
|
|
|
float upness = saturate(hitN.z * 0.5 + 0.5);
|
|
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.025);
|
|
lighting += GetSkyRadiance(hitN) * (0.035 + 0.035 * upness);
|
|
|
|
float3 fastAllLights = 0.0;
|
|
uint candidateCount = CountSecondaryBounceAreaLights(hitAreaNum);
|
|
uint fastBudget = GetSecondaryBounceFastLightBudget(candidateCount);
|
|
uint accepted = 0u;
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
if (accepted >= fastBudget)
|
|
break;
|
|
if (!SecondaryLightMatchesArea(gLights[i], hitAreaNum))
|
|
continue;
|
|
fastAllLights += EstimateFastBounceLight(hitPos, hitN, gLights[i]);
|
|
++accepted;
|
|
}
|
|
lighting += fastAllLights * (0.85 * GetSecondaryBounceFastLightScale(accepted, candidateCount));
|
|
|
|
return clamp(albedo * max(lighting, 0.0), 0.0, 8.0);
|
|
}
|
|
|
|
float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, bool hitIsSkeletal, Light Lgt, inout uint rng)
|
|
{
|
|
float3 spec = 0.0;
|
|
float3 sss = 0.0;
|
|
float3 diffuse = 0.0;
|
|
float3 hitSpecularAlbedo = LoadSceneSpecularAlbedo(hitPixel, hitAlbedo);
|
|
float hitSurfaceRoughness = StabilizeSpecularSurfaceRoughness(hitPixel, hitN, LoadSceneSurfaceRoughness(LoadSceneNormal(hitPixel)));
|
|
float subsurfaceMask = hitIsSkeletal ? EstimateSkeletalSubsurfaceMask(hitAlbedo, hitSpecularAlbedo) * 0.45 : 0.0;
|
|
|
|
// Use the same visibility-capable direct-light samplers as the primary hit.
|
|
// This supplies proper next-event estimation at secondary hits instead of the
|
|
// old unoccluded light-list approximation.
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
diffuse = PathTraceDirectPointLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitSurfaceRoughness, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
diffuse = PathTraceDirectSpotLight(hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitSurfaceRoughness, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
diffuse = PathTraceDirectRectLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitSurfaceRoughness, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss);
|
|
}
|
|
|
|
return clamp(diffuse + sss * 0.35, 0.0, 12.0);
|
|
}
|
|
|
|
struct LightReservoir
|
|
{
|
|
uint lightIndex;
|
|
float weightSum;
|
|
float selectedWeight;
|
|
uint candidateCount;
|
|
};
|
|
|
|
float EstimateBounceLightReservoirWeight(float3 hitPos, float3 hitN, Light Lgt)
|
|
{
|
|
float3 fast = EstimateFastBounceLight(hitPos, hitN, Lgt);
|
|
float lum = dot(max(fast, 0.0), float3(0.2126, 0.7152, 0.0722));
|
|
float peak = max(max(fast.r, fast.g), fast.b);
|
|
return max(lum + peak * 0.35, 1.0e-5);
|
|
}
|
|
|
|
void UpdateLightReservoir(inout LightReservoir reservoir, uint lightIndex, float weight, inout uint rng)
|
|
{
|
|
if (weight <= 0.0)
|
|
return;
|
|
|
|
reservoir.weightSum += weight;
|
|
reservoir.candidateCount += 1u;
|
|
|
|
if (Rand(rng) * reservoir.weightSum <= weight)
|
|
{
|
|
reservoir.lightIndex = lightIndex;
|
|
reservoir.selectedWeight = weight;
|
|
}
|
|
}
|
|
|
|
LightReservoir BuildBounceLightReservoir(float3 hitPos, float3 hitN, int hitAreaNum, uint seed, inout uint rng)
|
|
{
|
|
LightReservoir reservoir;
|
|
reservoir.lightIndex = 0u;
|
|
reservoir.weightSum = 0.0;
|
|
reservoir.selectedWeight = 0.0;
|
|
reservoir.candidateCount = 0u;
|
|
|
|
uint localRng = PcgHash(rng ^ seed ^ 0xA511E9B3u);
|
|
[loop]
|
|
for (uint i = 0u; i < gLightCount; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
if (!SecondaryLightMatchesArea(Lgt, hitAreaNum))
|
|
continue;
|
|
|
|
float weight = EstimateBounceLightReservoirWeight(hitPos, hitN, Lgt);
|
|
UpdateLightReservoir(reservoir, i, weight, localRng);
|
|
}
|
|
|
|
rng = PcgHash(localRng + reservoir.lightIndex + reservoir.candidateCount * 17u);
|
|
return reservoir;
|
|
}
|
|
|
|
float3 EstimateReservoirShadowCorrection(
|
|
uint2 hitPixel,
|
|
float3 hitPos,
|
|
float3 hitN,
|
|
float3 hitV,
|
|
float3 hitAlbedo,
|
|
bool hitIsSkeletal,
|
|
int hitAreaNum,
|
|
float fastLightScale,
|
|
uint fastBudget,
|
|
uint sampleIndex,
|
|
inout uint rng)
|
|
{
|
|
LightReservoir reservoir = BuildBounceLightReservoir(
|
|
hitPos,
|
|
hitN,
|
|
hitAreaNum,
|
|
PcgHash(sampleIndex * 0x9E3779B9u + 0xD1B54A35u),
|
|
rng);
|
|
|
|
if (reservoir.candidateCount == 0u || reservoir.selectedWeight <= 0.0)
|
|
return 0.0;
|
|
|
|
Light selected = gLights[reservoir.lightIndex];
|
|
float3 fast = 0.0;
|
|
uint fastRank = 0u;
|
|
|
|
[loop]
|
|
for (uint rankIndex = 0u; rankIndex <= reservoir.lightIndex && rankIndex < gLightCount; ++rankIndex)
|
|
{
|
|
if (SecondaryLightMatchesArea(gLights[rankIndex], hitAreaNum))
|
|
++fastRank;
|
|
}
|
|
|
|
if (fastRank > 0u && fastRank <= fastBudget)
|
|
fast = EstimateFastBounceLight(hitPos, hitN, selected) * fastLightScale;
|
|
|
|
float3 shadowed = EstimateShadowedBounceLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitIsSkeletal, selected, rng);
|
|
|
|
// Convert the selected reservoir light into a gentle many-light correction.
|
|
// The fast baseline still carries the full cheap estimate; the reservoir only
|
|
// spends visibility rays on the lights most likely to matter.
|
|
float selectionPdf = reservoir.selectedWeight / max(reservoir.weightSum, 1.0e-5);
|
|
float correctionScale = min(1.0 / max(selectionPdf, 1.0e-3), 2.0);
|
|
return (shadowed - fast) * correctionScale;
|
|
}
|
|
|
|
float3 EstimateBounceSkyLighting(float3 hitPos, float3 hitN, inout uint rng)
|
|
{
|
|
const float SKY_TMAX = 1000000.0;
|
|
|
|
uint skySamples = (gSamplesPerPixel >= 4u) ? 2u : 1u;
|
|
float3 accum = 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0u; i < skySamples; ++i)
|
|
{
|
|
float3 skyDir = (i == 0u)
|
|
? SampleConeWorld(GetSkyLightDirection10AM(), 0.18, rng)
|
|
: SampleCosineWorld(hitN, rng);
|
|
|
|
float NoSky = saturate(dot(hitN, skyDir));
|
|
if (NoSky <= 0.0)
|
|
continue;
|
|
|
|
float visibility = TraceVisibilityBiased(hitPos, hitN, skyDir, SKY_TMAX);
|
|
accum += GetSkyRadiance(skyDir) * visibility * NoSky;
|
|
}
|
|
|
|
return accum / (float)skySamples;
|
|
}
|
|
|
|
float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, uint hitGeoFlag, int hitAreaNum, inout uint rng)
|
|
{
|
|
bool hitIsSkeletal = (hitGeoFlag & GEOMETRY_FLAG_SKELETAL) != 0u;
|
|
bool hitIsUnlit = (hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u;
|
|
uint lightingRng = InitSpatialRng(hitPixel, hitPos, 0xB04157u);
|
|
|
|
hitN = SafeNormalizeOr(hitN, float3(0.0, 0.0, 1.0));
|
|
if (hitIsSkeletal)
|
|
hitN = StabilizeSkeletalNormal(hitPixel, hitPos, hitN);
|
|
hitV = SafeNormalizeOr(hitV, -hitN);
|
|
hitAlbedo = saturate(hitAlbedo);
|
|
|
|
// Treat unlit G-buffer surfaces as simple bounce cards. Glow-map emissive is
|
|
// intentionally excluded here so visible emissive no longer casts GI/light.
|
|
if (hitIsUnlit)
|
|
return clamp(hitAlbedo * 2.0 + GetSkyRadiance(hitN) * 0.04, 0.0, 6.0);
|
|
|
|
float upness = saturate(hitN.z * 0.5 + 0.5);
|
|
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.035);
|
|
|
|
// Real occluded sky contribution at the secondary hit. The previous GI path
|
|
// used a fixed sky term, so corners/cavities received too much indirect light.
|
|
lighting += EstimateBounceSkyLighting(hitPos, hitN, lightingRng) * (0.30 + 0.18 * upness);
|
|
|
|
// Baseline: secondary GI uses a capped important-light set. Primary direct
|
|
// lighting still evaluates every light, but bounce hits were multiplying the
|
|
// full light list by every stochastic path depth.
|
|
float3 fastAllLights = 0.0;
|
|
uint candidateCount = CountSecondaryBounceAreaLights(hitAreaNum);
|
|
uint fastBudget = GetSecondaryBounceFastLightBudget(candidateCount);
|
|
uint accepted = 0u;
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
if (accepted >= fastBudget)
|
|
break;
|
|
if (!SecondaryLightMatchesArea(gLights[i], hitAreaNum))
|
|
continue;
|
|
fastAllLights += EstimateFastBounceLight(hitPos, hitN, gLights[i]);
|
|
++accepted;
|
|
}
|
|
float fastLightScale = GetSecondaryBounceFastLightScale(accepted, candidateCount);
|
|
lighting += fastAllLights * fastLightScale;
|
|
|
|
// Visibility correction: replace a small stable subset of the unshadowed
|
|
// baseline with real shadowed direct lighting. Keying this to the hit
|
|
// position avoids frame-to-frame sparkle while keeping the all-lights bounce.
|
|
uint correctionBudget = GetSecondaryBounceCorrectionBudget(candidateCount);
|
|
|
|
if (correctionBudget > 0u)
|
|
{
|
|
float3 reservoirCorrection = 0.0;
|
|
[loop]
|
|
for (uint c = 0u; c < correctionBudget; ++c)
|
|
{
|
|
reservoirCorrection += EstimateReservoirShadowCorrection(
|
|
hitPixel,
|
|
hitPos,
|
|
hitN,
|
|
hitV,
|
|
hitAlbedo,
|
|
hitIsSkeletal,
|
|
hitAreaNum,
|
|
fastLightScale,
|
|
fastBudget,
|
|
c,
|
|
lightingRng);
|
|
}
|
|
|
|
lighting += reservoirCorrection / (float)correctionBudget;
|
|
}
|
|
|
|
//if (hitIsSkeletal)
|
|
// lighting *= 1.10;
|
|
|
|
// Outgoing diffuse radiance from the bounce surface. The primary surface's
|
|
// albedo is applied later in RayGen, so only the secondary hit albedo belongs
|
|
// here.
|
|
return clamp(hitAlbedo * max(lighting, 0.0), 0.0, 16.0);
|
|
}
|
|
|
|
)"
|
|
R"(
|
|
float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
|
|
{
|
|
// Current-frame final gather / irradiance reuse. The gather samples nearby
|
|
// G-buffer surfaces, shades them as bounce emitters, and now traces short
|
|
// visibility rays so color bleed does not leak through walls.
|
|
static const int2 kGatherTaps[6] =
|
|
{
|
|
int2( 7, 3),
|
|
int2( -9, 6),
|
|
int2( 5, -13),
|
|
int2( 17, -8),
|
|
int2(-22, -15),
|
|
int2( 29, 18)
|
|
};
|
|
|
|
uint sampleBudget = 3u;
|
|
|
|
// Keep the pass cheap when many lights or high SPP are active. This path
|
|
// evaluates bounce lighting against the light list and traces short
|
|
// visibility rays, so adapt the sample count instead of raising ray count.
|
|
if (gLightCount > 4u)
|
|
sampleBudget = 2u;
|
|
if (gLightCount > 10u)
|
|
sampleBudget = 1u;
|
|
if (gSamplesPerPixel >= 4u)
|
|
sampleBudget = min(sampleBudget, 2u);
|
|
if (gSamplesPerPixel >= 6u)
|
|
sampleBudget = min(sampleBudget, 1u);
|
|
|
|
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
float pixelScale = max(1.0, round(min(gScreenSize.x, gScreenSize.y) / 720.0));
|
|
|
|
// Doom/idTech-like unit scale: large enough to catch wall/floor color bleed,
|
|
// small enough to avoid room-to-room light leaking from unrelated screen hits.
|
|
const float MAX_GATHER_DISTANCE = 224.0;
|
|
|
|
float2 jitter = float2(
|
|
Hash12((float2)pixel + float2(13.7, 91.1)),
|
|
Hash12((float2)pixel + float2(47.3, 19.9))) - 0.5;
|
|
|
|
float3 accum = 0.0;
|
|
float weightSum = 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0u; i < 6u; ++i)
|
|
{
|
|
if (i >= sampleBudget)
|
|
break;
|
|
|
|
float2 tap = float2(kGatherTaps[i].x, kGatherTaps[i].y) * pixelScale;
|
|
int2 sp = int2(pixel) + int2(
|
|
(int)round(tap.x + jitter.x * pixelScale * 2.0),
|
|
(int)round(tap.y + jitter.y * pixelScale * 2.0));
|
|
sp = clamp(sp, int2(0, 0), maxPixel);
|
|
|
|
float sampleDepth = gDepthTex.Load(int3(sp, 0));
|
|
if (sampleDepth <= 0.0 || sampleDepth >= 1.0)
|
|
continue;
|
|
|
|
float4 samplePos4 = gPositionTex.Load(int3(sp, 0));
|
|
float3 samplePos = samplePos4.xyz;
|
|
uint sampleGeoFlag = DecodeGeometryFlag(samplePos4.w);
|
|
float3 sampleAlbedo = saturate(gAlbedoTex.Load(int3(sp, 0)).rgb);
|
|
float3 sampleNormal = SafeNormalizeOr(gNormalTex.Load(int3(sp, 0)).xyz, N);
|
|
|
|
float3 delta = samplePos - worldPos;
|
|
float distSq = dot(delta, delta);
|
|
if (distSq <= 1e-4)
|
|
continue;
|
|
|
|
float dist = sqrt(distSq);
|
|
if (dist >= MAX_GATHER_DISTANCE)
|
|
continue;
|
|
|
|
float3 dirToSample = delta / dist;
|
|
float receiverFacing = saturate(dot(N, dirToSample));
|
|
if (receiverFacing <= 0.02)
|
|
continue;
|
|
|
|
bool sampleIsUnlit = (sampleGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u;
|
|
float emitterFacing = sampleIsUnlit ? 1.0 : saturate(dot(sampleNormal, -dirToSample));
|
|
if (emitterFacing <= 0.02)
|
|
continue;
|
|
|
|
float distanceFade = saturate(1.0 - dist / MAX_GATHER_DISTANCE);
|
|
distanceFade *= distanceFade;
|
|
float distanceWeight = 1.0 / (1.0 + distSq * 0.00018);
|
|
|
|
// Favor concave/near-facing exchange and suppress unrelated background
|
|
// samples that happen to be close in screen-space but far in world-space.
|
|
float normalAffinity = saturate(dot(N, sampleNormal) * 0.35 + 0.65);
|
|
float formWeight = receiverFacing * emitterFacing * distanceFade * distanceWeight * normalAffinity;
|
|
|
|
if (formWeight <= 1e-5)
|
|
continue;
|
|
|
|
// Prevent screen-space final gather from leaking through walls. This is
|
|
// a short visibility ray, not another diffuse bounce, and it fixes the
|
|
// most obvious missing GI occlusion cases in doorways/corners.
|
|
float visibility = TraceVisibilityBiased(worldPos, N, dirToSample, dist);
|
|
if (visibility <= 0.0)
|
|
continue;
|
|
|
|
formWeight *= visibility;
|
|
|
|
float3 sampleView = SafeNormalizeOr(-dirToSample, V);
|
|
float3 outgoingRadiance = EstimateDirectLightingForBounceHit(
|
|
uint2(sp),
|
|
samplePos,
|
|
sampleNormal,
|
|
sampleView,
|
|
sampleAlbedo,
|
|
sampleGeoFlag,
|
|
-1,
|
|
rng);
|
|
|
|
accum += outgoingRadiance * formWeight;
|
|
weightSum += formWeight;
|
|
}
|
|
|
|
if (weightSum <= 1e-5)
|
|
return 0.0;
|
|
|
|
float3 gatheredRadiance = accum / weightSum;
|
|
|
|
// Coverage keeps one bright tap from flooding a pixel while still letting
|
|
// nearby high-confidence samples respond strongly to dynamic lights.
|
|
float coverage = saturate(weightSum * 2.15);
|
|
|
|
const float FINAL_GATHER_STRENGTH = 1.05;
|
|
return gatheredRadiance * coverage * FINAL_GATHER_STRENGTH;
|
|
}
|
|
)"
|
|
R"(
|
|
float2 GetStratifiedBounceXi(uint2 pixel, float3 worldPos, float3 pathNormal, uint sampleIndex)
|
|
{
|
|
uint spp = max(gSamplesPerPixel, 1u);
|
|
uint sequenceLength = max(spp * 64u, 64u);
|
|
uint rngFrameIndex = (gEnableDenoiser != 0u && gExternalDenoiser == 0u) ? gFrameIndex : 0u;
|
|
uint sequenceIndex = (sampleIndex + (rngFrameIndex & 63u) * spp) % sequenceLength;
|
|
|
|
float rand = Hash12(
|
|
(float2)pixel +
|
|
worldPos.xy * 0.013 +
|
|
float2(pathNormal.x * 19.19, pathNormal.y * 37.37));
|
|
|
|
return Hammersley2D(sequenceIndex, sequenceLength, rand);
|
|
}
|
|
|
|
float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, uint sampleIndex, inout uint rng)
|
|
{
|
|
const float BOUNCE_TMAX = 1000000.0;
|
|
|
|
uint maxIndirectDepth = (gMaxBounces > 1u) ? min(gMaxBounces - 1u, 3u) : 0u;
|
|
if (gSamplesPerPixel <= 1u)
|
|
maxIndirectDepth = min(maxIndirectDepth, 2u);
|
|
|
|
float3 accum = 0.0;
|
|
float3 throughput = 1.0;
|
|
|
|
float3 pathPos = worldPos;
|
|
float3 pathNormal = N;
|
|
float3 pathView = V;
|
|
uint2 pathPixel = pixel;
|
|
|
|
[loop]
|
|
for (uint depth = 0; depth < maxIndirectDepth; ++depth)
|
|
{
|
|
float2 bounceXi = (depth == 0u)
|
|
? GetStratifiedBounceXi(pixel, pathPos, pathNormal, sampleIndex)
|
|
: Rand2(rng);
|
|
float3 bounceDir = SampleCosineWorldXi(pathNormal, bounceXi);
|
|
float NoD = saturate(dot(pathNormal, bounceDir));
|
|
|
|
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoD);
|
|
float3 bounceOrigin =
|
|
pathPos +
|
|
pathNormal * normalBias +
|
|
bounceDir * (gShadowBias * 0.5);
|
|
|
|
float hitT = 0.0;
|
|
uint materialFlags = 0;
|
|
uint meshIndex = 0;
|
|
float3 rayHitNormal = 0.0;
|
|
float2 rayHitTexCoord = 0.0;
|
|
bool hit = TraceBounce(bounceOrigin, bounceDir, BOUNCE_TMAX, hitT, materialFlags, meshIndex, rayHitNormal, rayHitTexCoord);
|
|
|
|
if (!hit)
|
|
{
|
|
// Environment miss. This is part of the path throughput and is now
|
|
// allowed at every diffuse depth, not only at the first miss.
|
|
float missScale = (depth == 0u) ? 0.28 : 0.42;
|
|
accum += throughput * GetSkyRadiance(bounceDir) * missScale;
|
|
break;
|
|
}
|
|
|
|
float3 rayHitPos = bounceOrigin + bounceDir * hitT;
|
|
|
|
uint2 hitPixel = pathPixel;
|
|
float3 hitPos = rayHitPos;
|
|
float3 hitNormal = SafeNormalizeOr(rayHitNormal, SafeNormalizeOr(-bounceDir, pathNormal));
|
|
MeshMetadata meshMeta = GetMeshMetadataSafe(meshIndex);
|
|
float3 meshAverageColor = GetMeshAverageColor(meshIndex);
|
|
float3 hitAlbedo = meshAverageColor;
|
|
uint hitGeoFlag = GEOMETRY_FLAG_NONE;
|
|
int hitAreaNum = meshMeta.areaNum;
|
|
|
|
float3 materialNormal = hitNormal;
|
|
bool hasGBufferMaterial = TryFetchGBufferAtRayHit(
|
|
rayHitPos,
|
|
hitPixel,
|
|
hitPos,
|
|
hitNormal,
|
|
hitAlbedo,
|
|
hitGeoFlag);
|
|
hitNormal = materialNormal;
|
|
hitGeoFlag |= MeshMetadataGeometryFlags(meshMeta, materialFlags);
|
|
|
|
// If the G-buffer normal points away from the incoming bounce ray, flip it
|
|
// so direct lighting at the secondary hit is evaluated on the side that
|
|
// the ray actually reached.
|
|
if (dot(hitNormal, -bounceDir) < 0.0)
|
|
hitNormal = -hitNormal;
|
|
|
|
float3 hitV = SafeNormalizeOr(-bounceDir, pathView);
|
|
float3 bouncedRadiance = 0.0;
|
|
if (hasGBufferMaterial)
|
|
{
|
|
bouncedRadiance = EstimateDirectLightingForBounceHit(
|
|
hitPixel,
|
|
hitPos,
|
|
hitNormal,
|
|
hitV,
|
|
hitAlbedo,
|
|
hitGeoFlag,
|
|
hitAreaNum,
|
|
rng);
|
|
|
|
bouncedRadiance *= 1.28;
|
|
}
|
|
else
|
|
{
|
|
// The ray hit real TLAS geometry, but material lookup through the
|
|
// camera G-buffer failed or landed on an unrelated visible pixel.
|
|
// Use mesh-derived color instead of neutral grey/screen material.
|
|
hitPos = rayHitPos;
|
|
hitAlbedo = meshAverageColor;
|
|
bouncedRadiance = EstimateUnresolvedBounceRadiance(hitPos, hitNormal, hitAlbedo, hitAreaNum);
|
|
}
|
|
|
|
bouncedRadiance += GetMeshEmissiveRadiance(meshMeta);
|
|
|
|
bouncedRadiance = CompressEmissiveRadiance(bouncedRadiance, depth == 0u ? 6.0 : 3.5);
|
|
bouncedRadiance = LimitStochasticRadiance(
|
|
bouncedRadiance,
|
|
depth == 0u ? 2.20 : 1.35,
|
|
depth == 0u ? 3.80 : 2.20);
|
|
accum += throughput * bouncedRadiance;
|
|
|
|
if ((hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u)
|
|
break;
|
|
|
|
// Cosine-weighted diffuse sampling cancels the Lambertian cosine/pdf term;
|
|
// the remaining throughput is just the secondary surface albedo. Use a
|
|
// conservative energy scale because material data for off-screen hits is a
|
|
// G-buffer approximation.
|
|
throughput *= saturate(hitAlbedo) * 0.84;
|
|
|
|
float continuation = clamp(max(max(throughput.x, throughput.y), throughput.z), 0.12, 0.92);
|
|
if (depth >= 1u)
|
|
{
|
|
if (Rand(rng) > continuation)
|
|
break;
|
|
throughput /= continuation;
|
|
}
|
|
|
|
if (max(max(throughput.x, throughput.y), throughput.z) < 0.015)
|
|
break;
|
|
|
|
pathPos = hitPos;
|
|
pathNormal = hitNormal;
|
|
pathView = hitV;
|
|
pathPixel = hitPixel;
|
|
}
|
|
|
|
return accum;
|
|
}
|
|
|
|
float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, uint sampleIndex, inout uint rng)
|
|
{
|
|
if (gMaxBounces <= 1u)
|
|
return 0.0;
|
|
|
|
// One stochastic diffuse path per lighting sample. The path can contain
|
|
// several diffuse depths, while the temporal pass below handles convergence.
|
|
float3 accum = TraceOneIndirectBouncePath(pixel, worldPos, N, V, baseAlbedo, sampleIndex, rng);
|
|
|
|
const float INDIRECT_STRENGTH = 0.95;
|
|
return LimitStochasticRadiance(accum * INDIRECT_STRENGTH, 2.50, 4.00);
|
|
}
|
|
|
|
float3 ApplyPrimaryDiffusePost(float3 lightingAccum, float ao, float microShadow, bool isSkeletal)
|
|
{
|
|
if (isSkeletal)
|
|
{
|
|
ao = lerp(ao, 1.0, 0.35);
|
|
microShadow = lerp(microShadow, 1.0, 0.65);
|
|
}
|
|
|
|
lightingAccum *= ao;
|
|
lightingAccum *= microShadow;
|
|
|
|
//if (isSkeletal)
|
|
// lightingAccum *= 1.2;
|
|
|
|
return max(lightingAccum, 0.0);
|
|
}
|
|
|
|
float3 ApplyPrimarySpecularPost(float3 specularAccum, float ao, bool isSkeletal)
|
|
{
|
|
// AO is a diffuse/ambient visibility term here. Multiplying specular by AO
|
|
// directly made highlights disappear on creases, props, and normal-mapped
|
|
// surfaces. Keep a mild occlusion tint, but let direct-light specular read.
|
|
float specAo = lerp(0.76, 1.0, saturate(ao));
|
|
if (isSkeletal)
|
|
{
|
|
specAo = lerp(specAo, 1.0, 0.50);
|
|
specularAccum *= 0.42;
|
|
}
|
|
specularAccum *= specAo;
|
|
|
|
//if (isSkeletal)
|
|
// specularAccum *= 1.15;
|
|
|
|
return max(specularAccum, 0.0);
|
|
}
|
|
|
|
float3 PathTraceDeterministicLighting(
|
|
uint2 pixel,
|
|
float3 worldPos,
|
|
float3 N,
|
|
float3 V,
|
|
float3 baseAlbedo,
|
|
float3 specularAlbedo,
|
|
float surfaceRoughness,
|
|
bool isSkeletal,
|
|
float cavity,
|
|
float ao,
|
|
float skyVis,
|
|
float ambientSkyVis,
|
|
out float3 specularAccum,
|
|
out float3 subsurfaceAccum)
|
|
{
|
|
specularAccum = 0.0;
|
|
subsurfaceAccum = 0.0;
|
|
|
|
float microShadow = lerp(0.75, 1.0, cavity);
|
|
|
|
// These environment and direct-light terms are deterministic for a given
|
|
// pixel. The old RayGen evaluated them once for every SPP, which multiplied
|
|
// the AO/sky/direct shadow ray budget without adding new samples.
|
|
float upness = saturate(N.z * 0.5 + 0.5);
|
|
float3 skyColorRGB = float3(0.98, 0.55, 0.35);
|
|
float3 skyColor = skyColorRGB * (0.35 + 0.65 * upness);
|
|
|
|
float3 lightingAccum = float3(0, 0, 0); // gAmbientColor.rgb * (gAmbientColor.a * 0.04);
|
|
lightingAccum += skyColor * (0.42 * skyVis);
|
|
lightingAccum += ambientSkyVis * (skyColorRGB * 0.12);
|
|
|
|
// Glow-map emissive is direct/bloom-only for now. It is added after lighting
|
|
// in RayGen and is intentionally not injected into lightingAccum.
|
|
|
|
//if (isSkeletal)
|
|
// lightingAccum += 0.1;
|
|
|
|
// The current direct-light evaluators are deterministic. Keep the inout RNG
|
|
// argument only because the functions share the same signature as stochastic
|
|
// helpers; it is not consumed by PathTraceDirect* in the current shader.
|
|
uint directRng = InitRng(pixel, 0u, 0xD17EC7u);
|
|
float subsurfaceMask = isSkeletal ? EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo) : 0.0;
|
|
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
float3 spec = 0.0;
|
|
float3 sss = 0.0;
|
|
float3 diffuse = 0.0;
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
diffuse = PathTraceDirectPointLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, surfaceRoughness, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
diffuse = PathTraceDirectSpotLight(worldPos, N, V, baseAlbedo, specularAlbedo, surfaceRoughness, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
diffuse = PathTraceDirectRectLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, surfaceRoughness, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss);
|
|
}
|
|
|
|
lightingAccum += diffuse;
|
|
specularAccum += spec;
|
|
subsurfaceAccum += sss;
|
|
}
|
|
|
|
specularAccum = ApplyPrimarySpecularPost(specularAccum, ao, isSkeletal);
|
|
if (isSkeletal)
|
|
subsurfaceAccum *= lerp(0.72, 1.0, saturate(ao));
|
|
return ApplyPrimaryDiffusePost(lightingAccum, ao, microShadow, isSkeletal);
|
|
}
|
|
|
|
|
|
uint GetSpecularReflectionFastLightBudget()
|
|
{
|
|
if (gLightCount > 12u)
|
|
return 3u;
|
|
if (gLightCount > 6u)
|
|
return 2u;
|
|
return min(gLightCount, 2u);
|
|
}
|
|
|
|
float3 EstimateSpecularReflectionFastHitRadiance(float3 hitPos, float3 hitNormal, float3 incomingViewDir, float3 hitAlbedo, int hitAreaNum)
|
|
{
|
|
// Reflection rays can hit off-screen or camera-hidden TLAS geometry. In that
|
|
// case this pass has no per-triangle material/normal table, so use a neutral
|
|
// lit card instead of returning black. This keeps reflected objects visible
|
|
// without pretending every unknown hit is a perfect emissive surface.
|
|
incomingViewDir = SafeNormalizeOr(incomingViewDir, -hitNormal);
|
|
hitNormal = SafeNormalizeOr(hitNormal, incomingViewDir);
|
|
|
|
if (dot(hitNormal, incomingViewDir) < 0.0)
|
|
hitNormal = -hitNormal;
|
|
|
|
float upness = saturate(hitNormal.z * 0.5 + 0.5);
|
|
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.035);
|
|
lighting += GetSkyRadiance(hitNormal) * (0.12 + 0.08 * upness);
|
|
lighting += GetSkyRadiance(SafeNormalizeOr(reflect(-incomingViewDir, hitNormal), hitNormal)) * 0.055;
|
|
|
|
uint lightBudget = GetSpecularReflectionFastLightBudget();
|
|
uint accepted = 0u;
|
|
[loop]
|
|
for (uint i = 0; i < gLightCount; ++i)
|
|
{
|
|
if (accepted >= lightBudget)
|
|
break;
|
|
|
|
Light Lgt = gLights[i];
|
|
if (!SecondaryLightMatchesArea(Lgt, hitAreaNum))
|
|
continue;
|
|
|
|
lighting += EstimateFastBounceLight(hitPos, hitNormal, Lgt) * 0.72;
|
|
++accepted;
|
|
}
|
|
|
|
return clamp(max(saturate(hitAlbedo), float3(0.08, 0.08, 0.08)) * max(lighting, 0.0), 0.0, 8.0);
|
|
}
|
|
|
|
float3 EstimateFallbackReflectionHitRadiance(float3 hitPos, float3 hitNormal, float3 incomingViewDir)
|
|
{
|
|
const float3 NEUTRAL_UNKNOWN_ALBEDO = float3(0.58, 0.58, 0.58);
|
|
return EstimateSpecularReflectionFastHitRadiance(hitPos, hitNormal, incomingViewDir, NEUTRAL_UNKNOWN_ALBEDO, -1);
|
|
}
|
|
|
|
|
|
void ComputeSpecularReflectionLocalInfluence(float3 worldPos, float3 N, out float energy, out float maxDistance)
|
|
{
|
|
energy = 0.0;
|
|
maxDistance = 0.0;
|
|
|
|
uint lightBudget = GetSpecularReflectionFastLightBudget();
|
|
[loop]
|
|
for (uint i = 0; i < lightBudget; ++i)
|
|
{
|
|
Light Lgt = gLights[i];
|
|
|
|
float atten = 0.0;
|
|
float range = 0.0;
|
|
|
|
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
atten = ComputePointLightAttenuation(worldPos, Lgt);
|
|
range = GetPointLightMaxRadius(Lgt);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
{
|
|
atten = ComputeSpotLightAttenuation(worldPos, Lgt);
|
|
range = max(Lgt.radius, 1.0);
|
|
}
|
|
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
float dist = length(Lgt.position - worldPos);
|
|
range = max(Lgt.radius, 1.0);
|
|
atten = saturate((range - dist) / range);
|
|
atten = atten * atten;
|
|
}
|
|
|
|
if (atten <= 0.0)
|
|
continue;
|
|
|
|
float3 toLight = SafeNormalizeOr(Lgt.position - worldPos, N);
|
|
float facing = saturate((dot(N, toLight) + 0.18) / 1.18);
|
|
float lightPeak = max(max(Lgt.color.r, Lgt.color.g), Lgt.color.b);
|
|
|
|
energy += atten * facing * Lgt.intensity * lightPeak;
|
|
maxDistance = max(maxDistance, range * lerp(0.18, 0.55, saturate(atten)));
|
|
}
|
|
|
|
// Keep reflections tied to nearby light contribution instead of becoming
|
|
// a global mirror pass.
|
|
energy = saturate(energy * 0.12);
|
|
maxDistance = clamp(maxDistance, 24.0, 768.0);
|
|
}
|
|
|
|
float ComputeSpecularReflectionDistanceFade(float hitT, float maxT)
|
|
{
|
|
float t = saturate(hitT / max(maxT, 1.0));
|
|
|
|
// Strong near reflection, smooth fade before the end of the local volume.
|
|
float fade = 1.0 - smoothstep(0.35, 1.0, t);
|
|
|
|
return fade * fade;
|
|
}
|
|
)"
|
|
R"(
|
|
float3 EstimateRayTracedSpecularReflection(
|
|
uint2 pixel,
|
|
float3 worldPos,
|
|
float3 N,
|
|
float3 V,
|
|
float3 baseAlbedo,
|
|
float3 specularAlbedo,
|
|
float surfaceRoughness,
|
|
float cavity,
|
|
bool isSkeletal,
|
|
inout uint rng)
|
|
{
|
|
if (gEnableSpecular == 0u || gMaxBounces <= 1u)
|
|
return 0.0;
|
|
|
|
// Character materials should get soft direct specular, not mirror-like local
|
|
// reflections. The G-buffer material model has no skeletal roughness map, so
|
|
// reflected TLAS hits make skin/armor read much wetter than intended.
|
|
if (isSkeletal)
|
|
return 0.0;
|
|
|
|
N = SafeNormalizeOr(N, float3(0.0, 0.0, 1.0));
|
|
V = SafeNormalizeOr(V, -N);
|
|
float normalVariance = EstimateSpecularNormalVariance(pixel, N);
|
|
|
|
// Prefer authored specular maps, but let missing maps use the synthesized
|
|
// dielectric fallback. The visibility threshold below keeps fallback rays
|
|
// rare unless the surface is grazing and locally lit.
|
|
float4 rawSpecular = gSpecularTex.Load(int3(pixel, 0));
|
|
bool hasSpecularMap = LooksLikeAuthoredSpecularSample(rawSpecular, baseAlbedo);
|
|
|
|
float specPeak = SpecularPeak3(specularAlbedo);
|
|
if (specPeak <= (hasSpecularMap ? 0.035 : 0.012))
|
|
return 0.0;
|
|
|
|
float NoV = saturate(dot(N, V));
|
|
float3 R = SafeNormalizeOr(reflect(-V, N), N);
|
|
float NoR = saturate(dot(N, R));
|
|
if (NoR <= 0.001)
|
|
return 0.0;
|
|
|
|
// Schlick fresnel keeps reflections strongest at grazing angles while still
|
|
// honoring the artist-authored Doom/idTech-style specular map color. Missing
|
|
// spec maps use a much lower F0 so the fallback is glossy, not mirror-like.
|
|
float3 F0 = hasSpecularMap
|
|
? saturate(specularAlbedo)
|
|
: saturate(lerp(float3(0.02, 0.02, 0.02), specularAlbedo, 0.38));
|
|
float3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NoV, 5.0);
|
|
float roughness = EstimateSpecularRoughness(specularAlbedo, surfaceRoughness);
|
|
roughness = clamp(lerp(roughness, 0.78, smoothstep(0.12, 0.46, normalVariance)), 0.16, 0.84);
|
|
|
|
// Do not multiply by specPeak again here. specularAlbedo is already F0, and
|
|
// double-penalizing it made normal dielectric floors fall below the ray gate.
|
|
float reflectionVisibility = SpecularPeak3(fresnel) * saturate(1.0 - roughness * 0.72);
|
|
float visibilityThreshold = hasSpecularMap ? 0.035 : 0.030;
|
|
if (reflectionVisibility <= visibilityThreshold)
|
|
return 0.0;
|
|
|
|
float localReflectionEnergy = 0.0;
|
|
float reflectionMaxT = 0.0;
|
|
ComputeSpecularReflectionLocalInfluence(worldPos, N, localReflectionEnergy, reflectionMaxT);
|
|
if (localReflectionEnergy <= 0.001)
|
|
return 0.0;
|
|
|
|
float cavityFade = lerp(0.42, 1.0, saturate(cavity));
|
|
float reflectionStrength = hasSpecularMap
|
|
? saturate(specPeak * 1.35)
|
|
: saturate(specPeak * 4.0);
|
|
float reflectionScale = hasSpecularMap ? 0.72 : 0.34;
|
|
|
|
if (reflectionStrength <= 0.001)
|
|
return 0.0;
|
|
|
|
float rayVisibilityThreshold = hasSpecularMap ? 0.13 : 0.22;
|
|
if (reflectionVisibility < rayVisibilityThreshold)
|
|
{
|
|
float confidence = saturate(reflectionVisibility / max(rayVisibilityThreshold, 1.0e-4));
|
|
float3 stableGlossRadiance =
|
|
GetSkyRadiance(R) * 0.065 +
|
|
max(baseAlbedo, float3(0.05, 0.05, 0.05)) * (0.06 + localReflectionEnergy * 0.42);
|
|
|
|
return clamp(
|
|
stableGlossRadiance *
|
|
fresnel *
|
|
reflectionStrength *
|
|
cavityFade *
|
|
reflectionScale *
|
|
localReflectionEnergy *
|
|
lerp(0.35, 0.82, confidence),
|
|
0.0,
|
|
0.45);
|
|
}
|
|
|
|
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoR);
|
|
float3 reflectionOrigin = worldPos + N * normalBias + R * (gShadowBias * 0.5);
|
|
|
|
float hitT = 0.0;
|
|
uint materialFlags = 0u;
|
|
uint meshIndex = 0u;
|
|
float3 rayHitNormal = 0.0;
|
|
float2 rayHitTexCoord = 0.0;
|
|
bool hit = TraceSpecularReflection(reflectionOrigin, R, reflectionMaxT, hitT, materialFlags, meshIndex, rayHitNormal, rayHitTexCoord);
|
|
|
|
float3 reflectedRadiance = 0.0;
|
|
float distanceFade = 1.0;
|
|
float3 reflectedSurfaceAverageColor = 1.0;
|
|
|
|
if (!hit)
|
|
{
|
|
// Do not reflect sky forever. A missed finite reflection ray contributes
|
|
// only a tiny local glossy sheen.
|
|
reflectedRadiance = GetSkyRadiance(R) * 0.055;
|
|
distanceFade = 0.28;
|
|
}
|
|
else
|
|
{
|
|
distanceFade = ComputeSpecularReflectionDistanceFade(hitT, reflectionMaxT);
|
|
float3 rayHitPos = reflectionOrigin + R * hitT;
|
|
MeshMetadata reflectedMeshMeta = GetMeshMetadataSafe(meshIndex);
|
|
reflectedSurfaceAverageColor = GetMeshAverageColor(meshIndex);
|
|
int reflectedAreaNum = reflectedMeshMeta.areaNum;
|
|
float3 hitPos = rayHitPos;
|
|
float3 hitNormal = SafeNormalizeOr(rayHitNormal, SafeNormalizeOr(-R, N));
|
|
float3 hitAlbedo = reflectedSurfaceAverageColor;
|
|
|
|
if (dot(hitNormal, -R) < 0.0)
|
|
hitNormal = -hitNormal;
|
|
|
|
reflectedRadiance = EstimateSpecularReflectionFastHitRadiance(
|
|
hitPos,
|
|
hitNormal,
|
|
SafeNormalizeOr(-R, V),
|
|
hitAlbedo,
|
|
reflectedAreaNum);
|
|
reflectedRadiance += GetMeshEmissiveRadiance(reflectedMeshMeta);
|
|
}
|
|
|
|
reflectedRadiance *= reflectedSurfaceAverageColor;
|
|
reflectedRadiance = CompressEmissiveRadiance(reflectedRadiance, hasSpecularMap ? 2.4 : 1.6);
|
|
|
|
float3 reflectionResult =
|
|
reflectedRadiance *
|
|
fresnel *
|
|
reflectionStrength *
|
|
cavityFade *
|
|
reflectionScale *
|
|
localReflectionEnergy *
|
|
distanceFade;
|
|
|
|
return LimitStochasticRadiance(reflectionResult, hasSpecularMap ? 0.72 : 0.38, hasSpecularMap ? 1.15 : 0.62);
|
|
}
|
|
)"
|
|
R"(
|
|
float3 ApplyRealisticOutputCurve(float3 color)
|
|
{
|
|
color.r = ((color.r == color.r) && abs(color.r) < 65504.0) ? color.r : 0.0;
|
|
color.g = ((color.g == color.g) && abs(color.g) < 65504.0) ? color.g : 0.0;
|
|
color.b = ((color.b == color.b) && abs(color.b) < 65504.0) ? color.b : 0.0;
|
|
|
|
// Final photographic shoulder only: it does not change light radius or
|
|
// attenuation, but it prevents intense local lights/specular/bloom from
|
|
// clipping into a flat white patch. Values below 1.0 are left untouched.
|
|
color = max(color, 0.0);
|
|
|
|
float peak = max(max(color.r, color.g), color.b);
|
|
if (peak > 1.0)
|
|
{
|
|
float over = peak - 1.0;
|
|
float shoulderPeak = 1.0 + over / (1.0 + over * 0.38);
|
|
color *= shoulderPeak / max(peak, 1.0e-5);
|
|
}
|
|
|
|
return max(color, 0.0);
|
|
}
|
|
|
|
float3 PreparePathTraceOutputColor(float3 color)
|
|
{
|
|
color.r = ((color.r == color.r) && abs(color.r) < 65504.0) ? color.r : 0.0;
|
|
color.g = ((color.g == color.g) && abs(color.g) < 65504.0) ? color.g : 0.0;
|
|
color.b = ((color.b == color.b) && abs(color.b) < 65504.0) ? color.b : 0.0;
|
|
|
|
// External/hardware denoisers such as DLSS Ray Reconstruction need the noisy
|
|
// HDR lighting/GI signal. The display shoulder is applied after reconstruction.
|
|
if (gEnableDenoiser == 0u)
|
|
return max(color, 0.0);
|
|
|
|
return ApplyRealisticOutputCurve(color);
|
|
}
|
|
|
|
float PreparePathTraceHitDistance(float3 worldPos)
|
|
{
|
|
float d = length(gCameraPos.xyz - worldPos);
|
|
return ((d == d) && d >= 0.0 && d < 65504.0) ? d : 0.0;
|
|
}
|
|
|
|
[shader("raygeneration")]
|
|
void RayGen()
|
|
{
|
|
uint2 pixel = DispatchRaysIndex().xy;
|
|
|
|
if (pixel.x >= (uint)gScreenSize.x || pixel.y >= (uint)gScreenSize.y)
|
|
return;
|
|
|
|
float4 albedoSample = gAlbedoTex.Load(int3(pixel, 0));
|
|
float4 emissiveSample = gEmissiveTex.Load(int3(pixel, 0));
|
|
float3 emissiveSurface = CompressEmissiveRadiance(emissiveSample.rgb, 6.50);
|
|
float depthSample = gDepthTex.Load(int3(pixel, 0));
|
|
|
|
float3 emissiveBloom = EstimateEmissiveBloomAtPixel(pixel);
|
|
|
|
if (depthSample <= 0.0 || depthSample >= 1.0)
|
|
{
|
|
gOutputTex[pixel] = float4(PreparePathTraceOutputColor(albedoSample.rgb + emissiveSurface + emissiveBloom), 0.0);
|
|
return;
|
|
}
|
|
|
|
float3 baseAlbedo = albedoSample.rgb;
|
|
float3 specularAlbedo = LoadSceneSpecularAlbedo(pixel, baseAlbedo);
|
|
float4 positionSample = gPositionTex.Load(int3(pixel, 0));
|
|
float3 worldPos = positionSample.xyz;
|
|
float primaryHitDistance = PreparePathTraceHitDistance(worldPos);
|
|
uint geoFlag = DecodeGeometryFlag(positionSample.w);
|
|
bool isSkeletal = (geoFlag & GEOMETRY_FLAG_SKELETAL) != 0u;
|
|
bool isUnlit = (geoFlag & GEOMETRY_FLAG_UNLIT) != 0u;
|
|
float4 normalSample = LoadSceneNormal(pixel);
|
|
float surfaceRoughness = LoadSceneSurfaceRoughness(normalSample);
|
|
float3 N = EnhanceBumpNormal(pixel, worldPos, baseAlbedo, isSkeletal);
|
|
if (isSkeletal)
|
|
N = StabilizeSkeletalNormal(pixel, worldPos, N);
|
|
surfaceRoughness = StabilizeSpecularSurfaceRoughness(pixel, N, surfaceRoughness);
|
|
float3 V = normalize(gCameraPos.xyz - worldPos);
|
|
|
|
if (isUnlit)
|
|
{
|
|
gOutputTex[pixel] = float4(PreparePathTraceOutputColor(baseAlbedo + emissiveSurface + emissiveBloom), primaryHitDistance);
|
|
return;
|
|
}
|
|
|
|
uint spp = max(gSamplesPerPixel, 1u);
|
|
spp = min(spp, 8u);
|
|
|
|
// All four of these are deterministic for this pixel. Compute them once,
|
|
// then reuse them for every stochastic GI sample.
|
|
const bool externalCharacterPixel = (gExternalDenoiser != 0u) && isSkeletal;
|
|
float cavity = externalCharacterPixel ? 1.0 : ComputeCavity(pixel, worldPos, N);
|
|
if (isSkeletal)
|
|
cavity = lerp(cavity, 1.0, 0.65);
|
|
float ao = externalCharacterPixel ? 1.0 : ComputeAmbientOcclusion(worldPos, N, pixel, isSkeletal);
|
|
float skyVis = 0; //ComputeSkyVisibility(worldPos, N, pixel);
|
|
float ambientSkyVis = 0; //TraceStraightUpToSky(worldPos, N);
|
|
float microShadow = lerp(0.75, 1.0, cavity);
|
|
ApplyWorldGrime(worldPos, cavity, ao, isSkeletal, baseAlbedo, specularAlbedo);
|
|
|
|
float3 reactiveFinalGather = 0.0;
|
|
uint rngFrameIndex = (gEnableDenoiser != 0u && gExternalDenoiser == 0u) ? gFrameIndex : 0u;
|
|
if (gMaxBounces > 1u)
|
|
{
|
|
// Evaluate this deterministic screen-space irradiance reuse once per
|
|
// pixel, not once per SPP. That makes the GI more responsive without
|
|
// multiplying the light-list work inside the stochastic sample loop.
|
|
uint gatherRng = InitSurfaceStableRng(pixel, worldPos, N, 1337u, rngFrameIndex);
|
|
reactiveFinalGather = EstimateReactiveScreenSpaceFinalGather(
|
|
pixel,
|
|
worldPos,
|
|
N,
|
|
V,
|
|
baseAlbedo,
|
|
gatherRng);
|
|
}
|
|
|
|
float3 specularAccum = 0.0;
|
|
float3 subsurfaceAccum = 0.0;
|
|
float3 lightingAccum = PathTraceDeterministicLighting(
|
|
pixel,
|
|
worldPos,
|
|
N,
|
|
V,
|
|
baseAlbedo,
|
|
specularAlbedo,
|
|
surfaceRoughness,
|
|
isSkeletal,
|
|
cavity,
|
|
ao,
|
|
skyVis,
|
|
ambientSkyVis,
|
|
specularAccum,
|
|
subsurfaceAccum);
|
|
lightingAccum += EstimateScreenSpaceEmissiveParticleLighting(pixel, worldPos, N);
|
|
|
|
// Only the indirect bounce path uses per-SPP randomness now. Direct lights,
|
|
// AO, sky visibility, cavity, and final gather are deterministic and should
|
|
// not be re-traced spp times.
|
|
if (gMaxBounces > 1u && !externalCharacterPixel)
|
|
{
|
|
float3 indirectAccum = 0.0;
|
|
|
|
[loop]
|
|
for (uint s = 0; s < spp; ++s)
|
|
{
|
|
// Frame-vary only when the internal temporal accumulator is active.
|
|
// External denoisers get a surface-stable raw GI pattern so texture
|
|
// detail does not boil under reconstruction.
|
|
uint rng = InitSurfaceStableRng(pixel, worldPos, N, s, rngFrameIndex);
|
|
indirectAccum += EstimatePathTracedIndirectBounce(
|
|
pixel,
|
|
worldPos,
|
|
N,
|
|
V,
|
|
baseAlbedo,
|
|
s,
|
|
rng);
|
|
}
|
|
|
|
float3 indirectLighting = indirectAccum / (float)spp;
|
|
lightingAccum += ApplyPrimaryDiffusePost(indirectLighting, ao, microShadow, isSkeletal) * gGiAmount;
|
|
}
|
|
|
|
uint reflectionRng = InitRng(pixel, 0u, 0x5EECu);
|
|
float3 reflectedSpecular = EstimateRayTracedSpecularReflection(
|
|
pixel,
|
|
worldPos,
|
|
N,
|
|
V,
|
|
baseAlbedo,
|
|
specularAlbedo,
|
|
surfaceRoughness,
|
|
cavity,
|
|
isSkeletal,
|
|
reflectionRng);
|
|
|
|
float3 albedo = baseAlbedo * cavity;
|
|
float3 finalColor = (albedo * (lightingAccum)) + specularAccum + reflectedSpecular;
|
|
|
|
if (gMaxBounces > 1u)
|
|
{
|
|
// reactiveFinalGather is incoming indirect radiance. Apply the primary
|
|
// diffuse albedo here, matching the regular lighting path.
|
|
finalColor += baseAlbedo * reactiveFinalGather * gGiAmount;
|
|
}
|
|
|
|
finalColor += subsurfaceAccum;
|
|
|
|
// Volumetric light scattering is radiance in the camera ray, not surface
|
|
// reflectance, so add it after surface albedo/specular composition. Because
|
|
// it is written into the same path-trace target, the internal a-trous pass
|
|
// denoises the stochastic volume/GI signal together with the rest of the ray result.
|
|
uint volumeRng = InitRng(pixel, 0u, 0x51u);
|
|
finalColor += EstimatePathTracedVolumetricScattering(pixel, worldPos, volumeRng);
|
|
|
|
// Glow-map emission is direct radiance from the primary surface. It is added
|
|
// after lighting so it remains visible in darkness and under ray-traced shadows.
|
|
// emissiveBloom is the threshold-free halo, so emissive always blooms even if
|
|
// the eventual swap-chain/backbuffer is LDR.
|
|
finalColor += emissiveSurface + emissiveBloom;
|
|
|
|
gOutputTex[pixel] = float4(PreparePathTraceOutputColor(finalColor), primaryHitDistance);
|
|
}
|
|
)";
|
|
|
|
static const char* g_glRaytracingDenoiseHlsl = R"(
|
|
cbuffer LightingCB : register(b0)
|
|
{
|
|
float4x4 gInvViewProj;
|
|
float4x4 gInvViewMatrix;
|
|
float4x4 gViewProj;
|
|
float4 gCameraPos;
|
|
float4 gAmbientColor;
|
|
float4 gScreenSize;
|
|
float gNormalReconstructZ;
|
|
uint gLightCount;
|
|
uint gEnableSpecular;
|
|
uint gEnableHalfLambert;
|
|
float gShadowBias;
|
|
uint gFrameIndex;
|
|
uint gSamplesPerPixel;
|
|
uint gMaxBounces;
|
|
uint gEnableDenoiser;
|
|
uint gDenoisePassIndex;
|
|
float gDenoiseStepWidth;
|
|
float gDenoiseStrength;
|
|
float gDenoisePhiColor;
|
|
float gDenoisePhiNormal;
|
|
float gDenoisePhiPosition;
|
|
float gBumpStrength;
|
|
int gSecondaryLightBudget;
|
|
uint gSecondaryCorrectionBudget;
|
|
float gSecondaryLightCompensation;
|
|
uint gHistoryReset;
|
|
uint gExternalDenoiser;
|
|
float gGiAmount;
|
|
float gWorldGrimeAmount;
|
|
uint gPadding2;
|
|
};
|
|
|
|
Texture2D<float4> gAlbedoTex : register(t1);
|
|
Texture2D<float> gDepthTex : register(t2);
|
|
Texture2D<float4> gNormalTex : register(t3);
|
|
Texture2D<float4> gPositionTex : register(t4);
|
|
Texture2D<float4> gPathTraceTex : register(t6);
|
|
Texture2D<float4> gDenoiseATex : register(t7);
|
|
Texture2D<float4> gDenoiseBTex : register(t8);
|
|
Texture2D<float4> gTemporalTex : register(t10);
|
|
|
|
RWTexture2D<float4> gRayOutputTex : register(u0);
|
|
RWTexture2D<float4> gDenoiseAOutTex : register(u1);
|
|
RWTexture2D<float4> gDenoiseBOutTex : register(u2);
|
|
RWTexture2D<float4> gDenoisedOutputTex : register(u3);
|
|
|
|
static const float kKernel[5] = { 0.0625, 0.25, 0.375, 0.25, 0.0625 };
|
|
|
|
float3 SafeNormal(float3 n)
|
|
{
|
|
float lenSq = max(dot(n, n), 1e-8);
|
|
return n * rsqrt(lenSq);
|
|
}
|
|
|
|
float Luminance(float3 c)
|
|
{
|
|
return dot(c, float3(0.2126, 0.7152, 0.0722));
|
|
}
|
|
|
|
static const uint GEOMETRY_FLAG_SKELETAL = 1u;
|
|
static const uint GEOMETRY_FLAG_UNLIT = 2u;
|
|
static const uint GEOMETRY_FLAG_GLASS = 4u;
|
|
|
|
uint DecodeGeometryFlag(float geoFlag)
|
|
{
|
|
return (uint)floor(max(geoFlag, 0.0) + 0.5);
|
|
}
|
|
|
|
float3 SafeAlbedoDivisor(float3 albedo)
|
|
{
|
|
// Do not let black/dark textures explode when demodulating noisy lighting.
|
|
return max(abs(albedo), float3(0.06, 0.06, 0.06));
|
|
}
|
|
|
|
float3 DemodulateLighting(float3 radiance, float3 albedo)
|
|
{
|
|
return radiance / SafeAlbedoDivisor(albedo);
|
|
}
|
|
|
|
float3 RemodulateLighting(float3 lighting, float3 albedo)
|
|
{
|
|
return lighting * SafeAlbedoDivisor(albedo);
|
|
}
|
|
|
|
float4 LoadDenoiseSource(int2 p)
|
|
{
|
|
if (gDenoisePassIndex == 0u)
|
|
return gTemporalTex.Load(int3(p, 0));
|
|
if (gDenoisePassIndex == 1u)
|
|
return gDenoiseATex.Load(int3(p, 0));
|
|
return gDenoiseBTex.Load(int3(p, 0));
|
|
}
|
|
|
|
void StoreDenoiseOutput(uint2 p, float4 v)
|
|
{
|
|
if (gDenoisePassIndex == 0u)
|
|
gDenoiseAOutTex[p] = v;
|
|
else if (gDenoisePassIndex == 1u)
|
|
gDenoiseBOutTex[p] = v;
|
|
else
|
|
gDenoisedOutputTex[p] = v;
|
|
}
|
|
|
|
float GeometryAwareWeight(
|
|
float3 centerRadiance,
|
|
float3 sampleRadiance,
|
|
float3 centerAlbedo,
|
|
float3 sampleAlbedo,
|
|
float3 centerNormal,
|
|
float3 sampleNormal,
|
|
float3 centerPos,
|
|
float3 samplePos,
|
|
float centerDepth,
|
|
float sampleDepth,
|
|
uint centerGeoFlag,
|
|
uint sampleGeoFlag,
|
|
float kernelWeight)
|
|
{
|
|
if (sampleDepth <= 0.0 || sampleDepth >= 1.0)
|
|
return 0.0;
|
|
|
|
// Do not smear lighting across material-class boundaries. This is
|
|
// particularly important for skeletal pixels and glass, because both can sit
|
|
// in front of unrelated world lighting in the same screen neighborhood.
|
|
const uint classMask = GEOMETRY_FLAG_SKELETAL | GEOMETRY_FLAG_UNLIT | GEOMETRY_FLAG_GLASS;
|
|
if (((centerGeoFlag ^ sampleGeoFlag) & classMask) != 0u)
|
|
return 0.0;
|
|
|
|
float3 centerLighting = DemodulateLighting(centerRadiance, centerAlbedo);
|
|
float3 sampleLighting = DemodulateLighting(sampleRadiance, sampleAlbedo);
|
|
|
|
// Use albedo, not noisy lit radiance, as the main color edge guide. The
|
|
// previous filter used the shadowed/noisy signal itself as the guide, which
|
|
// rejected neighbors across shadow variation and left shadow noise intact.
|
|
float albedoDiff = length(centerAlbedo - sampleAlbedo);
|
|
float albedoWeight = exp(-albedoDiff * max(gDenoisePhiColor, 0.001));
|
|
|
|
// A deliberately soft lighting-domain gate keeps hard contact-shadow edges
|
|
// from being over-blurred, but still lets noisy penumbra/visibility samples
|
|
// converge across the same surface.
|
|
float centerLum = Luminance(centerLighting);
|
|
float sampleLum = Luminance(sampleLighting);
|
|
float illumDiff = abs(sampleLum - centerLum);
|
|
float relativeIllumDiff = illumDiff / max(max(abs(centerLum), abs(sampleLum)), 0.05);
|
|
|
|
// Keep this gate conservative. The noise fix is to stabilize and stratify the
|
|
// ray samples; over-loosening this filter smears direct lighting and makes the
|
|
// scene look noisier/blotchier.
|
|
float illuminationWeight = exp(-relativeIllumDiff * max(gDenoisePhiColor * 0.035, 0.10));
|
|
|
|
float normalWeight = pow(saturate(dot(centerNormal, sampleNormal)), max(gDenoisePhiNormal, 1.0));
|
|
float positionDiff = length(samplePos - centerPos);
|
|
float positionWeight = exp(-positionDiff * max(gDenoisePhiPosition, 0.001));
|
|
float depthDiff = abs(sampleDepth - centerDepth);
|
|
float depthWeight = exp(-depthDiff * 300.0);
|
|
|
|
return kernelWeight * albedoWeight * illuminationWeight * normalWeight * positionWeight * depthWeight;
|
|
}
|
|
|
|
[numthreads(8, 8, 1)]
|
|
void DenoiseCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|
{
|
|
uint2 pixel = dispatchThreadId.xy;
|
|
|
|
if (pixel.x >= (uint)gScreenSize.x || pixel.y >= (uint)gScreenSize.y)
|
|
return;
|
|
|
|
float4 albedoSample = gAlbedoTex.Load(int3(pixel, 0));
|
|
float depthSample = gDepthTex.Load(int3(pixel, 0));
|
|
float4 centerSource = LoadDenoiseSource(int2(pixel));
|
|
|
|
if (depthSample <= 0.0 || depthSample >= 1.0)
|
|
{
|
|
// Preserve raygen's emissive bloom on background/no-depth pixels.
|
|
// Returning albedo here would erase the halo whenever the internal
|
|
// temporal/a-trous denoiser is active.
|
|
StoreDenoiseOutput(pixel, centerSource);
|
|
return;
|
|
}
|
|
|
|
if (gEnableDenoiser == 0u)
|
|
{
|
|
StoreDenoiseOutput(pixel, centerSource);
|
|
return;
|
|
}
|
|
|
|
float3 centerAlbedo = saturate(albedoSample.rgb);
|
|
float3 centerNormal = SafeNormal(gNormalTex.Load(int3(pixel, 0)).xyz);
|
|
float4 centerPos4 = gPositionTex.Load(int3(pixel, 0));
|
|
float3 centerPos = centerPos4.xyz;
|
|
uint centerGeoFlag = DecodeGeometryFlag(centerPos4.w);
|
|
|
|
int stepI = max((int)round(max(gDenoiseStepWidth, 1.0)), 1);
|
|
float3 accumLighting = 0.0;
|
|
float weightSum = 0.0;
|
|
|
|
// Three-pass a-trous wavelet filter. The CPU dispatches this with step
|
|
// widths 1, 2, and 4. It is geometry-aware; temporal GI accumulation happens
|
|
// before this pass.
|
|
[unroll]
|
|
for (int ky = 0; ky < 5; ++ky)
|
|
{
|
|
[unroll]
|
|
for (int kx = 0; kx < 5; ++kx)
|
|
{
|
|
int2 sp = int2(pixel) + int2(kx - 2, ky - 2) * stepI;
|
|
|
|
if (sp.x < 0 || sp.y < 0 || sp.x >= (int)gScreenSize.x || sp.y >= (int)gScreenSize.y)
|
|
continue;
|
|
|
|
float sampleDepth = gDepthTex.Load(int3(sp, 0));
|
|
float4 sampleColor4 = LoadDenoiseSource(sp);
|
|
float3 sampleAlbedo = saturate(gAlbedoTex.Load(int3(sp, 0)).rgb);
|
|
float3 sampleNormal = SafeNormal(gNormalTex.Load(int3(sp, 0)).xyz);
|
|
float4 samplePos4 = gPositionTex.Load(int3(sp, 0));
|
|
float3 samplePos = samplePos4.xyz;
|
|
uint sampleGeoFlag = DecodeGeometryFlag(samplePos4.w);
|
|
float kernelWeight = kKernel[kx] * kKernel[ky];
|
|
|
|
float w = GeometryAwareWeight(
|
|
centerSource.rgb,
|
|
sampleColor4.rgb,
|
|
centerAlbedo,
|
|
sampleAlbedo,
|
|
centerNormal,
|
|
sampleNormal,
|
|
centerPos,
|
|
samplePos,
|
|
depthSample,
|
|
sampleDepth,
|
|
centerGeoFlag,
|
|
sampleGeoFlag,
|
|
kernelWeight);
|
|
|
|
accumLighting += DemodulateLighting(sampleColor4.rgb, sampleAlbedo) * w;
|
|
weightSum += w;
|
|
}
|
|
}
|
|
|
|
float3 filteredLighting = (weightSum > 1e-6)
|
|
? (accumLighting / weightSum)
|
|
: DemodulateLighting(centerSource.rgb, centerAlbedo);
|
|
|
|
float3 filtered = RemodulateLighting(filteredLighting, centerAlbedo);
|
|
|
|
// Final-pass firefly clamp against the raw neighborhood.
|
|
if (gDenoisePassIndex >= 2u)
|
|
{
|
|
float3 minRaw = gPathTraceTex.Load(int3(pixel, 0)).rgb;
|
|
float3 maxRaw = minRaw;
|
|
|
|
[unroll]
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
[unroll]
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
int2 sp = int2(pixel) + int2(x, y);
|
|
if (sp.x < 0 || sp.y < 0 || sp.x >= (int)gScreenSize.x || sp.y >= (int)gScreenSize.y)
|
|
continue;
|
|
float sampleDepth = gDepthTex.Load(int3(sp, 0));
|
|
if (sampleDepth <= 0.0 || sampleDepth >= 1.0)
|
|
continue;
|
|
|
|
float4 samplePos4 = gPositionTex.Load(int3(sp, 0));
|
|
uint sampleGeoFlag = DecodeGeometryFlag(samplePos4.w);
|
|
const uint classMask = GEOMETRY_FLAG_SKELETAL | GEOMETRY_FLAG_UNLIT | GEOMETRY_FLAG_GLASS;
|
|
if (((centerGeoFlag ^ sampleGeoFlag) & classMask) != 0u)
|
|
continue;
|
|
|
|
float3 raw = gPathTraceTex.Load(int3(sp, 0)).rgb;
|
|
minRaw = min(minRaw, raw);
|
|
maxRaw = max(maxRaw, raw);
|
|
}
|
|
}
|
|
|
|
// Keep the clamp tight. A wide clamp lets bright stochastic GI/volume
|
|
// outliers survive and was the main reason the previous patch looked worse.
|
|
filtered = clamp(filtered, minRaw - 0.15, maxRaw + 0.15);
|
|
}
|
|
|
|
float3 outColor = lerp(centerSource.rgb, filtered, saturate(gDenoiseStrength));
|
|
StoreDenoiseOutput(pixel, float4(max(outColor, 0.0), centerSource.a));
|
|
}
|
|
)";
|
|
|
|
|
|
static const char* g_glRaytracingTemporalHlsl = R"(
|
|
cbuffer LightingCB : register(b0)
|
|
{
|
|
float4x4 gInvViewProj;
|
|
float4x4 gInvViewMatrix;
|
|
float4x4 gViewProj;
|
|
float4 gCameraPos;
|
|
float4 gAmbientColor;
|
|
float4 gScreenSize;
|
|
float gNormalReconstructZ;
|
|
uint gLightCount;
|
|
uint gEnableSpecular;
|
|
uint gEnableHalfLambert;
|
|
float gShadowBias;
|
|
uint gFrameIndex;
|
|
uint gSamplesPerPixel;
|
|
uint gMaxBounces;
|
|
uint gEnableDenoiser;
|
|
uint gDenoisePassIndex;
|
|
float gDenoiseStepWidth;
|
|
float gDenoiseStrength;
|
|
float gDenoisePhiColor;
|
|
float gDenoisePhiNormal;
|
|
float gDenoisePhiPosition;
|
|
float gBumpStrength;
|
|
int gSecondaryLightBudget;
|
|
uint gSecondaryCorrectionBudget;
|
|
float gSecondaryLightCompensation;
|
|
uint gHistoryReset;
|
|
uint gExternalDenoiser;
|
|
float gGiAmount;
|
|
float gWorldGrimeAmount;
|
|
uint gPadding2;
|
|
};
|
|
|
|
Texture2D<float4> gAlbedoTex : register(t1);
|
|
Texture2D<float> gDepthTex : register(t2);
|
|
Texture2D<float4> gNormalTex : register(t3);
|
|
Texture2D<float4> gPositionTex : register(t4);
|
|
Texture2D<float4> gPathTraceTex : register(t6);
|
|
Texture2D<float4> gHistoryTex : register(t9);
|
|
Texture2D<float4> gVelocityTex : register(t22);
|
|
|
|
RWTexture2D<float4> gTemporalOutTex : register(u4);
|
|
RWTexture2D<float4> gHistoryOutTex : register(u5);
|
|
|
|
float LuminanceTemporal(float3 c)
|
|
{
|
|
return dot(c, float3(0.2126, 0.7152, 0.0722));
|
|
}
|
|
|
|
float3 SafeNormalTemporal(float3 n)
|
|
{
|
|
float lenSq = max(dot(n, n), 1e-8);
|
|
return n * rsqrt(lenSq);
|
|
}
|
|
|
|
uint DecodeGeometryFlagTemporal(float geoFlag)
|
|
{
|
|
return (uint)floor(max(geoFlag, 0.0) + 0.5);
|
|
}
|
|
|
|
float3 ClampHistoryToCurrent(float3 history, float3 current)
|
|
{
|
|
// Clamp enough to kill rare GI/volume fireflies before they enter history,
|
|
// while still allowing real dynamic light changes to pull the accumulator.
|
|
float3 radius = 0.12 + abs(current) * 0.42;
|
|
return clamp(history, current - radius, current + radius);
|
|
}
|
|
|
|
float4 LoadHistoryClamped(int2 p)
|
|
{
|
|
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
|
|
p = clamp(p, int2(0, 0), maxPixel);
|
|
return gHistoryTex.Load(int3(p, 0));
|
|
}
|
|
|
|
float4 SampleHistoryBilinear(float2 uv)
|
|
{
|
|
float2 pixelF = uv * gScreenSize.xy - 0.5;
|
|
int2 p0 = int2(floor(pixelF));
|
|
float2 f = frac(pixelF);
|
|
|
|
float4 h00 = LoadHistoryClamped(p0 + int2(0, 0));
|
|
float4 h10 = LoadHistoryClamped(p0 + int2(1, 0));
|
|
float4 h01 = LoadHistoryClamped(p0 + int2(0, 1));
|
|
float4 h11 = LoadHistoryClamped(p0 + int2(1, 1));
|
|
|
|
float4 hx0 = lerp(h00, h10, f.x);
|
|
float4 hx1 = lerp(h01, h11, f.x);
|
|
return lerp(hx0, hx1, f.y);
|
|
}
|
|
|
|
[numthreads(8, 8, 1)]
|
|
void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|
{
|
|
uint2 pixel = dispatchThreadId.xy;
|
|
|
|
if (pixel.x >= (uint)gScreenSize.x || pixel.y >= (uint)gScreenSize.y)
|
|
return;
|
|
|
|
float4 raw = gPathTraceTex.Load(int3(pixel, 0));
|
|
float depth = gDepthTex.Load(int3(pixel, 0));
|
|
float4 positionSample = gPositionTex.Load(int3(pixel, 0));
|
|
uint geoFlag = DecodeGeometryFlagTemporal(positionSample.w);
|
|
bool isSkeletal = (geoFlag & 1u) != 0u;
|
|
|
|
if (depth <= 0.0 || depth >= 1.0 || gMaxBounces <= 1u)
|
|
{
|
|
gTemporalOutTex[pixel] = raw;
|
|
gHistoryOutTex[pixel] = float4(raw.rgb, 0.0);
|
|
return;
|
|
}
|
|
|
|
float2 uv = ((float2)pixel + 0.5) / max(gScreenSize.xy, float2(1.0, 1.0));
|
|
float2 velocity = gVelocityTex.Load(int3(pixel, 0)).xy; // previousUV - currentUV
|
|
float2 prevUv = uv + velocity;
|
|
bool validHistoryUv =
|
|
prevUv.x >= 0.0 && prevUv.x <= 1.0 &&
|
|
prevUv.y >= 0.0 && prevUv.y <= 1.0 &&
|
|
all(abs(velocity) < 1.0);
|
|
|
|
float4 history = validHistoryUv ? SampleHistoryBilinear(prevUv) : float4(raw.rgb, 0.0);
|
|
float historyCount = (gFrameIndex == 0u || gHistoryReset != 0u || !validHistoryUv)
|
|
? 0.0
|
|
: clamp(history.a, 0.0, 63.0);
|
|
|
|
if (historyCount <= 0.0)
|
|
{
|
|
gTemporalOutTex[pixel] = raw;
|
|
gHistoryOutTex[pixel] = float4(raw.rgb, 1.0);
|
|
return;
|
|
}
|
|
|
|
float3 historyColor = ClampHistoryToCurrent(max(history.rgb, 0.0), max(raw.rgb, 0.0));
|
|
|
|
float rawLum = LuminanceTemporal(max(raw.rgb, 0.0));
|
|
float histLum = LuminanceTemporal(historyColor);
|
|
float relChange = abs(rawLum - histLum) / max(max(rawLum, histLum), 0.08);
|
|
float velocityPixels = length(velocity * gScreenSize.xy);
|
|
|
|
// Base accumulation approaches 64 frames, but large lighting changes raise
|
|
// current-frame weight so the accumulator does not leave obvious trails.
|
|
float currentWeight = max(1.0 / (historyCount + 1.0), 0.024);
|
|
currentWeight = max(currentWeight, saturate(relChange * 0.18));
|
|
currentWeight = max(currentWeight, saturate(velocityPixels * 0.016));
|
|
if (isSkeletal)
|
|
currentWeight = max(currentWeight, 0.18);
|
|
currentWeight = saturate(currentWeight);
|
|
|
|
float3 resolved = lerp(historyColor, max(raw.rgb, 0.0), currentWeight);
|
|
float nextCount = min(historyCount + 1.0, 63.0);
|
|
|
|
gTemporalOutTex[pixel] = float4(resolved, raw.a);
|
|
gHistoryOutTex[pixel] = float4(resolved, nextCount);
|
|
}
|
|
)";
|
|
|
|
static ComPtr<IDxcBlob> glRaytracingLightingCompileLibrary(const char* src)
|
|
{
|
|
ComPtr<IDxcUtils> utils;
|
|
ComPtr<IDxcCompiler3> compiler;
|
|
ComPtr<IDxcIncludeHandler> includeHandler;
|
|
|
|
HRESULT hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&utils));
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("DxcCreateInstance utils failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
hr = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler));
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("DxcCreateInstance compiler failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
hr = utils->CreateDefaultIncludeHandler(&includeHandler);
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("CreateDefaultIncludeHandler failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
DxcBuffer source = {};
|
|
source.Ptr = src;
|
|
source.Size = strlen(src);
|
|
source.Encoding = DXC_CP_UTF8;
|
|
|
|
const wchar_t* args[] =
|
|
{
|
|
L"-T", L"lib_6_3",
|
|
#if defined(_DEBUG)
|
|
L"-Zi",
|
|
L"-Qembed_debug",
|
|
#endif
|
|
// Keep the DXR library smaller to avoid long driver-side linking during
|
|
// CreateStateObject(). Compute/post shaders below still compile with O3.
|
|
L"-O1",
|
|
L"-all_resources_bound"
|
|
};
|
|
|
|
ComPtr<IDxcResult> result;
|
|
hr = compiler->Compile(&source, args, _countof(args), includeHandler.Get(), IID_PPV_ARGS(&result));
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("DXC compile failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
ComPtr<IDxcBlobUtf8> errors;
|
|
result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr);
|
|
if (errors && errors->GetStringLength() > 0)
|
|
{
|
|
OutputDebugStringA(errors->GetStringPointer());
|
|
OutputDebugStringA("\n");
|
|
}
|
|
|
|
HRESULT status = S_OK;
|
|
result->GetStatus(&status);
|
|
if (FAILED(status))
|
|
{
|
|
glRaytracingFatal("DXIL compile status failed 0x%08X", (unsigned)status);
|
|
return nullptr;
|
|
}
|
|
|
|
ComPtr<IDxcBlob> dxil;
|
|
result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&dxil), nullptr);
|
|
return dxil;
|
|
}
|
|
|
|
static ComPtr<IDxcBlob> glRaytracingLightingCompileCompute(const char* src, const wchar_t* entryPoint)
|
|
{
|
|
ComPtr<IDxcUtils> utils;
|
|
ComPtr<IDxcCompiler3> compiler;
|
|
ComPtr<IDxcIncludeHandler> includeHandler;
|
|
|
|
HRESULT hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&utils));
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("DxcCreateInstance utils failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
hr = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler));
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("DxcCreateInstance compiler failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
hr = utils->CreateDefaultIncludeHandler(&includeHandler);
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("CreateDefaultIncludeHandler failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
DxcBuffer source = {};
|
|
source.Ptr = src;
|
|
source.Size = strlen(src);
|
|
source.Encoding = DXC_CP_UTF8;
|
|
|
|
const wchar_t* args[] =
|
|
{
|
|
L"-E", entryPoint,
|
|
L"-T", L"cs_6_0",
|
|
#if defined(_DEBUG)
|
|
L"-Zi",
|
|
L"-Qembed_debug",
|
|
#endif
|
|
L"-O3",
|
|
L"-all_resources_bound"
|
|
};
|
|
|
|
ComPtr<IDxcResult> result;
|
|
hr = compiler->Compile(&source, args, _countof(args), includeHandler.Get(), IID_PPV_ARGS(&result));
|
|
if (FAILED(hr))
|
|
{
|
|
glRaytracingFatal("DXC compute compile failed 0x%08X", (unsigned)hr);
|
|
return nullptr;
|
|
}
|
|
|
|
ComPtr<IDxcBlobUtf8> errors;
|
|
result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr);
|
|
if (errors && errors->GetStringLength() > 0)
|
|
{
|
|
OutputDebugStringA(errors->GetStringPointer());
|
|
OutputDebugStringA("\n");
|
|
}
|
|
|
|
HRESULT status = S_OK;
|
|
result->GetStatus(&status);
|
|
if (FAILED(status))
|
|
{
|
|
glRaytracingFatal("DXIL compute compile status failed 0x%08X", (unsigned)status);
|
|
return nullptr;
|
|
}
|
|
|
|
ComPtr<IDxcBlob> dxil;
|
|
result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&dxil), nullptr);
|
|
return dxil;
|
|
}
|
|
|
|
static int glRaytracingLightingCreateDescriptorHeap(void)
|
|
{
|
|
D3D12_DESCRIPTOR_HEAP_DESC hd = {};
|
|
hd.NumDescriptors = GLR_DESC_COUNT;
|
|
hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
|
hd.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
|
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateDescriptorHeap(
|
|
&hd,
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.descriptorHeapRing[frame])));
|
|
}
|
|
|
|
g_glRaytracingLighting.descriptorHeap = g_glRaytracingLighting.descriptorHeapRing[0];
|
|
g_glRaytracingLighting.descriptorStride =
|
|
g_glRaytracingCmd.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingLightingCreateRootSignatures(void)
|
|
{
|
|
{
|
|
D3D12_DESCRIPTOR_RANGE ranges[2] = {};
|
|
|
|
ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
|
ranges[0].NumDescriptors = GLR_DESC_SRV_COUNT;
|
|
ranges[0].BaseShaderRegister = 0;
|
|
ranges[0].RegisterSpace = 0;
|
|
ranges[0].OffsetInDescriptorsFromTableStart = 0;
|
|
|
|
ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
|
|
ranges[1].NumDescriptors = GLR_DESC_UAV_COUNT;
|
|
ranges[1].BaseShaderRegister = 0;
|
|
ranges[1].RegisterSpace = 0;
|
|
ranges[1].OffsetInDescriptorsFromTableStart = 0;
|
|
|
|
D3D12_ROOT_PARAMETER params[3] = {};
|
|
|
|
params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
params[0].DescriptorTable.NumDescriptorRanges = 1;
|
|
params[0].DescriptorTable.pDescriptorRanges = &ranges[0];
|
|
params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
|
|
params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
params[1].DescriptorTable.NumDescriptorRanges = 1;
|
|
params[1].DescriptorTable.pDescriptorRanges = &ranges[1];
|
|
params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
|
|
params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
|
|
params[2].Descriptor.ShaderRegister = 0;
|
|
params[2].Descriptor.RegisterSpace = 0;
|
|
params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
|
|
D3D12_ROOT_SIGNATURE_DESC rsd = {};
|
|
rsd.NumParameters = _countof(params);
|
|
rsd.pParameters = params;
|
|
rsd.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE;
|
|
|
|
ComPtr<ID3DBlob> sig;
|
|
ComPtr<ID3DBlob> err;
|
|
GLR_CHECK(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &sig, &err));
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateRootSignature(
|
|
0, sig->GetBufferPointer(), sig->GetBufferSize(),
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.globalRootSig)));
|
|
}
|
|
|
|
{
|
|
D3D12_ROOT_SIGNATURE_DESC rsd = {};
|
|
rsd.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE;
|
|
|
|
ComPtr<ID3DBlob> sig;
|
|
ComPtr<ID3DBlob> err;
|
|
GLR_CHECK(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &sig, &err));
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateRootSignature(
|
|
0, sig->GetBufferPointer(), sig->GetBufferSize(),
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.localRootSig)));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingMapUploadBufferPersistent(const glRaytracingBuffer_t& buffer, void** mapped)
|
|
{
|
|
if (!buffer.resource || !mapped)
|
|
return 0;
|
|
|
|
*mapped = nullptr;
|
|
D3D12_RANGE readRange = {};
|
|
HRESULT hr = buffer.resource->Map(0, &readRange, mapped);
|
|
if (FAILED(hr) || !*mapped)
|
|
{
|
|
glRaytracingFatal("Persistent upload Map failed 0x%08X", (unsigned)hr);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingLightingSelectFrameResources(UINT frameSlot)
|
|
{
|
|
frameSlot %= GL_RAYTRACING_CMD_RING_SIZE;
|
|
|
|
g_glRaytracingLighting.descriptorHeap = g_glRaytracingLighting.descriptorHeapRing[frameSlot];
|
|
g_glRaytracingLighting.constantBuffer = g_glRaytracingLighting.constantBufferRing[frameSlot];
|
|
g_glRaytracingLighting.lightBuffer = g_glRaytracingLighting.lightBufferRing[frameSlot];
|
|
g_glRaytracingLighting.meshMetadataBuffer = g_glRaytracingLighting.meshMetadataBufferRing[frameSlot];
|
|
g_glRaytracingLighting.materialVertexBuffer = g_glRaytracingLighting.materialVertexBufferRing[frameSlot];
|
|
g_glRaytracingLighting.materialIndexBuffer = g_glRaytracingLighting.materialIndexBufferRing[frameSlot];
|
|
g_glRaytracingLighting.constantBufferMapped = g_glRaytracingLighting.constantBufferMappedRing[frameSlot];
|
|
g_glRaytracingLighting.lightBufferMapped = g_glRaytracingLighting.lightBufferMappedRing[frameSlot];
|
|
g_glRaytracingLighting.meshMetadataBufferMapped = g_glRaytracingLighting.meshMetadataBufferMappedRing[frameSlot];
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
g_glRaytracingLighting.denoiseConstantBuffer[i] = g_glRaytracingLighting.denoiseConstantBufferRing[frameSlot][i];
|
|
g_glRaytracingLighting.denoiseConstantBufferMapped[i] = g_glRaytracingLighting.denoiseConstantBufferMappedRing[frameSlot][i];
|
|
}
|
|
}
|
|
|
|
static int glRaytracingLightingCreateBuffers(void)
|
|
{
|
|
const UINT64 constantsBytes = glRaytracingAlignUp(sizeof(glRaytracingLightingConstants_t), 256);
|
|
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
g_glRaytracingLighting.constantBufferRing[frame] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
constantsBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
g_glRaytracingLighting.lightBufferRing[frame] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
sizeof(glRaytracingLight_t) * GL_RAYTRACING_MAX_LIGHTS,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
g_glRaytracingLighting.meshMetadataBufferRing[frame] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
sizeof(glRaytracingMeshMetadata_t) * 65536ull,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
if (!g_glRaytracingLighting.constantBufferRing[frame].resource ||
|
|
!g_glRaytracingLighting.lightBufferRing[frame].resource ||
|
|
!g_glRaytracingLighting.meshMetadataBufferRing[frame].resource)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
g_glRaytracingLighting.denoiseConstantBufferRing[frame][i] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
constantsBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
if (!g_glRaytracingLighting.denoiseConstantBufferRing[frame][i].resource)
|
|
return 0;
|
|
}
|
|
|
|
// These are small UPLOAD-heap buffers updated every pass. Keeping every
|
|
// per-frame copy persistently mapped avoids Map/Unmap overhead and makes
|
|
// the async command-ring safe: the CPU never overwrites constants that an
|
|
// older in-flight command list still reads.
|
|
if (!glRaytracingMapUploadBufferPersistent(
|
|
g_glRaytracingLighting.constantBufferRing[frame],
|
|
&g_glRaytracingLighting.constantBufferMappedRing[frame]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingMapUploadBufferPersistent(
|
|
g_glRaytracingLighting.lightBufferRing[frame],
|
|
&g_glRaytracingLighting.lightBufferMappedRing[frame]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!glRaytracingMapUploadBufferPersistent(
|
|
g_glRaytracingLighting.meshMetadataBufferRing[frame],
|
|
&g_glRaytracingLighting.meshMetadataBufferMappedRing[frame]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (!glRaytracingMapUploadBufferPersistent(
|
|
g_glRaytracingLighting.denoiseConstantBufferRing[frame][i],
|
|
&g_glRaytracingLighting.denoiseConstantBufferMappedRing[frame][i]))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
glRaytracingLightingSelectFrameResources(0);
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingLightingUploadConstantsTo(
|
|
const glRaytracingBuffer_t& dst,
|
|
const glRaytracingLightingConstants_t& constants)
|
|
{
|
|
if (!dst.resource)
|
|
return;
|
|
|
|
void* mapped = nullptr;
|
|
if (dst.resource.Get() == g_glRaytracingLighting.constantBuffer.resource.Get())
|
|
{
|
|
mapped = g_glRaytracingLighting.constantBufferMapped;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (dst.resource.Get() == g_glRaytracingLighting.denoiseConstantBuffer[i].resource.Get())
|
|
{
|
|
mapped = g_glRaytracingLighting.denoiseConstantBufferMapped[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mapped)
|
|
{
|
|
memcpy(mapped, &constants, sizeof(constants));
|
|
return;
|
|
}
|
|
|
|
glRaytracingMapCopy(dst.resource.Get(), &constants, sizeof(constants));
|
|
}
|
|
|
|
static void glRaytracingLightingUpdateConstants(void)
|
|
{
|
|
if (!g_glRaytracingLighting.uploadToCurrentFrameResource)
|
|
return;
|
|
|
|
glRaytracingLightingUploadConstantsTo(
|
|
g_glRaytracingLighting.constantBuffer,
|
|
g_glRaytracingLighting.constants);
|
|
}
|
|
|
|
static float glRaytracingLightingEstimateLightImportance(const glRaytracingLight_t& light)
|
|
{
|
|
float colorPeak = light.color.x;
|
|
if (light.color.y > colorPeak) colorPeak = light.color.y;
|
|
if (light.color.z > colorPeak) colorPeak = light.color.z;
|
|
|
|
float radius = light.radius;
|
|
if (light.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
{
|
|
radius = light.pointRadius.x;
|
|
if (light.pointRadius.y > radius) radius = light.pointRadius.y;
|
|
if (light.pointRadius.z > radius) radius = light.pointRadius.z;
|
|
}
|
|
else if (light.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
{
|
|
radius = (light.halfWidth > light.halfHeight) ? light.halfWidth : light.halfHeight;
|
|
radius *= 6.0f;
|
|
}
|
|
|
|
if (radius < 1.0f)
|
|
radius = 1.0f;
|
|
|
|
float importance = colorPeak * light.intensity * sqrtf(radius);
|
|
if (light.samples != 0u)
|
|
importance *= 1.15f;
|
|
if (light.volumetricScattering > 0.0f)
|
|
importance *= 1.10f;
|
|
if (light.iesStrength > 0.0f)
|
|
importance *= 1.05f;
|
|
return importance;
|
|
}
|
|
|
|
static void glRaytracingLightingUpdateLights(void)
|
|
{
|
|
if (!g_glRaytracingLighting.uploadToCurrentFrameResource)
|
|
return;
|
|
|
|
if (!g_glRaytracingLighting.lightBuffer.resource)
|
|
return;
|
|
|
|
if (g_glRaytracingLighting.cpuLights.empty())
|
|
return;
|
|
|
|
std::vector<glRaytracingLight_t>& uploadLights = g_glRaytracingLighting.uploadLights;
|
|
uploadLights = g_glRaytracingLighting.cpuLights;
|
|
std::sort(uploadLights.begin(), uploadLights.end(),
|
|
[](const glRaytracingLight_t& a, const glRaytracingLight_t& b)
|
|
{
|
|
return glRaytracingLightingEstimateLightImportance(a) > glRaytracingLightingEstimateLightImportance(b);
|
|
});
|
|
|
|
for (size_t i = 0; i < uploadLights.size(); ++i)
|
|
{
|
|
glRaytracingLight_t& light = uploadLights[i];
|
|
const uint32_t textureId = light.iesTextureId;
|
|
if (textureId == 0 || light.iesStrength <= 0.0f)
|
|
{
|
|
light.iesTextureId = 0;
|
|
light.iesStrength = 0.0f;
|
|
continue;
|
|
}
|
|
|
|
uint32_t slot = GL_RAYTRACING_MAX_IES_TEXTURES;
|
|
for (uint32_t j = 0; j < GL_RAYTRACING_MAX_IES_TEXTURES; ++j)
|
|
{
|
|
if (g_glRaytracingLighting.iesTextureIds[j] == textureId)
|
|
{
|
|
slot = j;
|
|
break;
|
|
}
|
|
if (g_glRaytracingLighting.iesTextureIds[j] == 0 && slot == GL_RAYTRACING_MAX_IES_TEXTURES)
|
|
{
|
|
slot = j;
|
|
}
|
|
}
|
|
|
|
if (slot == GL_RAYTRACING_MAX_IES_TEXTURES)
|
|
{
|
|
light.iesTextureId = 0;
|
|
light.iesStrength = 0.0f;
|
|
continue;
|
|
}
|
|
|
|
if (g_glRaytracingLighting.iesTextureIds[slot] == 0)
|
|
{
|
|
DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
UINT width = 0;
|
|
UINT height = 0;
|
|
ID3D12Resource* resource = QD3D12_GetTextureResource((GLuint)textureId, &format, &width, &height, g_glRaytracingCmd.cmdList.Get());
|
|
if (!resource || width == 0 || height == 0)
|
|
{
|
|
light.iesTextureId = 0;
|
|
light.iesStrength = 0.0f;
|
|
continue;
|
|
}
|
|
|
|
g_glRaytracingLighting.iesTextureIds[slot] = textureId;
|
|
g_glRaytracingLighting.iesTextures[slot] = resource;
|
|
g_glRaytracingLighting.iesFormats[slot] = format;
|
|
}
|
|
|
|
light.iesTextureId = slot + 1;
|
|
}
|
|
|
|
const size_t bytes = uploadLights.size() * sizeof(glRaytracingLight_t);
|
|
if (g_glRaytracingLighting.lightBufferMapped)
|
|
{
|
|
memcpy(g_glRaytracingLighting.lightBufferMapped, uploadLights.data(), bytes);
|
|
return;
|
|
}
|
|
|
|
glRaytracingMapCopy(
|
|
g_glRaytracingLighting.lightBuffer.resource.Get(),
|
|
uploadLights.data(),
|
|
bytes);
|
|
}
|
|
|
|
static bool glRaytracingLightingEnsureMaterialGeometryBuffers(uint32_t vertexCount, uint32_t indexCount)
|
|
{
|
|
const UINT frameSlot = g_glRaytracingCmd.cmdCurrentSlot % GL_RAYTRACING_CMD_RING_SIZE;
|
|
const UINT64 vertexBytes = glRaytracingAlignUp((UINT64)std::max<uint32_t>(vertexCount, 1u) * sizeof(glRaytracingVertex_t), 256);
|
|
const UINT64 indexBytes = glRaytracingAlignUp((UINT64)std::max<uint32_t>(indexCount, 1u) * sizeof(uint32_t), 256);
|
|
|
|
if (!g_glRaytracingLighting.materialVertexBufferRing[frameSlot].resource ||
|
|
g_glRaytracingLighting.materialVertexCapacityRing[frameSlot] < vertexCount)
|
|
{
|
|
g_glRaytracingLighting.materialVertexBufferRing[frameSlot] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
vertexBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
if (!g_glRaytracingLighting.materialVertexBufferRing[frameSlot].resource)
|
|
return false;
|
|
g_glRaytracingLighting.materialVertexCapacityRing[frameSlot] = vertexCount;
|
|
}
|
|
|
|
if (!g_glRaytracingLighting.materialIndexBufferRing[frameSlot].resource ||
|
|
g_glRaytracingLighting.materialIndexCapacityRing[frameSlot] < indexCount)
|
|
{
|
|
g_glRaytracingLighting.materialIndexBufferRing[frameSlot] = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
indexBytes,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
if (!g_glRaytracingLighting.materialIndexBufferRing[frameSlot].resource)
|
|
return false;
|
|
g_glRaytracingLighting.materialIndexCapacityRing[frameSlot] = indexCount;
|
|
}
|
|
|
|
g_glRaytracingLighting.materialVertexBuffer = g_glRaytracingLighting.materialVertexBufferRing[frameSlot];
|
|
g_glRaytracingLighting.materialIndexBuffer = g_glRaytracingLighting.materialIndexBufferRing[frameSlot];
|
|
return true;
|
|
}
|
|
|
|
static bool glRaytracingLightingUpdateMaterialGeometryBuffers(void)
|
|
{
|
|
if (!g_glRaytracingLighting.uploadToCurrentFrameResource)
|
|
return true;
|
|
|
|
uint64_t totalVertices = 0;
|
|
uint64_t totalIndices = 0;
|
|
for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i)
|
|
{
|
|
const glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[i];
|
|
if (!mesh.alive)
|
|
continue;
|
|
totalVertices += mesh.verticesCpu.size();
|
|
totalIndices += mesh.indicesCpu.size();
|
|
}
|
|
|
|
if (totalVertices > 0xFFFFFFFFull || totalIndices > 0xFFFFFFFFull)
|
|
return false;
|
|
|
|
const uint32_t vertexCount = (uint32_t)totalVertices;
|
|
const uint32_t indexCount = (uint32_t)totalIndices;
|
|
if (!glRaytracingLightingEnsureMaterialGeometryBuffers(vertexCount, indexCount))
|
|
return false;
|
|
|
|
std::vector<glRaytracingVertex_t> packedVertices;
|
|
std::vector<uint32_t> packedIndices;
|
|
packedVertices.reserve(vertexCount);
|
|
packedIndices.reserve(indexCount);
|
|
|
|
for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i)
|
|
{
|
|
glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[i];
|
|
if (!mesh.alive)
|
|
continue;
|
|
|
|
const uint32_t vertexOffset = (uint32_t)packedVertices.size();
|
|
const uint32_t indexOffset = (uint32_t)packedIndices.size();
|
|
|
|
mesh.materialVertexOffset = vertexOffset;
|
|
mesh.materialIndexOffset = indexOffset;
|
|
mesh.materialVertexCount = (uint32_t)mesh.verticesCpu.size();
|
|
mesh.materialIndexCount = (uint32_t)mesh.indicesCpu.size();
|
|
|
|
packedVertices.insert(packedVertices.end(), mesh.verticesCpu.begin(), mesh.verticesCpu.end());
|
|
for (size_t index = 0; index < mesh.indicesCpu.size(); ++index)
|
|
packedIndices.push_back(vertexOffset + mesh.indicesCpu[index]);
|
|
}
|
|
|
|
g_glRaytracingLighting.materialVertexCount = vertexCount;
|
|
g_glRaytracingLighting.materialIndexCount = indexCount;
|
|
|
|
if (vertexCount > 0)
|
|
{
|
|
glRaytracingMapCopy(
|
|
g_glRaytracingLighting.materialVertexBuffer.resource.Get(),
|
|
packedVertices.data(),
|
|
(size_t)vertexCount * sizeof(glRaytracingVertex_t));
|
|
}
|
|
if (indexCount > 0)
|
|
{
|
|
glRaytracingMapCopy(
|
|
g_glRaytracingLighting.materialIndexBuffer.resource.Get(),
|
|
packedIndices.data(),
|
|
(size_t)indexCount * sizeof(uint32_t));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void glRaytracingLightingUpdateMeshMetadata(void)
|
|
{
|
|
if (!g_glRaytracingLighting.uploadToCurrentFrameResource)
|
|
return;
|
|
|
|
if (!g_glRaytracingLighting.meshMetadataBuffer.resource)
|
|
return;
|
|
|
|
glRaytracingMeshMetadata_t fallback = {};
|
|
fallback.averageColor[0] = fallback.averageColor[1] = fallback.averageColor[2] = fallback.averageColor[3] = 1.0f;
|
|
fallback.areaNum = -1;
|
|
|
|
void* mapped = g_glRaytracingLighting.meshMetadataBufferMapped;
|
|
if (!mapped)
|
|
{
|
|
glRaytracingMapCopy(
|
|
g_glRaytracingLighting.meshMetadataBuffer.resource.Get(),
|
|
&fallback,
|
|
sizeof(fallback));
|
|
return;
|
|
}
|
|
|
|
glRaytracingMeshMetadata_t* metadata = (glRaytracingMeshMetadata_t*)mapped;
|
|
metadata[0] = fallback;
|
|
|
|
for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i)
|
|
{
|
|
const glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[i];
|
|
if (!mesh.alive)
|
|
continue;
|
|
|
|
const uint32_t metadataIndex = mesh.handle & GL_RAYTRACING_INSTANCE_USER_ID_MASK;
|
|
memcpy(metadata[metadataIndex].averageColor, mesh.averageColor, sizeof(metadata[metadataIndex].averageColor));
|
|
memcpy(metadata[metadataIndex].emissiveColor, mesh.emissiveColor, sizeof(metadata[metadataIndex].emissiveColor));
|
|
metadata[metadataIndex].procFlags = mesh.procFlags;
|
|
metadata[metadataIndex].areaNum = mesh.areaNum;
|
|
metadata[metadataIndex].vertexOffset = mesh.materialVertexOffset;
|
|
metadata[metadataIndex].indexOffset = mesh.materialIndexOffset;
|
|
metadata[metadataIndex].vertexCount = mesh.materialVertexCount;
|
|
metadata[metadataIndex].indexCount = mesh.materialIndexCount;
|
|
}
|
|
}
|
|
|
|
static void glRaytracingLightingUnmapUploadBuffers(void)
|
|
{
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
if (g_glRaytracingLighting.constantBufferRing[frame].resource &&
|
|
g_glRaytracingLighting.constantBufferMappedRing[frame])
|
|
{
|
|
g_glRaytracingLighting.constantBufferRing[frame].resource->Unmap(0, nullptr);
|
|
g_glRaytracingLighting.constantBufferMappedRing[frame] = nullptr;
|
|
}
|
|
|
|
if (g_glRaytracingLighting.lightBufferRing[frame].resource &&
|
|
g_glRaytracingLighting.lightBufferMappedRing[frame])
|
|
{
|
|
g_glRaytracingLighting.lightBufferRing[frame].resource->Unmap(0, nullptr);
|
|
g_glRaytracingLighting.lightBufferMappedRing[frame] = nullptr;
|
|
}
|
|
|
|
if (g_glRaytracingLighting.meshMetadataBufferRing[frame].resource &&
|
|
g_glRaytracingLighting.meshMetadataBufferMappedRing[frame])
|
|
{
|
|
g_glRaytracingLighting.meshMetadataBufferRing[frame].resource->Unmap(0, nullptr);
|
|
g_glRaytracingLighting.meshMetadataBufferMappedRing[frame] = nullptr;
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (g_glRaytracingLighting.denoiseConstantBufferRing[frame][i].resource &&
|
|
g_glRaytracingLighting.denoiseConstantBufferMappedRing[frame][i])
|
|
{
|
|
g_glRaytracingLighting.denoiseConstantBufferRing[frame][i].resource->Unmap(0, nullptr);
|
|
g_glRaytracingLighting.denoiseConstantBufferMappedRing[frame][i] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_glRaytracingLighting.constantBufferMapped = nullptr;
|
|
g_glRaytracingLighting.lightBufferMapped = nullptr;
|
|
g_glRaytracingLighting.meshMetadataBufferMapped = nullptr;
|
|
for (int i = 0; i < 3; ++i)
|
|
g_glRaytracingLighting.denoiseConstantBufferMapped[i] = nullptr;
|
|
}
|
|
|
|
static void glRaytracingLightingCreatePersistentLightSRV(void)
|
|
{
|
|
for (UINT frame = 0; frame < GL_RAYTRACING_CMD_RING_SIZE; ++frame)
|
|
{
|
|
if (!g_glRaytracingLighting.descriptorHeapRing[frame] ||
|
|
!g_glRaytracingLighting.lightBufferRing[frame].resource ||
|
|
!g_glRaytracingLighting.meshMetadataBufferRing[frame].resource)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
D3D12_CPU_DESCRIPTOR_HANDLE base =
|
|
g_glRaytracingLighting.descriptorHeapRing[frame]->GetCPUDescriptorHandleForHeapStart();
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC srv = {};
|
|
srv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
|
|
srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
srv.Format = DXGI_FORMAT_UNKNOWN;
|
|
srv.Buffer.FirstElement = 0;
|
|
srv.Buffer.NumElements = GL_RAYTRACING_MAX_LIGHTS;
|
|
srv.Buffer.StructureByteStride = sizeof(glRaytracingLight_t);
|
|
srv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(
|
|
g_glRaytracingLighting.lightBufferRing[frame].resource.Get(),
|
|
&srv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
|
|
|
|
srv.Buffer.NumElements = 65536;
|
|
srv.Buffer.StructureByteStride = sizeof(glRaytracingMeshMetadata_t);
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(
|
|
g_glRaytracingLighting.meshMetadataBufferRing[frame].resource.Get(),
|
|
&srv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_MESH_METADATA_SRV));
|
|
}
|
|
}
|
|
|
|
static int glRaytracingLightingCreateStateObject(void)
|
|
{
|
|
ComPtr<IDxcBlob> dxil = glRaytracingLightingCompileLibrary(g_glRaytracingLightingHlsl);
|
|
if (!dxil)
|
|
return 0;
|
|
|
|
D3D12_EXPORT_DESC exports[8] = {};
|
|
exports[0].Name = L"RayGen";
|
|
exports[1].Name = L"ShadowMiss";
|
|
exports[2].Name = L"ShadowAnyHit";
|
|
exports[3].Name = L"ShadowClosestHit";
|
|
exports[4].Name = L"BounceMiss";
|
|
exports[5].Name = L"BounceAnyHit";
|
|
exports[6].Name = L"BounceClosestHit";
|
|
exports[7].Name = L"ReflectionClosestHit";
|
|
|
|
D3D12_DXIL_LIBRARY_DESC libDesc = {};
|
|
D3D12_SHADER_BYTECODE libBytecode = {};
|
|
libBytecode.pShaderBytecode = dxil->GetBufferPointer();
|
|
libBytecode.BytecodeLength = dxil->GetBufferSize();
|
|
libDesc.DXILLibrary = libBytecode;
|
|
libDesc.NumExports = _countof(exports);
|
|
libDesc.pExports = exports;
|
|
|
|
D3D12_HIT_GROUP_DESC hitGroups[3] = {};
|
|
hitGroups[0].HitGroupExport = L"ShadowHitGroup";
|
|
hitGroups[0].AnyHitShaderImport = L"ShadowAnyHit";
|
|
hitGroups[0].ClosestHitShaderImport = L"ShadowClosestHit";
|
|
hitGroups[0].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
|
|
|
|
hitGroups[1].HitGroupExport = L"BounceHitGroup";
|
|
hitGroups[1].AnyHitShaderImport = L"BounceAnyHit";
|
|
hitGroups[1].ClosestHitShaderImport = L"BounceClosestHit";
|
|
hitGroups[1].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
|
|
|
|
hitGroups[2].HitGroupExport = L"ReflectionHitGroup";
|
|
hitGroups[2].ClosestHitShaderImport = L"ReflectionClosestHit";
|
|
hitGroups[2].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
|
|
|
|
D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {};
|
|
shaderConfig.MaxPayloadSizeInBytes = 48; // BouncePayload now carries hit normal/UV for secondary material shading.
|
|
shaderConfig.MaxAttributeSizeInBytes = 8;
|
|
|
|
D3D12_GLOBAL_ROOT_SIGNATURE globalRS = {};
|
|
globalRS.pGlobalRootSignature = g_glRaytracingLighting.globalRootSig.Get();
|
|
|
|
D3D12_LOCAL_ROOT_SIGNATURE localRS = {};
|
|
localRS.pLocalRootSignature = g_glRaytracingLighting.localRootSig.Get();
|
|
|
|
D3D12_STATE_SUBOBJECT subobjects[9] = {};
|
|
UINT sub = 0;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY;
|
|
subobjects[sub].pDesc = &libDesc;
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
|
|
subobjects[sub].pDesc = &hitGroups[0];
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
|
|
subobjects[sub].pDesc = &hitGroups[1];
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
|
|
subobjects[sub].pDesc = &hitGroups[2];
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG;
|
|
subobjects[sub].pDesc = &shaderConfig;
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE;
|
|
subobjects[sub].pDesc = &globalRS;
|
|
++sub;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE;
|
|
subobjects[sub].pDesc = &localRS;
|
|
++sub;
|
|
|
|
LPCWSTR localExports[] =
|
|
{
|
|
L"RayGen",
|
|
L"ShadowMiss",
|
|
L"ShadowHitGroup",
|
|
L"BounceMiss",
|
|
L"BounceHitGroup",
|
|
L"ReflectionHitGroup"
|
|
};
|
|
|
|
D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION assoc = {};
|
|
assoc.pSubobjectToAssociate = &subobjects[6];
|
|
assoc.NumExports = _countof(localExports);
|
|
assoc.pExports = localExports;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION;
|
|
subobjects[sub].pDesc = &assoc;
|
|
++sub;
|
|
|
|
D3D12_RAYTRACING_PIPELINE_CONFIG pipeConfig = {};
|
|
pipeConfig.MaxTraceRecursionDepth = 1;
|
|
|
|
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG;
|
|
subobjects[sub].pDesc = &pipeConfig;
|
|
++sub;
|
|
|
|
D3D12_STATE_OBJECT_DESC soDesc = {};
|
|
soDesc.Type = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE;
|
|
soDesc.NumSubobjects = sub;
|
|
soDesc.pSubobjects = subobjects;
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateStateObject(&soDesc, IID_PPV_ARGS(&g_glRaytracingLighting.rtStateObject)));
|
|
GLR_CHECK(g_glRaytracingLighting.rtStateObject.As(&g_glRaytracingLighting.rtStateProps));
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingLightingCreateShaderTables(void)
|
|
{
|
|
void* raygenId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"RayGen");
|
|
void* shadowMissId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowMiss");
|
|
void* bounceMissId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceMiss");
|
|
void* shadowHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowHitGroup");
|
|
void* bounceHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceHitGroup");
|
|
void* reflectionHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ReflectionHitGroup");
|
|
|
|
if (!raygenId || !shadowMissId || !bounceMissId || !shadowHitId || !bounceHitId || !reflectionHitId)
|
|
{
|
|
glRaytracingFatal("Failed to fetch shader identifiers");
|
|
return 0;
|
|
}
|
|
|
|
const UINT shaderIdSize = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES;
|
|
const UINT recordSize = (UINT)glRaytracingAlignUp(shaderIdSize, D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT);
|
|
const UINT missTableSize = recordSize * 2u;
|
|
const UINT hitTableSize = recordSize * 3u;
|
|
|
|
g_glRaytracingLighting.raygenTable = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
recordSize,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
g_glRaytracingLighting.missTable = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
missTableSize,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
g_glRaytracingLighting.hitTable = glRaytracingCreateBuffer(
|
|
g_glRaytracingCmd.device.Get(),
|
|
hitTableSize,
|
|
D3D12_HEAP_TYPE_UPLOAD,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
D3D12_RESOURCE_FLAG_NONE);
|
|
|
|
if (!g_glRaytracingLighting.raygenTable.resource ||
|
|
!g_glRaytracingLighting.missTable.resource ||
|
|
!g_glRaytracingLighting.hitTable.resource)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
std::vector<uint8_t> temp;
|
|
temp.resize((size_t)max(recordSize, max(missTableSize, hitTableSize)), 0);
|
|
|
|
memset(temp.data(), 0, temp.size());
|
|
memcpy(temp.data(), raygenId, shaderIdSize);
|
|
glRaytracingMapCopy(g_glRaytracingLighting.raygenTable.resource.Get(), temp.data(), recordSize);
|
|
|
|
memset(temp.data(), 0, temp.size());
|
|
memcpy(temp.data(), shadowMissId, shaderIdSize);
|
|
memcpy(temp.data() + recordSize, bounceMissId, shaderIdSize);
|
|
glRaytracingMapCopy(g_glRaytracingLighting.missTable.resource.Get(), temp.data(), missTableSize);
|
|
|
|
memset(temp.data(), 0, temp.size());
|
|
memcpy(temp.data(), shadowHitId, shaderIdSize);
|
|
memcpy(temp.data() + recordSize, bounceHitId, shaderIdSize);
|
|
memcpy(temp.data() + recordSize * 2u, reflectionHitId, shaderIdSize);
|
|
glRaytracingMapCopy(g_glRaytracingLighting.hitTable.resource.Get(), temp.data(), hitTableSize);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingLightingCreateDenoisePipeline(void)
|
|
{
|
|
ComPtr<IDxcBlob> dxil = glRaytracingLightingCompileCompute(g_glRaytracingDenoiseHlsl, L"DenoiseCS");
|
|
if (!dxil)
|
|
return 0;
|
|
|
|
D3D12_COMPUTE_PIPELINE_STATE_DESC pso = {};
|
|
pso.pRootSignature = g_glRaytracingLighting.globalRootSig.Get();
|
|
pso.CS.pShaderBytecode = dxil->GetBufferPointer();
|
|
pso.CS.BytecodeLength = dxil->GetBufferSize();
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateComputePipelineState(
|
|
&pso,
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.denoisePSO)));
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int glRaytracingLightingCreateTemporalPipeline(void)
|
|
{
|
|
ComPtr<IDxcBlob> dxil = glRaytracingLightingCompileCompute(g_glRaytracingTemporalHlsl, L"TemporalAccumCS");
|
|
if (!dxil)
|
|
return 0;
|
|
|
|
D3D12_COMPUTE_PIPELINE_STATE_DESC pso = {};
|
|
pso.pRootSignature = g_glRaytracingLighting.globalRootSig.Get();
|
|
pso.CS.pShaderBytecode = dxil->GetBufferPointer();
|
|
pso.CS.BytecodeLength = dxil->GetBufferSize();
|
|
|
|
GLR_CHECK(g_glRaytracingCmd.device->CreateComputePipelineState(
|
|
&pso,
|
|
IID_PPV_ARGS(&g_glRaytracingLighting.temporalPSO)));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height)
|
|
{
|
|
if (width == 0 || height == 0)
|
|
return 0;
|
|
|
|
if (g_glRaytracingLighting.pathTraceTexture[0].resource &&
|
|
g_glRaytracingLighting.pathTraceTexture[1].resource &&
|
|
g_glRaytracingLighting.temporalTexture.resource &&
|
|
g_glRaytracingLighting.historyTexture[0].resource &&
|
|
g_glRaytracingLighting.historyTexture[1].resource &&
|
|
g_glRaytracingLighting.denoiseTemp[0].resource &&
|
|
g_glRaytracingLighting.denoiseTemp[1].resource &&
|
|
g_glRaytracingLighting.denoiseWidth == width &&
|
|
g_glRaytracingLighting.denoiseHeight == height &&
|
|
g_glRaytracingLighting.denoiseFormat == GL_RAYTRACING_DENOISE_FORMAT)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (g_glRaytracingLighting.pathTraceTexture[0].resource ||
|
|
g_glRaytracingLighting.pathTraceTexture[1].resource ||
|
|
g_glRaytracingLighting.temporalTexture.resource ||
|
|
g_glRaytracingLighting.historyTexture[0].resource ||
|
|
g_glRaytracingLighting.historyTexture[1].resource ||
|
|
g_glRaytracingLighting.denoiseTemp[0].resource ||
|
|
g_glRaytracingLighting.denoiseTemp[1].resource)
|
|
{
|
|
glRaytracingWaitIdle();
|
|
}
|
|
|
|
g_glRaytracingLighting.pathTraceTexture[0] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.pathTraceTexture[1] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.temporalTexture = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.historyTexture[0] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.historyTexture[1] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.denoiseTemp[0] = glRaytracingTexture_t();
|
|
g_glRaytracingLighting.denoiseTemp[1] = glRaytracingTexture_t();
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
g_glRaytracingLighting.pathTraceTexture[i] = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
glRaytracingSetObjectName(g_glRaytracingLighting.pathTraceTexture[i].resource.Get(),
|
|
i == 0 ? L"GLR Raw Path Trace HDR 0" : L"GLR Raw Path Trace HDR 1");
|
|
}
|
|
|
|
g_glRaytracingLighting.temporalTexture = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
glRaytracingSetObjectName(g_glRaytracingLighting.temporalTexture.resource.Get(), L"GLR Temporal GI Accumulation");
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
g_glRaytracingLighting.historyTexture[i] = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
glRaytracingSetObjectName(g_glRaytracingLighting.historyTexture[i].resource.Get(),
|
|
i == 0 ? L"GLR GI History 0" : L"GLR GI History 1");
|
|
}
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
g_glRaytracingLighting.denoiseTemp[i] = glRaytracingCreateTexture2D(
|
|
g_glRaytracingCmd.device.Get(),
|
|
width,
|
|
height,
|
|
GL_RAYTRACING_DENOISE_FORMAT,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
|
glRaytracingSetObjectName(g_glRaytracingLighting.denoiseTemp[i].resource.Get(),
|
|
i == 0 ? L"GLR A-Trous Temp A" : L"GLR A-Trous Temp B");
|
|
}
|
|
|
|
if (!g_glRaytracingLighting.pathTraceTexture[0].resource ||
|
|
!g_glRaytracingLighting.pathTraceTexture[1].resource ||
|
|
!g_glRaytracingLighting.temporalTexture.resource ||
|
|
!g_glRaytracingLighting.historyTexture[0].resource ||
|
|
!g_glRaytracingLighting.historyTexture[1].resource ||
|
|
!g_glRaytracingLighting.denoiseTemp[0].resource ||
|
|
!g_glRaytracingLighting.denoiseTemp[1].resource)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
g_glRaytracingLighting.currentHistoryIndex = 0;
|
|
g_glRaytracingLighting.currentPathTraceIndex = 0;
|
|
g_glRaytracingLighting.denoiseWidth = width;
|
|
g_glRaytracingLighting.denoiseHeight = height;
|
|
g_glRaytracingLighting.denoiseFormat = GL_RAYTRACING_DENOISE_FORMAT;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingLightingCreatePerPassDescriptors(
|
|
const glRaytracingLightingPassDesc_t* pass,
|
|
ID3D12Resource* topLevelAS,
|
|
ID3D12Resource* rayOutputTexture,
|
|
ID3D12Resource* pathTraceTexture,
|
|
ID3D12Resource* denoiseATexture,
|
|
ID3D12Resource* denoiseBTexture,
|
|
ID3D12Resource* historyReadTexture,
|
|
ID3D12Resource* historyWriteTexture,
|
|
ID3D12Resource* temporalTexture)
|
|
{
|
|
D3D12_CPU_DESCRIPTOR_HANDLE base = g_glRaytracingLighting.descriptorHeap->GetCPUDescriptorHandleForHeapStart();
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC albedoSrv = {};
|
|
albedoSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
albedoSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
albedoSrv.Format = pass->albedoFormat;
|
|
albedoSrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(pass->albedoTexture, &albedoSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_ALBEDO_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC depthSrv = {};
|
|
depthSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
depthSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
depthSrv.Format = glRaytracingGetSrvFormatForDepth(pass->depthFormat);
|
|
depthSrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(pass->depthTexture, &depthSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DEPTH_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC normalSrv = {};
|
|
normalSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
normalSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
normalSrv.Format = pass->normalFormat;
|
|
normalSrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(pass->normalTexture, &normalSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_NORMAL_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC positionSrv = {};
|
|
positionSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
positionSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
positionSrv.Format = pass->positionFormat;
|
|
positionSrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(pass->positionTexture, &positionSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_POSITION_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC tlasSrv = {};
|
|
tlasSrv.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE;
|
|
tlasSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
tlasSrv.RaytracingAccelerationStructure.Location = topLevelAS->GetGPUVirtualAddress();
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(nullptr, &tlasSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_TLAS_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC denoiseSrv = {};
|
|
denoiseSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
denoiseSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
denoiseSrv.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
denoiseSrv.Texture2D.MipLevels = 1;
|
|
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(pathTraceTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(denoiseATexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_A_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(denoiseBTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_B_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(historyReadTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_HISTORY_SRV));
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(temporalTexture, &denoiseSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_TEMPORAL_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC emissiveSrv = {};
|
|
emissiveSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
emissiveSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
emissiveSrv.Format = g_glRaytracingLighting.emissiveFormat;
|
|
emissiveSrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(g_glRaytracingLighting.emissiveTexture, &emissiveSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_EMISSIVE_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC specularSrv = {};
|
|
specularSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
specularSrv.Texture2D.MipLevels = 1;
|
|
ID3D12Resource* specularResource = g_glRaytracingLighting.specularTexture;
|
|
if (specularResource)
|
|
{
|
|
specularSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
specularSrv.Format = g_glRaytracingLighting.specularFormat;
|
|
}
|
|
else
|
|
{
|
|
// Bind a harmless fallback descriptor when the shim has not provided a
|
|
// specular G-buffer. The shader sees alpha zero and uses the legacy fallback.
|
|
specularResource = pass->albedoTexture;
|
|
specularSrv.Shader4ComponentMapping = D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(
|
|
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_2,
|
|
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0);
|
|
specularSrv.Format = pass->albedoFormat;
|
|
}
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(specularResource, &specularSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_SPECULAR_SRV));
|
|
|
|
for (uint32_t i = 0; i < GL_RAYTRACING_MAX_IES_TEXTURES; ++i)
|
|
{
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC iesSrv = {};
|
|
iesSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
iesSrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
iesSrv.Format = g_glRaytracingLighting.iesTextures[i]
|
|
? g_glRaytracingLighting.iesFormats[i]
|
|
: DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
iesSrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(
|
|
g_glRaytracingLighting.iesTextures[i],
|
|
&iesSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_IES_TEXTURES_SRV + i));
|
|
}
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC velocitySrv = {};
|
|
velocitySrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
velocitySrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
velocitySrv.Format = pass->velocityTexture ? pass->velocityFormat : pass->positionFormat;
|
|
velocitySrv.Texture2D.MipLevels = 1;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(
|
|
pass->velocityTexture ? pass->velocityTexture : pass->positionTexture,
|
|
&velocitySrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_VELOCITY_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC materialVertexSrv = {};
|
|
materialVertexSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
materialVertexSrv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
|
|
materialVertexSrv.Format = DXGI_FORMAT_UNKNOWN;
|
|
materialVertexSrv.Buffer.FirstElement = 0;
|
|
materialVertexSrv.Buffer.NumElements = std::max<UINT>(g_glRaytracingLighting.materialVertexCount, 1u);
|
|
materialVertexSrv.Buffer.StructureByteStride = sizeof(glRaytracingVertex_t);
|
|
materialVertexSrv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(
|
|
g_glRaytracingLighting.materialVertexBuffer.resource.Get(),
|
|
&materialVertexSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_MATERIAL_VERTICES_SRV));
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC materialIndexSrv = {};
|
|
materialIndexSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
materialIndexSrv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
|
|
materialIndexSrv.Format = DXGI_FORMAT_UNKNOWN;
|
|
materialIndexSrv.Buffer.FirstElement = 0;
|
|
materialIndexSrv.Buffer.NumElements = std::max<UINT>(g_glRaytracingLighting.materialIndexCount, 1u);
|
|
materialIndexSrv.Buffer.StructureByteStride = sizeof(uint32_t);
|
|
materialIndexSrv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
|
|
g_glRaytracingCmd.device->CreateShaderResourceView(
|
|
g_glRaytracingLighting.materialIndexBuffer.resource.Get(),
|
|
&materialIndexSrv,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_MATERIAL_INDICES_SRV));
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC rayOutputUav = {};
|
|
rayOutputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
rayOutputUav.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(rayOutputTexture, nullptr, &rayOutputUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC denoiseUav = {};
|
|
denoiseUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
denoiseUav.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(denoiseATexture, nullptr, &denoiseUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_A_UAV));
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(denoiseBTexture, nullptr, &denoiseUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_DENOISE_B_UAV));
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC outputUav = {};
|
|
outputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
outputUav.Format = pass->outputFormat;
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(pass->outputTexture, nullptr, &outputUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_OUTPUT_UAV));
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC temporalUav = {};
|
|
temporalUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
|
temporalUav.Format = GL_RAYTRACING_DENOISE_FORMAT;
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(temporalTexture, nullptr, &temporalUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_TEMPORAL_UAV));
|
|
g_glRaytracingCmd.device->CreateUnorderedAccessView(historyWriteTexture, nullptr, &temporalUav,
|
|
glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_HISTORY_UAV));
|
|
}
|
|
|
|
|
|
|
|
// ============================================================
|
|
// Lighting public API
|
|
// ============================================================
|
|
|
|
static bool glRaytracingLightingExecuteInternal(
|
|
const glRaytracingLightingPassDesc_t* pass,
|
|
ID3D12Resource* topLevelAS)
|
|
{
|
|
if (!g_glRaytracingLighting.initialized || !pass || !topLevelAS)
|
|
return false;
|
|
|
|
if (!pass->albedoTexture || !pass->depthTexture || !pass->normalTexture || !pass->positionTexture || !pass->outputTexture)
|
|
return false;
|
|
|
|
if (pass->width == 0 || pass->height == 0)
|
|
return false;
|
|
|
|
if (!glRaytracingLightingEnsureDenoiseResources(pass->width, pass->height))
|
|
return false;
|
|
|
|
const bool useInternalDenoiser =
|
|
(g_glRaytracingLighting.constants.enableDenoiser != 0u) &&
|
|
!g_glRaytracingLighting.externalDenoiser;
|
|
|
|
ID3D12Resource* rayOutputTexture = useInternalDenoiser
|
|
? g_glRaytracingLighting.pathTraceTexture[g_glRaytracingLighting.currentPathTraceIndex & 1u].resource.Get()
|
|
: pass->outputTexture;
|
|
|
|
if (!glRaytracingBeginCmd())
|
|
return false;
|
|
|
|
glRaytracingLightingSelectFrameResources(g_glRaytracingCmd.cmdCurrentSlot);
|
|
|
|
g_glRaytracingLighting.constants.screenSize[0] = (float)pass->width;
|
|
g_glRaytracingLighting.constants.screenSize[1] = (float)pass->height;
|
|
g_glRaytracingLighting.constants.screenSize[2] = 1.0f / (float)pass->width;
|
|
g_glRaytracingLighting.constants.screenSize[3] = 1.0f / (float)pass->height;
|
|
g_glRaytracingLighting.constants.frameIndex = g_glRaytracingLighting.frameCounter;
|
|
g_glRaytracingLighting.constants.externalDenoiser = g_glRaytracingLighting.externalDenoiser ? 1u : 0u;
|
|
g_glRaytracingLighting.constants.lightCount =
|
|
(uint32_t)glRaytracingClamp<size_t>(g_glRaytracingLighting.cpuLights.size(), 0, GL_RAYTRACING_MAX_LIGHTS);
|
|
|
|
g_glRaytracingLighting.uploadToCurrentFrameResource = true;
|
|
glRaytracingLightingUpdateLights();
|
|
if (!glRaytracingLightingUpdateMaterialGeometryBuffers())
|
|
{
|
|
g_glRaytracingLighting.uploadToCurrentFrameResource = false;
|
|
if (g_glRaytracingCmd.cmdList)
|
|
g_glRaytracingCmd.cmdList->Close();
|
|
return false;
|
|
}
|
|
glRaytracingLightingUpdateMeshMetadata();
|
|
glRaytracingLightingUpdateConstants();
|
|
|
|
for (uint32_t passIndex = 0; passIndex < 3u; ++passIndex)
|
|
{
|
|
glRaytracingLightingConstants_t denoiseConstants = g_glRaytracingLighting.constants;
|
|
denoiseConstants.denoisePassIndex = passIndex;
|
|
denoiseConstants.denoiseStepWidth = (float)(1u << passIndex);
|
|
glRaytracingLightingUploadConstantsTo(g_glRaytracingLighting.denoiseConstantBuffer[passIndex], denoiseConstants);
|
|
}
|
|
g_glRaytracingLighting.uploadToCurrentFrameResource = false;
|
|
|
|
glRaytracingBeginPixEvent(g_glRaytracingCmd.cmdList.Get(), useInternalDenoiser
|
|
? L"GLR Path Traced Lighting With Internal Denoiser"
|
|
: L"GLR Path Traced Lighting Raw For External Denoiser");
|
|
|
|
const uint32_t historyReadIndex = g_glRaytracingLighting.currentHistoryIndex & 1u;
|
|
const uint32_t historyWriteIndex = (g_glRaytracingLighting.currentHistoryIndex ^ 1u) & 1u;
|
|
const uint32_t pathTraceIndex = g_glRaytracingLighting.currentPathTraceIndex & 1u;
|
|
glRaytracingTexture_t& pathTraceTexture = g_glRaytracingLighting.pathTraceTexture[pathTraceIndex];
|
|
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR Create Per-Pass Descriptors");
|
|
glRaytracingLightingCreatePerPassDescriptors(
|
|
pass,
|
|
topLevelAS,
|
|
rayOutputTexture,
|
|
pathTraceTexture.resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[0].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[1].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
|
|
g_glRaytracingLighting.temporalTexture.resource.Get());
|
|
}
|
|
|
|
if (useInternalDenoiser)
|
|
{
|
|
GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR transition raw path-trace target to UAV");
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
pathTraceTexture.resource.Get(),
|
|
pathTraceTexture.state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
pathTraceTexture.state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
}
|
|
else
|
|
{
|
|
GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR transition external denoiser output to UAV");
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
pass->outputTexture,
|
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
}
|
|
|
|
ID3D12DescriptorHeap* heaps[] = { g_glRaytracingLighting.descriptorHeap.Get() };
|
|
g_glRaytracingCmd.cmdList->SetDescriptorHeaps(_countof(heaps), heaps);
|
|
g_glRaytracingCmd.cmdList->SetComputeRootSignature(g_glRaytracingLighting.globalRootSig.Get());
|
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE gpuBase = g_glRaytracingLighting.descriptorHeap->GetGPUDescriptorHandleForHeapStart();
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(0,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(1,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.constantBuffer.gpuVA);
|
|
g_glRaytracingCmd.cmdList->SetPipelineState1(g_glRaytracingLighting.rtStateObject.Get());
|
|
|
|
const UINT timestampBase = g_glRaytracingCmd.cmdCurrentSlot * 2u;
|
|
const bool writeTimestamp =
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get() != nullptr &&
|
|
g_glRaytracingCmd.pathTracingTimestampReadback.resource.Get() != nullptr;
|
|
if (writeTimestamp)
|
|
{
|
|
g_glRaytracingCmd.cmdList->EndQuery(
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get(),
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
timestampBase + 0u);
|
|
}
|
|
|
|
const UINT shaderRecordSize = (UINT)glRaytracingAlignUp(
|
|
D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES,
|
|
D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT);
|
|
|
|
D3D12_DISPATCH_RAYS_DESC rays = {};
|
|
rays.RayGenerationShaderRecord.StartAddress = g_glRaytracingLighting.raygenTable.gpuVA;
|
|
rays.RayGenerationShaderRecord.SizeInBytes = shaderRecordSize;
|
|
rays.MissShaderTable.StartAddress = g_glRaytracingLighting.missTable.gpuVA;
|
|
rays.MissShaderTable.SizeInBytes = shaderRecordSize * 2u;
|
|
rays.MissShaderTable.StrideInBytes = shaderRecordSize;
|
|
rays.HitGroupTable.StartAddress = g_glRaytracingLighting.hitTable.gpuVA;
|
|
rays.HitGroupTable.SizeInBytes = shaderRecordSize * 3u;
|
|
rays.HitGroupTable.StrideInBytes = shaderRecordSize;
|
|
rays.Width = pass->width;
|
|
rays.Height = pass->height;
|
|
rays.Depth = 1;
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR DispatchRays RayGen GI");
|
|
g_glRaytracingCmd.cmdList->DispatchRays(&rays);
|
|
}
|
|
|
|
D3D12_RESOURCE_BARRIER rawUav = {};
|
|
rawUav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
rawUav.UAV.pResource = rayOutputTexture;
|
|
GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR raw ray output UAV barrier");
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &rawUav);
|
|
|
|
if (useInternalDenoiser)
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR Internal Temporal + A-Trous Denoise");
|
|
const UINT groupsX = (pass->width + 7u) / 8u;
|
|
const UINT groupsY = (pass->height + 7u) / 8u;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
pathTraceTexture.resource.Get(),
|
|
pathTraceTexture.state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
pathTraceTexture.state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.temporalTexture.resource.Get(),
|
|
g_glRaytracingLighting.temporalTexture.state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
g_glRaytracingLighting.temporalTexture.state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.historyTexture[historyReadIndex].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
|
|
g_glRaytracingCmd.cmdList->SetComputeRootSignature(g_glRaytracingLighting.globalRootSig.Get());
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(0,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(1,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.constantBuffer.gpuVA);
|
|
g_glRaytracingCmd.cmdList->SetPipelineState(g_glRaytracingLighting.temporalPSO.Get());
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR Temporal Accumulation CS");
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
}
|
|
|
|
D3D12_RESOURCE_BARRIER temporalUavs[2] = {};
|
|
temporalUavs[0].Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
temporalUavs[0].UAV.pResource = g_glRaytracingLighting.temporalTexture.resource.Get();
|
|
temporalUavs[1].Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
temporalUavs[1].UAV.pResource = g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get();
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(2, temporalUavs);
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.temporalTexture.resource.Get(),
|
|
g_glRaytracingLighting.temporalTexture.state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.temporalTexture.state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(),
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.historyTexture[historyWriteIndex].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[i].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[i].state,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
g_glRaytracingLighting.denoiseTemp[i].state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
|
|
}
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
pass->outputTexture,
|
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
|
|
g_glRaytracingCmd.cmdList->SetComputeRootSignature(g_glRaytracingLighting.globalRootSig.Get());
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(0,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_LIGHTS_SRV));
|
|
g_glRaytracingCmd.cmdList->SetComputeRootDescriptorTable(1,
|
|
glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV));
|
|
g_glRaytracingCmd.cmdList->SetPipelineState(g_glRaytracingLighting.denoisePSO.Get());
|
|
|
|
// Pass 0: temporally accumulated path trace -> temp A.
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[0].gpuVA);
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR A-Trous Denoise Pass 0");
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
}
|
|
|
|
D3D12_RESOURCE_BARRIER uavA = {};
|
|
uavA.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
uavA.UAV.pResource = g_glRaytracingLighting.denoiseTemp[0].resource.Get();
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &uavA);
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[0].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[0].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.denoiseTemp[0].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
// Pass 1: temp A -> temp B.
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[1].gpuVA);
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR A-Trous Denoise Pass 1");
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
}
|
|
|
|
D3D12_RESOURCE_BARRIER uavB = {};
|
|
uavB.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
uavB.UAV.pResource = g_glRaytracingLighting.denoiseTemp[1].resource.Get();
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &uavB);
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[1].resource.Get(),
|
|
g_glRaytracingLighting.denoiseTemp[1].state,
|
|
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
|
g_glRaytracingLighting.denoiseTemp[1].state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
|
|
|
// Pass 2: temp B -> final output.
|
|
g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[2].gpuVA);
|
|
{
|
|
GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR A-Trous Denoise Pass 2");
|
|
g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1);
|
|
}
|
|
|
|
D3D12_RESOURCE_BARRIER outputUav = {};
|
|
outputUav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
|
|
outputUav.UAV.pResource = pass->outputTexture;
|
|
g_glRaytracingCmd.cmdList->ResourceBarrier(1, &outputUav);
|
|
}
|
|
|
|
glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(),
|
|
pass->outputTexture,
|
|
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
|
|
|
if (writeTimestamp)
|
|
{
|
|
GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR resolve path tracing timestamp");
|
|
g_glRaytracingCmd.cmdList->EndQuery(
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get(),
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
timestampBase + 1u);
|
|
g_glRaytracingCmd.cmdList->ResolveQueryData(
|
|
g_glRaytracingCmd.pathTracingTimestampHeap.Get(),
|
|
D3D12_QUERY_TYPE_TIMESTAMP,
|
|
timestampBase,
|
|
2,
|
|
g_glRaytracingCmd.pathTracingTimestampReadback.resource.Get(),
|
|
sizeof(UINT64) * timestampBase);
|
|
}
|
|
|
|
glRaytracingEndPixEvent(g_glRaytracingCmd.cmdList.Get());
|
|
|
|
if (!glRaytracingEndCmd())
|
|
return false;
|
|
|
|
if (writeTimestamp)
|
|
g_glRaytracingCmd.pathTracingTimestampFenceRing[g_glRaytracingCmd.cmdCurrentSlot] = g_glRaytracingCmd.cmdLastFenceValue;
|
|
|
|
if (useInternalDenoiser)
|
|
{
|
|
g_glRaytracingLighting.currentHistoryIndex = historyWriteIndex;
|
|
g_glRaytracingLighting.currentPathTraceIndex = (pathTraceIndex ^ 1u);
|
|
}
|
|
|
|
++g_glRaytracingLighting.frameCounter;
|
|
return true;
|
|
}
|
|
|
|
static ID3D12Resource* glRaytracingResolveTLASForWorld(glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(worldHandle);
|
|
if (!world)
|
|
return nullptr;
|
|
|
|
if (!glRaytracingBuildDirtyMeshesInternal())
|
|
return nullptr;
|
|
|
|
if (!glRaytracingBuildSceneInternal(world))
|
|
return nullptr;
|
|
|
|
if (!world->tlasBuilt)
|
|
return nullptr;
|
|
|
|
return glRaytracingGetCurrentTLASBuffer(world)->resource.Get();
|
|
}
|
|
|
|
bool glRaytracingLightingInit(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingLighting.initialized)
|
|
return true;
|
|
|
|
if (!glRaytracingInitCmdContext())
|
|
return false;
|
|
|
|
if (!glRaytracingLightingCreateDescriptorHeap())
|
|
return false;
|
|
|
|
if (!glRaytracingLightingCreateRootSignatures())
|
|
return false;
|
|
|
|
if (!glRaytracingLightingCreateBuffers())
|
|
return false;
|
|
|
|
glRaytracingLightingCreatePersistentLightSRV();
|
|
|
|
if (!glRaytracingLightingCreateStateObject())
|
|
return false;
|
|
|
|
if (!glRaytracingLightingCreateShaderTables())
|
|
return false;
|
|
|
|
if (!glRaytracingLightingCreateDenoisePipeline())
|
|
return false;
|
|
|
|
if (!glRaytracingLightingCreateTemporalPipeline())
|
|
return false;
|
|
|
|
memset(&g_glRaytracingLighting.constants, 0, sizeof(g_glRaytracingLighting.constants));
|
|
g_glRaytracingLighting.cpuLights.reserve(GL_RAYTRACING_MAX_LIGHTS);
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewProj);
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewMatrix);
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.viewProj);
|
|
g_glRaytracingLighting.constants.ambientColor[0] = 0.08f;
|
|
g_glRaytracingLighting.constants.ambientColor[1] = 0.08f;
|
|
g_glRaytracingLighting.constants.ambientColor[2] = 0.09f;
|
|
g_glRaytracingLighting.constants.ambientColor[3] = 1.0f;
|
|
g_glRaytracingLighting.constants.enableSpecular = 1;
|
|
g_glRaytracingLighting.constants.enableHalfLambert = 1;
|
|
g_glRaytracingLighting.constants.normalReconstructZ = 1.0f;
|
|
g_glRaytracingLighting.constants.shadowBias = 1.5f;
|
|
g_glRaytracingLighting.constants.frameIndex = 0;
|
|
g_glRaytracingLighting.constants.samplesPerPixel = 1;
|
|
g_glRaytracingLighting.constants.maxBounces = 2;
|
|
g_glRaytracingLighting.constants.enableDenoiser = 1;
|
|
g_glRaytracingLighting.constants.denoisePassIndex = 0;
|
|
g_glRaytracingLighting.constants.denoiseStepWidth = 1.0f;
|
|
g_glRaytracingLighting.constants.denoiseStrength = 1.0f;
|
|
g_glRaytracingLighting.constants.denoisePhiColor = 8.0f;
|
|
g_glRaytracingLighting.constants.denoisePhiNormal = 64.0f;
|
|
g_glRaytracingLighting.constants.denoisePhiPosition = 0.045f;
|
|
g_glRaytracingLighting.constants.bumpStrength = 2.35f;
|
|
g_glRaytracingLighting.constants.secondaryLightBudget = 0;
|
|
g_glRaytracingLighting.constants.secondaryCorrectionBudget = 0;
|
|
g_glRaytracingLighting.constants.secondaryLightCompensation = 0.62f;
|
|
g_glRaytracingLighting.constants.historyReset = 1u;
|
|
g_glRaytracingLighting.constants.externalDenoiser = 0u;
|
|
g_glRaytracingLighting.constants.giAmount = 1.0f;
|
|
g_glRaytracingLighting.constants.worldGrimeAmount = 0.35f;
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
glRaytracingLightingUpdateConstants();
|
|
|
|
g_glRaytracingLighting.initialized = true;
|
|
glRaytracingLog("glRaytracingLightingInit ok");
|
|
return true;
|
|
}
|
|
|
|
void glRaytracingLightingShutdown(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingLighting.initialized)
|
|
return;
|
|
|
|
glRaytracingWaitIdle();
|
|
glRaytracingLightingUnmapUploadBuffers();
|
|
g_glRaytracingLighting = glRaytracingLightingState_t();
|
|
}
|
|
|
|
bool glRaytracingLightingIsInitialized(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
return g_glRaytracingLighting.initialized;
|
|
}
|
|
|
|
void glRaytracingLightingClearLights(bool clearPersistant)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (clearPersistant)
|
|
{
|
|
g_glRaytracingLighting.cpuLights.clear();
|
|
}
|
|
else
|
|
{
|
|
size_t writeIndex = 0;
|
|
|
|
for (size_t i = 0; i < g_glRaytracingLighting.cpuLights.size(); ++i)
|
|
{
|
|
if (g_glRaytracingLighting.cpuLights[i].persistant)
|
|
{
|
|
if (writeIndex != i)
|
|
{
|
|
g_glRaytracingLighting.cpuLights[writeIndex] = g_glRaytracingLighting.cpuLights[i];
|
|
}
|
|
++writeIndex;
|
|
}
|
|
}
|
|
|
|
g_glRaytracingLighting.cpuLights.resize(writeIndex);
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.lightCount =
|
|
(uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
bool glRaytracingLightingAddLight(const glRaytracingLight_t* light)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingLighting.initialized || !light)
|
|
return false;
|
|
|
|
if (g_glRaytracingLighting.cpuLights.size() >= GL_RAYTRACING_MAX_LIGHTS)
|
|
return false;
|
|
|
|
g_glRaytracingLighting.cpuLights.push_back(*light);
|
|
g_glRaytracingLighting.constants.lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
glRaytracingLightingUpdateLights();
|
|
glRaytracingLightingUpdateConstants();
|
|
return true;
|
|
}
|
|
|
|
void glRaytracingLightingSetAmbient(float r, float g, float b, float intensity)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingLighting.constants.ambientColor[0] != r ||
|
|
g_glRaytracingLighting.constants.ambientColor[1] != g ||
|
|
g_glRaytracingLighting.constants.ambientColor[2] != b ||
|
|
g_glRaytracingLighting.constants.ambientColor[3] != intensity)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.ambientColor[0] = r;
|
|
g_glRaytracingLighting.constants.ambientColor[1] = g;
|
|
g_glRaytracingLighting.constants.ambientColor[2] = b;
|
|
g_glRaytracingLighting.constants.ambientColor[3] = intensity;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetCameraPosition(float x, float y, float z)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
g_glRaytracingLighting.constants.cameraPos[0] = x;
|
|
g_glRaytracingLighting.constants.cameraPos[1] = y;
|
|
g_glRaytracingLighting.constants.cameraPos[2] = z;
|
|
g_glRaytracingLighting.constants.cameraPos[3] = 1.0f;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetInvViewProjMatrix(const float* m16)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!m16)
|
|
return;
|
|
|
|
memcpy(g_glRaytracingLighting.constants.invViewProj, m16, sizeof(float) * 16);
|
|
|
|
if (!glRaytracingInvertMatrix4x4(m16, g_glRaytracingLighting.constants.viewProj))
|
|
glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.viewProj);
|
|
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetInvViewMatrix(const float* m16)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!m16)
|
|
return;
|
|
|
|
memcpy(g_glRaytracingLighting.constants.invViewMatrix, m16, sizeof(float) * 16);
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetHistoryReset(int reset)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
g_glRaytracingLighting.constants.historyReset = reset ? 1u : 0u;
|
|
if (reset)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetNormalReconstructSign(float signValue)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingLighting.constants.normalReconstructZ != signValue)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.normalReconstructZ = signValue;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetBumpStrength(float strength)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
strength = glRaytracingClamp<float>(strength, 0.0f, 8.0f);
|
|
if (g_glRaytracingLighting.constants.bumpStrength != strength)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.bumpStrength = strength;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingEnableSpecular(int enable)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
uint32_t value = enable ? 1u : 0u;
|
|
if (g_glRaytracingLighting.constants.enableSpecular != value)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.enableSpecular = value;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingEnableHalfLambert(int enable)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
uint32_t value = enable ? 1u : 0u;
|
|
if (g_glRaytracingLighting.constants.enableHalfLambert != value)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.enableHalfLambert = value;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetShadowBias(float bias)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (g_glRaytracingLighting.constants.shadowBias != bias)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.shadowBias = bias;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetPathTracingOptions(uint32_t samplesPerPixel, uint32_t maxBounces, int enableDenoiser, float denoiseStrength)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
samplesPerPixel = glRaytracingClamp<uint32_t>(samplesPerPixel ? samplesPerPixel : 1u, 1u, 8u);
|
|
maxBounces = glRaytracingClamp<uint32_t>(maxBounces ? maxBounces : 1u, 1u, 4u);
|
|
uint32_t denoiser = enableDenoiser ? 1u : 0u;
|
|
denoiseStrength = glRaytracingClamp<float>(denoiseStrength, 0.0f, 1.0f);
|
|
|
|
if (g_glRaytracingLighting.constants.samplesPerPixel != samplesPerPixel ||
|
|
g_glRaytracingLighting.constants.maxBounces != maxBounces ||
|
|
g_glRaytracingLighting.constants.enableDenoiser != denoiser ||
|
|
g_glRaytracingLighting.constants.denoiseStrength != denoiseStrength)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.samplesPerPixel = samplesPerPixel;
|
|
g_glRaytracingLighting.constants.maxBounces = maxBounces;
|
|
g_glRaytracingLighting.constants.enableDenoiser = denoiser;
|
|
g_glRaytracingLighting.constants.denoiseStrength = denoiseStrength;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetGIAmount(float amount)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
amount = glRaytracingClamp<float>(amount, 0.0f, 8.0f);
|
|
if (g_glRaytracingLighting.constants.giAmount != amount)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.giAmount = amount;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetWorldGrime(float amount)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
amount = glRaytracingClamp<float>(amount, 0.0f, 4.0f);
|
|
if (g_glRaytracingLighting.constants.worldGrimeAmount != amount)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.constants.worldGrimeAmount = amount;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetSecondaryLightBudget(int budget, float compensation)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (budget < -1)
|
|
budget = -1;
|
|
if (budget > (int)GL_RAYTRACING_MAX_LIGHTS)
|
|
budget = (int)GL_RAYTRACING_MAX_LIGHTS;
|
|
|
|
compensation = glRaytracingClamp<float>(compensation, 0.0f, 1.0f);
|
|
|
|
if (g_glRaytracingLighting.constants.secondaryLightBudget != budget ||
|
|
g_glRaytracingLighting.constants.secondaryLightCompensation != compensation)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.secondaryLightBudget = budget;
|
|
g_glRaytracingLighting.constants.secondaryCorrectionBudget = 0;
|
|
g_glRaytracingLighting.constants.secondaryLightCompensation = compensation;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetDenoiseTuning(float phiColor, float phiNormal, float phiPosition)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
phiColor = glRaytracingClamp<float>(phiColor, 0.001f, 64.0f);
|
|
phiNormal = glRaytracingClamp<float>(phiNormal, 1.0f, 128.0f);
|
|
phiPosition = glRaytracingClamp<float>(phiPosition, 0.001f, 4.0f);
|
|
|
|
if (g_glRaytracingLighting.constants.denoisePhiColor != phiColor ||
|
|
g_glRaytracingLighting.constants.denoisePhiNormal != phiNormal ||
|
|
g_glRaytracingLighting.constants.denoisePhiPosition != phiPosition)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.constants.denoisePhiColor = phiColor;
|
|
g_glRaytracingLighting.constants.denoisePhiNormal = phiNormal;
|
|
g_glRaytracingLighting.constants.denoisePhiPosition = phiPosition;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength)
|
|
{
|
|
if (!light)
|
|
return;
|
|
|
|
// This uses glRaytracingLight_t::pad1, which is renamed to
|
|
// Light::volumetricScattering in HLSL. Keeping the existing pad slot avoids
|
|
// changing the StructuredBuffer stride for already-integrated callers.
|
|
light->pad1 = glRaytracingClamp<float>(strength, 0.0f, 16.0f);
|
|
}
|
|
|
|
void glRaytracingLightingSetExternalDenoiser(int enabled)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
const bool newValue = enabled ? true : false;
|
|
if (g_glRaytracingLighting.externalDenoiser != newValue)
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
|
|
g_glRaytracingLighting.externalDenoiser = newValue;
|
|
g_glRaytracingLighting.constants.externalDenoiser = newValue ? 1u : 0u;
|
|
glRaytracingLightingUpdateConstants();
|
|
}
|
|
|
|
void glRaytracingLightingUseExternalDenoiser(int enabled)
|
|
{
|
|
glRaytracingLightingSetExternalDenoiser(enabled);
|
|
}
|
|
|
|
|
|
void glRaytracingLightingSetEmissiveInput(ID3D12Resource* texture, DXGI_FORMAT format)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
g_glRaytracingLighting.emissiveTexture = texture;
|
|
g_glRaytracingLighting.emissiveFormat = (format == DXGI_FORMAT_UNKNOWN)
|
|
? DXGI_FORMAT_R16G16B16A16_FLOAT
|
|
: format;
|
|
}
|
|
|
|
void glRaytracingLightingSetSpecularInput(ID3D12Resource* texture, DXGI_FORMAT format)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
DXGI_FORMAT newFormat = (format == DXGI_FORMAT_UNKNOWN)
|
|
? DXGI_FORMAT_R8G8B8A8_UNORM
|
|
: format;
|
|
|
|
if (g_glRaytracingLighting.specularTexture != texture ||
|
|
g_glRaytracingLighting.specularFormat != newFormat)
|
|
{
|
|
glRaytracingLightingResetDenoiseHistory();
|
|
}
|
|
|
|
g_glRaytracingLighting.specularTexture = texture;
|
|
g_glRaytracingLighting.specularFormat = newFormat;
|
|
}
|
|
|
|
bool glRaytracingLightingExecuteForScene(const glRaytracingLightingPassDesc_t* pass, glRaytracingSceneHandle_t worldHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
ID3D12Resource* topLevelAS = glRaytracingResolveTLASForWorld(worldHandle);
|
|
if (!topLevelAS)
|
|
return false;
|
|
|
|
const glRaytracingRenderWorld_t* world = glRaytracingFindWorldConst(worldHandle);
|
|
g_glRaytracingLighting.lastProfileSceneHandle = worldHandle;
|
|
g_glRaytracingLighting.lastProfileInstanceCount = glRaytracingCountAliveInstances(world);
|
|
g_glRaytracingLighting.lastProfileWidth = pass ? pass->width : 0;
|
|
g_glRaytracingLighting.lastProfileHeight = pass ? pass->height : 0;
|
|
|
|
return glRaytracingLightingExecuteInternal(pass, topLevelAS);
|
|
}
|
|
|
|
glRaytracingLight_t glRaytracingLightingMakePointLight(
|
|
float px, float py, float pz,
|
|
float radiusX, float radiusY, float radiusZ,
|
|
float r, float g, float b,
|
|
float intensity)
|
|
{
|
|
glRaytracingLight_t l = {};
|
|
float ax = (radiusX < 0.0f) ? -radiusX : radiusX;
|
|
float ay = (radiusY < 0.0f) ? -radiusY : radiusY;
|
|
float az = (radiusZ < 0.0f) ? -radiusZ : radiusZ;
|
|
float maxRadius = ax;
|
|
if (ay > maxRadius) maxRadius = ay;
|
|
if (az > maxRadius) maxRadius = az;
|
|
if (maxRadius <= 0.0f) maxRadius = 1e-4f;
|
|
|
|
if (ax <= 0.0f) ax = maxRadius;
|
|
if (ay <= 0.0f) ay = maxRadius;
|
|
if (az <= 0.0f) az = maxRadius;
|
|
|
|
l.position.x = px;
|
|
l.position.y = py;
|
|
l.position.z = pz;
|
|
|
|
// Keep radius populated as a scalar fallback/max range, but point lights now
|
|
// attenuate using pointRadius.x/y/z in the ray generation shader.
|
|
l.radius = maxRadius;
|
|
l.pointRadius.x = ax;
|
|
l.pointRadius.y = ay;
|
|
l.pointRadius.z = az;
|
|
l.pointRadiusPad = 0.0f;
|
|
|
|
l.color.x = r;
|
|
l.color.y = g;
|
|
l.color.z = b;
|
|
l.intensity = intensity;
|
|
|
|
l.normal.x = 0.0f;
|
|
l.normal.y = 0.0f;
|
|
l.normal.z = 1.0f;
|
|
l.type = GL_RAYTRACING_LIGHT_TYPE_POINT;
|
|
|
|
l.axisU.x = 1.0f;
|
|
l.axisU.y = 0.0f;
|
|
l.axisU.z = 0.0f;
|
|
l.halfWidth = 0.0f;
|
|
|
|
l.axisV.x = 0.0f;
|
|
l.axisV.y = 1.0f;
|
|
l.axisV.z = 0.0f;
|
|
l.halfHeight = 0.0f;
|
|
|
|
l.samples = 1;
|
|
l.twoSided = 0;
|
|
l.persistant = 0.0f;
|
|
l.pad1 = 0.0f; // volumetric scattering disabled by default.
|
|
l.areaNum = -1;
|
|
return l;
|
|
}
|
|
|
|
|
|
glRaytracingLight_t glRaytracingLightingMakeSpotLight(
|
|
float px, float py, float pz,
|
|
float dx, float dy, float dz,
|
|
float ux, float uy, float uz,
|
|
float vx, float vy, float vz,
|
|
float nearPlane,
|
|
float farPlane,
|
|
float tanHalfWidth,
|
|
float tanHalfHeight,
|
|
float r, float g, float b,
|
|
float intensity,
|
|
uint32_t samples)
|
|
{
|
|
glRaytracingLight_t l = {};
|
|
|
|
glRaytracingNormalize3(dx, dy, dz);
|
|
|
|
// Make U perpendicular to D.
|
|
{
|
|
const float du = dx * ux + dy * uy + dz * uz;
|
|
ux -= dx * du;
|
|
uy -= dy * du;
|
|
uz -= dz * du;
|
|
|
|
const float uLenSq = ux * ux + uy * uy + uz * uz;
|
|
if (uLenSq <= 1e-20f)
|
|
{
|
|
const float absDz = (dz < 0.0f) ? -dz : dz;
|
|
if (absDz < 0.999f)
|
|
{
|
|
glRaytracingCross3(0.0f, 0.0f, 1.0f, dx, dy, dz, ux, uy, uz);
|
|
}
|
|
else
|
|
{
|
|
glRaytracingCross3(0.0f, 1.0f, 0.0f, dx, dy, dz, ux, uy, uz);
|
|
}
|
|
}
|
|
glRaytracingNormalize3(ux, uy, uz);
|
|
}
|
|
|
|
// Rebuild V from D x U so the basis is orthonormal, while preserving the
|
|
// sign of the caller-provided V whenever possible.
|
|
{
|
|
float builtVx, builtVy, builtVz;
|
|
glRaytracingCross3(dx, dy, dz, ux, uy, uz, builtVx, builtVy, builtVz);
|
|
glRaytracingNormalize3(builtVx, builtVy, builtVz);
|
|
|
|
const float sign = builtVx * vx + builtVy * vy + builtVz * vz;
|
|
if (sign < 0.0f)
|
|
{
|
|
builtVx = -builtVx;
|
|
builtVy = -builtVy;
|
|
builtVz = -builtVz;
|
|
}
|
|
|
|
vx = builtVx;
|
|
vy = builtVy;
|
|
vz = builtVz;
|
|
}
|
|
|
|
if (nearPlane < 0.0f)
|
|
nearPlane = 0.0f;
|
|
if (farPlane <= nearPlane)
|
|
farPlane = nearPlane + 1e-3f;
|
|
|
|
if (tanHalfWidth < 0.0f) tanHalfWidth = -tanHalfWidth;
|
|
if (tanHalfHeight < 0.0f) tanHalfHeight = -tanHalfHeight;
|
|
|
|
if (tanHalfWidth <= 1e-4f)
|
|
tanHalfWidth = 1e-4f;
|
|
if (tanHalfHeight <= 1e-4f)
|
|
tanHalfHeight = 1e-4f;
|
|
|
|
l.position.x = px;
|
|
l.position.y = py;
|
|
l.position.z = pz;
|
|
|
|
// For spot lights, radius stores the far clip distance while pointRadius.x
|
|
// stores the near clip distance.
|
|
l.radius = farPlane;
|
|
l.pointRadius.x = nearPlane;
|
|
l.pointRadius.y = 0.0f;
|
|
l.pointRadius.z = 0.0f;
|
|
l.pointRadiusPad = 0.0f;
|
|
|
|
l.color.x = r;
|
|
l.color.y = g;
|
|
l.color.z = b;
|
|
l.intensity = intensity;
|
|
|
|
l.normal.x = dx;
|
|
l.normal.y = dy;
|
|
l.normal.z = dz;
|
|
l.type = GL_RAYTRACING_LIGHT_TYPE_SPOT;
|
|
|
|
l.axisU.x = ux;
|
|
l.axisU.y = uy;
|
|
l.axisU.z = uz;
|
|
l.halfWidth = tanHalfWidth;
|
|
|
|
l.axisV.x = vx;
|
|
l.axisV.y = vy;
|
|
l.axisV.z = vz;
|
|
l.halfHeight = tanHalfHeight;
|
|
|
|
l.samples = samples ? samples : 1u;
|
|
l.twoSided = 0;
|
|
l.persistant = 0.0f;
|
|
l.pad1 = 0.0f; // volumetric scattering disabled by default.
|
|
l.areaNum = -1;
|
|
|
|
return l;
|
|
}
|
|
|
|
glRaytracingLight_t glRaytracingLightingMakeRectLight(
|
|
float px, float py, float pz,
|
|
float nx, float ny, float nz,
|
|
float ux, float uy, float uz,
|
|
float vx, float vy, float vz,
|
|
float halfWidth, float halfHeight,
|
|
float r, float g, float b,
|
|
float intensity,
|
|
uint32_t samples,
|
|
uint32_t twoSided)
|
|
{
|
|
glRaytracingLight_t l = {};
|
|
|
|
glRaytracingNormalize3(nx, ny, nz);
|
|
glRaytracingNormalize3(ux, uy, uz);
|
|
glRaytracingNormalize3(vx, vy, vz);
|
|
|
|
if ((nx == 0.0f && ny == 0.0f && nz == 0.0f) &&
|
|
!((ux == 0.0f && uy == 0.0f && uz == 0.0f) ||
|
|
(vx == 0.0f && vy == 0.0f && vz == 0.0f)))
|
|
{
|
|
glRaytracingCross3(ux, uy, uz, vx, vy, vz, nx, ny, nz);
|
|
glRaytracingNormalize3(nx, ny, nz);
|
|
}
|
|
|
|
l.position.x = px;
|
|
l.position.y = py;
|
|
l.position.z = pz;
|
|
|
|
// Reuse radius as influence/falloff range for the rect light.
|
|
l.radius = (halfWidth > halfHeight ? halfWidth : halfHeight) * 6.0f;
|
|
l.pointRadius.x = l.radius;
|
|
l.pointRadius.y = l.radius;
|
|
l.pointRadius.z = l.radius;
|
|
l.pointRadiusPad = 0.0f;
|
|
|
|
l.color.x = r;
|
|
l.color.y = g;
|
|
l.color.z = b;
|
|
l.intensity = intensity;
|
|
|
|
l.normal.x = nx;
|
|
l.normal.y = ny;
|
|
l.normal.z = nz;
|
|
l.type = GL_RAYTRACING_LIGHT_TYPE_RECT;
|
|
|
|
l.axisU.x = ux;
|
|
l.axisU.y = uy;
|
|
l.axisU.z = uz;
|
|
l.halfWidth = halfWidth;
|
|
|
|
l.axisV.x = vx;
|
|
l.axisV.y = vy;
|
|
l.axisV.z = vz;
|
|
l.halfHeight = halfHeight;
|
|
|
|
l.samples = samples ? samples : 4u;
|
|
l.twoSided = twoSided ? 1u : 0u;
|
|
l.persistant = 0.0f;
|
|
l.pad1 = 0.0f; // volumetric scattering disabled by default.
|
|
l.areaNum = -1;
|
|
|
|
return l;
|
|
}
|
|
|
|
uint32_t glRaytracingLightingGetLightCount(void)
|
|
{
|
|
return (uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
}
|
|
|
|
bool glRaytracingLightingGetProfileCounters(glRaytracingProfileCounters_t* counters)
|
|
{
|
|
if (!counters)
|
|
return false;
|
|
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
memset(counters, 0, sizeof(*counters));
|
|
|
|
const glRaytracingLightingConstants_t& constants = g_glRaytracingLighting.constants;
|
|
const uint32_t lightCount = (uint32_t)g_glRaytracingLighting.cpuLights.size();
|
|
const uint32_t samplesPerPixel = constants.samplesPerPixel ? constants.samplesPerPixel : 1u;
|
|
const uint32_t maxBounces = constants.maxBounces ? constants.maxBounces : 1u;
|
|
|
|
uint32_t directLightSamples = 0;
|
|
uint32_t directShadowRays = 0;
|
|
uint32_t volumetricShadowRays = 0;
|
|
for (size_t i = 0; i < g_glRaytracingLighting.cpuLights.size(); ++i)
|
|
{
|
|
const glRaytracingLight_t& light = g_glRaytracingLighting.cpuLights[i];
|
|
const uint32_t sampleCount = glRaytracingClamp<uint32_t>(light.samples ? light.samples : 1u, 1u, 4u);
|
|
|
|
if (light.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
|
++counters->pointLightCount;
|
|
else if (light.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
|
++counters->spotLightCount;
|
|
else if (light.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
|
++counters->rectLightCount;
|
|
|
|
directLightSamples += sampleCount;
|
|
if (light.samples != 0u)
|
|
{
|
|
++counters->shadowCastingLightCount;
|
|
directShadowRays += sampleCount;
|
|
}
|
|
if (light.volumetricScattering > 0.0f &&
|
|
(light.type == GL_RAYTRACING_LIGHT_TYPE_POINT || light.type == GL_RAYTRACING_LIGHT_TYPE_SPOT))
|
|
{
|
|
++counters->volumetricLightCount;
|
|
volumetricShadowRays += glRaytracingClamp<uint32_t>(light.samples ? light.samples : 3u, 3u, 6u);
|
|
}
|
|
}
|
|
|
|
uint32_t meshCount = 0;
|
|
for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i)
|
|
{
|
|
if (g_glRaytracingScene.meshes[i].alive)
|
|
++meshCount;
|
|
}
|
|
|
|
uint32_t indirectDepth = 0;
|
|
if (maxBounces > 1u)
|
|
{
|
|
indirectDepth = glRaytracingClamp<uint32_t>(maxBounces - 1u, 0u, 3u);
|
|
if (samplesPerPixel <= 1u)
|
|
indirectDepth = glRaytracingClamp<uint32_t>(indirectDepth, 0u, 2u);
|
|
}
|
|
|
|
uint32_t correctionBudget = 0;
|
|
if (lightCount > 0u)
|
|
{
|
|
if (constants.secondaryCorrectionBudget > 0u)
|
|
{
|
|
correctionBudget = glRaytracingClamp<uint32_t>(constants.secondaryCorrectionBudget, 0u, lightCount);
|
|
}
|
|
else
|
|
{
|
|
correctionBudget = (lightCount <= 2u) ? 1u : 0u;
|
|
if (samplesPerPixel >= 4u)
|
|
correctionBudget = glRaytracingClamp<uint32_t>(std::max<uint32_t>(correctionBudget, 1u), 0u, lightCount);
|
|
if (samplesPerPixel >= 6u && lightCount <= 4u)
|
|
correctionBudget = glRaytracingClamp<uint32_t>(correctionBudget + 1u, 0u, lightCount);
|
|
}
|
|
}
|
|
|
|
uint32_t fastBounceLightBudget = lightCount;
|
|
if (constants.secondaryLightBudget < 0)
|
|
{
|
|
fastBounceLightBudget = lightCount;
|
|
}
|
|
else if (constants.secondaryLightBudget > 0)
|
|
{
|
|
fastBounceLightBudget = glRaytracingClamp<uint32_t>((uint32_t)constants.secondaryLightBudget, 0u, lightCount);
|
|
}
|
|
else
|
|
{
|
|
if (lightCount > 16u)
|
|
fastBounceLightBudget = 8u;
|
|
else if (lightCount > 10u)
|
|
fastBounceLightBudget = 7u;
|
|
else if (lightCount > 6u)
|
|
fastBounceLightBudget = 6u;
|
|
}
|
|
|
|
uint32_t gatherSamples = 0;
|
|
if (maxBounces > 1u)
|
|
{
|
|
gatherSamples = 3u;
|
|
if (lightCount > 4u)
|
|
gatherSamples = 2u;
|
|
if (lightCount > 10u)
|
|
gatherSamples = 1u;
|
|
if (samplesPerPixel >= 4u)
|
|
gatherSamples = glRaytracingClamp<uint32_t>(gatherSamples, 0u, 2u);
|
|
if (samplesPerPixel >= 6u)
|
|
gatherSamples = glRaytracingClamp<uint32_t>(gatherSamples, 0u, 1u);
|
|
}
|
|
|
|
counters->frameCounter = g_glRaytracingLighting.frameCounter;
|
|
counters->width = g_glRaytracingLighting.lastProfileWidth;
|
|
counters->height = g_glRaytracingLighting.lastProfileHeight;
|
|
counters->lightCount = lightCount;
|
|
counters->samplesPerPixel = samplesPerPixel;
|
|
counters->maxBounces = maxBounces;
|
|
counters->enableSpecular = constants.enableSpecular;
|
|
counters->enableDenoiser = constants.enableDenoiser;
|
|
counters->meshCount = meshCount;
|
|
counters->instanceCount = g_glRaytracingLighting.lastProfileInstanceCount;
|
|
counters->directLightSamplesPerPixel = directLightSamples;
|
|
counters->directShadowRaysPerPixel = directShadowRays;
|
|
counters->indirectBounceRaysPerPixel = samplesPerPixel * indirectDepth;
|
|
counters->indirectLightEvalsPerPixel = samplesPerPixel * indirectDepth * (fastBounceLightBudget + correctionBudget);
|
|
counters->finalGatherRaysPerPixel = gatherSamples;
|
|
counters->reflectionRaysPerPixel = (constants.enableSpecular != 0u && maxBounces > 1u) ? 1u : 0u;
|
|
counters->volumetricShadowRaysPerPixel = volumetricShadowRays;
|
|
counters->gpuMsec = com_pathTracingGpuMsec;
|
|
return true;
|
|
}
|
|
|
|
int glRaytracingSetInstanceVisibilityUnlocked(
|
|
glRaytracingRenderWorld_t* world,
|
|
glRaytracingInstanceHandle_t instanceHandle,
|
|
int visible)
|
|
{
|
|
if (!world || !world->alive)
|
|
return 0;
|
|
|
|
glRaytracingInstanceRecord_t* inst =
|
|
glRaytracingFindInstance(world, instanceHandle);
|
|
|
|
if (!inst || !inst->alive)
|
|
return 0;
|
|
|
|
const uint32_t newMask = visible ? 0xFFu : 0u;
|
|
|
|
if ((((uint32_t)inst->descCpu.mask) & 0xFFu) == newMask)
|
|
return 1;
|
|
|
|
inst->descCpu.mask = newMask;
|
|
inst->dirty = 1;
|
|
|
|
glRaytracingMarkWorldNeedsUpdate(world);
|
|
return 1;
|
|
}
|
|
|
|
static void glRaytracingSetAllInstancesVisibleUnlocked(
|
|
glRaytracingRenderWorld_t* world,
|
|
int visible)
|
|
{
|
|
if (!world || !world->alive)
|
|
return;
|
|
|
|
const uint32_t newMask = visible ? 0xFFu : 0u;
|
|
int changed = 0;
|
|
|
|
for (size_t i = 0; i < world->instances.size(); ++i)
|
|
{
|
|
glRaytracingInstanceRecord_t& inst = world->instances[i];
|
|
|
|
if (!inst.alive)
|
|
continue;
|
|
|
|
const int wasVisible = inst.descCpu.mask != 0;
|
|
if (wasVisible == (visible != 0))
|
|
continue;
|
|
|
|
inst.descCpu.mask = newMask;
|
|
inst.dirty = 1;
|
|
changed = 1;
|
|
}
|
|
|
|
if (changed)
|
|
glRaytracingMarkWorldNeedsUpdate(world);
|
|
}
|
|
|
|
int glRaytracingSetInstanceVisibilityInScene(
|
|
glRaytracingSceneHandle_t sceneHandle,
|
|
glRaytracingInstanceHandle_t instanceHandle,
|
|
int visible)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
return glRaytracingSetInstanceVisibilityUnlocked(
|
|
world,
|
|
instanceHandle,
|
|
visible ? 1 : 0);
|
|
}
|
|
|
|
int glRaytracingGetInstanceVisibilityInScene(
|
|
glRaytracingSceneHandle_t sceneHandle,
|
|
glRaytracingInstanceHandle_t instanceHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return 0;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
if (!world)
|
|
return 0;
|
|
|
|
glRaytracingInstanceRecord_t* inst =
|
|
glRaytracingFindInstance(world, instanceHandle);
|
|
|
|
if (!inst || !inst->alive)
|
|
return 0;
|
|
|
|
return inst->descCpu.mask != 0 ? 1 : 0;
|
|
}
|
|
|
|
void glRaytracingHideAllInstancesInScene(glRaytracingSceneHandle_t sceneHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
glRaytracingSetAllInstancesVisibleUnlocked(world, 0);
|
|
}
|
|
|
|
void glRaytracingShowAllInstancesInScene(glRaytracingSceneHandle_t sceneHandle)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
glRaytracingRenderWorld_t* world = glRaytracingFindWorld(sceneHandle);
|
|
glRaytracingSetAllInstancesVisibleUnlocked(world, 1);
|
|
}
|
|
|
|
void glRaytracingHideAllInstances(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
glRaytracingSetAllInstancesVisibleUnlocked(&g_glRaytracingScene.worlds[i], 0);
|
|
}
|
|
|
|
void glRaytracingShowAllInstances(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
|
|
|
|
if (!g_glRaytracingScene.initialized)
|
|
return;
|
|
|
|
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
|
|
glRaytracingSetAllInstancesVisibleUnlocked(&g_glRaytracingScene.worlds[i], 1);
|
|
}
|