// // DeferredLightingEffect.cpp // interface/src/renderer // // Created by Andrzej Kapolka on 9/11/14. // Copyright 2014 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 "DeferredLightingEffect.h" #include #include #include #include #include #include #include "AbstractViewStateInterface.h" #include "GeometryCache.h" #include "TextureCache.h" #include "FramebufferCache.h" #include "simple_vert.h" #include "simple_textured_frag.h" #include "simple_textured_emisive_frag.h" #include "deferred_light_vert.h" #include "deferred_light_limited_vert.h" #include "deferred_light_spot_vert.h" #include "directional_light_frag.h" #include "directional_light_shadow_map_frag.h" #include "directional_light_cascaded_shadow_map_frag.h" #include "directional_ambient_light_frag.h" #include "directional_ambient_light_shadow_map_frag.h" #include "directional_ambient_light_cascaded_shadow_map_frag.h" #include "directional_skybox_light_frag.h" #include "directional_skybox_light_shadow_map_frag.h" #include "directional_skybox_light_cascaded_shadow_map_frag.h" #include "point_light_frag.h" #include "spot_light_frag.h" static const std::string glowIntensityShaderHandle = "glowIntensity"; struct LightLocations { int shadowDistances; int shadowScale; int radius; int ambientSphere; int lightBufferUnit; int atmosphereBufferUnit; int texcoordMat; int coneParam; int deferredTransformBuffer; }; static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); gpu::PipelinePointer DeferredLightingEffect::getPipeline(SimpleProgramKey config) { auto it = _simplePrograms.find(config); if (it != _simplePrograms.end()) { return it.value(); } auto state = std::make_shared(); if (config.isCulled()) { state->setCullMode(gpu::State::CULL_BACK); } else { state->setCullMode(gpu::State::CULL_NONE); } state->setDepthTest(true, true, gpu::LESS_EQUAL); if (config.hasDepthBias()) { state->setDepthBias(1.0f); state->setDepthBiasSlopeScale(1.0f); } state->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); gpu::ShaderPointer program = (config.isEmissive()) ? _emissiveShader : _simpleShader; gpu::PipelinePointer pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); _simplePrograms.insert(config, pipeline); return pipeline; } void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(simple_vert))); auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_frag))); auto PSEmissive = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_emisive_frag))); _simpleShader = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); _emissiveShader = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PSEmissive)); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), DeferredLightingEffect::NORMAL_FITTING_MAP_SLOT)); gpu::Shader::makeProgram(*_simpleShader, slotBindings); gpu::Shader::makeProgram(*_emissiveShader, slotBindings); _viewState = viewState; _directionalLightLocations = std::make_shared(); _directionalLightShadowMapLocations = std::make_shared(); _directionalLightCascadedShadowMapLocations = std::make_shared(); _directionalAmbientSphereLightLocations = std::make_shared(); _directionalAmbientSphereLightShadowMapLocations = std::make_shared(); _directionalAmbientSphereLightCascadedShadowMapLocations = std::make_shared(); _directionalSkyboxLightLocations = std::make_shared(); _directionalSkyboxLightShadowMapLocations = std::make_shared(); _directionalSkyboxLightCascadedShadowMapLocations = std::make_shared(); _pointLightLocations = std::make_shared(); _spotLightLocations = std::make_shared(); loadLightProgram(deferred_light_vert, directional_light_frag, false, _directionalLight, _directionalLightLocations); loadLightProgram(deferred_light_vert, directional_light_shadow_map_frag, false, _directionalLightShadowMap, _directionalLightShadowMapLocations); loadLightProgram(deferred_light_vert, directional_light_cascaded_shadow_map_frag, false, _directionalLightCascadedShadowMap, _directionalLightCascadedShadowMapLocations); loadLightProgram(deferred_light_vert, directional_ambient_light_frag, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); loadLightProgram(deferred_light_vert, directional_ambient_light_shadow_map_frag, false, _directionalAmbientSphereLightShadowMap, _directionalAmbientSphereLightShadowMapLocations); loadLightProgram(deferred_light_vert, directional_ambient_light_cascaded_shadow_map_frag, false, _directionalAmbientSphereLightCascadedShadowMap, _directionalAmbientSphereLightCascadedShadowMapLocations); loadLightProgram(deferred_light_vert, directional_skybox_light_frag, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); loadLightProgram(deferred_light_vert, directional_skybox_light_shadow_map_frag, false, _directionalSkyboxLightShadowMap, _directionalSkyboxLightShadowMapLocations); loadLightProgram(deferred_light_vert, directional_skybox_light_cascaded_shadow_map_frag, false, _directionalSkyboxLightCascadedShadowMap, _directionalSkyboxLightCascadedShadowMapLocations); loadLightProgram(deferred_light_limited_vert, point_light_frag, true, _pointLight, _pointLightLocations); loadLightProgram(deferred_light_spot_vert, spot_light_frag, true, _spotLight, _spotLightLocations); { //auto VSFS = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); //auto PSBlit = gpu::StandardShaderLib::getDrawTexturePS(); auto blitProgram = gpu::StandardShaderLib::getProgram(gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS, gpu::StandardShaderLib::getDrawTexturePS); gpu::Shader::makeProgram(*blitProgram); auto blitState = std::make_shared(); blitState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); blitState->setColorWriteMask(true, true, true, false); _blitLightBuffer = gpu::PipelinePointer(gpu::Pipeline::create(blitProgram, blitState)); } // Allocate a global light representing the Global Directional light casting shadow (the sun) and the ambient light _globalLights.push_back(0); _allocatedLights.push_back(std::make_shared()); model::LightPointer lp = _allocatedLights[0]; lp->setDirection(-glm::vec3(1.0f, 1.0f, 1.0f)); lp->setColor(glm::vec3(1.0f)); lp->setIntensity(1.0f); lp->setType(model::Light::SUN); lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(_ambientLightMode % gpu::SphericalHarmonics::NUM_PRESET)); } void DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch, bool textured, bool culled, bool emmisive, bool depthBias) { SimpleProgramKey config{textured, culled, emmisive, depthBias}; batch.setPipeline(getPipeline(config)); gpu::ShaderPointer program = (config.isEmissive()) ? _emissiveShader : _simpleShader; int glowIntensity = program->getUniforms().findLocation("glowIntensity"); batch._glUniform1f(glowIntensity, 1.0f); if (!config.isTextured()) { // If it is not textured, bind white texture and keep using textured pipeline batch.setResourceTexture(0, DependencyManager::get()->getWhiteTexture()); } batch.setResourceTexture(NORMAL_FITTING_MAP_SLOT, DependencyManager::get()->getNormalFittingTexture()); } void DeferredLightingEffect::renderSolidSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderSphere(batch, radius, slices, stacks, color); } void DeferredLightingEffect::renderWireSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderSphere(batch, radius, slices, stacks, color, false); } void DeferredLightingEffect::renderSolidCube(gpu::Batch& batch, float size, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderSolidCube(batch, size, color); } void DeferredLightingEffect::renderWireCube(gpu::Batch& batch, float size, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderWireCube(batch, size, color); } void DeferredLightingEffect::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, const glm::vec3& maxCorner, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, color); } void DeferredLightingEffect::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, const glm::vec4& color1, const glm::vec4& color2) { bindSimpleProgram(batch); DependencyManager::get()->renderLine(batch, p1, p2, color1, color2); } void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color, float intensity) { addSpotLight(position, radius, color, intensity); } void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color, float intensity, const glm::quat& orientation, float exponent, float cutoff) { unsigned int lightID = _pointLights.size() + _spotLights.size() + _globalLights.size(); if (lightID >= _allocatedLights.size()) { _allocatedLights.push_back(std::make_shared()); } model::LightPointer lp = _allocatedLights[lightID]; lp->setPosition(position); lp->setMaximumRadius(radius); lp->setColor(color); lp->setIntensity(intensity); //lp->setShowContour(quadraticAttenuation); if (exponent == 0.0f && cutoff == PI) { lp->setType(model::Light::POINT); _pointLights.push_back(lightID); } else { lp->setOrientation(orientation); lp->setSpotAngle(cutoff); lp->setSpotExponent(exponent); lp->setType(model::Light::SPOT); _spotLights.push_back(lightID); } } void DeferredLightingEffect::prepare(RenderArgs* args) { gpu::Batch batch; batch.enableStereo(false); batch.setStateScissorRect(args->_viewport); auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); batch.setFramebuffer(primaryFbo); // clear the normal and specular buffers batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR1, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), true); const float MAX_SPECULAR_EXPONENT = 128.0f; batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR2, glm::vec4(0.0f, 0.0f, 0.0f, 1.0f / MAX_SPECULAR_EXPONENT), true); args->_context->render(batch); } gpu::FramebufferPointer _copyFBO; void DeferredLightingEffect::render(RenderArgs* args) { gpu::Batch batch; // Allocate the parameters buffer used by all the deferred shaders if (!_deferredTransformBuffer[0]._buffer) { DeferredTransform parameters; _deferredTransformBuffer[0] = gpu::BufferView(std::make_shared(sizeof(DeferredTransform), (const gpu::Byte*) ¶meters)); _deferredTransformBuffer[1] = gpu::BufferView(std::make_shared(sizeof(DeferredTransform), (const gpu::Byte*) ¶meters)); } // Framebuffer copy operations cannot function as multipass stereo operations. batch.enableStereo(false); // perform deferred lighting, rendering to free fbo auto framebufferCache = DependencyManager::get(); QSize framebufferSize = framebufferCache->getFrameBufferSize(); // binding the first framebuffer _copyFBO = framebufferCache->getFramebuffer(); batch.setFramebuffer(_copyFBO); batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); batch.clearColorFramebuffer(_copyFBO->getBufferMask(), glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), true); batch.setResourceTexture(0, framebufferCache->getPrimaryColorTexture()); batch.setResourceTexture(1, framebufferCache->getPrimaryNormalTexture()); batch.setResourceTexture(2, framebufferCache->getPrimarySpecularTexture()); batch.setResourceTexture(3, framebufferCache->getPrimaryDepthTexture()); float sMin = args->_viewport.x / (float)framebufferSize.width(); float sWidth = args->_viewport.z / (float)framebufferSize.width(); float tMin = args->_viewport.y / (float)framebufferSize.height(); float tHeight = args->_viewport.w / (float)framebufferSize.height(); auto monoViewport = args->_viewport; // The view furstum is the mono frustum base auto viewFrustum = args->_viewFrustum; float left, right, bottom, top, nearVal, farVal; glm::vec4 nearClipPlane, farClipPlane; viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); float depthScale = (farVal - nearVal) / farVal; float nearScale = -1.0f / nearVal; float depthTexCoordScaleS = (right - left) * nearScale / sWidth; float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; // Eval the mono projection mat4 monoProjMat; viewFrustum->evalProjectionMatrix(monoProjMat); // The mono view transform Transform monoViewTransform; viewFrustum->evalViewTransform(monoViewTransform); // THe mono view matrix coming from the mono view transform glm::mat4 monoViewMat; monoViewTransform.getMatrix(monoViewMat); bool isStereo = args->_context->isStereo(); int numPasses = 1; mat4 projMats[2]; ivec4 viewports[2]; vec4 clipQuad[2]; vec2 screenBottomLeftCorners[2]; vec2 screenTopRightCorners[2]; vec4 fetchTexcoordRects[2]; DeferredTransform deferredTransforms[2]; if (isStereo) { numPasses = 2; mat4 eyeViews[2]; args->_context->getStereoProjections(projMats); args->_context->getStereoViews(eyeViews); float halfWidth = 0.5 * sWidth; for (int i = 0; i < numPasses; i++) { // In stereo, the 2 sides are layout side by side in the mono viewport and their width is half int sideWidth = monoViewport.z * 0.5; viewports[i] = ivec4(monoViewport.x + (i * sideWidth), monoViewport.y, sideWidth, monoViewport.w); auto sideViewMat = eyeViews[i] * monoViewMat; projMats[i] = projMats[i] * eyeViews[i]; deferredTransforms[i]._projection = projMats[i]; deferredTransforms[i]._viewInverse = monoViewMat; deferredTransforms[i].nearVal = nearVal; deferredTransforms[i].depthScale = depthScale; deferredTransforms[i].isStereo = (i == 0 ? -1.0f : 1.0f); deferredTransforms[i].depthTexCoordOffset = glm::vec2(depthTexCoordOffsetS, depthTexCoordOffsetT); deferredTransforms[i].depthTexCoordScale = glm::vec2(depthTexCoordScaleS, depthTexCoordScaleT); clipQuad[i] = glm::vec4(sMin + i * halfWidth, tMin, halfWidth, tHeight); screenBottomLeftCorners[i] = glm::vec2(-1.0f + i * 1.0f, -1.0f); screenTopRightCorners[i] = glm::vec2(i * 1.0f, 1.0f); fetchTexcoordRects[i] = glm::vec4(sMin + i * halfWidth, tMin, halfWidth, tHeight); } } else { viewports[0] = monoViewport; projMats[0] = monoProjMat; deferredTransforms[0]._projection = monoProjMat; deferredTransforms[0]._viewInverse = monoViewMat; deferredTransforms[0].nearVal = nearVal; deferredTransforms[0].depthScale = depthScale; deferredTransforms[0].isStereo = 0.0f; deferredTransforms[0].depthTexCoordOffset = glm::vec2(depthTexCoordOffsetS, depthTexCoordOffsetT); deferredTransforms[0].depthTexCoordScale = glm::vec2(depthTexCoordScaleS, depthTexCoordScaleT); clipQuad[0] = glm::vec4(sMin, tMin, sWidth, tHeight); screenBottomLeftCorners[0] = glm::vec2(-1.0f, -1.0f); screenTopRightCorners[0] = glm::vec2(1.0f, 1.0f); fetchTexcoordRects[0] = glm::vec4(sMin, tMin, sWidth, tHeight); } auto eyePoint = viewFrustum->getPosition(); float nearRadius = glm::distance(eyePoint, viewFrustum->getNearTopLeft()); for (int side = 0; side < numPasses; side++) { // Render in this side's viewport batch.setViewportTransform(viewports[side]); batch.setStateScissorRect(viewports[side]); // Sync and Bind the correct DeferredTransform ubo _deferredTransformBuffer[side]._buffer->setSubData(0, sizeof(DeferredTransform), (const gpu::Byte*) &deferredTransforms[side]); batch.setUniformBuffer(_directionalLightLocations->deferredTransformBuffer, _deferredTransformBuffer[side]); glm::vec2 topLeft(-1.0f, -1.0f); glm::vec2 bottomRight(1.0f, 1.0f); /* glm::vec2 texCoordTopLeft(sMin, tMin); glm::vec2 texCoordBottomRight(sMin + sWidth, tMin + tHeight);*/ /* glm::vec2 topLeft = screenBottomLeftCorners[side]; glm::vec2 bottomRight = screenTopRightCorners[side];*/ glm::vec2 texCoordTopLeft(clipQuad[side].x, clipQuad[side].y); glm::vec2 texCoordBottomRight(clipQuad[side].x + clipQuad[side].z, clipQuad[side].y + clipQuad[side].w); // First Global directional light and ambient pass bool useSkyboxCubemap = (_skybox) && (_skybox->getCubemap()); auto& program = _directionalLight; LightLocationsPtr locations = _directionalLightLocations; // FIXME: Note: we've removed the menu items to enable shadows, so this will always be false for now. // When we add back shadow support, this old approach may likely be removed and completely replaced // but I've left it in for now. /* bool shadowsEnabled = false; bool cascadeShadowsEnabled = false; if (shadowsEnabled) { batch.setResourceTexture(4, framebufferCache->getShadowFramebuffer()->getDepthStencilBuffer()); program = _directionalLightShadowMap; locations = _directionalLightShadowMapLocations; if (cascadeShadowsEnabled) { program = _directionalLightCascadedShadowMap; locations = _directionalLightCascadedShadowMapLocations; if (useSkyboxCubemap) { program = _directionalSkyboxLightCascadedShadowMap; locations = _directionalSkyboxLightCascadedShadowMapLocations; } else if (_ambientLightMode > -1) { program = _directionalAmbientSphereLightCascadedShadowMap; locations = _directionalAmbientSphereLightCascadedShadowMapLocations; } batch.setPipeline(program); batch._glUniform3fv(locations->shadowDistances, 1, (const float*) &_viewState->getShadowDistances()); } else { if (useSkyboxCubemap) { program = _directionalSkyboxLightShadowMap; locations = _directionalSkyboxLightShadowMapLocations; } else if (_ambientLightMode > -1) { program = _directionalAmbientSphereLightShadowMap; locations = _directionalAmbientSphereLightShadowMapLocations; } batch.setPipeline(program); } batch._glUniform1f(locations->shadowScale, 1.0f / framebufferCache->getShadowFramebuffer()->getWidth()); } else*/ { if (useSkyboxCubemap) { program = _directionalSkyboxLight; locations = _directionalSkyboxLightLocations; } else if (_ambientLightMode > -1) { program = _directionalAmbientSphereLight; locations = _directionalAmbientSphereLightLocations; } batch.setPipeline(program); } { // Setup the global lighting auto globalLight = _allocatedLights[_globalLights.front()]; if (locations->ambientSphere >= 0) { gpu::SphericalHarmonics sh = globalLight->getAmbientSphere(); if (useSkyboxCubemap && _skybox->getCubemap()->getIrradiance()) { sh = (*_skybox->getCubemap()->getIrradiance()); } for (int i =0; i ambientSphere + i, 1, (const float*) (&sh) + i * 4); } } if (useSkyboxCubemap) { batch.setResourceTexture(5, _skybox->getCubemap()); } if (locations->lightBufferUnit >= 0) { batch.setUniformBuffer(locations->lightBufferUnit, globalLight->getSchemaBuffer()); } if (_atmosphere && (locations->atmosphereBufferUnit >= 0)) { batch.setUniformBuffer(locations->atmosphereBufferUnit, _atmosphere->getDataBuffer()); } } { /* Transform model; model.setTranslation(glm::vec3(sMin, tMin, 0.0)); model.setScale(glm::vec3(sWidth, tHeight, 1.0)); batch.setModelTransform(model); */ batch.setModelTransform(Transform()); batch.setProjectionTransform(glm::mat4()); batch.setViewTransform(Transform()); glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); /* glm::vec2 topLeft = screenBottomLeftCorners[side]; // = glm::vec2(-1.0f, -1.0f); glm::vec2 bottomRight = screenTopRightCorners[side]; //=(1.0f, 1.0f); glm::vec2 texCoordTopLeft(sMin, tMin); glm::vec2 texCoordBottomRight(sMin + sWidth, tMin + tHeight); */ DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color); } if (useSkyboxCubemap) { batch.setResourceTexture(5, nullptr); } /* if (shadowsEnabled) { batch.setResourceTexture(4, nullptr); }*/ auto texcoordMat = glm::mat4(); /* texcoordMat[0] = glm::vec4(sWidth / 2.0f, 0.0f, 0.0f, sMin + sWidth / 2.0f); texcoordMat[1] = glm::vec4(0.0f, tHeight / 2.0f, 0.0f, tMin + tHeight / 2.0f); */ texcoordMat[0] = glm::vec4(fetchTexcoordRects[side].z / 2.0f, 0.0f, 0.0f, fetchTexcoordRects[side].x + fetchTexcoordRects[side].z / 2.0f); texcoordMat[1] = glm::vec4(0.0f, fetchTexcoordRects[side].w / 2.0f, 0.0f, fetchTexcoordRects[side].y + fetchTexcoordRects[side].w / 2.0f); texcoordMat[2] = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); texcoordMat[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); // enlarge the scales slightly to account for tesselation const float SCALE_EXPANSION = 0.05f; auto geometryCache = DependencyManager::get(); batch.setProjectionTransform(projMats[side]); batch.setViewTransform(monoViewTransform); if (!_pointLights.empty()) { batch.setPipeline(_pointLight); batch._glUniformMatrix4fv(_pointLightLocations->texcoordMat, 1, false, reinterpret_cast< const float* >(&texcoordMat)); for (auto lightID : _pointLights) { auto& light = _allocatedLights[lightID]; // IN DEBUG: light->setShowContour(true); if (_pointLightLocations->lightBufferUnit >= 0) { batch.setUniformBuffer(_pointLightLocations->lightBufferUnit, light->getSchemaBuffer()); } float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, // we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working... if (glm::distance(eyePoint, glm::vec3(light->getPosition())) < expandedRadius + nearRadius) { Transform model; model.setTranslation(glm::vec3(0.0f, 0.0f, -1.0f)); batch.setModelTransform(model); batch.setViewTransform(Transform()); batch.setProjectionTransform(glm::mat4()); glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); /* glm::vec2 topLeft(-1.0f, -1.0f); glm::vec2 bottomRight(1.0f, 1.0f); glm::vec2 texCoordTopLeft(sMin, tMin); glm::vec2 texCoordBottomRight(sMin + sWidth, tMin + tHeight); */ DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color); batch.setProjectionTransform(projMats[side]); batch.setViewTransform(monoViewTransform); } else { Transform model; model.setTranslation(glm::vec3(light->getPosition().x, light->getPosition().y, light->getPosition().z)); batch.setModelTransform(model); geometryCache->renderSphere(batch, expandedRadius, 32, 32, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); } } // _pointLights.clear(); } if (!_spotLights.empty()) { batch.setPipeline(_spotLight); batch._glUniformMatrix4fv(_spotLightLocations->texcoordMat, 1, false, reinterpret_cast< const float* >(&texcoordMat)); for (auto lightID : _spotLights) { auto light = _allocatedLights[lightID]; // IN DEBUG: light->setShowContour(true); batch.setUniformBuffer(_spotLightLocations->lightBufferUnit, light->getSchemaBuffer()); auto eyeLightPos = eyePoint - light->getPosition(); auto eyeHalfPlaneDistance = glm::dot(eyeLightPos, light->getDirection()); const float TANGENT_LENGTH_SCALE = 0.666f; glm::vec4 coneParam(light->getSpotAngleCosSin(), TANGENT_LENGTH_SCALE * tanf(0.5f * light->getSpotAngle()), 1.0f); float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, // we should be able to draw thre same geometry use DepthClamp but for unknown reason it's s not working... if ((eyeHalfPlaneDistance > -nearRadius) && (glm::distance(eyePoint, glm::vec3(light->getPosition())) < expandedRadius + nearRadius)) { coneParam.w = 0.0f; batch._glUniform4fv(_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); Transform model; model.setTranslation(glm::vec3(0.0f, 0.0f, -1.0f)); batch.setModelTransform(model); batch.setViewTransform(Transform()); batch.setProjectionTransform(glm::mat4()); glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); /* glm::vec2 topLeft(-1.0f, -1.0f); glm::vec2 bottomRight(1.0f, 1.0f); glm::vec2 texCoordTopLeft(sMin, tMin); glm::vec2 texCoordBottomRight(sMin + sWidth, tMin + tHeight); */ DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color); batch.setProjectionTransform( projMats[side]); batch.setViewTransform(monoViewTransform); } else { coneParam.w = 1.0f; batch._glUniform4fv(_spotLightLocations->coneParam, 1, reinterpret_cast< const float* >(&coneParam)); Transform model; model.setTranslation(light->getPosition()); model.postRotate(light->getOrientation()); model.postScale(glm::vec3(expandedRadius, expandedRadius, expandedRadius)); batch.setModelTransform(model); auto mesh = getSpotLightMesh(); batch.setIndexBuffer(mesh->getIndexBuffer()); batch.setInputBuffer(0, mesh->getVertexBuffer()); batch.setInputFormat(mesh->getVertexFormat()); auto& part = mesh->getPartBuffer().get(); batch.drawIndexed(model::Mesh::topologyToPrimitive(part._topology), part._numIndices, part._startIndex); } } // _spotLights.clear(); } } // Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target batch.setResourceTexture(0, nullptr); batch.setResourceTexture(1, nullptr); batch.setResourceTexture(2, nullptr); batch.setResourceTexture(3, nullptr); args->_context->render(batch); // End of the Lighting pass if (!_pointLights.empty()) { _pointLights.clear(); } if (!_spotLights.empty()) { _spotLights.clear(); } } void DeferredLightingEffect::copyBack(RenderArgs* args) { gpu::Batch batch; batch.enableStereo(false); auto framebufferCache = DependencyManager::get(); QSize framebufferSize = framebufferCache->getFrameBufferSize(); // TODO why doesn't this blit work? It only seems to affect a small area below the rear view mirror. // auto destFbo = framebufferCache->getPrimaryFramebuffer(); auto destFbo = framebufferCache->getPrimaryFramebufferDepthColor(); // gpu::Vec4i vp = args->_viewport; // batch.blit(_copyFBO, vp, framebufferCache->getPrimaryFramebuffer(), vp); batch.setFramebuffer(destFbo); batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(glm::mat4()); batch.setViewTransform(Transform()); { float sMin = args->_viewport.x / (float)framebufferSize.width(); float sWidth = args->_viewport.z / (float)framebufferSize.width(); float tMin = args->_viewport.y / (float)framebufferSize.height(); float tHeight = args->_viewport.w / (float)framebufferSize.height(); Transform model; batch.setPipeline(_blitLightBuffer); model.setTranslation(glm::vec3(sMin, tMin, 0.0)); model.setScale(glm::vec3(sWidth, tHeight, 1.0)); batch.setModelTransform(model); } batch.setResourceTexture(0, _copyFBO->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); args->_context->render(batch); framebufferCache->releaseFramebuffer(_copyFBO); } void DeferredLightingEffect::setupTransparent(RenderArgs* args, int lightBufferUnit) { auto globalLight = _allocatedLights[_globalLights.front()]; args->_batch->setUniformBuffer(lightBufferUnit, globalLight->getSchemaBuffer()); } static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(vertSource))); auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fragSource))); gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("diffuseMap"), 0)); slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), 1)); slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), 2)); slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), 3)); slotBindings.insert(gpu::Shader::Binding(std::string("shadowMap"), 4)); slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), 5)); const int LIGHT_GPU_SLOT = 3; slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), LIGHT_GPU_SLOT)); const int ATMOSPHERE_GPU_SLOT = 4; slotBindings.insert(gpu::Shader::Binding(std::string("atmosphereBufferUnit"), ATMOSPHERE_GPU_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("deferredTransformBuffer"), DeferredLightingEffect::DEFERRED_TRANSFORM_BUFFER_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); locations->shadowDistances = program->getUniforms().findLocation("shadowDistances"); locations->shadowScale = program->getUniforms().findLocation("shadowScale"); locations->radius = program->getUniforms().findLocation("radius"); locations->ambientSphere = program->getUniforms().findLocation("ambientSphere.L00"); locations->texcoordMat = program->getUniforms().findLocation("texcoordMat"); locations->coneParam = program->getUniforms().findLocation("coneParam"); locations->lightBufferUnit = program->getBuffers().findLocation("lightBuffer"); locations->atmosphereBufferUnit = program->getBuffers().findLocation("atmosphereBufferUnit"); locations->deferredTransformBuffer = program->getBuffers().findLocation("deferredTransformBuffer"); auto state = std::make_shared(); if (lightVolume) { state->setCullMode(gpu::State::CULL_BACK); // No need for z test since the depth buffer is not bound state->setDepthTest(true, false, gpu::LESS_EQUAL); // TODO: We should bind the true depth buffer both as RT and texture for the depth test // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases state->setDepthClampEnable(true); // additive blending state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } else { state->setCullMode(gpu::State::CULL_BACK); } pipeline.reset(gpu::Pipeline::create(program, state)); } void DeferredLightingEffect::setAmbientLightMode(int preset) { if ((preset >= 0) && (preset < gpu::SphericalHarmonics::NUM_PRESET)) { _ambientLightMode = preset; auto light = _allocatedLights.front(); light->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(preset % gpu::SphericalHarmonics::NUM_PRESET)); } else { // force to preset 0 setAmbientLightMode(0); } } void DeferredLightingEffect::setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity, float ambientIntensity) { auto light = _allocatedLights.front(); light->setDirection(direction); light->setColor(diffuse); light->setIntensity(intensity); light->setAmbientIntensity(ambientIntensity); } void DeferredLightingEffect::setGlobalSkybox(const model::SkyboxPointer& skybox) { _skybox = skybox; } model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { if (!_spotLightMesh) { _spotLightMesh = std::make_shared(); int slices = 32; int rings = 3; int vertices = 2 + rings * slices; int originVertex = vertices - 2; int capVertex = vertices - 1; int verticesSize = vertices * 3 * sizeof(float); int indices = 3 * slices * (1 + 1 + 2 * (rings -1)); int ringFloatOffset = slices * 3; float* vertexData = new float[verticesSize]; float* vertexRing0 = vertexData; float* vertexRing1 = vertexRing0 + ringFloatOffset; float* vertexRing2 = vertexRing1 + ringFloatOffset; for (int i = 0; i < slices; i++) { float theta = TWO_PI * i / slices; auto cosin = glm::vec2(cosf(theta), sinf(theta)); *(vertexRing0++) = cosin.x; *(vertexRing0++) = cosin.y; *(vertexRing0++) = 0.0f; *(vertexRing1++) = cosin.x; *(vertexRing1++) = cosin.y; *(vertexRing1++) = 0.33f; *(vertexRing2++) = cosin.x; *(vertexRing2++) = cosin.y; *(vertexRing2++) = 0.66f; } *(vertexRing2++) = 0.0f; *(vertexRing2++) = 0.0f; *(vertexRing2++) = -1.0f; *(vertexRing2++) = 0.0f; *(vertexRing2++) = 0.0f; *(vertexRing2++) = 1.0f; _spotLightMesh->setVertexBuffer(gpu::BufferView(new gpu::Buffer(verticesSize, (gpu::Byte*) vertexData), gpu::Element::VEC3F_XYZ)); delete[] vertexData; gpu::uint16* indexData = new gpu::uint16[indices]; gpu::uint16* index = indexData; for (int i = 0; i < slices; i++) { *(index++) = originVertex; int s0 = i; int s1 = ((i + 1) % slices); *(index++) = s0; *(index++) = s1; int s2 = s0 + slices; int s3 = s1 + slices; *(index++) = s1; *(index++) = s0; *(index++) = s2; *(index++) = s1; *(index++) = s2; *(index++) = s3; int s4 = s2 + slices; int s5 = s3 + slices; *(index++) = s3; *(index++) = s2; *(index++) = s4; *(index++) = s3; *(index++) = s4; *(index++) = s5; *(index++) = s5; *(index++) = s4; *(index++) = capVertex; } _spotLightMesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(sizeof(unsigned short) * indices, (gpu::Byte*) indexData), gpu::Element::INDEX_UINT16)); delete[] indexData; model::Mesh::Part part(0, indices, 0, model::Mesh::TRIANGLES); //DEBUG: model::Mesh::Part part(0, indices, 0, model::Mesh::LINE_STRIP); _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(sizeof(part), (gpu::Byte*) &part), gpu::Element::PART_DRAWCALL)); _spotLightMesh->makeBufferStream(); } return _spotLightMesh; }