/* =========================================================================== Copyright (C) 2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). Challenge Quake 3 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Challenge Quake 3 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Challenge Quake 3. If not, see . =========================================================================== */ // shared structure for a given scene view #pragma once #include "typedefs.h.hlsli" #if !defined(__cplusplus) # include "common.hlsli" #endif #if defined(__cplusplus) # pragma pack(push, 4) #endif // @TODO: move out struct ParticleEmitter { uint firstIndex; uint liveCount; // post-emission, pre-simulation uint liveCount2; // post-simulation uint deadCount; uint totalCount; uint emitCount; // how many added this frame float maxSeconds; }; // @TODO: move out struct Particle { // needed for injection, set by user float3 position; float radius; float3 scattering; // or emissive float absorption; float anisotropy; uint isEmissive; // needed for injection, private int3 froxelMin; int3 froxelMax; // extra data for simulation only float3 velocity; float lifeTime; // in seconds }; // @TODO: move out #define MAX_PARTICLES (1 << 20) #define MAX_PARTICLE_EMITTERS (1 << 10) // @TODO: move out struct DynamicLight { float3 position; float radius; float3 color; float padding; }; #if !defined(__cplusplus) struct ExtinctionCascade { // copied over Texture3D textures[4]; SamplerState linearClampSampler; float4 worldScale; float3 cameraPosition; float3 textureSize; // set by SetSamplingVolume uint lowestMipLevel; float mipLerp; void SetSamplingVolume(float voxelSize) { float mipLevel = max(log2(voxelSize / worldScale.x), 0.0); if(mipLevel <= 2.0) { lowestMipLevel = uint(floor(mipLevel)); mipLerp = frac(mipLevel); } else if(voxelSize >= worldScale.w) { lowestMipLevel = 3; mipLerp = 0.0; } else { float base = worldScale.w / worldScale.z; float mipLevelFrac = log2(voxelSize / worldScale.z) / log2(base); lowestMipLevel = 2; mipLerp = mipLevelFrac; } } float ExtinctionAt(float3 position) { float ext; if(ExtinctionAtMip(position, 0, ext)) { return ext; } if(ExtinctionAtMip(position, 1, ext)) { return ext; } if(ExtinctionAtMip(position, 2, ext)) { return ext; } float3 tc = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale.w); ext = textures[3].SampleLevel(linearClampSampler, tc, 0); return ext; } bool ExtinctionAtMip(float3 position, uint mip, out float ext) { ext = 0.0; if(lowestMipLevel == mip) { float3 tc0 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 0]); if(Is01(tc0)) // @TODO: make sure it's at least a half-texel inside { float3 tc1 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 1]); float ext0 = textures[mip + 0].SampleLevel(linearClampSampler, tc0, 0); float ext1 = textures[mip + 1].SampleLevel(linearClampSampler, tc1, 0); ext = lerp(ext0, ext1, mipLerp); return true; } } return false; } }; #endif #if !defined(__cplusplus) struct SunVShadowCascade { // copied over Texture3D textures[4]; SamplerState linearClampSampler; float4 worldScale; float3 cameraPosition; float3 textureSize; float3x3 zToSunMatrix; // set by SetSamplingVolume uint lowestMipLevel; float mipLerp; void SetSamplingVolume(float voxelSize) { float mipLevel = max(log2(voxelSize / worldScale.x), 0.0); if(mipLevel <= 2.0) { lowestMipLevel = uint(floor(mipLevel)); mipLerp = frac(mipLevel); } else if(voxelSize >= worldScale.w) { lowestMipLevel = 3; mipLerp = 0.0; } else { float base = worldScale.w / worldScale.z; float mipLevelFrac = log2(voxelSize / worldScale.z) / log2(base); lowestMipLevel = 2; mipLerp = mipLevelFrac; } } float TransmittanceAt(float3 positionWS) { float3 positionSS = cameraPosition + mul(zToSunMatrix, positionWS - cameraPosition); float ext; if(TransmittanceAtMip(positionSS, 0, ext)) { return ext; } if(TransmittanceAtMip(positionSS, 1, ext)) { return ext; } if(TransmittanceAtMip(positionSS, 2, ext)) { return ext; } float3 tc = AABoxWorldSpaceToTC(positionSS, cameraPosition, textureSize, worldScale.w); ext = textures[3].SampleLevel(linearClampSampler, tc, 0); return ext; } bool TransmittanceAtMip(float3 position, uint mip, out float ext) { ext = 0.0; if(lowestMipLevel == mip) { float3 tc0 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 0]); if(Is01(tc0)) // @TODO: make sure it's at least a half-texel inside { float3 tc1 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 1]); float ext0 = textures[mip + 0].SampleLevel(linearClampSampler, tc0, 0); float ext1 = textures[mip + 1].SampleLevel(linearClampSampler, tc1, 0); ext = lerp(ext0, ext1, mipLerp); return true; } } return false; } }; #endif struct SceneView { matrix projectionMatrix; matrix invProjectionMatrix; matrix viewMatrix; matrix invViewMatrix; matrix prevViewProjMatrix; matrix prevViewMatrix; matrix prevProjectionMatrix; float3x3 zToSunMatrix; float3x3 sunToZMatrix; float4 clipPlane; float4 debug; float3 cameraPosition; float sunIntensityDL; float3 sunDirection; float zNear; float3 sunColor; float zFar; float3 prevCameraPosition; float prevZNear; float3 cameraForward; float prevZFar; float3 cameraLeft; float sunIntensityVL; float3 cameraUp; float pointLightIntensityVL; float3 linearDepthConstants; float frameSeed; float3 ambientColor; float ambientIntensity; float4 extinctionWorldScale; uint4 extinctionTextureIndices; float4 sunVShadowWorldScale; uint4 sunVShadowTextureIndices; uint sceneViewIndex; uint frameIndex; uint depthTextureIndex; uint depthMinMaxTextureIndex; uint normalTextureIndex; uint shadingPositionTextureIndex; uint motionVectorTextureIndex; uint motionVectorMBTextureIndex; uint lightTextureIndex; uint sunlightTextureIndex; uint tlasBufferIndex; uint tlasInstanceBufferIndex; uint linearClampSamplerIndex; #if !defined(__cplusplus) float LinearDepth(float zwDepth) { return ::LinearDepth(zwDepth, linearDepthConstants); } float3 CamerayRay(float2 ndc) { float4 pointNDC = float4(ndc, 0.5, 1); float4 pointWSw = mul(invViewMatrix, mul(invProjectionMatrix, pointNDC)); float3 pointWS = pointWSw.xyz / pointWSw.w; float3 dir = pointWS - cameraPosition; float3 ray = normalize(dir); return ray; } #if 0 // exponential depth distribution like The Last of Us Part II float TLOU2SliceToViewDepth(float slice, float C) { const float Q = 1.0; float viewDepth = exp2((slice + Q * C) / C) - exp2(Q); return viewDepth; } float TLOU2ViewDepthToSlice(float viewDepth, float C) { const float Q = 1.0; float logArg = exp2(Q) + viewDepth; float slice = logArg <= 0.0 ? -666.0 : (C * (log2(logArg) - Q)); return slice; } float TLOU2CFromSliceAndViewDepth(float slice, float viewDepth) { const float Q = 1.0; float C = slice / (log2(viewDepth + exp2(Q)) - Q); return C; } float FroxelViewDepthToZ01Ex(float viewDepth, float sliceCount, float zn, float zf) { float depthRange = zf - zn; float C = TLOU2CFromSliceAndViewDepth(sliceCount - 1.0, depthRange); float slice = TLOU2ViewDepthToSlice(viewDepth - zn, C); float depth01 = slice / sliceCount; return depth01; } float FroxelZ01ToViewDepth(float depth01, float sliceCount) { float depthRange = zFar - zNear; float C = TLOU2CFromSliceAndViewDepth(sliceCount - 1.0, depthRange); float slice = depth01 * sliceCount; float viewDepth = zNear + TLOU2SliceToViewDepth(slice, C); return viewDepth; } #elif 1 // quadratic depth distribution float FroxelViewDepthToZ01Ex(float viewDepth, float sliceCount, float zn, float zf) { float depth01 = (viewDepth - zn) / (zf - zn); depth01 = sqrt(depth01); return depth01; } float FroxelZ01ToViewDepth(float depth01, float sliceCount) { depth01 *= depth01; float viewDepth = zNear + (zFar - zNear) * depth01; return viewDepth; } #else // linear depth distribution float FroxelViewDepthToZ01Ex(float viewDepth, float sliceCount, float zn, float zf) { float depth01 = (viewDepth - zn) / (zf - zn); return depth01; } float FroxelZ01ToViewDepth(float depth01, float sliceCount) { float viewDepth = zNear + (zFar - zNear) * depth01; return viewDepth; } #endif float FroxelViewDepthToZ01(float viewDepth, float sliceCount) { float depth01 = FroxelViewDepthToZ01Ex(viewDepth, sliceCount, zNear, zFar); return depth01; } float FroxelViewDepthToZ01PrevFrame(float viewDepth, float sliceCount) { float depth01 = FroxelViewDepthToZ01Ex(viewDepth, sliceCount, prevZNear, prevZFar); return depth01; } float3 FroxelTCToWorldSpace(float3 tc, float3 textureSize) { float pointDepth = FroxelZ01ToViewDepth(tc.z, textureSize.z); float2 xyNDC = TCToNDC(tc.xy); float zNDC = PostProjectionDepth(pointDepth, zNear, zFar); float4 pointNDC = float4(xyNDC, zNDC, 1); float4 pointWSw = mul(invViewMatrix, mul(invProjectionMatrix, pointNDC)); float3 pointWS = pointWSw.xyz / pointWSw.w; return pointWS; } float3 FroxelIndexToWorldSpace(int3 index, float3 textureSize) { float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize; float3 pointWS = FroxelTCToWorldSpace(tc, textureSize); return pointWS; } float3 FroxelWorldSpaceToTC(float3 positionWS, float3 textureSize) { float4 positionVSw = mul(viewMatrix, float4(positionWS, 1)); float viewDepth = -positionVSw.z / positionVSw.w; float z01 = FroxelViewDepthToZ01(viewDepth, textureSize.z); float4 positionCSw = mul(projectionMatrix, positionVSw); float2 xy01 = NDCToTC(positionCSw.xy / positionCSw.w); float3 tc = float3(xy01, z01); return tc; } int3 FroxelWorldSpaceToIndex(float3 positionWS, float3 textureSize) { float3 tc = FroxelWorldSpaceToTC(positionWS, textureSize); int3 index = int3(tc * textureSize); return index; } int2 FroxelSphereZExtents(float3 positionWS, float radius, float3 textureSize) { float4 positionVSw = mul(viewMatrix, float4(positionWS, 1)); float viewDepth = -positionVSw.z / positionVSw.w; float viewDepthMin = viewDepth - radius; float viewDepthMax = viewDepth + radius; float zMin01 = FroxelViewDepthToZ01(viewDepthMin, textureSize.z); float zMax01 = FroxelViewDepthToZ01(viewDepthMax, textureSize.z); int2 extents = int2(zMin01 * textureSize.z, ceil(zMax01 * textureSize.z)); return extents; } // @TODO: validate new logic float3 FroxelReproject01(int3 currIndex, float3 textureSize) { float3 currTC = (float3(currIndex) + float3(0.5, 0.5, 0.5)) / textureSize; float currDepth = FroxelZ01ToViewDepth(currTC.z, textureSize.z); float2 xyNDC = TCToNDC(currTC.xy); float zNDC = PostProjectionDepth(currDepth, zNear, zFar); float4 currNDC = float4(xyNDC, zNDC, 1); float4 positionWSw = mul(invViewMatrix, mul(invProjectionMatrix, currNDC)); float3 positionWS = positionWSw.xyz / positionWSw.w; float4 prevVSw = mul(prevViewMatrix, float4(positionWS, 1)); float prevDepth = -prevVSw.z / prevVSw.w; float z01 = FroxelViewDepthToZ01(prevDepth, textureSize.z); float4 prevCSw = mul(prevProjectionMatrix, prevVSw); float2 xy01 = NDCToTC(prevCSw.xy / prevCSw.w); float3 prevTC = float3(xy01, z01); return prevTC; } float FroxelVolume(uint3 index, float3 textureSize) { float3 tcBase = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize; float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize; float3 posL = FroxelTCToWorldSpace(tcBase + float3(-halfTexel.x, 0, 0), textureSize); float3 posR = FroxelTCToWorldSpace(tcBase + float3(halfTexel.x, 0, 0), textureSize); float w = distance(posL, posR); float3 posU = FroxelTCToWorldSpace(tcBase + float3(0, halfTexel.y, 0), textureSize); float3 posD = FroxelTCToWorldSpace(tcBase + float3(0, -halfTexel.y, 0), textureSize); float h = distance(posU, posD); float3 posF = FroxelTCToWorldSpace(tcBase + float3(0, 0, halfTexel.z), textureSize); float3 posB = FroxelTCToWorldSpace(tcBase + float3(0, 0, -halfTexel.z), textureSize); float d = distance(posF, posB); float volume = w * h * d; return volume; } float3 FroxelAverageDimensions(uint3 index, float3 textureSize) { float3 tcBase = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize; float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize; float3 posL = FroxelTCToWorldSpace(tcBase + float3(-halfTexel.x, 0, 0), textureSize); float3 posR = FroxelTCToWorldSpace(tcBase + float3(halfTexel.x, 0, 0), textureSize); float w = distance(posL, posR); float3 posU = FroxelTCToWorldSpace(tcBase + float3(0, halfTexel.y, 0), textureSize); float3 posD = FroxelTCToWorldSpace(tcBase + float3(0, -halfTexel.y, 0), textureSize); float h = distance(posU, posD); float3 posF = FroxelTCToWorldSpace(tcBase + float3(0, 0, -halfTexel.z), textureSize); float3 posB = FroxelTCToWorldSpace(tcBase + float3(0, 0, halfTexel.z), textureSize); float d = distance(posF, posB); float3 dimensions = float3(w, h, d); return dimensions; } float3 FroxelMaxDimensions(uint3 index, float3 textureSize) { float3 tcBase = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize; float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize; float3 posL = FroxelTCToWorldSpace(tcBase + float3(-halfTexel.x, 0, halfTexel.z), textureSize); float3 posR = FroxelTCToWorldSpace(tcBase + float3(halfTexel.x, 0, halfTexel.z), textureSize); float w = distance(posL, posR); float3 posU = FroxelTCToWorldSpace(tcBase + float3(0, halfTexel.y, halfTexel.z), textureSize); float3 posD = FroxelTCToWorldSpace(tcBase + float3(0, -halfTexel.y, halfTexel.z), textureSize); float h = distance(posU, posD); float3 posF = FroxelTCToWorldSpace(tcBase + float3(0, 0, -halfTexel.z), textureSize); float3 posB = FroxelTCToWorldSpace(tcBase + float3(0, 0, halfTexel.z), textureSize); float d = distance(posF, posB); float3 dimensions = float3(w, h, d); return dimensions; } #if 0 void FroxelAABB(out float boxMin, out float3 boxMax, int3 index, float3 textureSize) { float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize; float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize; float3 pointWS; ClearBoundingBox(boxMin, boxMax); pointWS = FroxelTCToWorldSpace(tc + float3(-halfTexel.x, 0, 0), textureSize); ExpandBoundingBox(boxMin, boxMax, pointWS); pointWS = FroxelTCToWorldSpace(tc + float3(halfTexel.x, 0, 0), textureSize); ExpandBoundingBox(boxMin, boxMax, pointWS); pointWS = FroxelTCToWorldSpace(tc + float3(0, -halfTexel.y, 0), textureSize); ExpandBoundingBox(boxMin, boxMax, pointWS); pointWS = FroxelTCToWorldSpace(tc + float3(0, halfTexel.y, 0), textureSize); ExpandBoundingBox(boxMin, boxMax, pointWS); pointWS = FroxelTCToWorldSpace(tc + float3(0, 0, -halfTexel.z), textureSize); ExpandBoundingBox(boxMin, boxMax, pointWS); pointWS = FroxelTCToWorldSpace(tc + float3(0, 0, halfTexel.z), textureSize); ExpandBoundingBox(boxMin, boxMax, pointWS); } #endif float3 ExtinctionIndexToWorldSpace(int3 index, float3 textureSize, float worldScale) { return AABoxIndexToWorldSpace(index, cameraPosition, textureSize, worldScale); } float3 ExtinctionTCToWorldSpace(float3 tc, float3 textureSize, float worldScale) { return AABoxTCToWorldSpace(tc, cameraPosition, textureSize, worldScale); } float3 ExtinctionWorldSpaceToTC(float3 position, float3 textureSize, float worldScale) { return AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale); } int3 ExtinctionWorldSpaceToIndex(float3 position, float3 textureSize, float worldScale) { return AABoxWorldSpaceToIndex(position, cameraPosition, textureSize, worldScale); } ExtinctionCascade GetExtinctionCascade(float voxelSize) { ExtinctionCascade cascade; cascade.textures[0] = ResourceDescriptorHeap[extinctionTextureIndices.x]; cascade.textures[1] = ResourceDescriptorHeap[extinctionTextureIndices.y]; cascade.textures[2] = ResourceDescriptorHeap[extinctionTextureIndices.z]; cascade.textures[3] = ResourceDescriptorHeap[extinctionTextureIndices.w]; cascade.linearClampSampler = SamplerDescriptorHeap[linearClampSamplerIndex]; cascade.worldScale = extinctionWorldScale; cascade.cameraPosition = cameraPosition; cascade.textureSize = GetTextureSize(cascade.textures[0]); cascade.SetSamplingVolume(voxelSize); return cascade; } SunVShadowCascade GetSunVShadowCascade(float voxelSize) { SunVShadowCascade cascade; cascade.textures[0] = ResourceDescriptorHeap[sunVShadowTextureIndices.x]; cascade.textures[1] = ResourceDescriptorHeap[sunVShadowTextureIndices.y]; cascade.textures[2] = ResourceDescriptorHeap[sunVShadowTextureIndices.z]; cascade.textures[3] = ResourceDescriptorHeap[sunVShadowTextureIndices.w]; cascade.linearClampSampler = SamplerDescriptorHeap[linearClampSamplerIndex]; cascade.worldScale = sunVShadowWorldScale; cascade.cameraPosition = cameraPosition; cascade.textureSize = GetTextureSize(cascade.textures[0]); cascade.zToSunMatrix = zToSunMatrix; cascade.SetSamplingVolume(voxelSize); return cascade; } #endif }; #if defined(__cplusplus) # pragma pack(pop) #endif #if defined(__cplusplus) static_assert(sizeof(DynamicLight) == 32, "sizeof(DynamicLight) is wrong"); #endif #if !defined(__cplusplus) SceneView GetSceneView() { StructuredBuffer sceneViewBuffer = ResourceDescriptorHeap[0]; SceneView sceneView = sceneViewBuffer[0]; return sceneView; } #endif