diff --git a/modules/volume/rendering/renderabletimevaryingvolume.cpp b/modules/volume/rendering/renderabletimevaryingvolume.cpp index 68e6e34a02..48a48ba61a 100644 --- a/modules/volume/rendering/renderabletimevaryingvolume.cpp +++ b/modules/volume/rendering/renderabletimevaryingvolume.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -44,7 +45,9 @@ #include #include #include +#include #include +#include #include #include @@ -133,6 +136,27 @@ namespace { openspace::properties::Property::Visibility::AdvancedUser }; + constexpr openspace::properties::Property::PropertyInfo VolumeSliceNormalInfo = { + "VolumeSliceNormal", + "Slice Plane Normal", + "Normal of the volume slice plane.", + openspace::properties::Property::Visibility::AdvancedUser + }; + + constexpr openspace::properties::Property::PropertyInfo VolumeSliceOffsetInfo = { + "VolumeSliceOffset", + "Slice Plane Offset", + "Offset of the volume slice plane in the volume.", + openspace::properties::Property::Visibility::AdvancedUser + }; + + constexpr openspace::properties::Property::PropertyInfo ShowVolumeSliceInfo = { + "Enabled", + "Show Volume Slice", + "Determine whether the volume slice plane should be visualized or not.", + openspace::properties::Property::Visibility::AdvancedUser + }; + struct [[codegen::Dictionary(RenderableTimeVaryingVolume)]] Parameters { // [[codegen::verbatim(SourceDirectoryInfo.description)]] std::filesystem::path sourceDirectory [[codegen::directory()]]; @@ -159,6 +183,16 @@ namespace { std::optional gridType [[codegen::inlist("Spherical", "Cartesian")]]; std::optional> clipPlanes [[codegen::reference("volume_volumeclipplane")]]; + + // [[codegen::verbatim(VolumeSliceNormalInfo.description)]] + std::optional planeSliceNormal + [[codegen::inrange(glm::vec3(- 1.f),glm::vec3(1.f))]]; + + // [[codegen::verbatim(VolumeSliceOffsetInfo.description)]] + std::optional planeSliceOffset [[codegen::inrange(-0.5f,0.5f)]]; + + // [[codegen::verbatim(ShowVolumeSliceInfo.description)]] + std::optional showVolumeSlice; }; #include "renderabletimevaryingvolume_codegen.cpp" } // namespace @@ -169,6 +203,23 @@ documentation::Documentation RenderableTimeVaryingVolume::Documentation() { return codegen::doc("volume_renderable_timevaryingvolume"); } +RenderableTimeVaryingVolume::VolumeSliceSettings::VolumeSliceSettings() + : properties::PropertyOwner({ + "SliceSettings", + "Slice Settings", + "Settings for the volume slice plane." + }) + , normal(VolumeSliceNormalInfo, glm::vec3{ 1.f, 0.f, 0.f }) + , offset(VolumeSliceOffsetInfo, 0, -0.5f, 0.5f) + , shouldRenderSlice(ShowVolumeSliceInfo, false) +{ + addProperty(shouldRenderSlice); + addProperty(normal); + addProperty(offset); +} + + + RenderableTimeVaryingVolume::RenderableTimeVaryingVolume( const ghoul::Dictionary& dictionary) : Renderable(dictionary) @@ -208,7 +259,9 @@ RenderableTimeVaryingVolume::RenderableTimeVaryingVolume( _secondsBefore = p.secondsBefore.value_or(_secondsBefore); _secondsAfter = p.secondsAfter; - const std::vector clipPlanes = p.clipPlanes.value_or(std::vector()); + const std::vector clipPlanes = p.clipPlanes.value_or( + std::vector() + ); _clipPlanes = std::make_shared(clipPlanes); if (p.gridType.has_value()) { @@ -218,6 +271,21 @@ RenderableTimeVaryingVolume::RenderableTimeVaryingVolume( addProperty(_brightness); addProperty(Fadeable::_opacity); + + _volumeSlice.normal = p.planeSliceNormal.value_or(_volumeSlice.normal); + _volumeSlice.offset = p.planeSliceOffset.value_or(_volumeSlice.offset); + _volumeSlice.shouldRenderSlice = p.showVolumeSlice.value_or( + _volumeSlice.shouldRenderSlice + ); + + _volumeSlice.normal.onChange([this]() { + _slicePlaneIsDirty = true; + }); + _volumeSlice.offset.onChange([this]() { + _slicePlaneIsDirty = true; + }); + + addPropertySubOwner(_volumeSlice); } RenderableTimeVaryingVolume::~RenderableTimeVaryingVolume() {} @@ -355,6 +423,22 @@ void RenderableTimeVaryingVolume::initializeGL() { ); _raycaster->setTransferFunction(_transferFunction); }); + + // Slice plane + glGenVertexArrays(1, &_quad); + glGenBuffers(1, &_vertexPositionBuffer); + createPlane(); + _shader = BaseModule::ProgramObjectManager.request( + "SlicePlane", + []() -> std::unique_ptr { + return global::renderEngine->buildRenderProgram( + "SlicePlane", + absPath("${MODULE_VOLUME}/shaders/plane_vs.glsl"), + absPath("${MODULE_VOLUME}/shaders/plane_fs.glsl") + ); + } + ); + ghoul::opengl::updateUniformLocations(*_shader, _uniformCache); } void RenderableTimeVaryingVolume::loadTimestepMetadata(const std::filesystem::path& path) @@ -457,20 +541,11 @@ void RenderableTimeVaryingVolume::update(const UpdateData&) { if (_raycaster) { Timestep* t = currentTimestep(); - // Set scale and translation matrices: - // The original data cube is a unit cube centered in 0 - // ie with lower bound from (-0.5, -0.5, -0.5) and upper bound (0.5, 0.5, 0.5) + // Set scale matrix: The original data cube is a unit cube centered in 0, i.e., + // with lower bound from (-0.5, -0.5, -0.5) and upper bound (0.5, 0.5, 0.5) if (t && t->texture) { if (_raycaster->gridType() == volume::VolumeGridType::Cartesian) { - const glm::dvec3 scale = - t->metadata.upperDomainBound - t->metadata.lowerDomainBound; - const glm::dvec3 translation = - (t->metadata.lowerDomainBound + t->metadata.upperDomainBound) * 0.5f; - - glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), translation); - const glm::dmat4 scaleMatrix = glm::scale(glm::dmat4(1.0), scale); - modelTransform = modelTransform * scaleMatrix; - _raycaster->setModelTransform(glm::mat4(modelTransform)); + _raycaster->setModelTransform(calculateModelTransform()); } else { // The diameter is two times the maximum radius. @@ -498,6 +573,11 @@ void RenderableTimeVaryingVolume::render(const RenderData& data, RendererTasks& if (_raycaster && _raycaster->volumeTexture()) { tasks.raycasterTasks.push_back({ _raycaster.get(), data }); } + + if (_volumeSlice.shouldRenderSlice) { + renderVolumeSlice(data); + } + } bool RenderableTimeVaryingVolume::isReady() const { @@ -509,6 +589,119 @@ void RenderableTimeVaryingVolume::deinitializeGL() { global::raycasterManager->detachRaycaster(*_raycaster); _raycaster = nullptr; } + + glDeleteVertexArrays(1, &_quad); + _quad = 0; + + glDeleteBuffers(1, &_vertexPositionBuffer); + _vertexPositionBuffer = 0; + + BaseModule::ProgramObjectManager.release( + "PlaneSlice", + [](ghoul::opengl::ProgramObject* p) { + global::renderEngine->removeRenderProgram(p); + } + ); + _shader = nullptr; +} + +glm::mat4 RenderableTimeVaryingVolume::calculateModelTransform() { + Timestep* t = currentTimestep(); + + if (!t) { + return glm::mat4(1.0f); + } + + const glm::dvec3 scale = + t->metadata.upperDomainBound - t->metadata.lowerDomainBound; + //const glm::dvec3 translation = + // (t->metadata.lowerDomainBound + t->metadata.upperDomainBound) * 0.5f; + + //glm::dmat4 translationMatrix = glm::translate(glm::dmat4(1.0), translation); + const glm::dmat4 scaleMatrix = glm::scale(glm::dmat4(1.0), scale); + glm::dmat4 modelTransform = /*translationMatrix **/ scaleMatrix; + return glm::mat4(modelTransform); +} + +void RenderableTimeVaryingVolume::createPlane() const { + const std::array vertexData = { + // x y + -1.f, -1.f, + 1.f, 1.f, + -1.f, 1.f, + -1.f, -1.f, + 1.f, -1.f, + 1.f, 1.f, + }; + + glBindVertexArray(_quad); + glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 2, nullptr); + + glBindVertexArray(0); +} + +void RenderableTimeVaryingVolume::renderVolumeSlice(const RenderData& data) { + Timestep* t = currentTimestep(); + + if (!t || !t->texture) { + return; + } + + _shader->activate(); + _shader->setUniform(_uniformCache.offset, _volumeSlice.offset); + _shader->setUniform(_uniformCache.normal, _volumeSlice.normal); + + _shader->setUniform(_uniformCache.modelTransform, + glm::mat4(calculateModelTransform()) + ); + + auto [modelTransform, modelViewTransform, modelViewProjectionTransform] = + calcAllTransforms(data); + + _shader->setUniform(_uniformCache.modelViewProjection, + glm::mat4(modelViewProjectionTransform) + ); + _shader->setUniform(_uniformCache.modelViewTransform, glm::mat4(modelViewTransform)); + + _shader->setUniform(_uniformCache.volumeResolution, + glm::vec3(t->metadata.dimensions) + ); + + // Upload volume texture + ghoul::opengl::TextureUnit textureUnit; + textureUnit.activate(); + t->texture->bind(); + _shader->setUniform(_uniformCache.volumeTexture, textureUnit); + // Upload transfer function texture + ghoul::opengl::TextureUnit transferFunctionUnit; + transferFunctionUnit.activate(); + _transferFunction->texture().bind(); + _shader->setUniform(_uniformCache.transferFunction, transferFunctionUnit); + + if (_slicePlaneIsDirty) { + // Create basis to convert points on the 2D plane into volume 3D coordinates + glm::vec3 n = glm::normalize(_volumeSlice.normal.value()); + + glm::vec3 up = glm::abs(n.z) < 0.999f ? glm::vec3(0.f, 0.f, 1.f) + : glm::vec3(1.f, 0.f, 0.f); + + glm::vec3 tangent = glm::normalize(glm::cross(up, n)); + glm::vec3 bitangent = glm::normalize(glm::cross(n, tangent)); + + _basisTransform = glm::mat3(tangent, bitangent, n); + } + + _shader->setUniform(_uniformCache.basis, _basisTransform); + + glDisable(GL_CULL_FACE); + glBindVertexArray(_quad); + glDrawArrays(GL_TRIANGLES, 0, 6); + glBindVertexArray(0); + glEnable(GL_CULL_FACE); + _shader->deactivate(); } } // namespace openspace::volume diff --git a/modules/volume/rendering/renderabletimevaryingvolume.h b/modules/volume/rendering/renderabletimevaryingvolume.h index 9a9520d5c6..03bd7d9204 100644 --- a/modules/volume/rendering/renderabletimevaryingvolume.h +++ b/modules/volume/rendering/renderabletimevaryingvolume.h @@ -33,7 +33,14 @@ #include #include #include +#include #include +#include + +namespace ghoul::opengl { + class ProgramObject; + class TextureUnit; +} // namespace ghoul::opengl namespace openspace { class Histogram; @@ -78,9 +85,14 @@ private: void loadTimestepMetadata(const std::filesystem::path& path); + glm::mat4 calculateModelTransform(); + void createPlane() const; + void renderVolumeSlice(const RenderData& data); + properties::OptionProperty _gridType; std::shared_ptr _clipPlanes; + properties::FloatProperty _stepSize; properties::FloatProperty _brightness; properties::FloatProperty _rNormalization; @@ -97,6 +109,26 @@ private: std::unique_ptr _raycaster; bool _invertDataAtZ; + + struct VolumeSliceSettings : properties::PropertyOwner { + VolumeSliceSettings(); + + properties::Vec3Property normal; + properties::FloatProperty offset; + properties::BoolProperty shouldRenderSlice; + }; + + VolumeSliceSettings _volumeSlice; + + bool _slicePlaneIsDirty = true; + glm::mat3 _basisTransform = glm::mat3(1.0f); + + GLuint _quad = 0; + GLuint _vertexPositionBuffer = 0; + ghoul::opengl::ProgramObject* _shader = nullptr; + UniformCache(normal, offset, basis, volumeTexture, transferFunction, modelTransform, + modelViewTransform, modelViewProjection, volumeResolution) _uniformCache; + std::shared_ptr _transferFunction; }; diff --git a/modules/volume/shaders/plane_fs.glsl b/modules/volume/shaders/plane_fs.glsl new file mode 100644 index 0000000000..0948a56d67 --- /dev/null +++ b/modules/volume/shaders/plane_fs.glsl @@ -0,0 +1,61 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * 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 "fragment.glsl" + +in vec3 texCoord; +in vec4 positionCameraSpace; +out vec4 outColor; + +uniform sampler3D volumeTexture; +uniform sampler1D transferFunction; +uniform vec3 volumeResolution; + +Fragment getFragment() { + // Discard fragments that lie outside the volume bounds + if (any(lessThan(texCoord, vec3(0.0))) || any(greaterThan(texCoord, vec3(1.0)))) { + discard; + } + + // Fixes color artifact at the edges, TODO come up with a better solution that allows + // lookup of texture coordinates at the extremes (0,1) + vec3 texelSize = 1.0 / volumeResolution; + vec3 coords = clamp(texCoord, texelSize, 1.0 - texelSize ); + + Fragment frag; + vec4 value = texture(volumeTexture, coords); + frag.color = texture(transferFunction, value.r); + + vec4 position = positionCameraSpace; + frag.depth = -position.z; + // TODO: ask alex about wether or not to pre multiply alpha values and what + // that means in terms of the interpretation of the values + + // TODO: Enable this as a property to show/hide background. Must also fix depth values + // so that the entire background is shown. Right now trails for example are not shown. + // Also need to fix so that the volume is shown behind the cut plane as well. + frag.color.rgb *= frag.color.a; + frag.color.a = 1.0; + + return frag; +} diff --git a/modules/volume/shaders/plane_vs.glsl b/modules/volume/shaders/plane_vs.glsl new file mode 100644 index 0000000000..4191bad709 --- /dev/null +++ b/modules/volume/shaders/plane_vs.glsl @@ -0,0 +1,59 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * 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. * + ****************************************************************************************/ + +#version __CONTEXT__ +#include "PowerScaling/powerScaling_vs.hglsl" + +layout(location = 0) in vec2 in_position; + +out vec3 texCoord; +out vec4 positionCameraSpace; + +uniform vec3 normal; +uniform float offset; +uniform mat3 basis; + +uniform mat4 modelViewProjection; +uniform mat4 modelViewTransform; +uniform mat4 modelTransform; + +void main() { + // The quad is covers -1.0 to 1.0 while the volume is defined for -0.5 to 0.5 + vec2 pos = in_position * 0.5; // map quad to the range -0.5 to 0.5 + // Scale plane so that it will cover entire volume on the diagonal + float quadScale = 1.42; + pos *= quadScale; + vec3 sliceCenter = normal * offset; + // Convert 2D quad coordinates into 3D slice position + vec3 localPos = sliceCenter + basis * vec3(pos, 0.0); + // Remap texture coordinates to the range 0 to 1 + texCoord = localPos + 0.5; + + positionCameraSpace = modelViewTransform * modelTransform * vec4(localPos, 1.0); + + vec4 positionClipSpace = modelViewProjection * modelTransform * vec4(localPos, 1.0); + vec4 positionScreenSpace = z_normalization(positionClipSpace); + + gl_Position = positionScreenSpace; +} \ No newline at end of file