// // LightClusters.cpp // // Created by Sam Gateau on 9/7/2016. // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "LightClusters.h" #include #include #include "lightClusters_drawGrid_vert.h" #include "lightClusters_drawGrid_frag.h" //#include "lightClusters_drawClusterFromDepth_vert.h" #include "lightClusters_drawClusterFromDepth_frag.h" #include "lightClusters_drawClusterContent_vert.h" #include "lightClusters_drawClusterContent_frag.h" enum LightClusterGridShader_MapSlot { DEFERRED_BUFFER_LINEAR_DEPTH_UNIT = 0, }; enum LightClusterGridShader_BufferSlot { LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT = 0, DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, CAMERA_CORRECTION_BUFFER_SLOT, LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT, LIGHT_INDEX_GPU_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, }; void FrustumGrid::generateGridPlanes(Planes& xPlanes, Planes& yPlanes, Planes& zPlanes) { xPlanes.resize(dims.x + 1); yPlanes.resize(dims.y + 1); zPlanes.resize(dims.z + 1); float centerY = float(dims.y) * 0.5; float centerX = float(dims.x) * 0.5; for (int z = 0; z < zPlanes.size(); z++) { ivec3 pos(0, 0, z); zPlanes[z] = glm::vec4(0.0f, 0.0f, -1.0f, frustumGrid_clusterPosToEye(pos, vec3(0.0)).z); } for (int x = 0; x < xPlanes.size(); x++) { auto slicePos = frustumGrid_clusterPosToEye(glm::vec3((float)x, centerY, 0.0)); auto sliceDir = glm::normalize(slicePos); xPlanes[x] = glm::vec4(sliceDir.z, 0.0, -sliceDir.x, 0.0); } for (int y = 0; y < yPlanes.size(); y++) { auto slicePos = frustumGrid_clusterPosToEye(glm::vec3(centerX, (float)y, 0.0)); auto sliceDir = glm::normalize(slicePos); yPlanes[y] = glm::vec4(0.0, sliceDir.z, -sliceDir.x, 0.0); } } #include "DeferredLightingEffect.h" const glm::uvec4 LightClusters::MAX_GRID_DIMENSIONS { 16, 16, 15, 16384 }; LightClusters::LightClusters() : _lightIndicesBuffer(std::make_shared()), _clusterGridBuffer(std::make_shared(), gpu::Element::INDEX_INT32), _clusterContentBuffer(std::make_shared(), gpu::Element::INDEX_INT32) { auto dims = _frustumGridBuffer.edit().dims; _frustumGridBuffer.edit().dims = ivec3(0); // make sure we go through the full reset of the dimensionts ion the setDImensions call setDimensions(dims, MAX_GRID_DIMENSIONS.w); } void LightClusters::setDimensions(glm::uvec3 gridDims, uint32_t listBudget) { ivec3 configDimensions; configDimensions.x = std::min(MAX_GRID_DIMENSIONS.x, gridDims.x); configDimensions.y = std::min(MAX_GRID_DIMENSIONS.y, gridDims.y); configDimensions.z = std::min(MAX_GRID_DIMENSIONS.z, gridDims.z); auto configListBudget = std::min(MAX_GRID_DIMENSIONS.w, listBudget); auto& dims = _frustumGridBuffer->dims; if ((dims.x != configDimensions.x) || (dims.y != configDimensions.y) || (dims.z != configDimensions.z)) { _frustumGridBuffer.edit().dims = configDimensions; _frustumGridBuffer.edit().generateGridPlanes(_gridPlanes[0], _gridPlanes[1], _gridPlanes[2]); } auto numClusters = _frustumGridBuffer.edit().frustumGrid_numClusters(); if (numClusters != _numClusters) { _numClusters = numClusters; _clusterGrid.clear(); _clusterGrid.resize(_numClusters, EMPTY_CLUSTER); _clusterGridBuffer._size = _clusterGridBuffer._buffer->resize(_numClusters * sizeof(uint32_t)); } if (configListBudget != _clusterContentBuffer.getNumElements()) { _clusterContent.clear(); _clusterContent.resize(configListBudget, INVALID_LIGHT); _clusterContentBuffer._size = _clusterContentBuffer._buffer->resize(configListBudget * sizeof(LightID)); } } void LightClusters::updateFrustum(const ViewFrustum& frustum) { _frustum = frustum; _frustumGridBuffer.edit().updateFrustum(frustum); } void LightClusters::updateLightStage(const LightStagePointer& lightStage) { _lightStage = lightStage; } void LightClusters::updateLightFrame(const LightStage::Frame& lightFrame, bool points, bool spots) { // start fresh _visibleLightIndices.clear(); // Now gather the lights // gather lights auto& srcPointLights = lightFrame._pointLights; auto& srcSpotLights = lightFrame._spotLights; int numPointLights = (int)srcPointLights.size(); // int offsetPointLights = 0; int numSpotLights = (int)srcSpotLights.size(); // int offsetSpotLights = numPointLights; _visibleLightIndices.resize(numPointLights + numSpotLights + 1); _visibleLightIndices[0] = 0; if (points && !srcPointLights.empty()) { memcpy(_visibleLightIndices.data() + (_visibleLightIndices[0] + 1), srcPointLights.data(), srcPointLights.size() * sizeof(int)); _visibleLightIndices[0] += (int)srcPointLights.size(); } if (spots && !srcSpotLights.empty()) { memcpy(_visibleLightIndices.data() + (_visibleLightIndices[0] + 1), srcSpotLights.data(), srcSpotLights.size() * sizeof(int)); _visibleLightIndices[0] += (int)srcSpotLights.size(); } _lightIndicesBuffer._buffer->setData(_visibleLightIndices.size() * sizeof(int), (const gpu::Byte*) _visibleLightIndices.data()); _lightIndicesBuffer._size = _visibleLightIndices.size() * sizeof(int); } glm::vec4 projectToPlane(glm::vec4& sphere, const glm::vec4& plane) { float distance = sphere.x * plane.x + sphere.y *plane.y + sphere.z * plane.z + plane.w; if (distance < sphere.w) { return glm::vec4(sphere.x - distance * plane.x, sphere.y - distance * plane.y, sphere.z - distance * plane.z, sqrt(sphere.w * sphere.w - distance * distance)); } else { return sphere; } } bool scanLightVolume(const FrustumGrid& grid, const FrustumGrid::Planes planes[3], int zMin, int zMax, int yMin, int yMax, int xMin, int xMax, LightClusters::LightID lightId, const glm::vec4& eyePosRadius, uint32_t& numClustersTouched, uint32_t maxNumIndices, std::vector< std::vector>& clusterGrid) { glm::ivec3 gridPosToOffset(1, grid.dims.x, grid.dims.x * grid.dims.y); int center_z = (zMax + zMin) >> 1; int center_y = (yMax + yMin) >> 1; bool hasBudget = true; for (auto z = zMin; (z <= zMax) && hasBudget; z++) { auto zSphere = eyePosRadius; if (z != center_z) { auto& plane = (z < center_z) ? planes[2][z + 1] : -planes[2][z]; zSphere = projectToPlane(zSphere, plane); } for (auto y = yMin; (y <= yMax) && hasBudget; y++) { auto ySphere = zSphere; if (y != center_y) { auto& plane = (y < center_y) ? planes[1][y + 1] : -planes[1][y]; ySphere = projectToPlane(ySphere, plane); } auto x = xMin; do { ++x; } while ((x < xMax) && (glm::dot(planes[0][x], glm::vec4(ySphere.x, ySphere.y, ySphere.z, 1.0)) >= ySphere.w)); auto xs = xMax; do { --xs; } while ((xs >= x) && (-glm::dot(planes[0][xs], glm::vec4(ySphere.x, ySphere.y, ySphere.z, 1.0)) >= ySphere.w)); for (--x; (x <= xs) && hasBudget; x++) { auto index = x + gridPosToOffset.y * y + gridPosToOffset.z * z; clusterGrid[index].emplace_back(lightId); numClustersTouched++; if (numClustersTouched >= maxNumIndices) { hasBudget = false; } } } } return hasBudget; } void LightClusters::updateClusters() { // Clean up last info std::vector< std::vector< LightID > > clusterGrid(_numClusters); _clusterGrid.resize(_numClusters, EMPTY_CLUSTER); uint32_t maxNumIndices = (uint32_t) _clusterContent.size(); _clusterContent.resize(maxNumIndices, INVALID_LIGHT); auto theFrustumGrid(_frustumGridBuffer.get()); glm::ivec3 gridPosToOffset(1, theFrustumGrid.dims.x, theFrustumGrid.dims.x * theFrustumGrid.dims.y); uint32_t numClusterTouched = 0; for (size_t lightNum = 1; lightNum < _visibleLightIndices.size(); ++lightNum) { auto lightId = _visibleLightIndices[lightNum]; auto light = _lightStage->getLight(lightId); if (!light) continue; auto worldOri = light->getPosition(); auto radius = light->getMaximumRadius(); // Bring into frustum eye space auto eyeOri = theFrustumGrid.frustumGrid_worldToEye(glm::vec4(worldOri, 1.0f)); // Remove light that slipped through and is not in the z range float eyeZMax = eyeOri.z - radius; if (eyeZMax > -theFrustumGrid.rangeNear) { continue; } float eyeZMin = eyeOri.z + radius; if (eyeZMin < -theFrustumGrid.rangeFar) { continue; } // Get z slices int zMin = theFrustumGrid.frustumGrid_eyeDepthToClusterLayer(eyeZMin); int zMax = theFrustumGrid.frustumGrid_eyeDepthToClusterLayer(eyeZMax); // That should never happen if (zMin == -1 && zMax == -1) { continue; } // is it a light whose origin is behind the near ? bool behindLight = (eyeOri.z >= -theFrustumGrid.rangeNear); // float eyeOriLen2 = glm::length2(eyeOri); // CLamp the z range zMin = std::max(0, zMin); // find 2D corners of the sphere in grid int xMin { 0 }; int xMax { theFrustumGrid.dims.x - 1 }; int yMin { 0 }; int yMax { theFrustumGrid.dims.y - 1 }; float radius2 = radius * radius; auto eyeOriH = eyeOri; auto eyeOriV = eyeOri; eyeOriH.y = 0.0f; eyeOriV.x = 0.0f; float eyeOriLen2H = glm::length2(eyeOriH); float eyeOriLen2V = glm::length2(eyeOriV); if ((eyeOriLen2H > radius2)) { float eyeOriLenH = sqrt(eyeOriLen2H); auto eyeOriDirH = glm::vec3(eyeOriH) / eyeOriLenH; float eyeToTangentCircleLenH = sqrt(eyeOriLen2H - radius2); float eyeToTangentCircleCosH = eyeToTangentCircleLenH / eyeOriLenH; float eyeToTangentCircleSinH = radius / eyeOriLenH; // rotate the eyeToOriDir (H & V) in both directions glm::vec3 leftDir(eyeOriDirH.x * eyeToTangentCircleCosH + eyeOriDirH.z * eyeToTangentCircleSinH, 0.0f, eyeOriDirH.x * -eyeToTangentCircleSinH + eyeOriDirH.z * eyeToTangentCircleCosH); glm::vec3 rightDir(eyeOriDirH.x * eyeToTangentCircleCosH - eyeOriDirH.z * eyeToTangentCircleSinH, 0.0f, eyeOriDirH.x * eyeToTangentCircleSinH + eyeOriDirH.z * eyeToTangentCircleCosH); auto lc = theFrustumGrid.frustumGrid_eyeToClusterDirH(leftDir); auto rc = theFrustumGrid.frustumGrid_eyeToClusterDirH(rightDir); xMin = std::max(xMin, lc); xMax = std::max(0, std::min(rc, xMax)); } if ((eyeOriLen2V > radius2)) { float eyeOriLenV = sqrt(eyeOriLen2V); auto eyeOriDirV = glm::vec3(eyeOriV) / eyeOriLenV; float eyeToTangentCircleLenV = sqrt(eyeOriLen2V - radius2); float eyeToTangentCircleCosV = eyeToTangentCircleLenV / eyeOriLenV; float eyeToTangentCircleSinV = radius / eyeOriLenV; // rotate the eyeToOriDir (H & V) in both directions glm::vec3 bottomDir(0.0f, eyeOriDirV.y * eyeToTangentCircleCosV + eyeOriDirV.z * eyeToTangentCircleSinV, eyeOriDirV.y * -eyeToTangentCircleSinV + eyeOriDirV.z * eyeToTangentCircleCosV); glm::vec3 topDir(0.0f, eyeOriDirV.y * eyeToTangentCircleCosV - eyeOriDirV.z * eyeToTangentCircleSinV, eyeOriDirV.y * eyeToTangentCircleSinV + eyeOriDirV.z * eyeToTangentCircleCosV); auto bc = theFrustumGrid.frustumGrid_eyeToClusterDirV(bottomDir); auto tc = theFrustumGrid.frustumGrid_eyeToClusterDirV(topDir); yMin = std::max(yMin, bc); yMax = std::max(yMin, std::min(tc, yMax)); } // now voxelize // bool hasBudget = scanLightVolume(theFrustumGrid, _gridPlanes, zMin, zMax, yMin, yMax, xMin, xMax, lightId, glm::vec4(glm::vec3(eyeOri), radius), numClusterTouched, maxNumIndices, clusterGrid); bool hasBudget = true; for (auto z = zMin; (z <= zMax) && hasBudget; z++) { for (auto y = yMin; (y <= yMax) && hasBudget; y++) { for (auto x = xMin; (x <= xMax) && hasBudget; x++) { auto index = x + gridPosToOffset.y * y + gridPosToOffset.z * z; clusterGrid[index].emplace_back(lightId); numClusterTouched++; if (numClusterTouched >= maxNumIndices) { hasBudget = false; } } } } if (!hasBudget) { break; } } // Lights have been gathered now reexpress in terms of 2 sequential buffers uint16_t indexOffset = 0; for (int i = 0; i < clusterGrid.size(); i++) { auto& cluster = clusterGrid[i]; uint16_t numLights = ((uint16_t)cluster.size()); uint16_t offset = indexOffset; _clusterGrid[i] = (uint32_t)((numLights << 16) | offset); if (numLights) { memcpy(_clusterContent.data() + indexOffset, cluster.data(), numLights * sizeof(LightID)); } indexOffset += numLights; } // update the buffers _clusterGridBuffer._buffer->setData(_clusterGridBuffer._size, (gpu::Byte*) _clusterGrid.data()); _clusterContentBuffer._buffer->setSubData(0, indexOffset * sizeof(LightID), (gpu::Byte*) _clusterContent.data()); } LightClusteringPass::LightClusteringPass() { } void LightClusteringPass::configure(const Config& config) { if (_lightClusters) { if (_lightClusters->_frustumGridBuffer->rangeNear != config.rangeNear) { _lightClusters->_frustumGridBuffer.edit().rangeNear = config.rangeNear; } if (_lightClusters->_frustumGridBuffer->rangeFar != config.rangeFar) { _lightClusters->_frustumGridBuffer.edit().rangeFar = config.rangeFar; } _lightClusters->setDimensions(glm::uvec3(config.dimX, config.dimY, config.dimZ), 10000); } _freeze = config.freeze; } void LightClusteringPass::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { auto args = renderContext->args; auto deferredTransform = inputs.get0(); auto lightingModel = inputs.get1(); auto surfaceGeometryFramebuffer = inputs.get2(); if (!_lightClusters) { _lightClusters = std::make_shared(); } // first update the Grid with the new frustum if (!_freeze) { _lightClusters->updateFrustum(args->getViewFrustum()); } // From the LightStage and the current frame, update the light cluster Grid auto deferredLightingEffect = DependencyManager::get(); auto lightStage = deferredLightingEffect->getLightStage(); _lightClusters->updateLightStage(lightStage); _lightClusters->updateLightFrame(lightStage->_currentFrame, lightingModel->isPointLightEnabled(), lightingModel->isSpotLightEnabled()); _lightClusters->updateClusters(); output = _lightClusters; } DebugLightClusters::DebugLightClusters() { } void DebugLightClusters::configure(const Config& config) { doDrawGrid = config.doDrawGrid; doDrawClusterFromDepth = config.doDrawClusterFromDepth; doDrawContent = config.doDrawContent; } const gpu::PipelinePointer DebugLightClusters::getDrawClusterGridPipeline() { if (!_drawClusterGrid) { auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert)); auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawGrid_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); auto state = std::make_shared(); state->setDepthTest(true, false, gpu::LESS_EQUAL); // Blend on transparent state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); // Good to go add the brand new pipeline _drawClusterGrid = gpu::Pipeline::create(program, state); } return _drawClusterGrid; } const gpu::PipelinePointer DebugLightClusters::getDrawClusterFromDepthPipeline() { if (!_drawClusterFromDepth) { // auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert)); auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawClusterFromDepth_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT)); slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), CAMERA_CORRECTION_BUFFER_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); auto state = std::make_shared(); // state->setDepthTest(true, false, gpu::LESS_EQUAL); // Blend on transparent state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); // Good to go add the brand new pipeline _drawClusterFromDepth = gpu::Pipeline::create(program, state); } return _drawClusterFromDepth; } const gpu::PipelinePointer DebugLightClusters::getDrawClusterContentPipeline() { if (!_drawClusterContent) { auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawClusterContent_vert)); auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawClusterContent_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); auto state = std::make_shared(); state->setDepthTest(true, false, gpu::LESS_EQUAL); // Blend on transparent state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); // Good to go add the brand new pipeline _drawClusterContent = gpu::Pipeline::create(program, state); } return _drawClusterContent; } void DebugLightClusters::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs) { auto deferredTransform = inputs.get0(); auto deferredFramebuffer = inputs.get1(); auto lightingModel = inputs.get2(); auto linearDepthTarget = inputs.get3(); auto lightClusters = inputs.get4(); auto args = renderContext->args; gpu::Batch batch; // Assign the camera transform batch.setViewportTransform(args->_viewport); glm::mat4 projMat; Transform viewMat; args->getViewFrustum().evalProjectionMatrix(projMat); args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat, true); // Then the actual ClusterGrid attributes batch.setModelTransform(Transform()); batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, lightClusters->_frustumGridBuffer); batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, lightClusters->_clusterGridBuffer); batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, lightClusters->_clusterContentBuffer); if (doDrawClusterFromDepth) { batch.setPipeline(getDrawClusterFromDepthPipeline()); batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, deferredTransform->getFrameTransformBuffer()); if (linearDepthTarget) { batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, linearDepthTarget->getLinearDepthTexture()); } batch.draw(gpu::TRIANGLE_STRIP, 4, 0); // Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, nullptr); batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, nullptr); } if (doDrawContent) { // bind the one gpu::Pipeline we need batch.setPipeline(getDrawClusterContentPipeline()); auto dims = lightClusters->_frustumGridBuffer->dims; glm::ivec3 summedDims(dims.x*dims.y * dims.z, dims.x*dims.y, dims.x); batch.drawInstanced(summedDims.x, gpu::LINES, 24, 0); } if (doDrawGrid) { // bind the one gpu::Pipeline we need batch.setPipeline(getDrawClusterGridPipeline()); auto dims = lightClusters->_frustumGridBuffer->dims; glm::ivec3 summedDims(dims.x*dims.y * dims.z, dims.x*dims.y, dims.x); batch.drawInstanced(summedDims.x, gpu::LINES, 24, 0); } args->_context->appendFrameBatch(batch); }