Commit e3f4c1fb authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Started work on implementing a particle field flow visualization:

* Introducing ParticleFlowRenderer processor implementing a particle simulation using OpenGL transform feedback
* Added tgt::Shader::selectSubroutine()
* Added tgt::VertexArrayObject::getId()
parent 3a6c7c04
......@@ -1322,6 +1322,16 @@ void Shader::setNormalizedAttribute(GLint index, const Vector4<GLuint>& v) {
glVertexAttrib4Nuiv(index, v.elem);
}
bool Shader::selectSubroutine(ShaderObject::ShaderType type, const std::string& subroutineName) {
GLuint index = glGetSubroutineIndex(id_, type, subroutineName.c_str());
if (index == -1) {
LWARNING("Failed to locate subroutine Location: " << subroutineName);
return false;
}
glUniformSubroutinesuiv(type, 1, &index);
return true;
}
//------------------------------------------------------------------------------
const string ShaderManager::loggerCat_("tgt.Shader.Manager");
......
......@@ -258,6 +258,10 @@ public:
std::string getLinkerLog() const;
// Subroutines
bool selectSubroutine(ShaderObject::ShaderType type, const std::string& subroutineName);
//
// Uniform stuff
//
......
......@@ -80,4 +80,8 @@ namespace tgt {
tgtAssert(false, "Could not find Vertex Attribute location for this BufferObject. You have to add it first using setVertexAttributePointer()!");
}
GLuint VertexArrayObject::getId() const {
return _id;
}
}
\ No newline at end of file
......@@ -30,6 +30,12 @@ namespace tgt {
*/
~VertexArrayObject();
/**
* Returns the OpneGL object ID
* \return _id
*/
GLuint getId() const;
/**
* Binds this VertexArrayObject to the current OpenGL context.
*/
......
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitaet Muenchen
// Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#include "tools/shading.frag"
in float ex_Transparency;
out vec4 out_Color; ///< outgoing fragment color
void main() {
out_Color = vec4(1.0, 0.5, 0.0, 1.0) * ex_Transparency;
#ifdef ENABLE_SHADING___
// compute gradient (needed for shading and normals)
vec3 gradient = ex_Normal;
out_Color.rgb = calculatePhongShading(ex_Position, _lightSource, _cameraPosition, gradient, _color.rgb, _color.rgb, vec3(1.0, 1.0, 1.0));
#endif
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitaet Muenchen
// Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#include "tools/texture3d.frag"
subroutine void RenderPassType();
subroutine uniform RenderPassType RenderPass;
layout(location = 0) in vec3 in_Position;
layout(location = 1) in vec3 in_Velocity;
layout(location = 2) in float in_StartTime;
layout(location = 3) in vec3 in_InitialPosition;
out vec3 ex_Position;
out vec3 ex_Velocity;
out float ex_StartTime;
out float ex_Transparency;
/// Matrix defining model-to-world transformation
uniform mat4 _modelMatrix = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
/// Matrix defining view transformation
uniform mat4 _viewMatrix = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
/// Matrix defining projection transformation
uniform mat4 _projectionMatrix = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
uniform float _time;
uniform float _frameLength;
uniform float _lifetime;
uniform sampler3D _volume;
uniform TextureParameters3D _volumeTextureParams;
subroutine (RenderPassType)
void update() {
if (_time > in_StartTime) {
float age = in_StartTime - _time;
if (age > _lifetime) {
// particle expired, recycle
ex_Position = in_InitialPosition;
ex_Velocity = texture(_volume, (_volumeTextureParams._worldToTextureMatrix * vec4(ex_Position, 1.0)).xyz).xyz;
ex_StartTime = _time;
}
else {
// particle alive, advance
ex_Position = in_Position + (in_Velocity * _frameLength * 0.1);
// compute new velocity by mixture of old velocity and flow at current location, model some inertia
vec3 v = texture(_volume, (_volumeTextureParams._worldToTextureMatrix * vec4(ex_Position, 1.0)).xyz).xyz;
ex_Velocity = mix(in_Velocity, v, 0.5);//smoothstep(0.5, 2.0, v/in_Velocity));
ex_StartTime = in_StartTime;
}
}
else {
ex_Position = in_Position;
ex_Velocity = in_Velocity;
ex_StartTime = in_StartTime;
}
}
subroutine (RenderPassType)
void render() {
float age = _time - in_StartTime;
ex_Transparency = 1.0;// - (age / _lifetime);
gl_Position = _projectionMatrix * (_viewMatrix * (_modelMatrix * vec4(in_Position, 1.0)));
//ex_Position = in_Position;
mat4 normalMatrix = transpose(inverse(_modelMatrix));
//vec4 normalTmp = (normalMatrix * vec4(in_Normal, 0.0));
// ex_Normal = normalize((normalTmp).xyz);
}
void main() {
// call the current subroutine
RenderPass();
}
......@@ -36,6 +36,7 @@ namespace campvis {
, _lsp()
, _imageReader()
, _vectorFieldReader()
, _pfr(&_canvasSize)
, _vectorFieldRenderer(&_canvasSize)
, _sliceRenderer(&_canvasSize)
, _rtc(&_canvasSize)
......@@ -53,6 +54,7 @@ namespace campvis {
addProcessor(&_lsp);
addProcessor(&_imageReader);
addProcessor(&_vectorFieldReader);
addProcessor(&_pfr);
addProcessor(&_vectorFieldRenderer);
addProcessor(&_sliceRenderer);
addProcessor(&_rtc);
......@@ -65,6 +67,7 @@ namespace campvis {
AutoEvaluationPipeline::init();
p_camera.addSharedProperty(&_vectorFieldRenderer.p_camera);
p_camera.addSharedProperty(&_pfr.p_camera);
p_camera.addSharedProperty(&_sliceRenderer.p_camera);
p_sliceNumber.addSharedProperty(&_vectorFieldRenderer.p_sliceNumber);
......@@ -78,6 +81,7 @@ namespace campvis {
_vectorFieldReader.p_url.setValue(CAMPVIS_SOURCE_DIR "/modules/vectorfield/sampledata/result_vec.mhd");
_vectorFieldReader.p_targetImageID.setValue("vectors");
_vectorFieldReader.p_targetImageID.addSharedProperty(&_pfr.p_inputVectors);
_vectorFieldReader.p_targetImageID.addSharedProperty(&_vectorFieldRenderer.p_inputVectors);
_vectorFieldRenderer.p_renderOutput.addSharedProperty(&_rtc.p_firstImageId);
......@@ -90,12 +94,21 @@ namespace campvis {
_vectorFieldRenderer.p_lenThresholdMax.setValue(400.f);
_vectorFieldRenderer.p_sliceOrientation.setValue(3);
_pfr.p_lenThresholdMin.setValue(100.f);
_pfr.p_flowProfile1.setValue(0.4716088614374652f);
_pfr.p_flowProfile2.setValue(0.0638348311845516f);
_pfr.p_flowProfile3.setValue(0.1713471562960614f);
_pfr.p_flowProfile4.setValue(0.1019371804834016f);
_pfr.p_lenThresholdMax.setValue(400.f);
_pfr.p_renderOutput.setValue("particles");
Geometry1DTransferFunction* tf = new Geometry1DTransferFunction(128, tgt::vec2(0.f, 1.f));
tf->addGeometry(TFGeometry1D::createQuad(tgt::vec2(0.f, 1.f), tgt::col4(0, 0, 0, 255), tgt::col4(255, 255, 255, 255)));
_sliceRenderer.p_transferFunction.replaceTF(tf);
_sliceRenderer.p_targetImageID.setValue("slice");
_sliceRenderer.p_targetImageID.addSharedProperty(&_rtc.p_secondImageId);
//_sliceRenderer.p_targetImageID.addSharedProperty(&_rtc.p_secondImageId);
_rtc.p_secondImageId.setValue("particles");
_rtc.p_compositingMethod.selectById("depth");
_rtc.p_targetImageId.setValue("composed");
......
......@@ -32,6 +32,7 @@
#include "modules/base/processors/lightsourceprovider.h"
#include "modules/io/processors/mhdimagereader.h"
#include "modules/vectorfield/processors/particleflowrenderer.h"
#include "modules/vectorfield/processors/vectorfieldrenderer.h"
#include "modules/vis/processors/slicerenderer3d.h"
#include "modules/vis/processors/rendertargetcompositor.h"
......@@ -68,6 +69,7 @@ namespace campvis {
LightSourceProvider _lsp;
MhdImageReader _imageReader;
MhdImageReader _vectorFieldReader;
ParticleFlowRenderer _pfr;
VectorFieldRenderer _vectorFieldRenderer;
SliceRenderer3D _sliceRenderer;
RenderTargetCompositor _rtc;
......
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitaet Muenchen
// Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#include "particleflowrenderer.h"
#include "tgt/buffer.h"
#include "tgt/tgt_math.h"
#include "tgt/logmanager.h"
#include "tgt/shadermanager.h"
#include "tgt/textureunit.h"
#include "tgt/vertexarrayobject.h"
#include "core/datastructures/imagedata.h"
#include "core/datastructures/imagerepresentationgl.h"
#include "core/datastructures/lightsourcedata.h"
#include "core/datastructures/geometrydatafactory.h"
#include "core/datastructures/renderdata.h"
namespace campvis {
const std::string ParticleFlowRenderer::loggerCat_ = "CAMPVis.modules.classification.ParticleFlowRenderer";
ParticleFlowRenderer::ParticleFlowRenderer(IVec2Property* viewportSizeProp)
: VisualizationProcessor(viewportSizeProp)
, p_resetButton("ResetButton", "Reset")
, p_stepButton("StepButton", "Step")
, p_stepInt("StepInt", "Step", 0, 0, 10)
, p_inputVectors("InputImage", "Input Image Vectors", "vectors", DataNameProperty::READ)
, p_renderOutput("RenderOutput", "Output Image", "ParticleFlowRenderer.output", DataNameProperty::WRITE)
, p_arrowSize("ArrowSize", "Arrow Size", 1.f, .001f, 5.f)
, p_lenThresholdMin("LenThresholdMin", "Length Threshold Min", .001f, 0.f, 1000.f, 0.005f)
, p_lenThresholdMax("LenThresholdMax", "Length Threshold Max", 10.f, 0.f, 10000.f, 10.f)
, p_flowProfile1("FlowSpline1", "Flow Profile - Spline 1", 1.f, .0f, 2.f)
, p_flowProfile2("FlowSpline2", "Flow Profile - Spline 2", 1.f, .0f, 2.f)
, p_flowProfile3("FlowSpline3", "Flow Profile - Spline 3", 1.f, .0f, 2.f)
, p_flowProfile4("FlowSpline4", "Flow Profile - Spline 4", 1.f, .0f, 2.f)
, p_Time("time", "Time", 0, 0, 100)
, p_enableShading("EnableShading", "Enable Shading", true)
, p_lightId("LightId", "Input Light Source", "lightsource", DataNameProperty::READ)
, p_camera("Camera", "Camera", tgt::Camera())
, p_sliceNumber("SliceNumber", "Slice Number", 0, 0, 0)
, _arrowGeometry(nullptr)
, _shader(nullptr)
, _positionBufferA(nullptr)
, _positionBufferB(nullptr)
, _velocityBufferA(nullptr)
, _velocityBufferB(nullptr)
, _startTimeBufferA(nullptr)
, _startTimeBufferB(nullptr)
, _initialPositionBuffer(nullptr)
, _vaoA(nullptr)
, _vaoB(nullptr)
{
addProperty(p_resetButton, INVALID_PROPERTIES);
addProperty(p_stepButton, INVALID_RESULT | FIRST_FREE_TO_USE_INVALIDATION_LEVEL);
addProperty(p_stepInt, INVALID_RESULT | FIRST_FREE_TO_USE_INVALIDATION_LEVEL);
addProperty(p_inputVectors, INVALID_RESULT | INVALID_PROPERTIES);
addProperty(p_renderOutput);
addProperty(p_arrowSize);
addProperty(p_lenThresholdMin);
addProperty(p_lenThresholdMax);
addProperty(p_camera);
addProperty(p_sliceNumber);
addProperty(p_Time);
addProperty(p_flowProfile1);
addProperty(p_flowProfile2);
addProperty(p_flowProfile3);
addProperty(p_flowProfile4);
addProperty(p_enableShading, INVALID_RESULT | INVALID_PROPERTIES | INVALID_SHADER);
addProperty(p_lightId);
}
ParticleFlowRenderer::~ParticleFlowRenderer() {
}
void ParticleFlowRenderer::init() {
VisualizationProcessor::init();
_shader = ShdrMgr.loadWithCustomGlslVersion("modules/vectorfield/glsl/particleflowrenderer.vert", "", "modules/vectorfield/glsl/particleflowrenderer.frag", generateGlslHeader(), "400");
const char* outputNames[] = { "ex_Position", "ex_Velocity", "ex_StartTime" };
glTransformFeedbackVaryings(_shader->getID(), 3, outputNames, GL_SEPARATE_ATTRIBS);
_shader->linkProgram();
tgtAssert(_shader->isLinked(), "Shader not linked!");
LGL_ERROR;
_arrowGeometry = GeometryDataFactory::createArrow(12, 0.35f, 0.05f, 0.09f);
}
void ParticleFlowRenderer::deinit() {
ShdrMgr.dispose(_shader);
delete _arrowGeometry;
_arrowGeometry = 0;
delete _positionBufferA;
delete _positionBufferB;
delete _velocityBufferA;
delete _velocityBufferB;
delete _startTimeBufferA;
delete _startTimeBufferB;
delete _initialPositionBuffer;
VisualizationProcessor::deinit();
}
void ParticleFlowRenderer::updateResult(DataContainer& dataContainer) {
const float frameLength = 0.1f;
if (_initialPositionBuffer == 0 ) {
LERROR("Transform-Feedback buffers not initialized.");
return;
}
//GenericImageRepresentationLocal<float, 3>::ScopedRepresentation vectors(dataContainer, p_inputVectors.getValue());
ImageRepresentationGL::ScopedRepresentation vectors(dataContainer, p_inputVectors.getValue());
if (vectors) {
ScopedTypedData<LightSourceData> light(dataContainer, p_lightId.getValue());
if (p_enableShading.getValue() == false || light != nullptr) {
const tgt::Camera& cam = p_camera.getValue();
const tgt::svec3& imgSize = vectors->getSize();
const int sliceNumber = p_sliceNumber.getValue();
float scale = getTemporalFlowScaling((float)p_Time.getValue() / 100.f,
p_flowProfile1.getValue(),
p_flowProfile2.getValue(),
p_flowProfile3.getValue(),
p_flowProfile4.getValue());
glEnable(GL_DEPTH_TEST);
_shader->activate();
_shader->setIgnoreUniformLocationError(true);
_shader->setUniform("_viewportSizeRCP", 1.f / tgt::vec2(getEffectiveViewportSize()));
_shader->setUniform("_projectionMatrix", cam.getProjectionMatrix());
_shader->setUniform("_viewMatrix", cam.getViewMatrix());
if (p_enableShading.getValue() && light != nullptr) {
light->bind(_shader, "_lightSource");
}
_shader->setIgnoreUniformLocationError(false);
if (getInvalidationLevel() & FIRST_FREE_TO_USE_INVALIDATION_LEVEL) {
// stage 1: perform 1 step of particle simulation
_shader->selectSubroutine(tgt::ShaderObject::VERTEX_SHADER, "update");
_shader->setUniform("_time", _currentTime);
_shader->setUniform("_frameLength", frameLength);
_shader->setUniform("_lifetime", 10.f);
tgt::TextureUnit flowUnit;
vectors->bind(_shader, flowUnit, "_volume", "_volumeTextureParams");
glEnable(GL_RASTERIZER_DISCARD);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, _feedback[_drawBuffer]);
glBeginTransformFeedback(GL_POINTS);
glBindVertexArray((_drawBuffer == 1) ? _vaoA->getId() : _vaoB->getId());
glDrawArrays(GL_POINTS, 0, _numParticles);
glEndTransformFeedback();
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
glDisable(GL_RASTERIZER_DISCARD);
_drawBuffer = 1 - _drawBuffer;
_currentTime += frameLength;
validate(FIRST_FREE_TO_USE_INVALIDATION_LEVEL);
LGL_ERROR;
}
// stage 2: render particles
FramebufferActivationGuard fag(this);
createAndAttachColorTexture();
createAndAttachDepthTexture();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_shader->selectSubroutine(tgt::ShaderObject::VERTEX_SHADER, "render");
glBindVertexArray((_drawBuffer == 1) ? _vaoA->getId() : _vaoB->getId());
glPointSize(10.f);
//glDrawTransformFeedback(GL_POINTS, _feedback[1-_drawBuffer]);
glDrawArrays(GL_POINTS, 0, _numParticles);
glPointSize(1.f);
_shader->deactivate();
glDisable(GL_DEPTH_TEST);
LGL_ERROR;
dataContainer.addData(p_renderOutput.getValue(), new RenderData(_fbo));
}
else {
LDEBUG("Could not load light source from DataContainer.");
}
}
else {
LERROR("Could not find suitable input data.");
}
}
void ParticleFlowRenderer::updateProperties(DataContainer& dataContainer) {
p_lightId.setVisible(p_enableShading.getValue());
GenericImageRepresentationLocal<float, 3>::ScopedRepresentation vectors(dataContainer, p_inputVectors.getValue());
if (vectors) {
// initialize buffers etc.
initializeTransformFeedbackBuffers(vectors);
}
else {
LERROR("No suitable input data found or size of images mismatch!");
}
}
void ParticleFlowRenderer::updateShader() {
_shader->setHeaders(generateGlslHeader());
_shader->rebuild();
}
std::string ParticleFlowRenderer::generateGlslHeader() const {
std::string toReturn;
if (p_enableShading.getValue())
toReturn += "#define ENABLE_SHADING\n";
return toReturn;
}
void ParticleFlowRenderer::renderVectorArrow(const GenericImageRepresentationLocal<float, 3>* vectors, const tgt::vec3& position, float scale) {
// avoid overflows
if(position.x >= vectors->getSize().x || position.x < 0 ||
position.y >= vectors->getSize().y || position.y < 0 ||
position.z >= vectors->getSize().z || position.z < 0)
return;
// gather vector direction
const tgt::vec3& dir = vectors->getElement(position);
float len = tgt::length(dir);
// threshold
if(len < p_lenThresholdMin.getValue() || len > p_lenThresholdMax.getValue())
return;
tgt::vec3 up(0.f, 0.f, 1.f);
tgt::vec3 dirNorm = tgt::normalize(dir);
tgt::vec3 axis = tgt::cross(up, dirNorm);
float dotPr = tgt::dot(up, dirNorm);
tgt::mat4 rotationMatrix;
if(abs(dotPr-1)<1.e-3f)
rotationMatrix = tgt::mat4::identity;
else if(abs(dotPr+1)<1.e-3f)
rotationMatrix = tgt::mat4::createRotation(tgt::PIf, tgt::vec3(1.f, 0.f, 0.f));
else {
rotationMatrix = tgt::mat4::createRotation(acos(dotPr), tgt::normalize(axis));
}
const tgt::mat4& voxelToWorldMatrix = vectors->getParent()->getMappingInformation().getVoxelToWorldMatrix();
// compute model matrix
tgt::mat4 modelMatrix = voxelToWorldMatrix * tgt::mat4::createTranslation(position) * rotationMatrix *
tgt::mat4::createScale(tgt::vec3(len * p_arrowSize.getValue())) * tgt::mat4::createScale(tgt::vec3(scale));
// setup shader
//_shader->setUniform("_color", tgt::vec4(dirNorm, 1.f));
float color = (len - p_lenThresholdMin.getValue()) / (p_lenThresholdMax.getValue() - p_lenThresholdMin.getValue());
_shader->setUniform("_color", tgt::vec4(1.f, 1-color, 1-color, 1.f));
// render single ellipsoid
_shader->setUniform("_modelMatrix", modelMatrix);
_arrowGeometry->render(GL_TRIANGLE_STRIP);
}
float ParticleFlowRenderer::getTemporalFlowScaling(float t, float Ct0, float Ct1, float Ct2, float Ct3)
{
const float halfPeriod = 0.5f;
const float spacing = 0.25f;
float St[4];
for(int j = 0; j < 4; ++j) { // iterate over spline positions
float splinePos = spacing * (j+1);