mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2025-12-31 16:30:07 -06:00
947 lines
35 KiB
C++
947 lines
35 KiB
C++
/*****************************************************************************************
|
|
* *
|
|
* OpenSpace *
|
|
* *
|
|
* Copyright (c) 2014-2022 *
|
|
* *
|
|
* 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 <modules/base/rendering/renderablemodel.h>
|
|
|
|
#include <modules/base/basemodule.h>
|
|
#include <openspace/documentation/documentation.h>
|
|
#include <openspace/documentation/verifier.h>
|
|
#include <openspace/engine/globals.h>
|
|
#include <openspace/rendering/framebufferrenderer.h>
|
|
#include <openspace/rendering/renderengine.h>
|
|
#include <openspace/util/time.h>
|
|
#include <openspace/util/timeconversion.h>
|
|
#include <openspace/util/updatestructures.h>
|
|
#include <openspace/scene/scene.h>
|
|
#include <openspace/scene/lightsource.h>
|
|
#include <ghoul/io/model/modelgeometry.h>
|
|
#include <ghoul/filesystem/filesystem.h>
|
|
#include <ghoul/logging/logmanager.h>
|
|
#include <ghoul/misc/invariants.h>
|
|
#include <ghoul/misc/profiling.h>
|
|
#include <ghoul/opengl/framebufferobject.h>
|
|
#include <ghoul/opengl/openglstatecache.h>
|
|
#include <ghoul/opengl/programobject.h>
|
|
#include <ghoul/opengl/textureunit.h>
|
|
#include <filesystem>
|
|
#include <optional>
|
|
|
|
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<std::string, int> 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 openspace::properties::Property::PropertyInfo EnableAnimationInfo = {
|
|
"EnableAnimation",
|
|
"Enable Animation",
|
|
"Enable or disable the animation for the model if it has any"
|
|
};
|
|
|
|
constexpr std::array<const char*, 11> UniformNames = {
|
|
"nLightSources", "lightDirectionsViewSpace", "lightIntensities",
|
|
"modelViewTransform", "normalTransform", "projectionTransform",
|
|
"performShading", "ambientIntensity", "diffuseIntensity",
|
|
"specularIntensity", "gBufferDepthTexture"
|
|
};
|
|
|
|
constexpr std::array<const char*, 5> UniformOpacityNames = {
|
|
"opacity", "colorTexture", "depthTexture", "positionTexture",
|
|
"normalTexture"
|
|
};
|
|
|
|
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,
|
|
|
|
// Weird units
|
|
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<std::variant<ScaleUnit, double>> 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<bool> invertModelScale;
|
|
|
|
// Set if invisible parts (parts with no textures or materials) of the model
|
|
// should be forced to render or not.
|
|
std::optional<bool> forceRenderInvisible;
|
|
|
|
// [[codegen::verbatim(EnableAnimationInfo.description)]]
|
|
std::optional<bool> enableAnimation;
|
|
|
|
// The date and time that the model animation should start.
|
|
// In format 'YYYY MM DD hh:mm:ss'.
|
|
std::optional<std::string> 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<std::variant<AnimationTimeUnit, float>> 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> animationMode;
|
|
|
|
// [[codegen::verbatim(AmbientIntensityInfo.description)]]
|
|
std::optional<float> ambientIntensity;
|
|
|
|
// [[codegen::verbatim(DiffuseIntensityInfo.description)]]
|
|
std::optional<float> diffuseIntensity;
|
|
|
|
// [[codegen::verbatim(SpecularIntensityInfo.description)]]
|
|
std::optional<float> specularIntensity;
|
|
|
|
// [[codegen::verbatim(ShadingInfo.description)]]
|
|
std::optional<bool> performShading;
|
|
|
|
// [[codegen::verbatim(EnableFaceCullingInfo.description)]]
|
|
std::optional<bool> enableFaceCulling;
|
|
|
|
// [[codegen::verbatim(ModelTransformInfo.description)]]
|
|
std::optional<glm::dmat3x3> modelTransform;
|
|
|
|
// [[codegen::verbatim(RotationVecInfo.description)]]
|
|
std::optional<glm::dvec3> rotationVector;
|
|
|
|
// [[codegen::verbatim(LightSourcesInfo.description)]]
|
|
std::optional<std::vector<ghoul::Dictionary>> lightSources
|
|
[[codegen::reference("core_light_source")]];
|
|
|
|
// [[codegen::verbatim(EnableDepthTestInfo.description)]]
|
|
std::optional<bool> enableDepthTest;
|
|
|
|
// [[codegen::verbatim(BlendingOptionInfo.description)]]
|
|
std::optional<std::string> blendingOption;
|
|
|
|
// The path to the vertex shader program that is used instead of the default
|
|
// shader.
|
|
std::optional<std::filesystem::path> vertexShader;
|
|
|
|
// The path to the fragment shader program that is used instead of the default
|
|
// shader.
|
|
std::optional<std::filesystem::path> fragmentShader;
|
|
};
|
|
#include "renderablemodel_codegen.cpp"
|
|
} // namespace
|
|
|
|
namespace openspace {
|
|
|
|
documentation::Documentation RenderableModel::Documentation() {
|
|
return codegen::doc<Parameters>("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<Parameters>(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<Parameters::ScaleUnit>(*p.modelScale)) {
|
|
Parameters::ScaleUnit scaleUnit =
|
|
std::get<Parameters::ScaleUnit>(*p.modelScale);
|
|
DistanceUnit distanceUnit = codegen::map<DistanceUnit>(scaleUnit);
|
|
_modelScale = toMeter(distanceUnit);
|
|
}
|
|
else if (std::holds_alternative<double>(*p.modelScale)) {
|
|
_modelScale = std::get<double>(*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<float>(*p.animationTimeScale)) {
|
|
_geometry->setTimeScale(std::get<float>(*p.animationTimeScale));
|
|
}
|
|
else if (std::holds_alternative<Parameters::AnimationTimeUnit>(
|
|
*p.animationTimeScale)
|
|
)
|
|
{
|
|
Parameters::AnimationTimeUnit animationTimeUnit =
|
|
std::get<Parameters::AnimationTimeUnit>(*p.animationTimeScale);
|
|
TimeUnit timeUnit = codegen::map<TimeUnit>(animationTimeUnit);
|
|
|
|
_geometry->setTimeScale(static_cast<float>(
|
|
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<ghoul::Dictionary> lightsources = *p.lightSources;
|
|
|
|
for (const ghoul::Dictionary& lsDictionary : lightsources) {
|
|
std::unique_ptr<LightSource> 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<LightSource>& 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<ghoul::opengl::ProgramObject> {
|
|
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<ghoul::opengl::ProgramObject> {
|
|
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),
|
|
(void*)(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;
|
|
|
|
if (distanceToCamera < maxDistance) {
|
|
_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>& 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);
|
|
|
|
// Bind the G-buffer depth texture for a manual depth test for the second pass
|
|
ghoul::opengl::TextureUnit gBufferDepthTextureUnit;
|
|
gBufferDepthTextureUnit.activate();
|
|
glBindTexture(
|
|
GL_TEXTURE_2D,
|
|
global::renderEngine->renderer()->gBufferDepthTexture()
|
|
);
|
|
_program->setUniform(
|
|
_uniformCache.gBufferDepthTexture,
|
|
gBufferDepthTextureUnit
|
|
);
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (!_enableFaceCulling) {
|
|
glDisable(GL_CULL_FACE);
|
|
}
|
|
|
|
// Prepare framebuffer
|
|
GLint defaultFBO = ghoul::opengl::FramebufferObject::getActiveObject();
|
|
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
|
|
glDrawBuffers(3, ColorAttachmentArray);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
glClearBufferfv(GL_COLOR, 1, glm::value_ptr(glm::vec4(0.f, 0.f, 0.f, 0.f)));
|
|
|
|
// Render Pass 1
|
|
// Render all parts of the model into the new framebuffer without opacity
|
|
const float o = opacity();
|
|
if ((o >= 0.f && o < 1.f) || !_enableDepthTest || _geometry->isTransparent()) {
|
|
setRenderBin(Renderable::RenderBin::PostDeferredTransparent);
|
|
}
|
|
else {
|
|
setRenderBin(_originalRenderBin);
|
|
}
|
|
|
|
_geometry->render(*_program);
|
|
|
|
if (!_enableFaceCulling) {
|
|
glEnable(GL_CULL_FACE);
|
|
}
|
|
_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 positionTextureUnit;
|
|
positionTextureUnit.activate();
|
|
glBindTexture(
|
|
GL_TEXTURE_2D,
|
|
global::renderEngine->renderer()->additionalColorTexture2()
|
|
);
|
|
_quadProgram->setUniform(
|
|
_uniformOpacityCache.positionTexture,
|
|
positionTextureUnit
|
|
);
|
|
|
|
ghoul::opengl::TextureUnit normalTextureUnit;
|
|
normalTextureUnit.activate();
|
|
glBindTexture(
|
|
GL_TEXTURE_2D,
|
|
global::renderEngine->renderer()->additionalColorTexture3()
|
|
);
|
|
_quadProgram->setUniform(_uniformOpacityCache.normalTexture, normalTextureUnit);
|
|
|
|
ghoul::opengl::TextureUnit depthTextureUnit;
|
|
depthTextureUnit.activate();
|
|
glBindTexture(
|
|
GL_TEXTURE_2D,
|
|
global::renderEngine->renderer()->additionalDepthTexture()
|
|
);
|
|
_quadProgram->setUniform(_uniformOpacityCache.depthTexture, depthTextureUnit);
|
|
|
|
// Draw
|
|
glBindVertexArray(_quadVao);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
_quadProgram->deactivate();
|
|
|
|
// Reset
|
|
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 - 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 - 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
|