/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2023 * * * * 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 namespace { constexpr std::string_view _loggerCat = "RenderableModel"; constexpr std::string_view ProgramName = "ModelProgram"; constexpr int DefaultBlending = 0; constexpr int AdditiveBlending = 1; constexpr int PointsAndLinesBlending = 2; constexpr int PolygonBlending = 3; constexpr int ColorAddingBlending = 4; std::map BlendingMapping = { { "Default", DefaultBlending }, { "Additive", AdditiveBlending }, { "Points and Lines", PointsAndLinesBlending }, { "Polygon", PolygonBlending }, { "Color Adding", ColorAddingBlending } }; const GLenum ColorAttachmentArray[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, }; constexpr std::array UniformNames = { "nLightSources", "lightDirectionsViewSpace", "lightIntensities", "modelViewTransform", "normalTransform", "projectionTransform", "performShading", "ambientIntensity", "diffuseIntensity", "specularIntensity", "performManualDepthTest", "gBufferDepthTexture", "resolution" }; constexpr std::array UniformOpacityNames = { "opacity", "colorTexture", "depthTexture", "viewport", "resolution" }; constexpr openspace::properties::Property::PropertyInfo EnableAnimationInfo = { "EnableAnimation", "Enable Animation", "Enable or disable the animation for the model if it has any" }; constexpr openspace::properties::Property::PropertyInfo AmbientIntensityInfo = { "AmbientIntensity", "Ambient Intensity", "A multiplier for ambient lighting" }; constexpr openspace::properties::Property::PropertyInfo DiffuseIntensityInfo = { "DiffuseIntensity", "Diffuse Intensity", "A multiplier for diffuse lighting" }; constexpr openspace::properties::Property::PropertyInfo SpecularIntensityInfo = { "SpecularIntensity", "Specular Intensity", "A multiplier for specular lighting" }; constexpr openspace::properties::Property::PropertyInfo ShadingInfo = { "PerformShading", "Perform Shading", "This value determines whether this model should be shaded by using the position " "of the Sun" }; constexpr openspace::properties::Property::PropertyInfo EnableFaceCullingInfo = { "EnableFaceCulling", "Enable Face Culling", "Enable OpenGL automatic face culling optimization" }; constexpr openspace::properties::Property::PropertyInfo ModelTransformInfo = { "ModelTransform", "Model Transform", "This value specifies the model transform that is applied to the model before " "all other transformations are applied" }; constexpr openspace::properties::Property::PropertyInfo RotationVecInfo = { "RotationVector", "Rotation Vector", "Rotation Vector using degrees" }; constexpr openspace::properties::Property::PropertyInfo LightSourcesInfo = { "LightSources", "Light Sources", "A list of light sources that this model should accept light from" }; constexpr openspace::properties::Property::PropertyInfo EnableDepthTestInfo = { "EnableDepthTest", "Enable Depth Test", "Enable Depth Testing for the Model" }; constexpr openspace::properties::Property::PropertyInfo BlendingOptionInfo = { "BlendingOption", "Blending Options", "Changes the blending function used to calculate the colors of the model with " "respect to the opacity" }; struct [[codegen::Dictionary(RenderableModel)]] Parameters { // The file or files that should be loaded in this RenderableModel. The file can // contain filesystem tokens. This specifies the model that is rendered by // the Renderable. std::filesystem::path geometryFile; enum class [[codegen::map(openspace::DistanceUnit)]] ScaleUnit { Nanometer, Micrometer, Millimeter, Centimeter, Decimeter, Meter, Kilometer, Thou, Inch, Foot, Yard, Chain, Furlong, Mile }; // The scale of the model. For example if the model is in centimeters // then ModelScale = Centimeter or ModelScale = 0.01 std::optional> modelScale; // By default the given ModelScale is used to scale the model down, // by setting this setting to true the model is instead scaled up with the // given ModelScale std::optional invertModelScale; // Set if invisible parts (parts with no textures or materials) of the model // should be forced to render or not. std::optional forceRenderInvisible; // [[codegen::verbatim(EnableAnimationInfo.description)]] std::optional enableAnimation; // The date and time that the model animation should start. // In format 'YYYY MM DD hh:mm:ss'. std::optional animationStartTime [[codegen::datetime()]]; enum class [[codegen::map(openspace::TimeUnit)]] AnimationTimeUnit { Nanosecond, Microsecond, Millisecond, Second, Minute }; // The time scale for the animation relative to seconds. // Ex, if animation is in milliseconds then AnimationTimeScale = 0.001 or // AnimationTimeScale = Millisecond, default is Second std::optional> animationTimeScale; enum class AnimationMode { Once, LoopFromStart, LoopInfinitely, BounceFromStart, BounceInfinitely }; // The mode of how the animation should be played back. // Default is animation is played back once at the start time. // For a more detailed description see: // http://wiki.openspaceproject.com/docs/builders/model-animation std::optional animationMode; // [[codegen::verbatim(AmbientIntensityInfo.description)]] std::optional ambientIntensity; // [[codegen::verbatim(DiffuseIntensityInfo.description)]] std::optional diffuseIntensity; // [[codegen::verbatim(SpecularIntensityInfo.description)]] std::optional specularIntensity; // [[codegen::verbatim(ShadingInfo.description)]] std::optional performShading; // [[codegen::verbatim(EnableFaceCullingInfo.description)]] std::optional enableFaceCulling; // [[codegen::verbatim(ModelTransformInfo.description)]] std::optional modelTransform; // [[codegen::verbatim(RotationVecInfo.description)]] std::optional rotationVector; // [[codegen::verbatim(LightSourcesInfo.description)]] std::optional> lightSources [[codegen::reference("core_light_source")]]; // [[codegen::verbatim(EnableDepthTestInfo.description)]] std::optional enableDepthTest; // [[codegen::verbatim(BlendingOptionInfo.description)]] std::optional blendingOption; // The path to the vertex shader program that is used instead of the default // shader. std::optional vertexShader; // The path to the fragment shader program that is used instead of the default // shader. std::optional fragmentShader; }; #include "renderablemodel_codegen.cpp" } // namespace namespace openspace { documentation::Documentation RenderableModel::Documentation() { return codegen::doc("base_renderable_model"); } RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _enableAnimation(EnableAnimationInfo, false) , _ambientIntensity(AmbientIntensityInfo, 0.2f, 0.f, 1.f) , _diffuseIntensity(DiffuseIntensityInfo, 1.f, 0.f, 1.f) , _specularIntensity(SpecularIntensityInfo, 1.f, 0.f, 1.f) , _performShading(ShadingInfo, true) , _enableFaceCulling(EnableFaceCullingInfo, true) , _modelTransform( ModelTransformInfo, glm::dmat3(1.0), glm::dmat3(-1.0), glm::dmat3(1.0) ) , _rotationVec(RotationVecInfo, glm::dvec3(0.0), glm::dvec3(0.0), glm::dvec3(360.0)) , _enableDepthTest(EnableDepthTestInfo, true) , _blendingFuncOption( BlendingOptionInfo, properties::OptionProperty::DisplayType::Dropdown ) , _lightSourcePropertyOwner({ "LightSources", "Light Sources" }) { const Parameters p = codegen::bake(dictionary); addProperty(_opacity); registerUpdateRenderBinFromOpacity(); if (p.forceRenderInvisible.has_value()) { _forceRenderInvisible = *p.forceRenderInvisible; if (!_forceRenderInvisible) { // Asset file have specifically said to not render invisible parts, // do not notify in the log if invisible parts are detected and dropped _notifyInvisibleDropped = false; } } std::string file = absPath(p.geometryFile.string()).string(); _geometry = ghoul::io::ModelReader::ref().loadModel( file, ghoul::io::ModelReader::ForceRenderInvisible(_forceRenderInvisible), ghoul::io::ModelReader::NotifyInvisibleDropped(_notifyInvisibleDropped) ); _invertModelScale = p.invertModelScale.value_or(_invertModelScale); if (p.modelScale.has_value()) { if (std::holds_alternative(*p.modelScale)) { Parameters::ScaleUnit scaleUnit = std::get(*p.modelScale); DistanceUnit distanceUnit = codegen::map(scaleUnit); _modelScale = toMeter(distanceUnit); } else if (std::holds_alternative(*p.modelScale)) { _modelScale = std::get(*p.modelScale); } else { throw ghoul::MissingCaseException(); } if (_invertModelScale) { _modelScale = 1.0 / _modelScale; } } if (p.animationStartTime.has_value()) { if (!_geometry->hasAnimation()) { LWARNING("Animation start time given to model without animation"); } _animationStart = *p.animationStartTime; } if (p.enableAnimation.has_value()) { if (!_geometry->hasAnimation()) { LWARNING("Attempting to enable animation for a model that does not have any"); } else if (*p.enableAnimation && _animationStart.empty()) { LWARNING("Cannot enable animation without a given start time"); } else { _enableAnimation = *p.enableAnimation; _geometry->enableAnimation(_enableAnimation); } } if (p.animationTimeScale.has_value()) { if (!_geometry->hasAnimation()) { LWARNING("Animation time scale given to model without animation"); } else if (std::holds_alternative(*p.animationTimeScale)) { _geometry->setTimeScale(std::get(*p.animationTimeScale)); } else if (std::holds_alternative( *p.animationTimeScale) ) { Parameters::AnimationTimeUnit animationTimeUnit = std::get(*p.animationTimeScale); TimeUnit timeUnit = codegen::map(animationTimeUnit); _geometry->setTimeScale(static_cast( convertTime(1.0, timeUnit, TimeUnit::Second)) ); } else { throw ghoul::MissingCaseException(); } } if (p.animationMode.has_value()) { if (!_geometry->hasAnimation()) { LWARNING("Animation mode given to model without animation"); } switch (*p.animationMode) { case Parameters::AnimationMode::LoopFromStart: _animationMode = AnimationMode::LoopFromStart; break; case Parameters::AnimationMode::LoopInfinitely: _animationMode = AnimationMode::LoopInfinitely; break; case Parameters::AnimationMode::BounceFromStart: _animationMode = AnimationMode::BounceFromStart; break; case Parameters::AnimationMode::BounceInfinitely: _animationMode = AnimationMode::BounceInfinitely; break; case Parameters::AnimationMode::Once: _animationMode = AnimationMode::Once; break; default: throw ghoul::MissingCaseException(); } } if (p.modelTransform.has_value()) { _modelTransform = *p.modelTransform; } _ambientIntensity = p.ambientIntensity.value_or(_ambientIntensity); _diffuseIntensity = p.diffuseIntensity.value_or(_diffuseIntensity); _specularIntensity = p.specularIntensity.value_or(_specularIntensity); _performShading = p.performShading.value_or(_performShading); _enableDepthTest = p.enableDepthTest.value_or(_enableDepthTest); _enableFaceCulling = p.enableFaceCulling.value_or(_enableFaceCulling); if (p.vertexShader.has_value()) { _vertexShaderPath = p.vertexShader->string(); } if (p.fragmentShader.has_value()) { _fragmentShaderPath = p.fragmentShader->string(); } if (p.lightSources.has_value()) { std::vector lightsources = *p.lightSources; for (const ghoul::Dictionary& lsDictionary : lightsources) { std::unique_ptr lightSource = LightSource::createFromDictionary(lsDictionary); _lightSourcePropertyOwner.addPropertySubOwner(lightSource.get()); _lightSources.push_back(std::move(lightSource)); } } if (_geometry->hasAnimation()) { addProperty(_enableAnimation); } addPropertySubOwner(_lightSourcePropertyOwner); addProperty(_ambientIntensity); addProperty(_diffuseIntensity); addProperty(_specularIntensity); addProperty(_performShading); addProperty(_enableFaceCulling); addProperty(_enableDepthTest); addProperty(_modelTransform); addProperty(_rotationVec); _rotationVec.onChange([this]() { _modelTransform = glm::mat4_cast(glm::quat(glm::radians(_rotationVec.value()))); }); _enableAnimation.onChange([this]() { if (!_geometry->hasAnimation()) { LWARNING("Attempting to enable animation for a model that does not have any"); } else if (_enableAnimation && _animationStart.empty()) { LWARNING("Cannot enable animation without a given start time"); _enableAnimation = false; } else { _geometry->enableAnimation(_enableAnimation); } }); if (p.rotationVector.has_value()) { _rotationVec = *p.rotationVector; } _blendingFuncOption.addOption(DefaultBlending, "Default"); _blendingFuncOption.addOption(AdditiveBlending, "Additive"); _blendingFuncOption.addOption(PolygonBlending, "Polygon"); _blendingFuncOption.addOption(ColorAddingBlending, "Color Adding"); addProperty(_blendingFuncOption); if (p.blendingOption.has_value()) { const std::string blendingOpt = *p.blendingOption; _blendingFuncOption.set(BlendingMapping[blendingOpt]); } _originalRenderBin = renderBin(); } bool RenderableModel::isReady() const { return _program && _quadProgram; } void RenderableModel::initialize() { ZoneScoped; if (_geometry->hasAnimation() && _enableAnimation && _animationStart.empty()) { LWARNING("Model with animation not given any start time"); } else if (_geometry->hasAnimation() && !_enableAnimation) { LINFO("Model with deactivated animation was found. " "The animation can be activated by entering a start time in the asset file" ); } for (const std::unique_ptr& ls : _lightSources) { ls->initialize(); } } void RenderableModel::initializeGL() { ZoneScoped; std::string program = std::string(ProgramName); if (!_vertexShaderPath.empty()) { program += "|vs=" + _vertexShaderPath; } if (!_fragmentShaderPath.empty()) { program += "|fs=" + _fragmentShaderPath; } _program = BaseModule::ProgramObjectManager.request( program, [&]() -> std::unique_ptr { std::filesystem::path vs = _vertexShaderPath.empty() ? absPath("${MODULE_BASE}/shaders/model_vs.glsl") : std::filesystem::path(_vertexShaderPath); std::filesystem::path fs = _fragmentShaderPath.empty() ? absPath("${MODULE_BASE}/shaders/model_fs.glsl") : std::filesystem::path(_fragmentShaderPath); return global::renderEngine->buildRenderProgram(program, vs, fs); } ); // We don't really know what kind of shader the user provides us with, so we can't // make the assumption that we are going to use all uniforms _program->setIgnoreUniformLocationError( ghoul::opengl::ProgramObject::IgnoreError::Yes ); ghoul::opengl::updateUniformLocations(*_program, _uniformCache, UniformNames); _quadProgram = BaseModule::ProgramObjectManager.request( "ModelOpacityProgram", [&]() -> std::unique_ptr { std::filesystem::path vs = absPath("${MODULE_BASE}/shaders/modelOpacity_vs.glsl"); std::filesystem::path fs = absPath("${MODULE_BASE}/shaders/modelOpacity_fs.glsl"); return global::renderEngine->buildRenderProgram("ModelOpacityProgram", vs, fs); } ); ghoul::opengl::updateUniformLocations( *_quadProgram, _uniformOpacityCache, UniformOpacityNames ); // Screen quad VAO const GLfloat quadVertices[] = { // x y s t -1.f, -1.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f, -1.f, 1.f, 0.f, 1.f, -1.f, -1.f, 0.f, 0.f, 1.f, -1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 1.f }; glGenVertexArrays(1, &_quadVao); glBindVertexArray(_quadVao); glGenBuffers(1, &_quadVbo); glBindBuffer(GL_ARRAY_BUFFER, _quadVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), nullptr); glEnableVertexAttribArray(1); glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), reinterpret_cast(2 * sizeof(GLfloat)) ); // Generate textures and the frame buffer glGenFramebuffers(1, &_framebuffer); // Bind textures to the framebuffer glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); glFramebufferTexture( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, global::renderEngine->renderer().additionalColorTexture1(), 0 ); glFramebufferTexture( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, global::renderEngine->renderer().additionalColorTexture2(), 0 ); glFramebufferTexture( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, global::renderEngine->renderer().additionalColorTexture3(), 0 ); glFramebufferTexture( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, global::renderEngine->renderer().additionalDepthTexture(), 0 ); if (glbinding::Binding::ObjectLabel.isResolved()) { glObjectLabel(GL_FRAMEBUFFER, _framebuffer, -1, "RenderableModel Framebuffer"); } // Check status GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { LERROR("Framebuffer is not complete"); } glBindFramebuffer(GL_FRAMEBUFFER, 0); // Initialize geometry _geometry->initialize(); _geometry->calculateBoundingRadius(); } void RenderableModel::deinitializeGL() { _geometry->deinitialize(); _geometry.reset(); glDeleteFramebuffers(1, &_framebuffer); glDeleteBuffers(1, &_quadVbo); glDeleteVertexArrays(1, &_quadVao); std::string program = std::string(ProgramName); if (!_vertexShaderPath.empty()) { program += "|vs=" + _vertexShaderPath; } if (!_fragmentShaderPath.empty()) { program += "|fs=" + _fragmentShaderPath; } BaseModule::ProgramObjectManager.release( program, [](ghoul::opengl::ProgramObject* p) { global::renderEngine->removeRenderProgram(p); } ); BaseModule::ProgramObjectManager.release( "ModelOpacityProgram", [](ghoul::opengl::ProgramObject* p) { global::renderEngine->removeRenderProgram(p); } ); _program = nullptr; _quadProgram = nullptr; ghoul::opengl::FramebufferObject::deactivate(); } void RenderableModel::render(const RenderData& data, RendererTasks&) { const double distanceToCamera = glm::distance( data.camera.positionVec3(), data.modelTransform.translation ); // This distance will be enough to render the model as one pixel if the field of // view is 'fov' radians and the screen resolution is 'res' pixels. // Formula from RenderableGlobe constexpr double tfov = 0.5773502691896257; constexpr int res = 2880; const double maxDistance = res * boundingSphere() / tfov; // Don't render if model is too far away if (distanceToCamera >= maxDistance) { return; } _program->activate(); // Model transform and view transform needs to be in double precision const glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * glm::dmat4(data.modelTransform.rotation) * glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)) * glm::scale(_modelTransform.value(), glm::dvec3(_modelScale)); const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform; int nLightSources = 0; _lightIntensitiesBuffer.resize(_lightSources.size()); _lightDirectionsViewSpaceBuffer.resize(_lightSources.size()); for (const std::unique_ptr& lightSource : _lightSources) { if (!lightSource->isEnabled()) { continue; } _lightIntensitiesBuffer[nLightSources] = lightSource->intensity(); _lightDirectionsViewSpaceBuffer[nLightSources] = lightSource->directionViewSpace(data); ++nLightSources; } _program->setUniform( _uniformCache.nLightSources, nLightSources ); _program->setUniform( _uniformCache.lightIntensities, _lightIntensitiesBuffer ); _program->setUniform( _uniformCache.lightDirectionsViewSpace, _lightDirectionsViewSpaceBuffer ); _program->setUniform( _uniformCache.modelViewTransform, glm::mat4(modelViewTransform) ); glm::dmat4 normalTransform = glm::transpose(glm::inverse(modelViewTransform)); _program->setUniform( _uniformCache.normalTransform, glm::mat4(normalTransform) ); _program->setUniform( _uniformCache.projectionTransform, data.camera.projectionMatrix() ); _program->setUniform(_uniformCache.ambientIntensity, _ambientIntensity); _program->setUniform(_uniformCache.diffuseIntensity, _diffuseIntensity); _program->setUniform(_uniformCache.specularIntensity, _specularIntensity); _program->setUniform(_uniformCache.performShading, _performShading); if (!_enableFaceCulling) { glDisable(GL_CULL_FACE); } // Configure blending glEnablei(GL_BLEND, 0); switch (_blendingFuncOption) { case DefaultBlending: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; case AdditiveBlending: glBlendFunc(GL_ONE, GL_ONE); break; case PolygonBlending: glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE); break; case ColorAddingBlending: glBlendFunc(GL_SRC_COLOR, GL_DST_COLOR); break; }; if (!_enableDepthTest) { glDisable(GL_DEPTH_TEST); } // Only render two pass if the model is in any way transparent bool shouldRenderTwice = false; const float o = opacity(); if ((o >= 0.f && o < 1.f) || _geometry->isTransparent()) { setRenderBin(Renderable::RenderBin::PostDeferredTransparent); shouldRenderTwice = true; } else { setRenderBin(_originalRenderBin); } if (!shouldRenderTwice) { // Reset manual depth test _program->setUniform( _uniformCache.performManualDepthTest, false ); _geometry->render(*_program); } else { // Prepare framebuffer GLint defaultFBO = ghoul::opengl::FramebufferObject::getActiveObject(); glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); // Re-bind first texture to use the currently not used Ping-Pong texture in the // FramebufferRenderer glFramebufferTexture( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, global::renderEngine->renderer().additionalColorTexture1(), 0 ); // Check status GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { LERROR("Framebuffer is not complete"); } glDrawBuffers(3, ColorAttachmentArray); glClearBufferfv(GL_COLOR, 1, glm::value_ptr(glm::vec4(0.f, 0.f, 0.f, 0.f))); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Use a manuel depth test to make the models aware of the rest of the scene _program->setUniform( _uniformCache.performManualDepthTest, _enableDepthTest ); // Bind the G-buffer depth texture for a manual depth test towards the rest // of the scene ghoul::opengl::TextureUnit gBufferDepthTextureUnit; gBufferDepthTextureUnit.activate(); glBindTexture( GL_TEXTURE_2D, global::renderEngine->renderer().gBufferDepthTexture() ); _program->setUniform( _uniformCache.gBufferDepthTexture, gBufferDepthTextureUnit ); // Will also need the resolution to get a texture coordinate for the G-buffer // depth texture _program->setUniform( _uniformCache.resolution, glm::vec2(global::windowDelegate->currentDrawBufferResolution()) ); // Render Pass 1 // Render all parts of the model into the new framebuffer without opacity _geometry->render(*_program); _program->deactivate(); // Render pass 2 // Render the whole model into the G-buffer with the correct opacity glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); // Screen-space quad should not be discarded due to depth test, // but we still want to be able to write to the depth buffer -> GL_ALWAYS glEnable(GL_DEPTH_TEST); glDepthFunc(GL_ALWAYS); _quadProgram->activate(); _quadProgram->setUniform(_uniformOpacityCache.opacity, opacity()); // Bind textures ghoul::opengl::TextureUnit colorTextureUnit; colorTextureUnit.activate(); glBindTexture( GL_TEXTURE_2D, global::renderEngine->renderer().additionalColorTexture1() ); _quadProgram->setUniform(_uniformOpacityCache.colorTexture, colorTextureUnit); ghoul::opengl::TextureUnit depthTextureUnit; depthTextureUnit.activate(); glBindTexture( GL_TEXTURE_2D, global::renderEngine->renderer().additionalDepthTexture() ); _quadProgram->setUniform(_uniformOpacityCache.depthTexture, depthTextureUnit); // Will also need the resolution and viewport to get a texture coordinate _quadProgram->setUniform( _uniformOpacityCache.resolution, glm::vec2(global::windowDelegate->currentDrawBufferResolution()) ); GLint vp[4] = { 0 }; global::renderEngine->openglStateCache().viewport(vp); glm::ivec4 viewport = glm::ivec4(vp[0], vp[1], vp[2], vp[3]); _quadProgram->setUniform( _uniformOpacityCache.viewport, viewport[0], viewport[1], viewport[2], viewport[3] ); // Draw glBindVertexArray(_quadVao); glDrawArrays(GL_TRIANGLES, 0, 6); _quadProgram->deactivate(); } // Reset if (!_enableFaceCulling) { glEnable(GL_CULL_FACE); } if (!_enableDepthTest) { glEnable(GL_DEPTH_TEST); } global::renderEngine->openglStateCache().resetBlendState(); global::renderEngine->openglStateCache().resetDepthState(); glActiveTexture(GL_TEXTURE0); } void RenderableModel::update(const UpdateData& data) { if (_program->isDirty()) { _program->rebuildFromFile(); ghoul::opengl::updateUniformLocations(*_program, _uniformCache, UniformNames); } setBoundingSphere(_geometry->boundingRadius() * _modelScale * glm::compMax(data.modelTransform.scale) ); // Set Interaction sphere size to be 10% of the bounding sphere setInteractionSphere(_boundingSphere * 0.1); if (_geometry->hasAnimation() && !_animationStart.empty()) { double relativeTime; double now = data.time.j2000Seconds(); double startTime = data.time.convertTime(_animationStart); double duration = _geometry->animationDuration(); // The animation works in a time range 0 to duration where 0 in the animation is // the given _animationStart time in OpenSpace. The time in OpenSpace then has to // be converted to the animation time range, so the animation knows which // keyframes it should interpolate between for each frame. The conversion is // done in different ways depending on the animation mode. // Explanation: s indicates start time, / indicates animation is played once // forwards, \ indicates animation is played once backwards, time moves to the // right. switch (_animationMode) { case AnimationMode::LoopFromStart: // Start looping from the start time // s//// ... relativeTime = std::fmod(now - startTime, duration); break; case AnimationMode::LoopInfinitely: // Loop both before and after the start time where the model is // in the initial position at the start time. std::fmod is not a // true modulo function, it just calculates the remainder of the division // which can be negative. To make it true modulo it is bumped up to // positive values when it is negative // //s// ... relativeTime = std::fmod(now - startTime, duration); if (relativeTime < 0.0) { relativeTime += duration; } break; case AnimationMode::BounceFromStart: // Bounce from the start position. Bounce means to do the animation // and when it ends, play the animation in reverse to make sure the model // goes back to its initial position before starting again. Avoids a // visible jump from the last position to the first position when loop // starts again // s/\/\/\/\ ... relativeTime = duration - std::abs(fmod(now - startTime, 2 * duration) - duration); break; case AnimationMode::BounceInfinitely: { // Bounce both before and after the start time where the model is // in the initial position at the start time // /\/\s/\/\ ... double modulo = fmod(now - startTime, 2 * duration); if (modulo < 0.0) { modulo += 2 * duration; } relativeTime = duration - std::abs(modulo - duration); break; } case AnimationMode::Once: // Play animation once starting from the start time and stay at the // animation's last position when animation is over // s/ relativeTime = now - startTime; if (relativeTime > duration) { relativeTime = duration; } break; default: throw ghoul::MissingCaseException(); } _geometry->update(relativeTime); } } } // namespace openspace