From fc9878ef94b0b8b4df86dfd6cf1a0f99579eeaa3 Mon Sep 17 00:00:00 2001 From: "David C. Lonie" <david.lonie@kitware.com> Date: Fri, 16 Sep 2016 15:34:08 -0400 Subject: [PATCH] Add an FXAA implementation. FXAA is an antialiasing technique that is applied in a post-processing pass. This implementation: - Is a single-pass fragment shader. - Detects edges. - Searches for edge endpoints. - Interpolates colors along the edge to give a smooth, antialiased color gradient. - Detects and corrects sub-pixel antialiasing. --- Rendering/Core/vtkRenderer.cxx | 9 + Rendering/Core/vtkRenderer.h | 41 + Rendering/OpenGL2/CMakeLists.txt | 2 + Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt | 1 + .../OpenGL2/Testing/Cxx/TestFXAAFilter.cxx | 173 ++++ .../Data/Baseline/TestFXAAFilter.png.md5 | 1 + Rendering/OpenGL2/glsl/vtkFXAAFilterFS.glsl | 788 ++++++++++++++++++ Rendering/OpenGL2/module.cmake | 1 + Rendering/OpenGL2/vtkOpenGLFXAAFilter.cxx | 449 ++++++++++ Rendering/OpenGL2/vtkOpenGLFXAAFilter.h | 241 ++++++ Rendering/OpenGL2/vtkOpenGLRenderer.cxx | 39 + Rendering/OpenGL2/vtkOpenGLRenderer.h | 5 + 12 files changed, 1750 insertions(+) create mode 100644 Rendering/OpenGL2/Testing/Cxx/TestFXAAFilter.cxx create mode 100644 Rendering/OpenGL2/Testing/Data/Baseline/TestFXAAFilter.png.md5 create mode 100644 Rendering/OpenGL2/glsl/vtkFXAAFilterFS.glsl create mode 100644 Rendering/OpenGL2/vtkOpenGLFXAAFilter.cxx create mode 100644 Rendering/OpenGL2/vtkOpenGLFXAAFilter.h diff --git a/Rendering/Core/vtkRenderer.cxx b/Rendering/Core/vtkRenderer.cxx index c17207335ff..b8a5bfdd38d 100644 --- a/Rendering/Core/vtkRenderer.cxx +++ b/Rendering/Core/vtkRenderer.cxx @@ -117,6 +117,15 @@ vtkRenderer::vtkRenderer() this->GL2PSSpecialPropCollection = NULL; + this->UseFXAA = false; + this->FXAARelativeContrastThreshold = 1.f / 8.f; + this->FXAAHardContrastThreshold = 1.f / 16.f; + this->FXAASubpixelBlendLimit = 3.f / 4.f; + this->FXAASubpixelContrastThreshold = 1.f / 4.f; + this->FXAAEndpointSearchIterations = 12; + this->FXAAUseHighQualityEndpoints = true; + this->FXAADebugOption = 0; + this->UseShadows = 0; this->UseHiddenLineRemoval = 0; diff --git a/Rendering/Core/vtkRenderer.h b/Rendering/Core/vtkRenderer.h index 0f394868ff7..0809201dddf 100644 --- a/Rendering/Core/vtkRenderer.h +++ b/Rendering/Core/vtkRenderer.h @@ -528,6 +528,31 @@ public: // method to release graphics resources in any derived renderers. virtual void ReleaseGraphicsResources(vtkWindow *); + // Description: + // Turn on/off FXAA anti-aliasing, if supported. Initial value is off. + vtkSetMacro(UseFXAA, bool) + vtkGetMacro(UseFXAA, bool) + vtkBooleanMacro(UseFXAA, bool) + + // Description: + // Tuning parameters for FXAA. See vtkOpenGLFXAAFilter.h for documentation + // and suggested values. + vtkSetClampMacro(FXAARelativeContrastThreshold, float, 0.f, 1.f) + vtkGetMacro(FXAARelativeContrastThreshold, float) + vtkSetClampMacro(FXAAHardContrastThreshold, float, 0.f, 1.f) + vtkGetMacro(FXAAHardContrastThreshold, float) + vtkSetClampMacro(FXAASubpixelBlendLimit, float, 0.f, 1.f) + vtkGetMacro(FXAASubpixelBlendLimit, float) + vtkSetClampMacro(FXAASubpixelContrastThreshold, float, 0.f, 1.f) + vtkGetMacro(FXAASubpixelContrastThreshold, float) + vtkSetClampMacro(FXAAEndpointSearchIterations, int, 0, VTK_INT_MAX) + vtkGetMacro(FXAAEndpointSearchIterations, int) + vtkSetMacro(FXAAUseHighQualityEndpoints, bool) + vtkGetMacro(FXAAUseHighQualityEndpoints, bool) + vtkBooleanMacro(FXAAUseHighQualityEndpoints, bool) + vtkSetMacro(FXAADebugOption, int) + vtkGetMacro(FXAADebugOption, int) + // Description: // Turn on/off rendering of shadows if supported // Initial value is off. @@ -691,6 +716,22 @@ protected: // This is only used internally. vtkCamera *GetActiveCameraAndResetIfCreated(); + // Description: + // If this flag is on and the rendering engine supports it, FXAA will be used + // to antialias the scene. Default is off. + bool UseFXAA; + + // Description: + // Parameters for FXAA. See vtkOpenGLFXAAFilter.h for documentation and + // suggested values. + float FXAARelativeContrastThreshold; + float FXAAHardContrastThreshold; + float FXAASubpixelBlendLimit; + float FXAASubpixelContrastThreshold; + int FXAAEndpointSearchIterations; + bool FXAAUseHighQualityEndpoints; + int FXAADebugOption; + // Description: // If this flag is on and the rendering engine supports it render shadows // Initial value is off. diff --git a/Rendering/OpenGL2/CMakeLists.txt b/Rendering/OpenGL2/CMakeLists.txt index 66608366537..3e16cae7c2b 100644 --- a/Rendering/OpenGL2/CMakeLists.txt +++ b/Rendering/OpenGL2/CMakeLists.txt @@ -33,6 +33,7 @@ set(Module_SRCS vtkOpenGLActor.cxx vtkOpenGLBufferObject.cxx vtkOpenGLCamera.cxx + vtkOpenGLFXAAFilter.cxx vtkOpenGLGL2PSHelper.cxx vtkOpenGLGlyph3DHelper.cxx vtkOpenGLGlyph3DMapper.cxx @@ -148,6 +149,7 @@ set(shader_files glsl/vtkEDLBilateralFilterFS.glsl glsl/vtkEDLComposeFS.glsl glsl/vtkEDLShadeFS.glsl + glsl/vtkFXAAFilterFS.glsl glsl/vtkGaussianBlurPassFS.glsl glsl/vtkGaussianBlurPassVS.glsl glsl/vtkGlyph3DVS.glsl diff --git a/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt b/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt index e6940ffeadb..4811005ac66 100644 --- a/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt +++ b/Rendering/OpenGL2/Testing/Cxx/CMakeLists.txt @@ -6,6 +6,7 @@ vtk_add_test_cxx(${vtk-module}CxxTests tests TestDepthOfFieldPass.cxx TestDepthPeelingPass.cxx TestEDLPass.cxx + TestFXAAFilter.cxx TestGaussianBlurPass.cxx TestLightingMapLuminancePass.cxx TestLightingMapNormalsPass.cxx diff --git a/Rendering/OpenGL2/Testing/Cxx/TestFXAAFilter.cxx b/Rendering/OpenGL2/Testing/Cxx/TestFXAAFilter.cxx new file mode 100644 index 00000000000..d85866df083 --- /dev/null +++ b/Rendering/OpenGL2/Testing/Cxx/TestFXAAFilter.cxx @@ -0,0 +1,173 @@ +/*========================================================================= + + Program: Visualization Toolkit + + 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 is unlikely to fail if FXAA isn't working, but can be used to +// quickly check the same scene with/without FXAA enabled. +// + +#include "vtkActor.h" +#include "vtkCamera.h" +#include "vtkConeSource.h" +#include "vtkCylinderSource.h" +#include "vtkDiskSource.h" +#include "vtkLineSource.h" +#include "vtkNew.h" +#include "vtkOpenGLRenderer.h" +#include "vtkPolyDataMapper.h" +#include "vtkProperty.h" +#include "vtkRegressionTestImage.h" +#include "vtkRenderWindow.h" +#include "vtkRenderWindowInteractor.h" +#include "vtkSphereSource.h" +#include "vtkTextActor.h" +#include "vtkTextProperty.h" + +namespace { + +void BuildRenderer(vtkRenderer *renderer, int widthBias) +{ + const size_t NUM_LINES = 10; + + vtkNew<vtkLineSource> lines[NUM_LINES]; + vtkNew<vtkPolyDataMapper> mappers[NUM_LINES]; + vtkNew<vtkActor> actors[NUM_LINES]; + for (size_t i = 0; i < NUM_LINES; ++i) + { + double c = static_cast<double>(2 * i) / + static_cast<double>(NUM_LINES - 1) - 1.; + lines[i]->SetPoint1(-1, c, 0.); + lines[i]->SetPoint2( 1, -c, 0.); + + mappers[i]->SetInputConnection(lines[i]->GetOutputPort()); + + actors[i]->SetMapper(mappers[i].Get()); + actors[i]->GetProperty()->SetColor(0., 1., 0.); + actors[i]->GetProperty()->SetRepresentationToWireframe(); + actors[i]->GetProperty()->SetLineWidth((i + widthBias) % 2 ? 1 : 3); + renderer->AddActor(actors[i].Get()); + } + + vtkNew<vtkSphereSource> sphere; + sphere->SetCenter(0., 0.6, 0.); + sphere->SetThetaResolution(80); + sphere->SetPhiResolution(80); + sphere->SetRadius(0.4); + vtkNew<vtkPolyDataMapper> sphereMapper; + sphereMapper->SetInputConnection(sphere->GetOutputPort()); + vtkNew<vtkActor> sphereActor; + sphereActor->SetMapper(sphereMapper.Get()); + sphereActor->GetProperty()->SetColor(0.9, 0.4, 0.2); + sphereActor->GetProperty()->SetAmbient(.6); + sphereActor->GetProperty()->SetDiffuse(.4); + renderer->AddActor(sphereActor.Get()); + + vtkNew<vtkConeSource> cone; + cone->SetCenter(0., 0.5, -0.5); + cone->SetResolution(160); + cone->SetRadius(.9); + cone->SetHeight(0.9); + cone->SetDirection(0., -1., 0.); + vtkNew<vtkPolyDataMapper> coneMapper; + coneMapper->SetInputConnection(cone->GetOutputPort()); + vtkNew<vtkActor> coneActor; + coneActor->SetMapper(coneMapper.Get()); + coneActor->GetProperty()->SetColor(0.9, .6, 0.8); + coneActor->GetProperty()->SetAmbient(.6); + coneActor->GetProperty()->SetDiffuse(.4); + renderer->AddActor(coneActor.Get()); + + vtkNew<vtkDiskSource> disk; + disk->SetCircumferentialResolution(80); + disk->SetInnerRadius(0); + disk->SetOuterRadius(0.5); + vtkNew<vtkPolyDataMapper> diskMapper; + diskMapper->SetInputConnection(disk->GetOutputPort()); + vtkNew<vtkActor> diskActor; + diskActor->SetPosition(0., -0.5, -0.5); + diskActor->SetMapper(diskMapper.Get()); + diskActor->GetProperty()->SetColor(.3, .1, .4); + diskActor->GetProperty()->SetAmbient(.6); + diskActor->GetProperty()->SetDiffuse(.4); + renderer->AddActor(diskActor.Get()); + + vtkNew<vtkCylinderSource> cyl; + cyl->SetCenter(0., -.5, 0.); + cyl->SetHeight(.6); + cyl->SetRadius(.2); + cyl->SetResolution(80); + vtkNew<vtkPolyDataMapper> cylMapper; + cylMapper->SetInputConnection(cyl->GetOutputPort()); + vtkNew<vtkActor> cylActor; + cylActor->SetOrigin(cyl->GetCenter()); + cylActor->RotateWXYZ(35, -0.2, 0., 1.); + cylActor->SetMapper(cylMapper.Get()); + cylActor->GetProperty()->SetColor(0.3, .9, .4); + cylActor->GetProperty()->SetAmbient(.6); + cylActor->GetProperty()->SetDiffuse(.4); + renderer->AddActor(cylActor.Get()); + + renderer->SetBackground(0., 0., 0.); + renderer->GetActiveCamera()->ParallelProjectionOn(); + renderer->ResetCamera(); + renderer->ResetCameraClippingRange(); + renderer->GetActiveCamera()->SetParallelScale(0.9); +} + +} // end anon namespace + +int TestFXAAFilter(int argc, char *argv[]) +{ + vtkNew<vtkRenderWindowInteractor> iren; + vtkNew<vtkRenderWindow> renWin; + renWin->SetMultiSamples(0); + iren->SetRenderWindow(renWin.Get()); + + vtkNew<vtkRenderer> renderer; + vtkNew<vtkRenderer> rendererFXAA; + rendererFXAA->UseFXAAOn(); + + vtkNew<vtkTextActor> label; + label->SetInput("No FXAA"); + label->GetTextProperty()->SetFontSize(20); + label->GetTextProperty()->SetJustificationToCentered(); + label->GetTextProperty()->SetVerticalJustificationToBottom(); + label->SetPosition(85, 10); + renderer->AddActor2D(label.Get()); + + vtkNew<vtkTextActor> labelFXAA; + labelFXAA->SetInput("FXAA"); + labelFXAA->GetTextProperty()->SetFontSize(20); + labelFXAA->GetTextProperty()->SetJustificationToCentered(); + labelFXAA->GetTextProperty()->SetVerticalJustificationToBottom(); + labelFXAA->SetPosition(85, 10); + rendererFXAA->AddActor2D(labelFXAA.Get()); + + renderer->SetViewport(0., 0., .5, 1.); + BuildRenderer(renderer.Get(), 0); + renWin->AddRenderer(renderer.Get()); + + rendererFXAA->SetViewport(.5, 0., 1., 1.); + BuildRenderer(rendererFXAA.Get(), 1); + renWin->AddRenderer(rendererFXAA.Get()); + + renWin->SetSize(1000, 500); + renWin->Render(); + + int retVal = vtkRegressionTestImage(renWin.Get()); + if (retVal == vtkRegressionTester::DO_INTERACTOR) + { + iren->Start(); + } + return !retVal; +} diff --git a/Rendering/OpenGL2/Testing/Data/Baseline/TestFXAAFilter.png.md5 b/Rendering/OpenGL2/Testing/Data/Baseline/TestFXAAFilter.png.md5 new file mode 100644 index 00000000000..16ccf2d1448 --- /dev/null +++ b/Rendering/OpenGL2/Testing/Data/Baseline/TestFXAAFilter.png.md5 @@ -0,0 +1 @@ +eb48d32d6179d54d4f259ca96c5b70a1 diff --git a/Rendering/OpenGL2/glsl/vtkFXAAFilterFS.glsl b/Rendering/OpenGL2/glsl/vtkFXAAFilterFS.glsl new file mode 100644 index 00000000000..c43f07a1bf2 --- /dev/null +++ b/Rendering/OpenGL2/glsl/vtkFXAAFilterFS.glsl @@ -0,0 +1,788 @@ +//VTK::System::Dec + +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkFXAAFilterFS.glsl + + 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. + +=========================================================================*/ +// Fragment shader for vtkOpenGLFXAAFilter. +// +// Based on the following implementation and description: +// +// Whitepaper: +// http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf +// +// Sample implementation: +// https://github.com/NVIDIAGameWorks/GraphicsSamples/blob/master/samples/es3-kepler/FXAA/FXAA3_11.h + +//VTK::Output::Dec + +//======================== Debugging Options: ================================== + +// Output a greyscale image showing the detected amount of subpixel aliasing. +//#define FXAA_DEBUG_SUBPIXEL_ALIASING + +// Output vertical edges in red, and horizontal edges in blue. +//#define FXAA_DEBUG_EDGE_DIRECTION + +// Output (number of steps taken) / (EndpointSearchIterations). Negative steps +// in the red channel, positive steps in the blue. +//#define FXAA_DEBUG_EDGE_NUM_STEPS + +// Output degrees of red if the edge is near the negative edge endpoint, or +// shades of blue if near the positive edge endpoint. Pixels near an edge but +// not eligible for edgeAA (e.g. they are on the unaliased side of an edge) +// are shown in yellow. +//#define FXAA_DEBUG_EDGE_DISTANCE + +// Output the length of the edge anti-aliasing offset vector in the red channel. +//#define FXAA_DEBUG_EDGE_SAMPLE_OFFSET + +// Only apply a single form of anti-aliasing: +// 1 - Only apply sub-pixel anti-aliasing. +// 2 - Only apply edge anti-aliasing. +// Other / undefined - Apply both sub-pixel and edge anti-aliasing. +//#define FXAA_DEBUG_ONLY_SUBPIX_AA +//#define FXAA_DEBUG_ONLY_EDGE_AA + +// Replacement stub for vtkShaderProgram::Substitute: +//VTK::DebugOptions::Def + +//========================== Tuning Define: ==================================== + +// Which edge search implementation to use. If defined, use VTK's endpoint +// algorithm, otherwise use NVIDIA's. +// +// NVIDIA is faster, but gives poor results on single pixel lines (e.g. +// vtkPolyDataMapper's wireframe/edges). VTK is slower, but gives nicer results +// on single pixel lines. +//#define FXAA_USE_HIGH_QUALITY_ENDPOINTS; + +// Replacement stub for vtkShaderProgram::Substitute: +//VTK::EndpointAlgo::Def + +//========================= Input Parameters: ================================== + +// Current fragment texture coordinate: +in vec2 texCoord; + +// Aliased color buffer (should be sRGB, ideally) +uniform sampler2D Input; + +// 1.f/Input.width, 1.f/Input.height: +uniform vec2 InvTexSize; + +//======================== Tuning Parameters: ================================== + +// See the vtkOpenGLFXAAFilter class documentation for details on these. + +// Minimum change in luminosity (relative to maxLum) to use FXAA: +uniform float RelativeContrastThreshold; + +// Absolute minimum lum change required for FXAA (overrides +// RelativeContrastThreshold value, not scaled): +uniform float HardContrastThreshold; + +// Maximum amount of lowpass blending for subpixel anti-aliasing: +uniform float SubpixelBlendLimit; + +// Ignore subpixel anti-aliasing that contributes less than this amount to the +// total contrast: +uniform float SubpixelContrastThreshold; + +// Maximum number of steps to take when searching for line edges: +uniform int EndpointSearchIterations; + +//============================ Helper Methods ================================== +// Converts rgb to luminosity: +const vec3 LUMINOSITY_VEC = vec3(0.299, 0.587, 0.114); +float luminosity(vec3 rgb) +{ + return dot(rgb, LUMINOSITY_VEC); +} + +//======================= Endpoint Search Routines ============================= +// Identify the endpoints of a detected edge and compute a sampling offset to +// correct for aliasing. The computed offset accounts for distance from edge +// to create a gradient of antialiased values. +// +// Input parameters: +// - posC: The texture coordinate position of the current pixel. +// - lumC: The luminosity of the current pixel. +// - lumHC: The luminosity of the highest contrast pixel to HC that is +// perpendicular to the detected edge. +// - lengthSign: Single component magnitude and direction (in texture +// coordinates) from the center of C pointing to HC. +// - tcPixel: (Width, Height) of a single pixel in texture coordinate units. +// - horzSpan: True if the detected edge is horizontal. +// - posEdgeAA: Output parameter with the position to resample the input texture +// to get an edge anti-aliased rgb value for the current pixel. +// +// Implementations: +// - nvidiaEndpointSearch: The algorithm proposed by nVidia in their whitepaper +// and sample implementations. Faster, but poorly handles single-pixel lines. +// - vtkEndpointSearch: Modified endpoint search that does more texture lookups, +// but does better detection of single pixel line endpoints. +// +// Return values for endpoint searches: +const int FXAA_NO_EDGE_AA = 0; // Edge AA not required. +const int FXAA_NEED_EDGE_AA = 1; // Edge AA required. +const int FXAA_ABORT_EDGE_AA = 2; // Instruct to return. Used for debugging. + +//================ nVidia's Endpoint Search Implementation ===================== + +int nvidiaEndpointSearch(vec2 posC, float lumC, float lumHC, float lengthSign, + vec2 tcPixel, bool horzSpan, out vec2 posEdgeAA) +{ + /***************************************************************************** + * End of Edge Search * + *===========================================================================* + * Search along the direction of the detected edge to find both endpoints. * + * * + * We define HC as the Highest Contrast neighbor perpendicular to the edge * + * direction (i.e. the pixel on the other side of the edge). * + * * + * The luminosity of HC is lumHC, the contrast between C and HC is * + * contrastCHC, and the average luminosity of HC and C is lumAveCHC. * + * * + * We'll walk along the edge boundary in both direction, sampling the average* + * luminosity of the pixels on both sides of the edge: lumAveN for the * + * negative direction, lumAveP for the positive direction. We determine the * + * end of the edge to be where: * + * * + * abs(lumAve[NP] - lumCHC) >= contrastHC / 4. * + * * + * which indicates that the average luminosities have diverged enough to no * + * longer be considered part of the edge. * + ****************************************************************************/ + + float contrastCHC = abs(lumC - lumHC); + + // Point on the boundary of C and HC: + vec2 boundaryCHC = posC; // Will be shifted later. + + // Direction of the edge + vec2 edgeDir = vec2(0.f); // Component is set below: + + if (horzSpan) + { + boundaryCHC.y += lengthSign * 0.5f; + edgeDir.x = tcPixel.x; + } + else + { + boundaryCHC.x += lengthSign * 0.5f; + edgeDir.y = tcPixel.y; + } + + // Prepare for the search loop: + float contrastThreshold = contrastCHC / 4.f; + float lumAveCHC = 0.5f * (lumC + lumHC); + float lumAveN; + float lumAveP; + bool doneN = false; + bool doneP = false; + vec2 posN = boundaryCHC - edgeDir; + vec2 posP = boundaryCHC + edgeDir; + +#ifdef FXAA_DEBUG_EDGE_NUM_STEPS + int stepsN = 0; + int stepsP = 0; +#endif // FXAA_DEBUG_EDGE_NUM_STEPS + + for (int i = 0; i < EndpointSearchIterations; ++i) + { +#ifdef FXAA_DEBUG_EDGE_NUM_STEPS + if (!doneN) stepsN += 1; + if (!doneP) stepsP += 1; +#endif // FXAA_DEBUG_EDGE_NUM_STEPS + + // Sample on the edge boundary in both directions: + if (!doneN) lumAveN = luminosity(texture2D(Input, posN).rgb); + if (!doneP) lumAveP = luminosity(texture2D(Input, posP).rgb); + + // Edge endpoint is where the contrast changes significantly: + doneN = doneN || (abs(lumAveN - lumAveCHC) >= contrastThreshold); + doneP = doneP || (abs(lumAveP - lumAveCHC) >= contrastThreshold); + if (doneN && doneP) break; + + // Step to next pixel: + if (!doneN) posN -= edgeDir; + if (!doneP) posP += edgeDir; + } + +#ifdef FXAA_DEBUG_EDGE_NUM_STEPS + gl_FragData[0] = vec4(float(stepsN) / float(EndpointSearchIterations), 0.f, + float(stepsP) / float(EndpointSearchIterations), 1.f); + return FXAA_ABORT_EDGE_AA; +#endif // FXAA_DEBUG_EDGE_NUM_STEPS + + /***************************************************************************** + * Edge Search Analysis * + *===========================================================================* + * We've located the ends of the edge at this point. Next we figure out how * + * to interpolate the edge. * + * * + * First we need to find out which end of the edge (N or P) is changing * + * contrast relative to boundaryCHC. This is best explained visually: * + * * + * +------------+ * + * |XX E | * + * |NXXXHXXP | * + * |N C PXXXX| * + * | X| * + * +------------+ * + * * + * In the above, an X represents a dark pixel, and a blank space is a light * + * pixel. C is the current pixel, and H is pixel HC. The negative endpoint N* + * of the edge is the midpoint between the first set of blank pixels to the * + * left of C and H, while the positive endpoint P is the first set of dark * + * pixels to the right. The pixels under the "N" are light, while the pixels* + * under "P" are dark. The "P" side of the edge is changing contrast * + * relative to C. We compute this condition as: * + * * + * bool lumCLessThanAve = lumC < lumAveCHC; * + * bool lumNLessThanAve = lumAveN < lumAveCHC; * + * bool lumPLessThanAve = lumAveP < lumAveCHC; * + * bool shadeIfNearN = lumCLessThanAve != lumNLessThanAve; * + * bool shadeIfNearP = lumCLessThanAve != lumPLessThanAve; * + * * + * If shadeIfNearN is true, N is changing contrast relative to C. The same * + * is true for P. Thus, the change in the average contrast of the the * + * endpoint relative to lumAveHC must be opposite to the change in contrast * + * from C to lumAveHC. * + * * + * In addition to checking the change in contrast, we also identify which * + * endpoint is nearest to C. As the variable names suggest, we will only * + * apply edge anti-aliasing if we're nearest an endpoint that has the * + * desired contrast change. This prevents shading edge neighbors that do not* + * follow the direction of the line, such as point E in the diagram. * + * * + * bool CisNearN = (norm(posN - boundaryCHC) < norm(posP - boundaryCHC)); * + * * + * If both of the above conditions are met (the nearest endpoint has the * + * proper contrast change), then we compute the ratio of C's distance from * + * the desired endpoint to the total length of the edge. This ratio is the * + * fraction of a pixel that we shift C towards HC to resample C for * + * anti-aliasing. * + ****************************************************************************/ + + // Check both endpoints for the contrast change condition: + bool lumCLessThanAve = lumC < lumAveCHC; + bool lumNLessThanAve = lumAveN < lumAveCHC; + bool lumPLessThanAve = lumAveP < lumAveCHC; + bool shadeIfNearN = lumCLessThanAve != lumNLessThanAve; + bool shadeIfNearP = lumCLessThanAve != lumPLessThanAve; + + // Identify the closest point: + float dstN; + float dstP; + if (horzSpan) + { + dstN = boundaryCHC.x - posN.x; + dstP = posP.x - boundaryCHC.x; + } + else + { + dstN = boundaryCHC.y - posN.y; + dstP = posP.y - boundaryCHC.y; + } + bool nearestEndpointIsN = dstN < dstP; + float dst = min(dstN, dstP); + + // Finally determine if we need shading: + bool needEdgeAA = nearestEndpointIsN ? shadeIfNearN : shadeIfNearP; + +#ifdef FXAA_DEBUG_EDGE_DISTANCE + if (needEdgeAA) + { + float maxDistance = EndpointSearchIterations; + if (nearestEndpointIsN) + { + gl_FragData[0] = vec4(1.f - dstN / maxDistance, 0.f, 0.f, 1.f); + } + else + { + gl_FragData[0] = vec4(0.f, 0.f, 1.f - dstP / maxDistance, 1.f); + } + } + else + { + gl_FragData[0] = vec4(1.f, 1.f, 0.f, 1.f); + } + return FXAA_ABORT_EDGE_AA; +#endif // FXAA_DEBUG_EDGE_DISTANCE + + // Compute the pixel offset: + float invNegSpanLength = -1.f / (dstN + dstP); + float pixelOffset = dst * invNegSpanLength + 0.5; + +#ifdef FXAA_DEBUG_EDGE_SAMPLE_OFFSET + if (needEdgeAA) + { // x2, since the max value is 0.5: + gl_FragData[0] = vec4(-2.f * dst * invNegSpanLength, 0.f, 0.f, 1.f); + return FXAA_ABORT_EDGE_AA; + } +#endif // FXAA_DEBUG_EDGE_SAMPLE_OFFSET + + // Resample the edge anti-aliased value: + posEdgeAA = posC; + if (horzSpan) + { + posEdgeAA.y += pixelOffset * lengthSign; + } + else + { + posEdgeAA.x += pixelOffset * lengthSign; + } + + return needEdgeAA ? 1 : 0; +} + +//================== VTK's Endpoint Search Implementation ====================== + +int vtkEndpointSearch(vec2 posC, float lumC, float lumHC, float lengthSign, + vec2 tcPixel, bool horzSpan, out vec2 posEdgeAA) +{ + /***************************************************************************** + * End of Edge Search * + *===========================================================================* + * Search along the direction of the detected edge to find both endpoints. * + * +------------+ * + * |X | nVidia's endpoint detector handles this case poorly. If C * + * | XXXXXX C | is the current pixel, it will detect N as the leftmost * + * | XXHXX| column of pixels, since it samples the average luminosity * + * | X| at the border of the rows containing C and HC. The actual * + * +------------+ endpoint is 3 pixels to the left from C, but the average * + * luminosity does not change at this point. * + * * + * We adapt the algorithm to sample both rows/columns containing C and HC on * + * the texel centers, rather than the interpolated border. We then detect * + * the edge endpoints when: * + * * + * abs(lumHCN - lumHC) > abs(lumHCN - lumC) || * + * abs(lumCN - lumC) > abs(lumCN - lumHC) * + * * + * where lumHCN is the luminosity of the sample in HC's row in the negative * + * direction, lumCN is the luminosity of the sample in C's row in the * + * negative direction, lumHC is the luminosity of HC, and lumC is the * + * luminosity of C. Thus, the endpoint is where a sampled luminosity in C's * + * row is closer to HC, or vice-versa. The positive endpoint is determined * + * similarly. * + * * + * After the endpoints has been determined, we decide whether or not the * + * current pixel needs resampling. This is similar to nVidia's algorithm. * + * We determine if the luminosity of the nearest endpoint's C sample is * + * closer to C or HC. If it's closer to HC, it gets shaded. The resampling * + * offset is computed identically to nVidia's algorithm. * + ****************************************************************************/ + + // Point on the boundary of C and HC: + vec2 posHC = posC; // Will be shifted later. + + // Direction of the edge + vec2 edgeDir = vec2(0.f); // Component is set below: + + if (horzSpan) + { + posHC.y += lengthSign; + edgeDir.x = tcPixel.x; + } + else + { + posHC.x += lengthSign; + edgeDir.y = tcPixel.y; + } + + // Prepare for the search loop: + float lumHCN; + float lumHCP; + float lumCN; + float lumCP; + bool doneN = false; + bool doneP = false; + vec2 posHCN = posHC - edgeDir; + vec2 posHCP = posHC + edgeDir; + vec2 posCN = posC - edgeDir; + vec2 posCP = posC + edgeDir; + +#ifdef FXAA_DEBUG_EDGE_NUM_STEPS + int stepsN = 0; + int stepsP = 0; +#endif // FXAA_DEBUG_EDGE_NUM_STEPS + + for (int i = 0; i < EndpointSearchIterations; ++i) + { +#ifdef FXAA_DEBUG_EDGE_NUM_STEPS + if (!doneN) stepsN += 1; + if (!doneP) stepsP += 1; +#endif // FXAA_DEBUG_EDGE_NUM_STEPS + + // Sample the luminosities along the edge: + if (!doneN) + { + lumHCN = luminosity(texture2D(Input, posHCN).rgb); + lumCN = luminosity(texture2D(Input, posCN).rgb); + } + if (!doneP) + { + lumHCP = luminosity(texture2D(Input, posHCP).rgb); + lumCP = luminosity(texture2D(Input, posCP).rgb); + } + + // Check contrast to detect endpoint: + doneN = doneN || abs(lumHCN - lumHC) > abs(lumHCN - lumC) + || abs(lumCN - lumC) > abs(lumCN - lumHC); + doneP = doneP || abs(lumHCP - lumHC) > abs(lumHCP - lumC) + || abs(lumCP - lumC) > abs(lumCP - lumHC); + + if (doneN && doneP) + { + break; + } + + // Take next step. + if (!doneN) + { + posHCN -= edgeDir; + posCN -= edgeDir; + } + if (!doneP) + { + posHCP += edgeDir; + posCP += edgeDir; + } + } + +#ifdef FXAA_DEBUG_EDGE_NUM_STEPS + gl_FragData[0] = vec4(float(stepsN) / float(EndpointSearchIterations), 0.f, + float(stepsP) / float(EndpointSearchIterations), 1.f); + return FXAA_ABORT_EDGE_AA; +#endif // FXAA_DEBUG_EDGE_NUM_STEPS + + // Identify the closest point: + float dstN; + float dstP; + + if (horzSpan) + { + dstN = posC.x - posCN.x; + dstP = posCP.x - posC.x; + } + else + { + dstN = posC.y - posCN.y; + dstP = posCP.y - posC.y; + } + + bool nearestEndpointIsN = dstN < dstP; + float dst = min(dstN, dstP); + float lumCNear = nearestEndpointIsN ? lumCN : lumCP; + + // Resample if the nearest endpoint sample in C's row is closer in luminosity + // to HC than C. + bool needEdgeAA = abs(lumCNear - lumHC) < abs(lumCNear - lumC); + +#ifdef FXAA_DEBUG_EDGE_DISTANCE + if (needEdgeAA) + { + float maxDistance = EndpointSearchIterations; + if (nearestEndpointIsN) + { + gl_FragData[0] = vec4(1.f - dstN / maxDistance, 0.f, 0.f, 1.f); + } + else + { + gl_FragData[0] = vec4(0.f, 0.f, 1.f - dstP / maxDistance, 1.f); + } + } + else + { + gl_FragData[0] = vec4(1.f, 1.f, 0.f, 1.f); + } + return FXAA_ABORT_EDGE_AA; +#endif // FXAA_DEBUG_EDGE_DISTANCE + + // Compute the pixel offset: + float invNegSpanLength = -1.f / (dstN + dstP); + float pixelOffset = dst * invNegSpanLength + 0.5f; + +#ifdef FXAA_DEBUG_EDGE_SAMPLE_OFFSET + if (needEdgeAA) + { // x2, since the max value is 0.5: + gl_FragData[0] = vec4(-2.f * dst * invNegSpanLength, 0.f, 0.f, 1.f); + return FXAA_ABORT_EDGE_AA; + } +#endif // FXAA_DEBUG_EDGE_SAMPLE_OFFSET + + // Resample the edge anti-aliased value: + posEdgeAA = posC; + if (horzSpan) + { + posEdgeAA.y += pixelOffset * lengthSign; + } + else + { + posEdgeAA.x += pixelOffset * lengthSign; + } + + return needEdgeAA ? 1 : 0; +} + +//=============================== FXAA Body ==================================== + +void main() +{ + // Pixel step size in texture coordinate units: + vec2 tcPixel = InvTexSize; + + /**************************************************************************** + * Compute Local Contrast Range And Early Abort * + *==========================================================================* + * Determine the contrast range for the current pixel and its neightbors * + * to the North, South, West, and East. If the range is less than both of: * + * * + * a) RelativeContrastThreshold * lumMax * + * * + * and * + * * + * b) HardContrastThreshold * + * * + * then skip anti-aliasing for this pixel. * + ****************************************************************************/ + + // First compute the texture coordinates: + vec2 tcC = texCoord; + vec2 tcN = texCoord + vec2( 0.f, -tcPixel.y); + vec2 tcS = texCoord + vec2( 0.f, tcPixel.y); + vec2 tcW = texCoord + vec2(-tcPixel.x, 0.f); + vec2 tcE = texCoord + vec2( tcPixel.x, 0.f); + + // Extract the rgb values of these pixels: + vec3 rgbC = texture2D(Input, tcC).rgb; + vec3 rgbN = texture2D(Input, tcN).rgb; + vec3 rgbS = texture2D(Input, tcS).rgb; + vec3 rgbW = texture2D(Input, tcW).rgb; + vec3 rgbE = texture2D(Input, tcE).rgb; + + // Convert to luminosity: + float lumC = luminosity(rgbC); + float lumN = luminosity(rgbN); + float lumS = luminosity(rgbS); + float lumW = luminosity(rgbW); + float lumE = luminosity(rgbE); + + // The min, max, and range of luminosity for CNSWE: + float lumMin = min(lumC, min(min(lumN, lumS), min(lumW, lumE))); + float lumMax = max(lumC, max(max(lumN, lumS), max(lumW, lumE))); + float lumRange = lumMax - lumMin; + float lumThresh = max(HardContrastThreshold, + RelativeContrastThreshold * lumMax); + + // Don't apply FXAA unless there's a significant change in luminosity around + // the current pixel: + if (lumRange < lumThresh) + { + gl_FragData[0] = vec4(rgbC, 1.f); // original color + return; + } + + /**************************************************************************** + * Fetch texels for complete 3x3 neighborhood. * + ****************************************************************************/ + + // Fetch additional texels for edge detection / subpixel antialiasing: + vec2 tcNE = texCoord + vec2( tcPixel.x, -tcPixel.y); + vec2 tcSE = texCoord + vec2( tcPixel.x, tcPixel.y); + vec2 tcNW = texCoord + vec2(-tcPixel.x, -tcPixel.y); + vec2 tcSW = texCoord + vec2(-tcPixel.x, tcPixel.y); + vec3 rgbNE = texture2D(Input, tcNE).rgb; + vec3 rgbSE = texture2D(Input, tcSE).rgb; + vec3 rgbNW = texture2D(Input, tcNW).rgb; + vec3 rgbSW = texture2D(Input, tcSW).rgb; + float lumNE = luminosity(rgbNE); + float lumSE = luminosity(rgbSE); + float lumNW = luminosity(rgbNW); + float lumSW = luminosity(rgbSW); + + // Precompute some combined luminosities. These are reused later. + float lumNS = lumN + lumS; + float lumWE = lumW + lumE; + float lumNSWE = lumNS + lumWE; + float lumNWNE = lumNW + lumNE; + float lumSWSE = lumSW + lumSE; + float lumNWSW = lumNW + lumSW; + float lumNESE = lumNE + lumSE; + + /**************************************************************************** + * Subpixel Anti-aliasing * + *==========================================================================* + * Check if the current pixel is very high contrast to it's neighbors (e.g. * + * specular aliasing, noisy shadow textures, etc). If it is, compute the * + * average color over the 3x3 neighborhood and a blending factor. * + * * + * The blending factor is computed as the minimum of: * + * * + * 1) max(0.f, abs([average NSWE lum] - lumC) - SubpixelContrastThreshold) * + * FXAA_SUBPIX_TRIM_SCALE * + * * + * or * + * * + * 2) SubpixelBlendLimit * + ****************************************************************************/ + + // Check for sub-pixel aliasing (e.g. current pixel has high contrast from + // neighbors): + float lumAveNSWE = 0.25f * (lumNSWE); + float lumSubRange = abs(lumAveNSWE - lumC); + + // Compute the subpixel blend amount: + float blendSub = max(0.f, (lumSubRange / lumRange) - + SubpixelContrastThreshold); + blendSub = min(SubpixelBlendLimit, + blendSub * (1.f / (1.f - SubpixelContrastThreshold))); + +#ifdef FXAA_DEBUG_SUBPIXEL_ALIASING + if (blendSub > 0.f) + { + gl_FragData[0] = vec4(vec3(blendSub / SubpixelBlendLimit), 1.f); + } + else + { + gl_FragData[0] = vec4(rgbC, 1.f); + } + return; +#endif // FXAA_DEBUG_SUBPIXEL_ALIASING + + // Compute the subpixel blend color. Average the 3x3 neighborhood: + vec3 rgbSub = (1.f/9.f) * + (rgbNW + rgbN + rgbNE + + rgbW + rgbC + rgbE + + rgbSW + rgbS + rgbSE); + + /**************************************************************************** + * Edge Testing * + *==========================================================================* + * Apply vertical and horizontal edge detection techniques to determine the * + * direction of any edges in the 3x3 neighborhood. * + ****************************************************************************/ + + // Check for vertical edge. Pixel coeffecients are: + // 1 -2 1 + // 2 -4 2 + // 1 -2 1 + // The absolute value of each row is taken, summed, and divided by 12. + // Operations are decomposed here to take advantage of FMA ops. + float edgeVertRow1 = abs(-2.f * lumN + lumNWNE); + float edgeVertRow2 = abs(-2.f * lumC + lumWE); + float edgeVertRow3 = abs(-2.f * lumS + lumSWSE); + float edgeVert = ((2.f * edgeVertRow2 + edgeVertRow1) + edgeVertRow3) / 12.f; + + // Check for horizontal edge. Pixel coeffecients are: + // 1 2 1 + // -2 -4 -2 + // 1 2 1 + // The absolute value of each column is taken, summed, and divided by 12. + // Operations are decomposed here to take advantage of FMA ops. + float edgeHorzCol1 = abs(-2.f * lumW + lumNWSW); + float edgeHorzCol2 = abs(-2.f * lumC + lumNS); + float edgeHorzCol3 = abs(-2.f * lumE + lumNESE); + float edgeHorz = ((2.f * edgeHorzCol2 + edgeHorzCol1) + edgeHorzCol3) / 12.f; + + // Indicates that the edge span is horizontal: + bool horzSpan = edgeHorz >= edgeVert; + +#ifdef FXAA_DEBUG_EDGE_DIRECTION + gl_FragData[0] = horzSpan ? vec4(0.f, 0.f, 1.f, 1.f) + : vec4(1.f, 0.f, 0.f, 1.f); + return; +#endif // FXAA_DEBUG_EDGE_DIRECTION + + /**************************************************************************** + * Endpoint Search Preparation * + *==========================================================================* + * Compute inputs for an endpoint detection algorithm. Mainly concerned * + * locating HC -- the Highest Contrast pixel (relative to C) that's on the * + * opposite side of the detected edge from C. * + ****************************************************************************/ + + // The two neighbor pixels perpendicular to the edge: + float lumHC1; + float lumHC2; + + // Single-pixel texture coordinate offset that points from C to HC. + float lengthSign; + + if (horzSpan) + { + lumHC1 = lumN; + lumHC2 = lumS; + lengthSign = -tcPixel.y; // Assume N for now. + } + else + { + lumHC1 = lumW; + lumHC2 = lumE; + lengthSign = -tcPixel.x; // Assume W for now. + } + + // Luminosity of the NSWE pixel perpendicular to the edge with the highest + // contrast to C: + float lumHC; + if (abs(lumC - lumHC1) >= abs(lumC - lumHC2)) + { + lumHC = lumHC1; + } + else + { + lumHC = lumHC2; + // Also reverse the offset direction in this case: + lengthSign = -lengthSign; + } + + vec2 posEdgeAA; // Position to resample C at to get edge-antialiasing. + +#ifdef FXAA_USE_HIGH_QUALITY_ENDPOINTS + int endpointResult = vtkEndpointSearch(tcC, lumC, lumHC, lengthSign, + tcPixel, horzSpan, posEdgeAA); +#else // FXAA_USE_HIGH_QUALITY_ENDPOINTS + int endpointResult = nvidiaEndpointSearch(tcC, lumC, lumHC, lengthSign, + tcPixel, horzSpan, posEdgeAA); +#endif // FXAA_USE_HIGH_QUALITY_ENDPOINTS + + // Only sample texture if needed. Reuse rgbC otherwise. + vec3 rgbEdgeAA = rgbC; + + switch (endpointResult) + { + case FXAA_ABORT_EDGE_AA: // Used for debugging (endpoint search set colors) + return; + + case FXAA_NEED_EDGE_AA: // Resample the texture at the requested position. + rgbEdgeAA = texture2D(Input, posEdgeAA).rgb; + break; + + case FXAA_NO_EDGE_AA: // Current pixel does not need edge anti-aliasing. + default: + break; + } + +#ifdef FXAA_DEBUG_ONLY_SUBPIX_AA + rgbEdgeAA = rgbC; +#endif // FXAA_DEBUG_ONLY_SUBPIX_AA +#ifdef FXAA_DEBUG_ONLY_EDGE_AA + blendSub = 0.f; +#endif // FXAA_DEBUG_ONLY_EDGE_AA + + // Blend the edgeAA and subpixelAA results together: + gl_FragData[0] = vec4(mix(rgbEdgeAA, rgbSub, blendSub), 1.f); +} diff --git a/Rendering/OpenGL2/module.cmake b/Rendering/OpenGL2/module.cmake index 94896376026..604d01e0ff9 100644 --- a/Rendering/OpenGL2/module.cmake +++ b/Rendering/OpenGL2/module.cmake @@ -11,6 +11,7 @@ vtk_module(vtkRenderingOpenGL2 TEST_DEPENDS vtkIOLegacy vtkRenderingImage + vtkRenderingFreeType vtkTestingCore vtkTestingRendering vtkImagingGeneral diff --git a/Rendering/OpenGL2/vtkOpenGLFXAAFilter.cxx b/Rendering/OpenGL2/vtkOpenGLFXAAFilter.cxx new file mode 100644 index 00000000000..89e5be7a4b9 --- /dev/null +++ b/Rendering/OpenGL2/vtkOpenGLFXAAFilter.cxx @@ -0,0 +1,449 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkOpenGLFXAAFilter.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 "vtkOpenGLFXAAFilter.h" + +#include "vtk_glew.h" + +#include "vtkObjectFactory.h" +#include "vtkOpenGLBufferObject.h" +#include "vtkOpenGLError.h" +#include "vtkOpenGLRenderer.h" +#include "vtkOpenGLRenderUtilities.h" +#include "vtkOpenGLRenderWindow.h" +#include "vtkOpenGLShaderCache.h" +#include "vtkOpenGLVertexArrayObject.h" +#include "vtkShaderProgram.h" +#include "vtkTextureObject.h" +#include "vtkTimerLog.h" +#include "vtkTypeTraits.h" + +#include <algorithm> +#include <cassert> + +// Our fragment shader: +#include "vtkFXAAFilterFS.h" + +// Define to perform/dump benchmarking info: +//#define FXAA_BENCHMARK + +vtkStandardNewMacro(vtkOpenGLFXAAFilter) + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::PrintSelf(std::ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::Execute(vtkOpenGLRenderer *ren) +{ + assert(ren); + this->Renderer = ren; + + this->StartTimeQuery(this->PreparationTimeQuery, + this->PreparationTimeQueryActive); + + this->Prepare(); + this->LoadInput(); + + this->EndTimeQuery(this->PreparationTimeQuery, + this->PreparationTimeQueryActive); + this->StartTimeQuery(this->FXAATimeQuery, this->FXAATimeQueryActive); + + this->ApplyFilter(); + + this->EndTimeQuery(this->FXAATimeQuery, this->FXAATimeQueryActive); + + this->Finalize(); + + this->Renderer = NULL; +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::ReleaseGraphicsResources() +{ + this->FreeGLObjects(); +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::SetUseHighQualityEndpoints(bool val) +{ + if (this->UseHighQualityEndpoints != val) + { + this->NeedToRebuildShader = true; + this->Modified(); + this->UseHighQualityEndpoints = val; + } +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::SetDebugOptionValue(DebugOption opt) +{ + if (this->DebugOptionValue != opt) + { + this->NeedToRebuildShader = true; + this->Modified(); + this->DebugOptionValue = opt; + } +} + +//------------------------------------------------------------------------------ +vtkOpenGLFXAAFilter::vtkOpenGLFXAAFilter() + : BlendState(false), + DepthTestState(false), + PreparationTimeQueryActive(false), + FXAATimeQueryActive(false), + PreparationTimeQuery(0), + FXAATimeQuery(0), + PreparationTime(0), + FXAATime(0), + RelativeContrastThreshold(1.f/8.f), + HardContrastThreshold(1.f/16.f), + SubpixelBlendLimit(3.f/4.f), + SubpixelContrastThreshold(1.f/4.f), + EndpointSearchIterations(12), + UseHighQualityEndpoints(true), + NeedToRebuildShader(true), + Renderer(NULL), + Input(NULL), + Program(NULL), + VAO(NULL), + VBO(NULL) +{ + std::fill(this->Viewport, this->Viewport + 4, 0); +} + +//------------------------------------------------------------------------------ +vtkOpenGLFXAAFilter::~vtkOpenGLFXAAFilter() +{ + this->FreeGLObjects(); +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::Prepare() +{ + this->Renderer->GetTiledSizeAndOrigin(&this->Viewport[2], &this->Viewport[3], + &this->Viewport[0], &this->Viewport[1]); + + // Check if we need to create a new working texture: + if (this->Input) + { + unsigned int rendererWidth = static_cast<unsigned int>(this->Viewport[2]); + unsigned int rendererHeight = static_cast<unsigned int>(this->Viewport[3]); + if (this->Input->GetWidth() != rendererWidth || + this->Input->GetHeight() != rendererHeight) + { + this->FreeGLObjects(); + } + } + + if (!this->Input) + { + this->CreateGLObjects(); + } + + this->BlendState = glIsEnabled(GL_BLEND) == GL_TRUE; + this->DepthTestState = glIsEnabled(GL_DEPTH_TEST) == GL_TRUE; + + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + + vtkOpenGLCheckErrorMacro("Error after saving GL state."); +} + +//------------------------------------------------------------------------------ +// Delete the vtkObject subclass pointed at by ptr if it is set. +namespace { +template <typename T> void DeleteHelper(T *& ptr) +{ + if (ptr) + { + ptr->Delete(); + ptr = NULL; + } +} +} // end anon namespace + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::FreeGLObjects() +{ + DeleteHelper(this->Input); +// DeleteHelper(this->Program); // Managed by the shader cache + DeleteHelper(this->VAO); + DeleteHelper(this->VBO); + + if (this->FXAATimeQuery > 0) + { + glDeleteQueries(1, &this->FXAATimeQuery); + this->FXAATimeQuery = 0; + } + if (this->PreparationTimeQuery > 0) + { + glDeleteQueries(1, &this->PreparationTimeQuery); + this->PreparationTimeQuery = 0; + } +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::CreateGLObjects() +{ + assert(!this->Input); + this->Input = vtkTextureObject::New(); + this->Input->SetContext(static_cast<vtkOpenGLRenderWindow*>( + this->Renderer->GetRenderWindow())); + this->Input->SetFormat(GL_RGB); + this->Input->SetInternalFormat(GL_RGB8); + + // Required for FXAA, since we interpolate texels for blending. + this->Input->SetMinificationFilter(vtkTextureObject::Linear); + this->Input->SetMagnificationFilter(vtkTextureObject::Linear); + + // Clamp to edge, since we'll be sampling off-texture texels: + this->Input->SetWrapS(vtkTextureObject::ClampToEdge); + this->Input->SetWrapT(vtkTextureObject::ClampToEdge); + this->Input->SetWrapR(vtkTextureObject::ClampToEdge); + + this->Input->Allocate2D(this->Viewport[2], this->Viewport[3], 4, + vtkTypeTraits<vtkTypeUInt8>::VTK_TYPE_ID); +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::LoadInput() +{ + this->Input->CopyFromFrameBuffer(this->Viewport[0], this->Viewport[1], 0, 0, + this->Viewport[2], this->Viewport[3]); +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::ApplyFilter() +{ + typedef vtkOpenGLRenderUtilities GLUtil; + + vtkOpenGLRenderWindow *renWin = static_cast<vtkOpenGLRenderWindow*>( + this->Renderer->GetRenderWindow()); + + this->Input->Activate(); + + if (this->NeedToRebuildShader) + { + DeleteHelper(this->VAO); + DeleteHelper(this->VBO); + this->Program = NULL; // Don't free, shader cache manages these. + this->NeedToRebuildShader = false; + } + + if (!this->Program) + { + std::string fragShader = vtkFXAAFilterFS; + this->SubstituteFragmentShader(fragShader); + this->Program = renWin->GetShaderCache()->ReadyShaderProgram( + GLUtil::GetFullScreenQuadVertexShader().c_str(), + fragShader.c_str(), + GLUtil::GetFullScreenQuadGeometryShader().c_str()); + } + else + { + renWin->GetShaderCache()->ReadyShaderProgram(this->Program); + } + + if (!this->VAO) + { + this->VBO = vtkOpenGLBufferObject::New(); + this->VAO = vtkOpenGLVertexArrayObject::New(); + GLUtil::PrepFullScreenVAO(this->VBO, this->VAO, this->Program); + } + + this->Program->SetUniformi("Input", this->Input->GetTextureUnit()); + float invTexSize[2] = { 1.f / static_cast<float>(this->Viewport[2]), + 1.f / static_cast<float>(this->Viewport[3]) }; + this->Program->SetUniform2f("InvTexSize", invTexSize); + + this->Program->SetUniformf("RelativeContrastThreshold", + this->RelativeContrastThreshold); + this->Program->SetUniformf("HardContrastThreshold", + this->HardContrastThreshold); + this->Program->SetUniformf("SubpixelBlendLimit", + this->SubpixelBlendLimit); + this->Program->SetUniformf("SubpixelContrastThreshold", + this->SubpixelContrastThreshold); + this->Program->SetUniformi("EndpointSearchIterations", + this->EndpointSearchIterations); + + this->VAO->Bind(); + GLUtil::DrawFullScreenQuad(); + this->VAO->Release(); + this->Input->Deactivate(); +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::SubstituteFragmentShader(std::string &fragShader) +{ + if (this->UseHighQualityEndpoints) + { + vtkShaderProgram::Substitute(fragShader, "//VTK::EndpointAlgo::Def", + "#define FXAA_USE_HIGH_QUALITY_ENDPOINTS"); + } + +#define DEBUG_OPT_CASE(optName) \ + case optName: \ + vtkShaderProgram::Substitute(fragShader, "//VTK::DebugOptions::Def", \ + "#define " #optName); \ + break + + + switch (this->DebugOptionValue) + { + default: + case FXAA_NO_DEBUG: + break; + DEBUG_OPT_CASE(FXAA_DEBUG_SUBPIXEL_ALIASING); + DEBUG_OPT_CASE(FXAA_DEBUG_EDGE_DIRECTION); + DEBUG_OPT_CASE(FXAA_DEBUG_EDGE_NUM_STEPS); + DEBUG_OPT_CASE(FXAA_DEBUG_EDGE_DISTANCE); + DEBUG_OPT_CASE(FXAA_DEBUG_EDGE_SAMPLE_OFFSET); + DEBUG_OPT_CASE(FXAA_DEBUG_ONLY_SUBPIX_AA); + DEBUG_OPT_CASE(FXAA_DEBUG_ONLY_EDGE_AA); + } + +#undef DEBUG_OPT_CASE +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::Finalize() +{ + if (this->BlendState) + { + glEnable(GL_BLEND); + } + if (this->DepthTestState) + { + glEnable(GL_DEPTH_TEST); + } + + vtkOpenGLCheckErrorMacro("Error after restoring GL state."); + +#ifdef FXAA_BENCHMARK + if (this->GetTimeQueryResult(this->PreparationTimeQuery, + this->PreparationTime) || + this->GetTimeQueryResult(this->FXAATimeQuery, + this->FXAATime)) + { + this->PrintBenchmark(); + } +#endif // FXAA_BENCHMARK +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::StartTimeQuery(unsigned int &queryId, bool &tracker) +{ +#ifdef FXAA_BENCHMARK + // Don't start a new query until the old results are in: + if (tracker || queryId != 0) + { + return; + } + + glGenQueries(1, &queryId); + glBeginQuery(GL_TIME_ELAPSED, queryId); + tracker = true; + + vtkOpenGLCheckErrorMacro("Error after starting GL_TIME_ELAPSED query."); +#else // FXAA_BENCHMARK + (void)queryId; + (void)tracker; +#endif // FXAA_BENCHMARK +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::EndTimeQuery(unsigned int &queryId, bool &tracker) +{ +#ifdef FXAA_BENCHMARK + if (!tracker || queryId == 0) + { // Shouldn't happen, but just in case... + return; + } + + glEndQuery(GL_TIME_ELAPSED); + tracker = false; + + vtkOpenGLCheckErrorMacro("Error after ending GL_TIME_ELAPSED query."); +#else // FXAA_BENCHMARK + (void)queryId; + (void)tracker; +#endif // FXAA_BENCHMARK +} + +//------------------------------------------------------------------------------ +bool vtkOpenGLFXAAFilter::GetTimeQueryResult(unsigned int &queryId, + unsigned int &result) +{ +#ifdef FXAA_BENCHMARK + if (queryId == 0) + { + return false; + } + + GLint ready; + glGetQueryObjectiv(queryId, GL_QUERY_RESULT_AVAILABLE, &ready); + vtkOpenGLCheckErrorMacro("Error after checking GL_TIME_ELAPSED status."); + if (!ready) + { + return false; + } + + glGetQueryObjectuiv(queryId, GL_QUERY_RESULT, &result); + vtkOpenGLCheckErrorMacro("Error after checking GL_TIME_ELAPSED result."); + + // Free the query: + glDeleteQueries(1, &queryId); + queryId = 0; + + return true; +#else // FXAA_BENCHMARK + (void)queryId; + (void)result; + return false; +#endif // FXAA_BENCHMARK +} + +//------------------------------------------------------------------------------ +void vtkOpenGLFXAAFilter::PrintBenchmark() +{ +#ifdef FXAA_BENCHMARK + int numPixels = this->Input->GetWidth() * this->Input->GetHeight(); + float ptime = static_cast<float>(this->PreparationTime); + float ftime = static_cast<float>(this->FXAATime); + float ttime = ptime + ftime; + + float ptimePerPixel = ptime / numPixels; + float ftimePerPixel = ftime / numPixels; + float ttimePerPixel = ttime / numPixels; + + // Convert the non-per-pixel times to ms: + ptime *= 1e-6f; + ftime *= 1e-6f; + ttime *= 1e-6f; + + std::cerr << "FXAA Info:\n" + << " - Number of pixels: " << numPixels << "\n" + << " - Preparation time: " << ptime << "ms (" + << ptimePerPixel << "ns per pixel)\n" + << " - FXAA time: " << ftime << "ms (" + << ftimePerPixel << "ns per pixel)\n" + << " - Total time: " << ttime << "ms (" + << ttimePerPixel << "ns per pixel)\n"; +#endif // FXAA_BENCHMARK +} diff --git a/Rendering/OpenGL2/vtkOpenGLFXAAFilter.h b/Rendering/OpenGL2/vtkOpenGLFXAAFilter.h new file mode 100644 index 00000000000..3ca4db0e6e3 --- /dev/null +++ b/Rendering/OpenGL2/vtkOpenGLFXAAFilter.h @@ -0,0 +1,241 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkOpenGLFXAAFilter.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. + +=========================================================================*/ + +// .NAME vtkOpenGLFXAAFilter - Perform FXAA antialiasing on the current +// framebuffer. +// +// .SECTION Description +// Call Execute() to run a FXAA antialiasing pass on the current OpenGL +// framebuffer. See method documentation for tunable parameters. +// +// Based on the following implementation and description: +// +// Whitepaper: +// http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf +// +// Sample implementation: +// https://github.com/NVIDIAGameWorks/GraphicsSamples/blob/master/samples/es3-kepler/FXAA/FXAA3_11.h +// +// TODO there are currently some "banding" artifacts on some edges, particularly +// single pixel lines. These seem to be caused by using a linear RGB input, +// rather than a gamma-correct sRGB input. Future work should combine this pass +// with a gamma correction pass to correct this. Bonus points for precomputing +// luminosity into the sRGB's alpha channel to save cycles in the FXAA shader! + +#ifndef vtkOpenGLFXAAFilter_h +#define vtkOpenGLFXAAFilter_h + +#include "vtkRenderingOpenGL2Module.h" // For export macro +#include "vtkObject.h" + +class vtkOpenGLBufferObject; +class vtkOpenGLVertexArrayObject; +class vtkOpenGLRenderer; +class vtkShaderProgram; +class vtkTextureObject; + +class VTKRENDERINGOPENGL2_EXPORT vtkOpenGLFXAAFilter: public vtkObject +{ +public: + static vtkOpenGLFXAAFilter* New(); + vtkTypeMacro(vtkOpenGLFXAAFilter, vtkObject) + virtual void PrintSelf(ostream &os, vtkIndent indent); + + // Description: + // Perform FXAA on the current render buffer in @a ren. + void Execute(vtkOpenGLRenderer *ren); + + // Description: + // Release all OpenGL state. + void ReleaseGraphicsResources(); + + // Description: + // Threshold for applying FXAA to a pixel, relative to the maximum luminosity + // of its 4 immediate neighbors. + // + // The luminosity of the current pixel and it's NSWE neighbors is computed. + // The maximum luminosity and luminosity range (contrast) of all 5 pixels is + // found. If the contrast is less than RelativeContrastThreshold * maxLum, + // the pixel is not considered aliased and will not be affected by FXAA. + // + // Suggested settings: + // - 1/3: Too little + // - 1/4: Low quality + // - 1/8: High quality (default) + // - 1/16: Overkill + vtkSetClampMacro(RelativeContrastThreshold, float, 0.f, 1.f) + vtkGetMacro(RelativeContrastThreshold, float) + + // Description: + // Similar to RelativeContrastThreshold, but not scaled by the maximum + // luminosity. + // + // If the contrast of the current pixel and it's 4 immediate NSWE neighbors is + // less than HardContrastThreshold, the pixel is not considered aliased and + // will not be affected by FXAA. + // + // Suggested settings: + // - 1/32: Visible limit + // - 1/16: High quality (default) + // - 1/12: Upper limit (start of visible unfiltered edges) + vtkSetClampMacro(HardContrastThreshold, float, 0.f, 1.f) + vtkGetMacro(HardContrastThreshold, float) + + // Description: + // Subpixel aliasing is corrected by applying a lowpass filter to the current + // pixel. This is implemented by blending an average of the 3x3 neighborhood + // around the pixel into the final result. The amount of blending is + // determined by comparing the detected amount of subpixel aliasing to the + // total contrasting of the CNSWE pixels: + // + // SubpixelBlending = abs(lumC - lumAveNSWE) / (lumMaxCNSWE - lumMinCNSWE) + // + // This parameter sets an upper limit to the amount of subpixel blending to + // prevent the image from simply getting blurred. + // + // Suggested settings: + // - 1/2: Low amount of blending. + // - 3/4: Medium amount of blending (default) + // - 7/8: High amount of blending. + // - 1: Maximum amount of blending. + vtkSetClampMacro(SubpixelBlendLimit, float, 0.f, 1.f) + vtkGetMacro(SubpixelBlendLimit, float) + + // Description: + // Minimum amount of subpixel aliasing required for subpixel antialiasing to + // be applied. + // + // Subpixel aliasing is corrected by applying a lowpass filter to the current + // pixel. This is implemented by blending an average of the 3x3 neighborhood + // around the pixel into the final result. The amount of blending is + // determined by comparing the detected amount of subpixel aliasing to the + // total contrasting of the CNSWE pixels: + // + // SubpixelBlending = abs(lumC - lumAveNSWE) / (lumMaxCNSWE - lumMinCNSWE) + // + // If SubpixelBlending is less than this threshold, no lowpass blending will + // occur. + // + // Suggested settings: + // - 1/2: Low subpixel aliasing removal + // - 1/3: Medium subpixel aliasing removal + // - 1/4: Default subpixel aliasing removal + // - 1/8: High subpixel aliasing removal + // - 0: Complete subpixel aliasing removal + vtkSetClampMacro(SubpixelContrastThreshold, float, 0.f, 1.f) + vtkGetMacro(SubpixelContrastThreshold, float) + + // Description: + // Use an improved edge endpoint detection algorithm. + // + // If true, a modified edge endpoint detection algorithm is used that requires + // more texture lookups, but will properly detect aliased single-pixel lines. + // + // If false, the edge endpoint algorithm proposed by NVIDIA will by used. This + // algorithm is faster (fewer lookups), but will fail to detect endpoints of + // single pixel edge steps. + // + // Default setting is true. + virtual void SetUseHighQualityEndpoints(bool val); + vtkGetMacro(UseHighQualityEndpoints, bool) + vtkBooleanMacro(UseHighQualityEndpoints, bool) + + // Description: + // Set the number of iterations for the endpoint search algorithm. Increasing + // this value will increase runtime, but also properly detect longer edges. + // The current implementation steps one pixel in both the positive and + // negative directions per iteration. The default value is 12, which will + // resolve endpoints of edges < 25 pixels long (2 * 12 + 1). + vtkSetClampMacro(EndpointSearchIterations, int, 0, VTK_INT_MAX) + vtkGetMacro(EndpointSearchIterations, int) + + // Description: + // Debugging options that affect the output color buffer. See + // vtkFXAAFilterFS.glsl for details. + enum DebugOption + { + FXAA_NO_DEBUG = 0, + FXAA_DEBUG_SUBPIXEL_ALIASING, + FXAA_DEBUG_EDGE_DIRECTION, + FXAA_DEBUG_EDGE_NUM_STEPS, + FXAA_DEBUG_EDGE_DISTANCE, + FXAA_DEBUG_EDGE_SAMPLE_OFFSET, + FXAA_DEBUG_ONLY_SUBPIX_AA, + FXAA_DEBUG_ONLY_EDGE_AA + }; + + // Description: + // Debugging options that affect the output color buffer. See + // vtkFXAAFilterFS.glsl for details. Only one may be active at a time. + virtual void SetDebugOptionValue(DebugOption opt); + vtkGetMacro(DebugOptionValue, DebugOption) + +protected: + vtkOpenGLFXAAFilter(); + ~vtkOpenGLFXAAFilter(); + + void Prepare(); + void FreeGLObjects(); + void CreateGLObjects(); + void LoadInput(); + void ApplyFilter(); + void SubstituteFragmentShader(std::string &fragShader); + void Finalize(); + + void StartTimeQuery(unsigned int &queryId, bool &tracker); + void EndTimeQuery(unsigned int &queryId, bool &tracker); + bool GetTimeQueryResult(unsigned int &queryId, unsigned int &result); + void PrintBenchmark(); + + // Cache GL state that we modify + bool BlendState; + bool DepthTestState; + + int Viewport[4]; // x, y, width, height + + // Used to measure execution time: + bool PreparationTimeQueryActive; + bool FXAATimeQueryActive; + unsigned int PreparationTimeQuery; + unsigned int FXAATimeQuery; + unsigned int PreparationTime; + unsigned int FXAATime; + + // Parameters: + float RelativeContrastThreshold; + float HardContrastThreshold; + float SubpixelBlendLimit; + float SubpixelContrastThreshold; + int EndpointSearchIterations; + + bool UseHighQualityEndpoints; + DebugOption DebugOptionValue; + + // Set to true when the shader definitions change so we know when to rebuild. + bool NeedToRebuildShader; + + vtkOpenGLRenderer *Renderer; + vtkTextureObject *Input; + + vtkShaderProgram *Program; + vtkOpenGLVertexArrayObject *VAO; + vtkOpenGLBufferObject *VBO; + +private: + vtkOpenGLFXAAFilter(const vtkOpenGLFXAAFilter&) VTK_DELETE_FUNCTION; + void operator=(const vtkOpenGLFXAAFilter&) VTK_DELETE_FUNCTION; +}; + +#endif // vtkOpenGLFXAAFilter_h diff --git a/Rendering/OpenGL2/vtkOpenGLRenderer.cxx b/Rendering/OpenGL2/vtkOpenGLRenderer.cxx index 23240406c51..7671dcaf2e7 100644 --- a/Rendering/OpenGL2/vtkOpenGLRenderer.cxx +++ b/Rendering/OpenGL2/vtkOpenGLRenderer.cxx @@ -27,6 +27,7 @@ PURPOSE. See the above copyright notice for more information. #include "vtkObjectFactory.h" #include "vtkOpenGLCamera.h" #include "vtkOpenGLError.h" +#include "vtkOpenGLFXAAFilter.h" #include "vtkOpenGLRenderWindow.h" #include "vtkPointData.h" #include "vtkPoints.h" @@ -73,6 +74,7 @@ vtkOpenGLRenderer::vtkOpenGLRenderer() this->PickInfo->NumPicked = 0; this->PickedZ = 0; + this->FXAAFilter = 0; this->DepthPeelingPass = 0; this->ShadowMapPass = 0; this->DepthPeelingHigherLayer=0; @@ -228,6 +230,33 @@ int vtkOpenGLRenderer::UpdateGeometry() } } + // Apply FXAA before volumes and overlays. Volumes don't need AA, and overlays + // are usually things like text, which are already antialiased. + if (this->UseFXAA) + { + if (!this->FXAAFilter) + { + this->FXAAFilter = vtkOpenGLFXAAFilter::New(); + } + + this->FXAAFilter->SetRelativeContrastThreshold( + this->FXAARelativeContrastThreshold); + this->FXAAFilter->SetHardContrastThreshold( + this->FXAAHardContrastThreshold); + this->FXAAFilter->SetSubpixelBlendLimit( + this->FXAASubpixelBlendLimit); + this->FXAAFilter->SetSubpixelContrastThreshold( + this->FXAASubpixelContrastThreshold); + this->FXAAFilter->SetEndpointSearchIterations( + this->FXAAEndpointSearchIterations); + this->FXAAFilter->SetUseHighQualityEndpoints( + this->FXAAUseHighQualityEndpoints); + this->FXAAFilter->SetDebugOptionValue( + static_cast<vtkOpenGLFXAAFilter::DebugOption>(this->FXAADebugOption)); + + this->FXAAFilter->Execute(this); + } + // loop through props and give them a chance to // render themselves as volumetric geometry. for ( i = 0; i < this->PropArrayCount; i++ ) @@ -587,6 +616,10 @@ void vtkOpenGLRenderer::ReleaseGraphicsResources(vtkWindow *w) { this->Pass->ReleaseGraphicsResources(w); } + if (this->FXAAFilter) + { + this->FXAAFilter->ReleaseGraphicsResources(); + } if (w && this->DepthPeelingPass) { this->DepthPeelingPass->ReleaseGraphicsResources(w); @@ -715,6 +748,12 @@ vtkOpenGLRenderer::~vtkOpenGLRenderer() this->Pass = NULL; } + if (this->FXAAFilter) + { + this->FXAAFilter->Delete(); + this->FXAAFilter = 0; + } + if (this->ShadowMapPass) { this->ShadowMapPass->Delete(); diff --git a/Rendering/OpenGL2/vtkOpenGLRenderer.h b/Rendering/OpenGL2/vtkOpenGLRenderer.h index 1f2adafe835..5da5f7df6f4 100644 --- a/Rendering/OpenGL2/vtkOpenGLRenderer.h +++ b/Rendering/OpenGL2/vtkOpenGLRenderer.h @@ -24,6 +24,7 @@ #include "vtkRenderer.h" #include <vector> // STL Header +class vtkOpenGLFXAAFilter; class vtkRenderPass; class vtkOpenGLTexture; class vtkTextureObject; @@ -107,6 +108,10 @@ protected: friend class vtkOpenGLImageSliceMapper; friend class vtkOpenGLImageResliceMapper; + // Description: + // FXAA is delegated to an instance of vtkOpenGLFXAAFilter + vtkOpenGLFXAAFilter *FXAAFilter; + // Description: // Depth peeling is delegated to an instance of vtkDepthPeelingPass vtkDepthPeelingPass *DepthPeelingPass; -- GitLab