From 413e52d05b68fbc4ac8b8199e001109f7ca120e7 Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Wed, 22 Jul 2026 18:19:10 +0200 Subject: [PATCH 1/2] RTSS: allow referencing shared_params --- .../include/OgreShaderParameter.h | 2 ++ .../include/OgreShaderProgram.h | 5 +++ .../src/OgreShaderCGProgramWriter.cpp | 27 ++++++++++++++++ .../src/OgreShaderCGProgramWriter.h | 1 + .../src/OgreShaderGLSLProgramWriter.cpp | 31 +++++++++++++++++++ .../src/OgreShaderGLSLProgramWriter.h | 2 ++ .../src/OgreShaderParameter.cpp | 6 ++++ .../src/OgreShaderProgramProcessor.cpp | 4 +++ OgreMain/include/OgreGpuProgramParams.h | 3 ++ OgreMain/src/OgreGpuProgramParams.cpp | 9 ++++++ 10 files changed, 90 insertions(+) diff --git a/Components/RTShaderSystem/include/OgreShaderParameter.h b/Components/RTShaderSystem/include/OgreShaderParameter.h index 4aff48b19ec..d9e0c71a759 100644 --- a/Components/RTShaderSystem/include/OgreShaderParameter.h +++ b/Components/RTShaderSystem/include/OgreShaderParameter.h @@ -577,6 +577,8 @@ class _OgreRTSSExport ParameterFactory static ParameterPtr createConstParam(const Vector4& val); static ParameterPtr createConstParam(float val); + static ParameterPtr createSharedParam(const GpuSharedParametersPtr& parent, const String& name); + static UniformParameterPtr createSampler(GpuConstantType type, int index); static UniformParameterPtr createSampler1D(int index); static UniformParameterPtr createSampler2D(int index); diff --git a/Components/RTShaderSystem/include/OgreShaderProgram.h b/Components/RTShaderSystem/include/OgreShaderProgram.h index 3da69023a25..ff1df325d3c 100644 --- a/Components/RTShaderSystem/include/OgreShaderProgram.h +++ b/Components/RTShaderSystem/include/OgreShaderProgram.h @@ -184,6 +184,9 @@ class _OgreRTSSExport Program : public RTShaderSystemAlloc void setUseLinearColours(bool useLinear) { mUseLinearColours = useLinear; } bool getUseLinearColours() const { return mUseLinearColours; } + void addSharedParameters(GpuSharedParametersPtr sharedParams) { mSharedParameters.push_back(sharedParams); } + const std::vector& getSharedParameters() const { return mSharedParameters; } + /** Class destructor */ ~Program(); // Protected methods. @@ -213,6 +216,8 @@ class _OgreRTSSExport Program : public RTShaderSystemAlloc StringVector mDependencies; /// preprocessor definitions String mPreprocessorDefines; + /// Shared parameter sets referenced by this program + std::vector mSharedParameters; // Skeletal animation calculation bool mSkeletalAnimation; // Whether to pass matrices as column-major. diff --git a/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.cpp b/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.cpp index 43260b87917..95cb153b6ac 100644 --- a/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.cpp +++ b/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.cpp @@ -86,6 +86,30 @@ void CGProgramWriter::initializeStringMaps() mParamSemanticMap[Parameter::SPS_LAYER] = "SV_RenderTargetArrayIndex"; } +void CGProgramWriter::writeSharedParams(std::ostream& os, const String& name, int registerIdx, const GpuSharedParametersPtr& params) +{ + // In HLSL/D3D11 constant buffers are bound to register(bN). + // Only SM4+ (ps_4_0 and up) understands cbuffer/register(b#). + bool useRegister = GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0"); + + os << "cbuffer " << name; + if (useRegister) + os << " : register(b" << registerIdx << ")"; + os << " {\n"; + + for (const auto& e : params->getConstantDefinitionsSorted()) + { + const GpuConstantDefinition& def = e.second; + + os << "\t" << mGpuConstTypeMap[def.constType] << " " << e.first; + if (def.arraySize > 1) + os << "[" << def.arraySize << "]"; + os << ";\n"; + } + + os << "};\n"; +} + //----------------------------------------------------------------------- void CGProgramWriter::writeSourceCode(std::ostream& os, Program* program) { @@ -108,6 +132,9 @@ void CGProgramWriter::writeSourceCode(std::ostream& os, Program* program) } os << std::endl; + int sharedRegister = 1; + for (const auto& shared : program->getSharedParameters()) + writeSharedParams(os, shared->getName(), sharedRegister++, shared); Function* curFunction = program->getMain(); diff --git a/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.h b/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.h index bd84632c9d4..19af0266a9a 100644 --- a/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.h +++ b/Components/RTShaderSystem/src/OgreShaderCGProgramWriter.h @@ -77,6 +77,7 @@ class CGProgramWriter : public ProgramWriter // Protected methods. protected: + void writeSharedParams(std::ostream& os, const String& name, int registerIdx, const GpuSharedParametersPtr& params); /** Initialize string maps. */ void initializeStringMaps(); diff --git a/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp b/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp index a5082689b50..daa34e652d9 100644 --- a/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp +++ b/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp @@ -151,6 +151,33 @@ void GLSLProgramWriter::writeUniformBlock(std::ostream& os, const String& name, os << "};\n"; } +void GLSLProgramWriter::writeUniformBlock(std::ostream& os, const String& name, int binding, + const GpuSharedParametersPtr& params) +{ + // Explicit binding needs GLSL 420 / SPIR-V; otherwise the RS assigns it by block name. + bool explicitBinding = mIsVulkan || mGLSLVersion >= 420; + + os << "layout("; + if (explicitBinding) + os << "binding = " << binding << ", "; + os << "std140, row_major) uniform " << name << " {\n"; + + for (const auto& e : params->getConstantDefinitionsSorted()) + { + const GpuConstantDefinition& def = e.second; + + if (def.constType == GCT_MATRIX_3X4 || def.constType == GCT_MATRIX_2X4) + os << "layout(column_major) "; + + os << "\t" << mGpuConstTypeMap[def.constType] << " " << e.first; + if (def.arraySize > 1) + os << "[" << def.arraySize << "]"; + os << ";\n"; + } + + os << "};\n"; +} + void GLSLProgramWriter::writeMainSourceCode(std::ostream& os, Program* program) { GpuProgramType gpuType = program->getType(); @@ -188,6 +215,10 @@ void GLSLProgramWriter::writeMainSourceCode(std::ostream& os, Program* program) uniforms.clear(); } + int sharedBinding = GPT_FRAGMENT_PROGRAM + 1; + for (const auto& shared : program->getSharedParameters()) + writeUniformBlock(os, shared->getName(), sharedBinding++, shared); + int uniformLoc = 0; for (const auto& uparam : uniforms) { diff --git a/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.h b/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.h index 0434dab4a04..0839a2f1d42 100644 --- a/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.h +++ b/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.h @@ -88,6 +88,8 @@ class GLSLProgramWriter : public ProgramWriter void writeUniformBlock(std::ostream& os, const String& name, int binding, const UniformParameterList& uniforms); + void writeUniformBlock(std::ostream& os, const String& name, int binding, const GpuSharedParametersPtr& params); + protected: typedef std::map ParamSemanticToStringMap; diff --git a/Components/RTShaderSystem/src/OgreShaderParameter.cpp b/Components/RTShaderSystem/src/OgreShaderParameter.cpp index 36909a5a239..fc615dd30ca 100644 --- a/Components/RTShaderSystem/src/OgreShaderParameter.cpp +++ b/Components/RTShaderSystem/src/OgreShaderParameter.cpp @@ -635,6 +635,12 @@ ParameterPtr ParameterFactory::createConstParam(float val) Parameter::SPC_UNKNOWN)); } +ParameterPtr ParameterFactory::createSharedParam(const GpuSharedParametersPtr& parent, const String& name) +{ + auto p = parent->getConstantDefinition(name); + return std::make_shared(p.constType, name, Parameter::SPS_UNKNOWN, 0, p.arraySize); +} + //----------------------------------------------------------------------- UniformParameterPtr ParameterFactory::createUniform(GpuConstantType type, int index, uint16 variability, diff --git a/Components/RTShaderSystem/src/OgreShaderProgramProcessor.cpp b/Components/RTShaderSystem/src/OgreShaderProgramProcessor.cpp index 8e78ecf40f2..1078084c434 100644 --- a/Components/RTShaderSystem/src/OgreShaderProgramProcessor.cpp +++ b/Components/RTShaderSystem/src/OgreShaderProgramProcessor.cpp @@ -77,6 +77,10 @@ void ProgramProcessor::bindAutoParameters(Program* pCpuProgram, GpuProgramPtr pG { GpuProgramParametersSharedPtr pGpuParams = pGpuProgram->getDefaultParameters(); + // Forward shared parameter sets to the concrete GpuProgramParameters, + for (const auto& sharedParams : pCpuProgram->getSharedParameters()) + pGpuParams->addSharedParameters(sharedParams); + pGpuParams->setUseLinearColours(pCpuProgram->getUseLinearColours()); for (const auto& p : pCpuProgram->getParameters()) { diff --git a/OgreMain/include/OgreGpuProgramParams.h b/OgreMain/include/OgreGpuProgramParams.h index 78b524a1e83..3a995373247 100644 --- a/OgreMain/include/OgreGpuProgramParams.h +++ b/OgreMain/include/OgreGpuProgramParams.h @@ -489,6 +489,9 @@ namespace Ogre { */ const GpuNamedConstants& getConstantDefinitions() const; + /// Get the constant definitions ordered by their physical index. + std::vector> getConstantDefinitionsSorted() const; + /** @copydoc GpuProgramParameters::setNamedConstant(const String&, Real) */ template void setNamedConstant(const String& name, T val) { diff --git a/OgreMain/src/OgreGpuProgramParams.cpp b/OgreMain/src/OgreGpuProgramParams.cpp index ff8e4a67f26..982d06caff6 100644 --- a/OgreMain/src/OgreGpuProgramParams.cpp +++ b/OgreMain/src/OgreGpuProgramParams.cpp @@ -486,6 +486,15 @@ namespace Ogre { return mNamedConstants; } + std::vector> GpuSharedParameters::getConstantDefinitionsSorted() const + { + std::vector> ordered(mNamedConstants.map.begin(), + mNamedConstants.map.end()); + std::sort(ordered.begin(), ordered.end(), + [](const auto& a, const auto& b) { return a.second.physicalIndex < b.second.physicalIndex; }); + + return ordered; + } //--------------------------------------------------------------------- void GpuSharedParameters::setNamedConstant(const String& name, const Matrix4& m) { From 411471ab668d8049d95a94180b142274320980ed Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Sat, 27 Jun 2026 18:02:18 +0200 Subject: [PATCH 2/2] Main: add support for clustered (froxel) lighting --- .../src/OgreShaderExIntegratedPSSM3.cpp | 13 ++ Media/RTShaderLib/RTSLib_Froxels.glsl | 104 ++++++++++ OgreMain/include/OgreAutoParamDataSource.h | 10 + OgreMain/include/OgreGpuProgramParams.h | 6 + OgreMain/src/OgreAutoParamDataSource.cpp | 73 +++++++ OgreMain/src/OgreFroxelizer.cpp | 180 ++++++++++++++++++ OgreMain/src/OgreFroxelizer.h | 58 ++++++ OgreMain/src/OgreGpuProgramParams.cpp | 13 +- 8 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 Media/RTShaderLib/RTSLib_Froxels.glsl create mode 100644 OgreMain/src/OgreFroxelizer.cpp create mode 100644 OgreMain/src/OgreFroxelizer.h diff --git a/Components/RTShaderSystem/src/OgreShaderExIntegratedPSSM3.cpp b/Components/RTShaderSystem/src/OgreShaderExIntegratedPSSM3.cpp index 35a475e864d..11d668ed564 100644 --- a/Components/RTShaderSystem/src/OgreShaderExIntegratedPSSM3.cpp +++ b/Components/RTShaderSystem/src/OgreShaderExIntegratedPSSM3.cpp @@ -64,6 +64,7 @@ void IntegratedPSSM3::updateGpuProgramsParams(Renderable* rend, const Pass* pass const AutoParamDataSource* source, const LightList* pLightList) { + const_cast(source)->updateFroxelData(); if (mMultiLightCount > 1) return; @@ -274,6 +275,7 @@ bool IntegratedPSSM3::resolveDependencies(ProgramSet* programSet) { Program* psProgram = programSet->getCpuProgram(GPT_FRAGMENT_PROGRAM); psProgram->addDependency(SGX_LIB_INTEGRATEDPSSM); + psProgram->addDependency("RTSLib_Froxels"); psProgram->addPreprocessorDefines(StringUtil::format("PSSM_NUM_SPLITS=%zu,PCF_XSAMPLES=%.1f,SHADOWLIGHT_COUNT=%d", mShadowTextureParamsList.size(), mPCFxSamples, mMultiLightCount)); @@ -377,6 +379,17 @@ bool IntegratedPSSM3::addPSInvocation(Program* psProgram, const int groupOrder) stage.callFunction("SGX_ComputeShadowFactor_PSSM3", params); } + auto aps = ShaderGenerator::getSingleton().getActiveSceneManager()->_getAutoParamDataSource(); + const_cast(aps)->updateFroxelData(); + auto froxelData = GpuProgramManager::getSingleton().getSharedParameters("OgreFroxels"); + psProgram->addSharedParameters(froxelData); + + auto sceneCol = psProgram->resolveParameter(GpuProgramParameters::ACT_DERIVED_SCENE_COLOUR); + auto froxelTP = psProgram->resolveParameter(GpuProgramParameters::ACT_FROXEL_TILE_PARAMS); + auto froxelDP = psProgram->resolveParameter(GpuProgramParameters::ACT_FROXEL_DEPTH_PARAMS); + auto froxelGrid = ParameterFactory::createSharedParam(froxelData, "froxelGrid"); + stage.callFunction("debugFroxelOccupancy", {In(mPSInDepth).xyz(), In(froxelTP), In(froxelDP), In(froxelGrid), InOut(sceneCol)}); + // shadow factor is applied by lighting stages return true; } diff --git a/Media/RTShaderLib/RTSLib_Froxels.glsl b/Media/RTShaderLib/RTSLib_Froxels.glsl new file mode 100644 index 00000000000..780693b797e --- /dev/null +++ b/Media/RTShaderLib/RTSLib_Froxels.glsl @@ -0,0 +1,104 @@ +#define FROXEL_GRID_VEC4_COUNT 1024 // 4096 froxels / 4 per uvec4 +#define FROXEL_RECORD_VEC4_COUNT 1024 // 16384 light indices / 16 per uvec4 +#if 0 +layout(std140) uniform OgreFroxels { + uvec4 froxelGrid[FROXEL_GRID_VEC4_COUNT]; // (offset << 8) | count + uvec4 froxelRecords[FROXEL_RECORD_VEC4_COUNT]; +}; +#endif + +void debugFroxel(inout vec3 color, int index) +{ + const vec3 debugColors[17] = vec3[]( + vec3(0.0, 0.0, 0.0), // black + vec3(0.0, 0.0, 0.1647), // darkest blue + vec3(0.0, 0.0, 0.3647), // darker blue + vec3(0.0, 0.0, 0.6647), // dark blue + vec3(0.0, 0.0, 0.9647), // blue + vec3(0.0, 0.9255, 0.9255), // cyan + vec3(0.0, 0.5647, 0.0), // dark green + vec3(0.0, 0.7843, 0.0), // green + vec3(1.0, 1.0, 0.0), // yellow + vec3(0.90588, 0.75294, 0.0), // yellow-orange + vec3(1.0, 0.5647, 0.0), // orange + vec3(1.0, 0.0, 0.0), // bright red + vec3(0.8392, 0.0, 0.0), // red + vec3(1.0, 0.0, 1.0), // magenta + vec3(0.6, 0.3333, 0.7882), // purple + vec3(1.0, 1.0, 1.0), // white + vec3(1.0, 1.0, 1.0) // white + ); + color = mix(color, debugColors[clamp(index, 0, 16)], 0.8); +} + +#define DEBUG_FROXELS 1 + +// fragCoord = gl_FragCoord.xyz: .xy in pixels, .z = screen depth +// froxel_params = (countX, countY, countZ, tileSizePx) +// froxel_z_params = (scaleZ, biasZ, linZ, sliceCount) +uint getFroxelIndex(in vec3 fragCoord, in vec4 froxel_params, in vec4 froxel_z_params +#if DEBUG_FROXELS + , inout vec4 color +#endif +) { + uint x = min(uint(fragCoord.x / froxel_params.w), uint(froxel_params.x) - 1u); + uint y = min(uint(fragCoord.y / froxel_params.w), uint(froxel_params.y) - 1u); + +#if DEBUG_FROXELS == 1 + uint tile = y * uint(froxel_params.x) + x; + int idx = int(tile % 16); + + debugFroxel(color.rgb, idx); +#endif + + float depth = fragCoord.z; +#if OGRE_REVERSED_Z + depth = 1.0 - depth; // reversed-z is exactly 1 - standard depth for perspective +#endif + + // screen depth -> reciprocal normalized view-Z (linear in depth) + float recipViewZ = froxel_z_params.x * depth + froxel_z_params.y; + recipViewZ = max(recipViewZ, 1e-6); // guard: far plane -> log2(0) + float sliceCount = froxel_z_params.w; + float sliceZ = log2(recipViewZ) * froxel_z_params.z; // linearizerNeg is negative + uint z = uint(clamp(sliceZ + sliceCount, 0.0, sliceCount - 1.0)); + +#if DEBUG_FROXELS == 2 + debugFroxel(color.rgb, int(z)); +#endif + + return (z * uint(froxel_params.y) + y) * uint(froxel_params.x) + x; +} + +#define FROXEL_MAX_OCCUPANCY 2 +void debugFroxelOccupancy(vec3 fragCoord, in vec4 froxel_params, in vec4 froxel_z_params,in uvec4 froxelGrid[FROXEL_GRID_VEC4_COUNT], inout vec4 color) +{ + vec4 dummy = vec4(0.0); + uint f = getFroxelIndex(fragCoord, froxel_params, froxel_z_params, dummy); + uint grid = froxelGrid[f >> 2][f & 3u]; + uint count = grid & 0xFFu; + + if (count == 0u) + return; + + float t = saturate(float(count) / FROXEL_MAX_OCCUPANCY); + color = mix(color, vec4(1.0, 0.0, 0.0, 0.0), t); +} + +uint getLightIndex(uint record, in uvec4 froxelRecords[FROXEL_RECORD_VEC4_COUNT]) { + uint word = froxelRecords[record >> 4][(record >> 2) & 3u]; // which uint + return (word >> ((record & 3u) * 8u)) & 0xFFu; // which byte +} + +#if !DEBUG_FROXELS +void shadeClustered(vec3 fragCoord, in uvec4 froxelGrid[FROXEL_GRID_VEC4_COUNT], in uvec4 froxelRecords[FROXEL_RECORD_VEC4_COUNT], in vec4 froxel_params, in vec4 froxel_z_params) { + uint f = getFroxelIndex(fragCoord, froxel_params, froxel_z_params); + uint grid = froxelGrid[f >> 2][f & 3u]; + uint offset = grid >> 8; + uint count = grid & 0xFFu; + + for (uint i = 0u; i < count; ++i) { + uint lightIndex = getLightIndex(offset + i, froxelRecords); + } +} +#endif \ No newline at end of file diff --git a/OgreMain/include/OgreAutoParamDataSource.h b/OgreMain/include/OgreAutoParamDataSource.h index 3dc4b3bcf5a..cb0c889cb6f 100644 --- a/OgreMain/include/OgreAutoParamDataSource.h +++ b/OgreMain/include/OgreAutoParamDataSource.h @@ -37,6 +37,7 @@ namespace Ogre { // forward decls struct VisibleObjectsBoundsInfo; + struct Froxelizer; /** \addtogroup Core * @{ @@ -120,7 +121,10 @@ namespace Ogre { mutable bool mSceneDepthRangeDirty; mutable bool mLodCameraPositionDirty; mutable bool mLodCameraPositionObjectSpaceDirty; + mutable bool mFroxelStructureDirty; + std::unique_ptr mFroxelizer; + GpuSharedParametersPtr mFroxelParams; const Renderable* mCurrentRenderable; const Camera* mCurrentCamera; std::vector mCameraArray; @@ -144,6 +148,7 @@ namespace Ogre { bool mCurrentUseIdentityProj; public: AutoParamDataSource(); + ~AutoParamDataSource(); /** Updates the current renderable */ void setCurrentRenderable(const Renderable* rend); /** Sets the world matrices, avoid query from renderable again */ @@ -298,6 +303,11 @@ namespace Ogre { uint16 getGpuParamsDirty() const { return mGpuParamsDirty; } void resetGpuParamsDirty() { mGpuParamsDirty = 0; } void updateLightCustomGpuParameter(const GpuProgramParameters::AutoConstantEntry& constantEntry, GpuProgramParameters *params) const; + + const Vector4f& getFroxelTileParams() const; + const Vector4f& getFroxelDepthParams() const; + // Updates the froxel data in the shared parameters + void updateFroxelData(); }; /** @} */ /** @} */ diff --git a/OgreMain/include/OgreGpuProgramParams.h b/OgreMain/include/OgreGpuProgramParams.h index 3a995373247..b51e682b672 100644 --- a/OgreMain/include/OgreGpuProgramParams.h +++ b/OgreMain/include/OgreGpuProgramParams.h @@ -1146,6 +1146,12 @@ namespace Ogre { ACT_POINT_PARAMS, /// the LOD index as selected by the active LodStrategy ACT_MATERIAL_LOD_INDEX, + /// Clustered (froxel) lighting grid size parameters + /// packed as `(countX, countY, yFix, tileSizePx)` + ACT_FROXEL_TILE_PARAMS, + /// Clustered (froxel) lighting depth parameters + /// packed as `(scaleZ, biasZ, linZ, sliceCount)` + ACT_FROXEL_DEPTH_PARAMS, }; /** Defines the type of the extra data item used by the auto constant. diff --git a/OgreMain/src/OgreAutoParamDataSource.cpp b/OgreMain/src/OgreAutoParamDataSource.cpp index 92b2e6bea69..f57acf8ca1c 100644 --- a/OgreMain/src/OgreAutoParamDataSource.cpp +++ b/OgreMain/src/OgreAutoParamDataSource.cpp @@ -31,6 +31,9 @@ THE SOFTWARE. #include "OgreRenderable.h" #include "OgreControllerManager.h" #include "OgreViewport.h" +#include "OgreFroxelizer.h" + +#include "OgreGpuProgramManager.h" namespace Ogre { //----------------------------------------------------------------------------- @@ -56,6 +59,7 @@ namespace Ogre { mSceneDepthRangeDirty(true), mLodCameraPositionDirty(true), mLodCameraPositionObjectSpaceDirty(true), + mFroxelStructureDirty(true), mCurrentRenderable(0), mCurrentCamera(0), mCameraRelativeRendering(false), @@ -75,6 +79,8 @@ namespace Ogre { mBlankLight.setSpecularColour(ColourValue::Black); mBlankLight.setAttenuation(0,1,0,0); mDummyNode.attachObject(&mBlankLight); + mFroxelizer = std::make_unique(); + for(size_t i = 0; i < OGRE_MAX_SIMULTANEOUS_LIGHTS; ++i) { mTextureViewProjMatrixDirty[i] = true; @@ -86,6 +92,7 @@ namespace Ogre { } } + AutoParamDataSource::~AutoParamDataSource() = default; //----------------------------------------------------------------------------- const Camera* AutoParamDataSource::getCurrentCamera() const { @@ -163,6 +170,7 @@ namespace Ogre { mCameraPositionDirty = true; mLodCameraPositionObjectSpaceDirty = true; mLodCameraPositionDirty = true; + mFroxelStructureDirty = true; } void AutoParamDataSource::setCameraArray(const std::vector cameras) { @@ -194,6 +202,7 @@ namespace Ogre { mSpotlightWorldViewProjMatrixDirty[i] = true; } + mFroxelStructureDirty = true; } //--------------------------------------------------------------------- float AutoParamDataSource::getLightNumber(size_t index) const @@ -1290,5 +1299,69 @@ namespace Ogre { } } + const Vector4f& AutoParamDataSource::getFroxelTileParams() const + { + if(mFroxelStructureDirty) + { + mFroxelizer->update(mCurrentCamera, mCurrentViewport, *mCurrentLightList); + mFroxelStructureDirty = false; + } + + return mFroxelizer->getTileParams(); + } + + const Vector4f& AutoParamDataSource::getFroxelDepthParams() const + { + if(mFroxelStructureDirty) + { + mFroxelizer->update(mCurrentCamera, mCurrentViewport, *mCurrentLightList); + mFroxelStructureDirty = false; + } + + return mFroxelizer->getDepthParams(); + } + + void AutoParamDataSource::updateFroxelData() + { + if (mFroxelStructureDirty) + { + mFroxelizer->update(mCurrentCamera, mCurrentViewport, *mCurrentLightList); + mFroxelStructureDirty = false; + } + + const auto& grid = mFroxelizer->getGrid(); + const auto& records = mFroxelizer->getRecords(); + + OgreAssert(grid.size() == Froxelizer::MAX_FROXELS, "Assuming a 16x16x16 froxel grid for now"); + OgreAssert(records.size() <= Froxelizer::MAX_FROXEL_RECORDS, "max of 16384 froxel records for now"); + + auto& mgr = GpuProgramManager::getSingleton(); + if(!mFroxelParams) + { + if(mgr.getAvailableSharedParameters().count("OgreFroxels") == 0) + { + mFroxelParams = mgr.createSharedParameters("OgreFroxels"); + // packed as uvec4 + mFroxelParams->addConstantDefinition("froxelGrid", GCT_UINT4, Froxelizer::MAX_FROXELS/4); + // 4 light bytes per uint, 16 per uvec4 + mFroxelParams->addConstantDefinition("froxelRecords", GCT_UINT4, Froxelizer::MAX_FROXEL_RECORDS/16); + } + else + { + mFroxelParams = mgr.getSharedParameters("OgreFroxels"); + } + } + + // Packs 16 light indices into one uvec4 + const uint32 recordWords = (std::max(records.size(), 1u) + 3) / 4; + + std::vector recordData(recordWords, 0); + for (uint32 i = 0; i < records.size(); ++i) + recordData[i >> 2] |= uint32(records[i]) << ((i & 3u) * 8u); + + // Upload grid (packed offset << 8 | count) + mFroxelParams->setNamedConstant("froxelGrid", grid.data(), grid.size()); + mFroxelParams->setNamedConstant("froxelRecords", recordData.data(), recordData.size()); + } } diff --git a/OgreMain/src/OgreFroxelizer.cpp b/OgreMain/src/OgreFroxelizer.cpp new file mode 100644 index 00000000000..7774b064dd2 --- /dev/null +++ b/OgreMain/src/OgreFroxelizer.cpp @@ -0,0 +1,180 @@ +#include "OgreStableHeaders.h" + +#include "OgreFroxelizer.h" +#include "OgreViewport.h" + +namespace Ogre +{ +/// Compute the froxel layout (XY tile count + tile size) for a given viewport and buffer budget +static Vector4f computeFroxelLayout(const Viewport* viewport, int sliceCount, uint32 bufferEntryCount) +{ + int width = std::max(1, viewport->getActualWidth()); + int height = std::max(1, viewport->getActualHeight()); + sliceCount = std::max(2, sliceCount); + + // Number of froxels in the XY plane; the Z slices consume buffer entries: + // froxelPlaneCount = bufferEntryCount / sliceCount + const size_t froxelPlaneCount = bufferEntryCount / sliceCount; + + // Goal: countX * countY <= froxelPlaneCount with near-square froxels: + // countX / countY ~= aspect => countY <= sqrt(froxelPlaneCount / aspect) + const float aspect = float(width) / float(height); + + uint32 countYTmp = uint32(std::sqrt(double(froxelPlaneCount) / double(aspect))); + countYTmp = std::max(1u, countYTmp); + uint32 countXTmp = std::max(1u, uint32(froxelPlaneCount) / countYTmp); + + // Square froxel edge length in pixels (round up to the larger ratio so the + // resulting tile count never exceeds the budget) + uint32 tileSizePx = uint32(std::ceil(std::max(float(width) / float(countXTmp), float(height) / float(countYTmp)))); + tileSizePx = std::max(1u, tileSizePx); + + // Final tile count derived from the final tile size + uint32 countX = (width + tileSizePx - 1) / tileSizePx; + uint32 countY = (height + tileSizePx - 1) / tileSizePx; + + const RenderTarget* rt = viewport->getTarget(); + const float yFix = rt->requiresTextureFlipping() ? float(height) : -1.0f; + + return Vector4f(countX, countY, yFix, tileSizePx); +} + +/// Compute the logarithmic view-space Z slicing parameters used for froxel lighting. +static Vector4f getFroxelZParams(const Frustum* frustum, uint32 sliceCount) +{ + sliceCount = std::max(2u, sliceCount); + + float n = frustum->getNearClipDistance(); + float f = frustum->getBoundingRadius(); // always finite! + + // always standard depth [0,1]; shader flips depth itself for reversed-z + const float scaleZ = -f * (f - n) / (f * n); + const float biasZ = f / n; + const float linZ = -(float)sliceCount / std::log2(f / n); // negative + + return Vector4f(scaleZ, biasZ, linZ, sliceCount); +} + +//----------------------------------------------------------------------------- +void Froxelizer::rebuildLayout(const Camera* cam, const Viewport* viewport) +{ + mTileParams = computeFroxelLayout(viewport, MAX_FROXEL_SLICES, MAX_FROXELS); + mDepthParams = getFroxelZParams(cam, MAX_FROXEL_SLICES); + + mLastWidth = viewport->getActualWidth(); + mLastHeight = viewport->getActualHeight(); +} + +//----------------------------------------------------------------------------- +void Froxelizer::binLights(const Camera* cam, const LightList& lights) +{ + auto cx = int(mTileParams[0]), cy = int(mTileParams[1]), cz = int(mDepthParams[3]); + const uint32 froxelCount = uint32(cx * cy * cz); + + mGrid.assign(MAX_FROXELS, 0); + mRecords.clear(); + + if (lights.empty() || froxelCount == 0) + return; + + mFroxelLights.resize(froxelCount); + for (auto& bucket : mFroxelLights) + bucket.clear(); + + const Affine3& view = cam->getViewMatrix(); + + // SAME sources as getFroxelZParams() -> consistent slicing with the shader + const float zNear = cam->getNearClipDistance(); + const float zFar = cam->getBoundingRadius(); // finite + const float invLogFN = 1.0f / std::log2(zFar / zNear); + + // inverse of shader: slice = sliceCount * log2(L/n) / log2(f/n) + auto zToSlice = [&](float z) -> int { + z = Math::Clamp(z, zNear, zFar); + float s = float(cz) * std::log2(z / zNear) * invLogFN; + return Math::Clamp(int(std::floor(s)), 0, cz - 1); + }; + + const uint8 lightCount = std::min(lights.size(), MAX_LIGHTS); + for (uint8 li = 0; li < lightCount; ++li) + { + const Light* l = lights[li]; + + // Directional lights cover the whole frustum -> handle outside the cluster grid + if (l->getType() == Light::LT_DIRECTIONAL) + continue; + + const Real radius = l->getAttenuationRange(); + const Vector3 posWorld = l->getDerivedPosition(); + const Vector3 centerVS = view * posWorld; // view space (looking down -Z) + + // --- Z slice range from positive linear depth --- + float zMinLin = -centerVS.z - radius; + float zMaxLin = -centerVS.z + radius; + if (zMaxLin <= zNear) + continue; // entirely behind the near plane + + int sliceMin = zToSlice(zMinLin); + int sliceMax = zToSlice(zMaxLin); + + // --- Screen-space XY tile range via sphere projection --- + Real nl, nt, nr, nb; // NDC [-1,1], y up + cam->projectSphere(Sphere(posWorld, radius), &nl, &nt, &nr, &nb); + + // NDC -> tile coords (flip Y so tile 0 is at the top) + int tileXMin = int(std::floor((nl * 0.5f + 0.5f) * cx)); + int tileXMax = int(std::floor((nr * 0.5f + 0.5f) * cx)); + int tileYMin = int(std::floor((-nt * 0.5f + 0.5f) * cy)); + int tileYMax = int(std::floor((-nb * 0.5f + 0.5f) * cy)); + if (tileXMin > tileXMax) std::swap(tileXMin, tileXMax); + if (tileYMin > tileYMax) std::swap(tileYMin, tileYMax); + + // Reject lights entirely outside the screen boundaries + if (tileXMax < 0 || tileXMin >= cx || tileYMax < 0 || tileYMin >= cy) + continue; + + tileXMin = Math::Clamp(tileXMin, 0, cx - 1); + tileXMax = Math::Clamp(tileXMax, 0, cx - 1); + tileYMin = Math::Clamp(tileYMin, 0, cy - 1); + tileYMax = Math::Clamp(tileYMax, 0, cy - 1); + + // --- Assign to the froxels inside the conservative AABB --- + for (int z = sliceMin; z <= sliceMax; ++z) + for (int y = tileYMin; y <= tileYMax; ++y) + for (int x = tileXMin; x <= tileXMax; ++x) + { + const uint32 idx = (z * cy + y) * cx + x; + auto& bucket = mFroxelLights[idx]; + if (bucket.size() < MAX_LIGHTS) // count is stored in 8 bits + bucket.push_back(li); + } + } + + // --- Flatten into grid (offset << 8 | count) + records --- + for (uint32 f = 0; f < froxelCount; ++f) + { + uint32 offset = uint32(mRecords.size()); + uint32 count = uint32(mFroxelLights[f].size()); + if (mRecords.size() + count > MAX_FROXEL_RECORDS) + count = uint32(MAX_FROXEL_RECORDS - mRecords.size()); + + mGrid[f] = (offset << 8) | (count & 0xFFu); + mRecords.insert(mRecords.end(), mFroxelLights[f].begin(), mFroxelLights[f].begin() + count); + + if (mRecords.size() >= MAX_FROXEL_RECORDS) + break; + } +} + +//----------------------------------------------------------------------------- +void Froxelizer::update(const Camera* cam, const Viewport* viewport, const LightList& lights) +{ + bool layoutChanged = viewport->getActualWidth() != mLastWidth || viewport->getActualHeight() != mLastHeight; + + if (layoutChanged) + rebuildLayout(cam, viewport); + + binLights(cam, lights); +} + +} // namespace Ogre \ No newline at end of file diff --git a/OgreMain/src/OgreFroxelizer.h b/OgreMain/src/OgreFroxelizer.h new file mode 100644 index 00000000000..cabc914bcb9 --- /dev/null +++ b/OgreMain/src/OgreFroxelizer.h @@ -0,0 +1,58 @@ +#ifndef OGRE_FROXELIZER_H +#define OGRE_FROXELIZER_H + +#include "OgreLight.h" +#include "OgrePrerequisites.h" +#include "OgreVector.h" +#include + +namespace Ogre +{ + +/** Builds a frustum-voxel ("froxel") grid for the active camera and bins + lights into it, in the style of Filament's Froxelizer. The result feeds + two GPU buffers (grid + records) consumed by clustered shading. */ +struct Froxelizer +{ + Froxelizer() = default; + + /// Rebuild the layout and re-bin the lights if needed + void update(const Camera* cam, const Viewport* viewport, const LightList& lights); + + // shader-facing scalars + const Vector4f& getTileParams() const { return mTileParams; } // (countX, countY, yFix, tileSizePx) + const Vector4f& getDepthParams() const { return mDepthParams; } // (scaleZ, biasZ, linZ, sliceCount) + + // GPU buffer data + const std::vector& getGrid() const { return mGrid; } // (offset << 8) | count per froxel + const std::vector& getRecords() const { return mRecords; } // flat light-index list + + enum : uint32 + { + /// 4096 froxels fit in a 16 KiB buffer, the minimum guaranteed in GLES 3.x and Vulkan 1.1 + MAX_FROXELS = 4096, + MAX_FROXEL_SLICES = 16, + /// 16384 light indices (uint8) fit in a 16 KiB buffer + MAX_FROXEL_RECORDS = 16384, + MAX_LIGHTS = 255 ///< @note stored as uint8 in the froxel records + }; + +private: + void rebuildLayout(const Camera* cam, const Viewport* viewport); + void binLights(const Camera* cam, const LightList& lights); + + int mLastWidth = 0, mLastHeight = 0; + + Vector4f mTileParams = Vector4f(0); + Vector4f mDepthParams = Vector4f(0); + // fixed 4096 entries: (offset << 8) | count + std::vector mGrid; + // one byte per light index (max 256 lights) + std::vector mRecords; + // temporary per-froxel light lists + std::vector> mFroxelLights; +}; + +} // namespace Ogre + +#endif \ No newline at end of file diff --git a/OgreMain/src/OgreGpuProgramParams.cpp b/OgreMain/src/OgreGpuProgramParams.cpp index 982d06caff6..c9c87ae4d8a 100644 --- a/OgreMain/src/OgreGpuProgramParams.cpp +++ b/OgreMain/src/OgreGpuProgramParams.cpp @@ -185,6 +185,8 @@ namespace Ogre AutoConstantDefinition(ACT_LIGHT_CUSTOM, "light_custom", 4, ET_REAL, ACDT_INT), AutoConstantDefinition(ACT_POINT_PARAMS, "point_params", 4, ET_REAL, ACDT_NONE), AutoConstantDefinition(ACT_MATERIAL_LOD_INDEX, "material_lod_index", 1, ET_INT, ACDT_NONE), + AutoConstantDefinition(ACT_FROXEL_TILE_PARAMS, "froxel_tile_params", 4, ET_REAL, ACDT_NONE), + AutoConstantDefinition(ACT_FROXEL_DEPTH_PARAMS, "froxel_depth_params", 4, ET_REAL, ACDT_NONE), // NOTE: new auto constants must be added before this line, as the following are merely aliases // to allow legacy world_ names in scripts @@ -661,7 +663,7 @@ namespace Ogre , mActivePassIterationIndex(std::numeric_limits::max()) , mUseLinearColours(false) { - static_assert((sizeof(AutoConstantDictionary) / sizeof(AutoConstantDefinition) - 5) == ACT_MATERIAL_LOD_INDEX, + static_assert((sizeof(AutoConstantDictionary) / sizeof(AutoConstantDefinition) - 5) == ACT_FROXEL_DEPTH_PARAMS, "AutoConstantDictionary out of sync"); } GpuProgramParameters::~GpuProgramParameters() {} @@ -1094,6 +1096,8 @@ namespace Ogre case ACT_SPOTLIGHT_VIEWPROJ_MATRIX: case ACT_SPOTLIGHT_VIEWPROJ_MATRIX_ARRAY: case ACT_LIGHT_CUSTOM: + case ACT_FROXEL_TILE_PARAMS: + case ACT_FROXEL_DEPTH_PARAMS: return (uint16)GPV_LIGHTS; @@ -2091,7 +2095,12 @@ namespace Ogre source->getSpotlightViewProjMatrix(l),ac.elementCount); } break; - + case ACT_FROXEL_TILE_PARAMS: + _writeRawConstant(ac.physicalIndex, source->getFroxelTileParams(), ac.elementCount); + break; + case ACT_FROXEL_DEPTH_PARAMS: + _writeRawConstant(ac.physicalIndex, source->getFroxelDepthParams(), ac.elementCount); + break; default: break; };