mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-05 19:19:39 -06:00
add volume slice plane
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <modules/volume/rendering/renderabletimevaryingvolume.h>
|
||||
|
||||
#include <modules/base/basemodule.h>
|
||||
#include <modules/volume/rendering/basicvolumeraycaster.h>
|
||||
#include <modules/volume/rendering/volumeclipplanes.h>
|
||||
#include <modules/volume/transferfunctionhandler.h>
|
||||
@@ -44,7 +45,9 @@
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/lua/lua_helper.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <ghoul/opengl/texture.h>
|
||||
#include <ghoul/opengl/textureunit.h>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
@@ -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<std::string> gridType [[codegen::inlist("Spherical", "Cartesian")]];
|
||||
|
||||
std::optional<std::vector<ghoul::Dictionary>> clipPlanes [[codegen::reference("volume_volumeclipplane")]];
|
||||
|
||||
// [[codegen::verbatim(VolumeSliceNormalInfo.description)]]
|
||||
std::optional<glm::vec3> planeSliceNormal
|
||||
[[codegen::inrange(glm::vec3(- 1.f),glm::vec3(1.f))]];
|
||||
|
||||
// [[codegen::verbatim(VolumeSliceOffsetInfo.description)]]
|
||||
std::optional<float> planeSliceOffset [[codegen::inrange(-0.5f,0.5f)]];
|
||||
|
||||
// [[codegen::verbatim(ShowVolumeSliceInfo.description)]]
|
||||
std::optional<bool> showVolumeSlice;
|
||||
};
|
||||
#include "renderabletimevaryingvolume_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -169,6 +203,23 @@ documentation::Documentation RenderableTimeVaryingVolume::Documentation() {
|
||||
return codegen::doc<Parameters>("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<ghoul::Dictionary> clipPlanes = p.clipPlanes.value_or(std::vector<ghoul::Dictionary>());
|
||||
const std::vector<ghoul::Dictionary> clipPlanes = p.clipPlanes.value_or(
|
||||
std::vector<ghoul::Dictionary>()
|
||||
);
|
||||
_clipPlanes = std::make_shared<volume::VolumeClipPlanes>(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<ghoul::opengl::ProgramObject> {
|
||||
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<GLfloat, 36> 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
|
||||
|
||||
@@ -33,7 +33,14 @@
|
||||
#include <openspace/properties/misc/triggerproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/scalar/intproperty.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <openspace/rendering/transferfunction.h>
|
||||
#include <ghoul/opengl/uniformcache.h>
|
||||
|
||||
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<VolumeClipPlanes> _clipPlanes;
|
||||
|
||||
|
||||
properties::FloatProperty _stepSize;
|
||||
properties::FloatProperty _brightness;
|
||||
properties::FloatProperty _rNormalization;
|
||||
@@ -97,6 +109,26 @@ private:
|
||||
std::unique_ptr<BasicVolumeRaycaster> _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<openspace::TransferFunction> _transferFunction;
|
||||
};
|
||||
|
||||
|
||||
61
modules/volume/shaders/plane_fs.glsl
Normal file
61
modules/volume/shaders/plane_fs.glsl
Normal file
@@ -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;
|
||||
}
|
||||
59
modules/volume/shaders/plane_vs.glsl
Normal file
59
modules/volume/shaders/plane_vs.glsl
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user