Commit 4a842da9 authored by Michael Migliore's avatar Michael Migliore

Add a panoramic projection pass

This pass render the scene in all direction and projects those renders
using different types of projection (equirectangular or azimuthal projection)
parent 0857be6f
......@@ -71,6 +71,7 @@ set(Module_SRCS
vtkOpenGLVertexBufferObjectGroup.cxx
vtkOrderIndependentTranslucentPass.cxx
vtkOverlayPass.cxx
vtkPanoramicProjectionPass.cxx
vtkPixelBufferObject.cxx
vtkPointFillPass.cxx
vtkRenderPassCollection.cxx
......
......@@ -26,6 +26,7 @@ vtk_add_test_cxx(vtkRenderingOpenGL2CxxTests tests
TestMultiTexturing.cxx
TestOffscreenRenderingResize.cxx
TestOrderIndependentTranslucentPass.cxx
TestPanoramicProjectionPass.cxx,NO_DATA
TestPointFillPass.cxx
TestPointGaussianSelection.cxx,NO_DATA
TestPropPicker2Renderers.cxx,NO_DATA
......
/*=========================================================================
Program: Visualization Toolkit
Module: TestPanoramicProjectionPass.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
// This test covers the tone mapping post-processing render pass.
// It renders an opaque actor with a lot of lights.
#include "vtkRegressionTestImage.h"
#include "vtkTestUtilities.h"
#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkCameraPass.h"
#include "vtkCullerCollection.h"
#include "vtkPanoramicProjectionPass.h"
#include "vtkLight.h"
#include "vtkLightsPass.h"
#include "vtkOpaquePass.h"
#include "vtkOpenGLRenderer.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
#include "vtkRenderPassCollection.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkSequencePass.h"
#include "vtkSphereSource.h"
int TestPanoramicProjectionPass(int argc, char* argv[])
{
vtkNew<vtkRenderWindow> renWin;
renWin->SetSize(400, 400);
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(renWin);
vtkNew<vtkSphereSource> sphere;
sphere->SetRadius(1.0);
vtkNew<vtkRenderer> renderer;
renderer->GetCullers()->RemoveAllItems();
renderer->SetBackground(1.0, 1.0, 1.0);
renderer->AutomaticLightCreationOff();
vtkNew<vtkLight> light;
light->SetPosition(0.0, 10.0, 0.0);
light->SetFocalPoint(0.0, 0.0, 0.0);
light->SetLightTypeToSceneLight();
renderer->AddLight(light);
// custom passes
vtkNew<vtkCameraPass> cameraP;
vtkNew<vtkSequencePass> seq;
vtkNew<vtkOpaquePass> opaque;
vtkNew<vtkLightsPass> lights;
vtkNew<vtkRenderPassCollection> passes;
passes->AddItem(lights);
passes->AddItem(opaque);
seq->SetPasses(passes);
cameraP->SetDelegatePass(seq);
vtkNew<vtkPanoramicProjectionPass> projectionP;
projectionP->SetProjectionTypeToAzimuthal();
projectionP->SetAngle(360.0);
projectionP->SetDelegatePass(cameraP);
vtkOpenGLRenderer::SafeDownCast(renderer)->SetPass(projectionP);
renWin->AddRenderer(renderer);
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(sphere->GetOutputPort());
for (int i = 0; i < 4; i++)
{
double f = i & 1 ? -2.0 : 2.0;
double x = i & 2 ? 1.0 : 0.0;
double c[3] = { static_cast<double>((i + 1) & 1),
static_cast<double>(((i + 1) >> 1) & 1),
static_cast<double>(((i + 1) >> 2) & 1) };
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
actor->SetPosition(f * x, 0.0, f * (1.0 - x));
actor->GetProperty()->SetColor(c);
renderer->AddActor(actor);
}
vtkNew<vtkCamera> camera;
camera->SetPosition(0.0, 0.0, 0.0);
camera->SetFocalPoint(0.0, 0.0, 1.0);
camera->SetViewUp(0.0, 1.0, 0.0);
renderer->SetActiveCamera(camera);
renWin->Render();
int retVal = vtkRegressionTestImage(renWin);
if (retVal == vtkRegressionTester::DO_INTERACTOR)
{
iren->Start();
}
return !retVal;
}
11e1f1020ba1c5681a90095c9cc2415c9d20c0ebff5941c352add3bd93d79404fbe87987aec172dc0e4f8e925c6323836beefcc75988ef912337ad9b2b4ea3a5
/*=========================================================================
Program: Visualization Toolkit
Module: vtkPanoramicProjectionPass.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkPanoramicProjectionPass.h"
#include "vtkCamera.h"
#include "vtkCullerCollection.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLError.h"
#include "vtkOpenGLFramebufferObject.h"
#include "vtkOpenGLQuadHelper.h"
#include "vtkOpenGLRenderer.h"
#include "vtkOpenGLRenderUtilities.h"
#include "vtkOpenGLRenderWindow.h"
#include "vtkOpenGLShaderCache.h"
#include "vtkOpenGLState.h"
#include "vtkOpenGLVertexArrayObject.h"
#include "vtkPerspectiveTransform.h"
#include "vtkRenderState.h"
#include "vtkRenderer.h"
#include "vtkShaderProgram.h"
#include "vtkTextureObject.h"
#include "vtkTransform.h"
#include <sstream>
vtkStandardNewMacro(vtkPanoramicProjectionPass);
// ----------------------------------------------------------------------------
void vtkPanoramicProjectionPass::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os,indent);
os << indent << "CubeResolution: " << this->CubeResolution << "\n";
os << indent << "ProjectionType: ";
switch (this->ProjectionType)
{
case Equirectangular:
os << "Equirectangular\n";
break;
case Azimuthal:
os << "Azimuthal\n";
break;
default:
os << "Unknown\n";
}
os << indent << "Angle: " << this->Angle << "\n";
}
// ----------------------------------------------------------------------------
void vtkPanoramicProjectionPass::Render(const vtkRenderState* s)
{
vtkOpenGLClearErrorMacro();
this->NumberOfRenderedProps = 0;
vtkRenderer* r = s->GetRenderer();
vtkOpenGLRenderWindow* renWin = static_cast<vtkOpenGLRenderWindow*>(r->GetRenderWindow());
vtkOpenGLState* ostate = renWin->GetState();
vtkOpenGLState::ScopedglEnableDisable bsaver(ostate, GL_BLEND);
vtkOpenGLState::ScopedglEnableDisable dsaver(ostate, GL_DEPTH_TEST);
if (this->DelegatePass == nullptr)
{
vtkWarningMacro("no delegate in vtkPanoramicProjectionPass.");
return;
}
int x, y, w, h;
r->GetTiledSizeAndOrigin(&w, &h, &x, &y);
// create FBO and cubemap
this->InitOpenGLResources(renWin);
ostate->vtkglViewport(0, 0, this->CubeResolution, this->CubeResolution);
ostate->vtkglScissor(0, 0, this->CubeResolution, this->CubeResolution);
// render all direction into a cubemap face
for (int faceIndex = GL_TEXTURE_CUBE_MAP_POSITIVE_X; faceIndex <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
faceIndex++)
{
this->RenderOnFace(s, faceIndex);
}
ostate->vtkglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ostate->vtkglDisable(GL_BLEND);
ostate->vtkglDisable(GL_DEPTH_TEST);
ostate->vtkglDisable(GL_SCISSOR_TEST);
ostate->vtkglViewport(x, y, w, h);
ostate->vtkglScissor(x, y, w, h);
this->Project(renWin);
vtkOpenGLCheckErrorMacro("failed after Render");
}
// ----------------------------------------------------------------------------
void vtkPanoramicProjectionPass::InitOpenGLResources(vtkOpenGLRenderWindow* renWin)
{
if (this->CubeMapTexture && this->CubeMapTexture->GetMTime() < this->MTime)
{
this->CubeMapTexture->Delete();
this->CubeMapTexture = nullptr;
}
if (this->CubeMapTexture == nullptr)
{
// the cubemap is used to render the complete scene
// linear interpolation give better results at lower resolutions
// wrap mode must be clamped to avoid artifacts on seams
// alpha channel is also mandatory for remote rendering
this->CubeMapTexture = vtkTextureObject::New();
this->CubeMapTexture->SetContext(renWin);
this->CubeMapTexture->SetMinificationFilter(vtkTextureObject::Linear);
this->CubeMapTexture->SetMagnificationFilter(vtkTextureObject::Linear);
this->CubeMapTexture->SetWrapS(vtkTextureObject::ClampToEdge);
this->CubeMapTexture->SetWrapT(vtkTextureObject::ClampToEdge);
this->CubeMapTexture->SetWrapR(vtkTextureObject::ClampToEdge);
this->CubeMapTexture->CreateCubeFromRaw(
this->CubeResolution, this->CubeResolution, 4, VTK_UNSIGNED_CHAR, nullptr);
}
if (this->FrameBufferObject && this->FrameBufferObject->GetMTime() < this->MTime)
{
this->FrameBufferObject->Delete();
this->FrameBufferObject = nullptr;
}
if (this->FrameBufferObject == nullptr)
{
this->FrameBufferObject = vtkOpenGLFramebufferObject::New();
this->FrameBufferObject->SetContext(renWin);
this->FrameBufferObject->SaveCurrentBindingsAndBuffers();
this->FrameBufferObject->Resize(this->CubeResolution, this->CubeResolution);
this->FrameBufferObject->AddDepthAttachment(vtkOpenGLFramebufferObject::GetDrawMode());
this->FrameBufferObject->RestorePreviousBindingsAndBuffers();
}
}
// ----------------------------------------------------------------------------
void vtkPanoramicProjectionPass::Project(vtkOpenGLRenderWindow* renWin)
{
if (this->QuadHelper && this->MTime > this->QuadHelper->ShaderChangeValue)
{
delete this->QuadHelper;
this->QuadHelper = nullptr;
}
if (!this->QuadHelper)
{
std::string FSSource = vtkOpenGLRenderUtilities::GetFullScreenQuadFragmentShaderTemplate();
vtkShaderProgram::Substitute(FSSource,
"//VTK::FSQ::Decl",
"uniform samplerCube source;\n"
"uniform float angle;\n"
"uniform vec2 scale;\n"
"uniform vec2 shift;\n\n");
std::stringstream ss;
// in case of tile rendering, we need to scale and shift coords
ss << " float x = texCoord.x * scale.x + shift.x;\n"
" float y = texCoord.y * scale.y + shift.y;\n";
switch (this->ProjectionType)
{
case Equirectangular:
ss << " const float pi = 3.14159265359;\n"
" float phi = y * pi;\n"
" float theta = angle * x + (pi - 0.5 * angle);\n"
" vec3 dir = vec3(-sin(phi)*sin(theta), cos(phi), -sin(phi)*cos(theta));\n"
" gl_FragData[0] = texture(source, dir);\n";
break;
case Azimuthal:
ss << " vec2 v = 2.0 * vec2(x - 0.5, 0.5 - y);\n"
" float phi = length(v);\n"
" if (phi <= 1.0)\n"
" {\n"
" phi *= 0.5 * angle;\n"
" float theta = atan(v.y, v.x);\n"
" vec3 dir = vec3(sin(phi)*cos(theta), sin(theta)*sin(phi), cos(phi));\n"
" gl_FragData[0] = texture(source, dir);\n"
" }\n"
" else\n"
" {\n"
" gl_FragData[0] = vec4(0.0, 0.0, 0.0, 1.0);\n"
" }\n";
break;
default:
vtkErrorMacro("Projection type unknown");
break;
}
vtkShaderProgram::Substitute(FSSource, "//VTK::FSQ::Impl", ss.str());
this->QuadHelper = new vtkOpenGLQuadHelper(renWin,
vtkOpenGLRenderUtilities::GetFullScreenQuadVertexShader().c_str(),
FSSource.c_str(),
"");
this->QuadHelper->ShaderChangeValue = this->MTime;
}
else
{
renWin->GetShaderCache()->ReadyShaderProgram(this->QuadHelper->Program);
}
if (!this->QuadHelper->Program || !this->QuadHelper->Program->GetCompiled())
{
vtkErrorMacro("Couldn't build the shader program.");
return;
}
this->CubeMapTexture->Activate();
this->QuadHelper->Program->SetUniformi("source", this->CubeMapTexture->GetTextureUnit());
this->QuadHelper->Program->SetUniformf("angle", vtkMath::RadiansFromDegrees(this->Angle));
double viewport[4];
renWin->GetTileViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
float scale[2] = { static_cast<float>(viewport[2] - viewport[0]),
static_cast<float>(viewport[3] - viewport[1]) };
float shift[2] = { static_cast<float>(viewport[0]), static_cast<float>(viewport[1]) };
this->QuadHelper->Program->SetUniform2f("scale", scale);
this->QuadHelper->Program->SetUniform2f("shift", shift);
this->QuadHelper->Render();
this->CubeMapTexture->Deactivate();
}
// ----------------------------------------------------------------------------
void vtkPanoramicProjectionPass::RenderOnFace(const vtkRenderState* s, int faceIndex)
{
if (faceIndex == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z && this->Angle < 180.0)
{
// it's not necessary to render -Z if angle is less than 180 degrees
return;
}
vtkOpenGLRenderer* r = vtkOpenGLRenderer::SafeDownCast(s->GetRenderer());
vtkRenderState s2(r);
s2.SetPropArrayAndCount(s->GetPropArray(), s->GetPropArrayCount());
// Adapt camera to square rendering
vtkSmartPointer<vtkCamera> oldCamera = r->GetActiveCamera();
vtkNew<vtkCamera> newCamera;
newCamera->DeepCopy(oldCamera);
r->SetActiveCamera(newCamera);
if (r->GetRenderWindow()->GetStereoRender())
{
double right[3];
double pos[3];
double sign = newCamera->GetLeftEye() ? -1.0 : 1.0;
vtkMath::Cross(newCamera->GetDirectionOfProjection(), newCamera->GetViewUp(), right);
newCamera->GetPosition(pos);
newCamera->SetPosition(pos[0], pos[1] + sign * newCamera->GetEyeSeparation(), pos[2]);
}
double range[2];
newCamera->GetClippingRange(range);
vtkNew<vtkPerspectiveTransform> perspectiveTransform;
// the fov is 90 degree in each direction, the frustum can be simplified
// xmin and ymin are -near and xmax and ymax are +near
perspectiveTransform->Frustum(-range[0], range[0], -range[0], range[0], range[0], range[1]);
// lights should not be rotated with camera, so we use an inverse transform for lights
vtkNew<vtkTransform> lightsTransform;
switch (faceIndex)
{
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
newCamera->Yaw(-90);
lightsTransform->RotateY(90);
break;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
newCamera->Yaw(90);
lightsTransform->RotateY(-90);
break;
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
newCamera->Pitch(-90);
lightsTransform->RotateX(90);
break;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
newCamera->Pitch(90);
lightsTransform->RotateX(-90);
break;
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
break;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
newCamera->Yaw(180);
lightsTransform->RotateY(180);
break;
default:
break;
}
newCamera->UseExplicitProjectionTransformMatrixOn();
newCamera->SetExplicitProjectionTransformMatrix(perspectiveTransform->GetMatrix());
s2.SetFrameBuffer(this->FrameBufferObject);
this->CubeMapTexture->Activate();
this->FrameBufferObject->SaveCurrentBindingsAndBuffers();
this->FrameBufferObject->RemoveColorAttachments(vtkOpenGLFramebufferObject::GetBothMode(), 6);
this->FrameBufferObject->AddColorAttachment(
vtkOpenGLFramebufferObject::GetBothMode(), 0, this->CubeMapTexture, 0, faceIndex);
this->FrameBufferObject->ActivateBuffer(0);
this->FrameBufferObject->Start(this->CubeResolution, this->CubeResolution);
r->SetUserLightTransform(lightsTransform);
this->DelegatePass->Render(&s2);
this->NumberOfRenderedProps += this->DelegatePass->GetNumberOfRenderedProps();
r->SetUserLightTransform(nullptr);
this->FrameBufferObject->RestorePreviousBindingsAndBuffers();
this->CubeMapTexture->Deactivate();
r->SetActiveCamera(oldCamera);
}
// ----------------------------------------------------------------------------
void vtkPanoramicProjectionPass::ReleaseGraphicsResources(vtkWindow* w)
{
this->Superclass::ReleaseGraphicsResources(w);
delete this->QuadHelper;
this->QuadHelper = nullptr;
if (this->FrameBufferObject)
{
this->FrameBufferObject->Delete();
this->FrameBufferObject = nullptr;
}
if (this->CubeMapTexture)
{
this->CubeMapTexture->Delete();
this->CubeMapTexture = nullptr;
}
}
/*=========================================================================
Program: Visualization Toolkit
Module: vtkPanoramicProjectionPass.h
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
/**
* @class vtkPanoramicProjectionPass
* @brief Render pass that render the scene in a cubemap and project
* these six renderings to a single quad.
* There are currently two differents projections implemented (Equirectangular and Azimuthal).
* This pass can be used to produce images that can be visualize with specific devices that re-maps
* the distorted image to a panoramic view (for instance VR headsets, domes, panoramic screens)
*
* Note that it is often necessary to disable frustum cullers in order to render
* properly objects that are behind the camera.
*
* @sa
* vtkRenderPass
*/
#ifndef vtkPanoramicProjectionPass_h
#define vtkPanoramicProjectionPass_h
#include "vtkImageProcessingPass.h"
#include "vtkRenderingOpenGL2Module.h" // For export macro
class vtkOpenGLFramebufferObject;
class vtkOpenGLQuadHelper;
class vtkTextureObject;
class VTKRENDERINGOPENGL2_EXPORT vtkPanoramicProjectionPass : public vtkImageProcessingPass
{
public:
static vtkPanoramicProjectionPass* New();
vtkTypeMacro(vtkPanoramicProjectionPass, vtkImageProcessingPass);
void PrintSelf(ostream& os, vtkIndent indent) override;
/**
* Perform rendering according to a render state.
*/
void Render(const vtkRenderState* s) override;
/**
* Release graphics resources and ask components to release their own resources.
*/
void ReleaseGraphicsResources(vtkWindow* w) override;
//@{
/**
* Get/Set the cubemap textures resolution used to render (offscreen) all directions.
* Default is 300.
*/
vtkGetMacro(CubeResolution, unsigned int);
vtkSetMacro(CubeResolution, unsigned int);
//@}
/**
* Enumeration of projection types.
*/
enum : int
{
Equirectangular = 1, /**< Equirectangular projection */
Azimuthal = 2 /**< Azimuthal equidistant projection */
};
//@{
/**
* Get/Set the type of projection.
* Equirectangular projection maps meridians to vertical straight lines and circles of latitude to
* horizontal straight lines.
* Azimuthal equidistant projection maps all points of the scene based on their distance to the
* view direction. This projection produces a fisheye effect.
* Default is Equirectangular.
*/
vtkGetMacro(ProjectionType, int);
vtkSetClampMacro(ProjectionType, int, Equirectangular, Azimuthal);
void SetProjectionTypeToEquirectangular() { this->SetProjectionType(Equirectangular); }
void SetProjectionTypeToAzimuthal() { this->SetProjectionType(Azimuthal); }
//@}
//@{
/**
* Get/Set the vertical angle of projection.
* 180 degrees is a half sphere, 360 degrees is a full sphere,
* but any values in the range (90;360) can be set.
* Default is 180 degrees.
*/
vtkGetMacro(Angle, double);
vtkSetClampMacro(Angle, double, 90.0, 360.0);
//@}
protected:
vtkPanoramicProjectionPass() = default;
~vtkPanoramicProjectionPass() override = default;
void RenderOnFace(const vtkRenderState* s, int index);
void Project(vtkOpenGLRenderWindow* renWin);
void InitOpenGLResources(vtkOpenGLRenderWindow* renWin);
/**
* Graphics resources.
*/
vtkOpenGLFramebufferObject* FrameBufferObject = nullptr;
vtkTextureObject* CubeMapTexture = nullptr;
vtkOpenGLQuadHelper* QuadHelper = nullptr;
unsigned int CubeResolution = 300;
int ProjectionType = Equirectangular;
double Angle = 180.0;
private:
vtkPanoramicProjectionPass(const vtkPanoramicProjectionPass&) = delete;
void operator=(const vtkPanoramicProjectionPass&) = delete;
};
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment