/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2021 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr const char* _loggerCat = "ShadowComponent"; constexpr openspace::properties::Property::PropertyInfo SaveDepthTextureInfo = { "SaveDepthTextureInfo", "Save Depth Texture", "Debug" }; constexpr openspace::properties::Property::PropertyInfo DistanceFractionInfo = { "DistanceFraction", "Distance Fraction", "Distance fraction of original distance from light source to the globe to be " "considered as the new light source distance." }; constexpr openspace::properties::Property::PropertyInfo DepthMapSizeInfo = { "DepthMapSize", "Depth Map Size", "The depth map size in pixels. You must entry the width and height values." }; constexpr const GLfloat ShadowBorder[] = { 1.f, 1.f, 1.f, 1.f }; void checkFrameBufferState(const std::string& codePosition) { if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LERROR("Framework not built. " + codePosition); GLenum fbErr = glCheckFramebufferStatus(GL_FRAMEBUFFER); switch (fbErr) { case GL_FRAMEBUFFER_UNDEFINED: LERROR("Indefined framebuffer."); break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: LERROR("Incomplete, missing attachement."); break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: LERROR("Framebuffer doesn't have at least one image attached to it."); break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: LERROR( "Returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is " "GL_NONE for any color attachment point(s) named by GL_DRAW_BUFFERi." ); break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: LERROR( "Returned if GL_READ_BUFFER is not GL_NONE and the value of " "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color " "attachment point named by GL_READ_BUFFER."); break; case GL_FRAMEBUFFER_UNSUPPORTED: LERROR( "Returned if the combination of internal formats of the attached " "images violates an implementation - dependent set of restrictions." ); break; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: LERROR( "Returned if the value of GL_RENDERBUFFE_r_samples is not the same " "for all attached renderbuffers; if the value of GL_TEXTURE_SAMPLES " "is the not same for all attached textures; or , if the attached " "images are a mix of renderbuffers and textures, the value of " "GL_RENDERBUFFE_r_samples does not match the value of " "GL_TEXTURE_SAMPLES." ); LERROR( "Returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not " "the same for all attached textures; or , if the attached images are " "a mix of renderbuffers and textures, the value of " "GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached " "textures." ); break; case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: LERROR( "Returned if any framebuffer attachment is layered, and any " "populated attachment is not layered, or if all populated color " "attachments are not from textures of the same target." ); break; default: LDEBUG("No error found checking framebuffer: " + codePosition); break; } } } struct [[codegen::Dictionary(ShadowComponent)]] Parameters { // [[codegen::verbatim(DistanceFractionInfo.description)]] std::optional distanceFraction; // [[codegen::verbatim(DepthMapSizeInfo.description)]] std::optional depthMapSize; }; #include "shadowcomponent_codegen.cpp" } // namespace namespace openspace { documentation::Documentation ShadowComponent::Documentation() { documentation::Documentation doc = codegen::doc(); doc.id = "globebrowsing_shadows_component"; return doc; } ShadowComponent::ShadowComponent(const ghoul::Dictionary& dictionary) : properties::PropertyOwner({ "ShadowsComponent" }) , _saveDepthTexture(SaveDepthTextureInfo) , _distanceFraction(DistanceFractionInfo, 20, 1, 10000) , _enabled({ "Enabled", "Enabled", "Enable/Disable Shadows" }, true) { using ghoul::filesystem::File; // @TODO (abock, 2021-03-25) This is not really a nice solution as this key name is // coded into the RenderableGlobe. Instead, the parent should unpack the dictionary // and pass the unpacked dictionary in here; Or maybe we don't want a dictionary at // this state anyway? if (!dictionary.hasValue("Shadows")) { return; } ghoul::Dictionary d = dictionary.value("Shadows"); const Parameters p = codegen::bake(d); addProperty(_enabled); _distanceFraction = p.distanceFraction.value_or(_distanceFraction); addProperty(_distanceFraction); _saveDepthTexture.onChange([&]() { _executeDepthTextureSave = true; }); if (p.depthMapSize.has_value()) { _shadowDepthTextureWidth = p.depthMapSize->x; _shadowDepthTextureHeight = p.depthMapSize->y; _dynamicDepthTextureRes = false; } else { glm::ivec2 renderingResolution = global::renderEngine->renderingResolution(); _shadowDepthTextureWidth = renderingResolution.x * 2; _shadowDepthTextureHeight = renderingResolution.y * 2; _dynamicDepthTextureRes = true; } addProperty(_saveDepthTexture); } void ShadowComponent::initialize() { buildDDepthTexture(); } bool ShadowComponent::isReady() const { return true; } void ShadowComponent::initializeGL() { ZoneScoped createDepthTexture(); createShadowFBO(); } void ShadowComponent::deinitializeGL() { glDeleteTextures(1, &_shadowDepthTexture); glDeleteTextures(1, &_positionInLightSpaceTexture); glDeleteTextures(1, &_dDepthTexture); glDeleteFramebuffers(1, &_shadowFBO); } RenderData ShadowComponent::begin(const RenderData& data) { // =========================================== // Builds light's ModelViewProjectionMatrix: // =========================================== glm::dvec3 diffVector = glm::dvec3(_sunPosition) - data.modelTransform.translation; double originalLightDistance = glm::length(diffVector); glm::dvec3 lightDirection = glm::normalize(diffVector); // Percentage of the original light source distance (to avoid artifacts) //double multiplier = originalLightDistance * // (static_cast(_distanceFraction)/1.0E5); double multiplier = originalLightDistance * (static_cast(_distanceFraction) / 1E17); // New light source position //glm::dvec3 lightPosition = data.modelTransform.translation + // (lightDirection * multiplier); glm::dvec3 lightPosition = data.modelTransform.translation + (diffVector * multiplier); //// Light Position //glm::dvec3 lightPosition = glm::dvec3(_sunPosition); //=============== Manually Created Camera Matrix =================== //================================================================== // camera Z glm::dvec3 cameraZ = lightDirection; // camera X glm::dvec3 upVector = glm::dvec3(0.0, 1.0, 0.0); glm::dvec3 cameraX = glm::normalize(glm::cross(upVector, cameraZ)); // camera Y glm::dvec3 cameraY = glm::cross(cameraZ, cameraX); // init 4x4 matrix glm::dmat4 cameraRotationMatrix(1.0); double* matrix = glm::value_ptr(cameraRotationMatrix); matrix[0] = cameraX.x; matrix[4] = cameraX.y; matrix[8] = cameraX.z; matrix[1] = cameraY.x; matrix[5] = cameraY.y; matrix[9] = cameraY.z; matrix[2] = cameraZ.x; matrix[6] = cameraZ.y; matrix[10] = cameraZ.z; // set translation part // We aren't setting the position here because it is set in // the camera->setPosition() //matrix[12] = -glm::dot(cameraX, lightPosition); //matrix[13] = -glm::dot(cameraY, lightPosition); //matrix[14] = -glm::dot(cameraZ, lightPosition); _lightCamera = std::make_unique(data.camera); _lightCamera->setPositionVec3(lightPosition); _lightCamera->setRotation(glm::dquat(glm::inverse(cameraRotationMatrix))); //======================================================================= //======================================================================= //============= Light Matrix by Camera Matrices Composition ============= //======================================================================= glm::dmat4 lightProjectionMatrix = glm::dmat4(_lightCamera->projectionMatrix()); // The model transformation missing in the final shadow matrix is add when rendering // each object (using its transformations provided by the RenderData structure) _shadowData.shadowMatrix = _toTextureCoordsMatrix * lightProjectionMatrix * _lightCamera->combinedViewMatrix(); // Saves current state glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_currentFBO); global::renderEngine->openglStateCache().viewport(_mViewport); glBindFramebuffer(GL_FRAMEBUFFER, _shadowFBO); GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_NONE, GL_NONE }; glDrawBuffers(3, drawBuffers); glViewport(0, 0, _shadowDepthTextureWidth, _shadowDepthTextureHeight); glClearDepth(1.0f); glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //glEnable(GL_CULL_FACE); //checkGLError("begin() -- enabled cull face"); //glCullFace(GL_FRONT); //checkGLError("begin() -- set cullface to front"); //glEnable(GL_POLYGON_OFFSET_FILL); //checkGLError("begin() -- enabled polygon offset fill"); //glPolygonOffset(2.5f, 10.0f); //checkGLError("begin() -- set values for polygon offset"); RenderData lightRenderData{ *_lightCamera, data.time, data.renderBinMask, data.modelTransform }; return lightRenderData; } void ShadowComponent::end() { if (_executeDepthTextureSave) { saveDepthBuffer(); _executeDepthTextureSave = false; } // Restores system state glBindFramebuffer(GL_FRAMEBUFFER, _currentFBO); GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, drawBuffers); glViewport(_mViewport[0], _mViewport[1], _mViewport[2], _mViewport[3]); // Restores OpenGL Rendering State global::renderEngine->openglStateCache().resetColorState(); global::renderEngine->openglStateCache().resetBlendState(); global::renderEngine->openglStateCache().resetDepthState(); global::renderEngine->openglStateCache().resetPolygonAndClippingState(); if (_blendIsEnabled) { glEnable(GL_BLEND); } if (_viewDepthMap) { if (!_renderDMProgram) { _renderDMProgram = global::renderEngine->buildRenderProgram( "ShadowMappingDebuggingProgram", absPath("${MODULE_GLOBEBROWSING}/shaders/smviewer_vs.glsl"), absPath("${MODULE_GLOBEBROWSING}/shaders/smviewer_fs.glsl") ); } if (!_quadVAO) { glGenVertexArrays(1, &_quadVAO); } _renderDMProgram->activate(); ghoul::opengl::TextureUnit shadowMapUnit; shadowMapUnit.activate(); glBindTexture(GL_TEXTURE_2D, _shadowDepthTexture); _renderDMProgram->setUniform("shadowMapTexture", shadowMapUnit); glBindVertexArray(_quadVAO); glDrawArrays(GL_TRIANGLES, 0, 6); _renderDMProgram->deactivate(); } } void ShadowComponent::update(const UpdateData&) { ZoneScoped _sunPosition = global::renderEngine->scene()->sceneGraphNode("Sun")->worldPosition(); glm::ivec2 renderingResolution = global::renderEngine->renderingResolution(); if (_dynamicDepthTextureRes && ((_shadowDepthTextureWidth != renderingResolution.x) || (_shadowDepthTextureHeight != renderingResolution.y))) { _shadowDepthTextureWidth = renderingResolution.x * 2; _shadowDepthTextureHeight = renderingResolution.y * 2; updateDepthTexture(); } } void ShadowComponent::createDepthTexture() { glGenTextures(1, &_shadowDepthTexture); updateDepthTexture(); _shadowData.shadowDepthTexture = _shadowDepthTexture; //_shadowData.positionInLightSpaceTexture = _positionInLightSpaceTexture; } void ShadowComponent::createShadowFBO() { // Saves current FBO first glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_currentFBO); glGenFramebuffers(1, &_shadowFBO); glBindFramebuffer(GL_FRAMEBUFFER, _shadowFBO); glFramebufferTexture( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, _shadowDepthTexture, 0 ); //glFramebufferTexture( // GL_FRAMEBUFFER, // GL_COLOR_ATTACHMENT0, // _positionInLightSpaceTexture, // 0 //); //GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_NONE, GL_NONE }; GLenum drawBuffers[] = { GL_NONE, GL_NONE, GL_NONE }; glDrawBuffers(3, drawBuffers); checkFrameBufferState("createShadowFBO()"); // Restores system state glBindFramebuffer(GL_FRAMEBUFFER, _currentFBO); } void ShadowComponent::updateDepthTexture() { glBindTexture(GL_TEXTURE_2D, _shadowDepthTexture); //glTexStorage2D( // GL_TEXTURE_2D, // 1, // GL_DEPTH_COMPONENT32F, // _shadowDepthTextureWidth, // _shadowDepthTextureHeight //); glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, _shadowDepthTextureWidth, _shadowDepthTextureHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, ShadowBorder); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); //glGenTextures(1, &_positionInLightSpaceTexture); //glBindTexture(GL_TEXTURE_2D, _positionInLightSpaceTexture); //glTexImage2D( // GL_TEXTURE_2D, // 0, // GL_RGB32F, // _shadowDepthTextureWidth, // _shadowDepthTextureHeight, // 0, // GL_RGBA, // GL_FLOAT, // nullptr //); //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); } void ShadowComponent::buildDDepthTexture() { glGenTextures(1, &_dDepthTexture); glBindTexture(GL_TEXTURE_2D, _dDepthTexture); glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 1, 1, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); glBindTexture(GL_TEXTURE_2D, 0); } void ShadowComponent::saveDepthBuffer() { int size = _shadowDepthTextureWidth * _shadowDepthTextureHeight; std::vector buffer(size); glReadPixels( 0, 0, _shadowDepthTextureWidth, _shadowDepthTextureHeight, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, buffer.data() ); std::fstream ppmFile; ppmFile.open("depthBufferShadowMapping.ppm", std::fstream::out); if (ppmFile.is_open()) { ppmFile << "P3" << std::endl; ppmFile << _shadowDepthTextureWidth << " " << _shadowDepthTextureHeight << std::endl; ppmFile << "255" << std::endl; std::cout << "\n\nSaving depth texture to file depthBufferShadowMapping.ppm\n\n"; int k = 0; for (int i = 0; i < _shadowDepthTextureWidth; i++) { for (int j = 0; j < _shadowDepthTextureHeight; j++, k++) { unsigned int val = static_cast(buffer[k]); ppmFile << val << " " << val << " " << val << " "; } ppmFile << std::endl; } ppmFile.close(); std::cout << "Texture saved to file depthBufferShadowMapping.ppm\n\n"; } buffer.clear(); std::vector bBuffer(size * 4); glReadBuffer(GL_COLOR_ATTACHMENT3); glReadPixels( 0, 0, _shadowDepthTextureWidth, _shadowDepthTextureHeight, GL_RGBA, GL_FLOAT, bBuffer.data() ); ppmFile.clear(); ppmFile.open("positionBufferShadowMapping.ppm", std::fstream::out); if (ppmFile.is_open()) { ppmFile << "P3" << std::endl; ppmFile << _shadowDepthTextureWidth << " " << _shadowDepthTextureHeight << std::endl; ppmFile << "255" << std::endl; std::cout << "\n\nSaving texture position to positionBufferShadowMapping.ppm\n\n"; float biggestValue = 0.f; int k = 0; for (int i = 0; i < _shadowDepthTextureWidth; i++) { for (int j = 0; j < _shadowDepthTextureHeight; j++) { biggestValue = bBuffer[k] > biggestValue ? bBuffer[k] : biggestValue; k += 4; } } biggestValue /= 255.f; k = 0; for (int i = 0; i < _shadowDepthTextureWidth; i++) { for (int j = 0; j < _shadowDepthTextureHeight; j++) { ppmFile << static_cast(bBuffer[k] / biggestValue) << " " << static_cast(bBuffer[k + 1] / biggestValue) << " " << static_cast(bBuffer[k + 2] / biggestValue) << " "; k += 4; } ppmFile << std::endl; } ppmFile.close(); LINFO("Texture saved to file positionBufferShadowMapping.ppm"); } } bool ShadowComponent::isEnabled() const { return _enabled; } ShadowComponent::ShadowMapData ShadowComponent::shadowMapData() const { return _shadowData; } void ShadowComponent::setViewDepthMap(bool enable) { _viewDepthMap = enable; } GLuint ShadowComponent::dDepthTexture() const { return _dDepthTexture; } } // namespace openspace