From 5b569942b85e3980dcad955ef05677aadcabfd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 20 Mar 2020 21:42:32 +0100 Subject: [PATCH] Shaders: tangent space visualization in MeshVisualizer3D. --- doc/changelog.dox | 5 + doc/generated/primitives.cpp | 3 +- doc/generated/shaders.cpp | 5 +- doc/snippets/MagnumShaders.cpp | 49 ++- src/Magnum/Shaders/MeshVisualizer.cpp | 186 ++++++++-- src/Magnum/Shaders/MeshVisualizer.frag | 81 +++- src/Magnum/Shaders/MeshVisualizer.geom | 154 +++++++- src/Magnum/Shaders/MeshVisualizer.h | 280 ++++++++++++-- src/Magnum/Shaders/MeshVisualizer.vert | 81 +++- .../Shaders/Test/MeshVisualizerGLTest.cpp | 349 +++++++++++++++++- .../bitangents-from-tangents.tga | Bin 0 -> 856 bytes .../Test/MeshVisualizerTestFiles/tbn-wide.tga | Bin 0 -> 4259 bytes .../Test/MeshVisualizerTestFiles/tbn.tga | Bin 0 -> 1622 bytes .../wireframe-tn-smooth.tga | Bin 0 -> 2039 bytes .../MeshVisualizerTestFiles/wireframe-tn.tga | Bin 0 -> 1676 bytes 15 files changed, 1110 insertions(+), 83 deletions(-) create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/bitangents-from-tangents.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/tbn-wide.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/tbn.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn-smooth.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn.tga diff --git a/doc/changelog.dox b/doc/changelog.dox index aa1448267..920ebe76e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -206,6 +206,7 @@ See also: @subsubsection changelog-latest-new-shaders Shaders library - New @ref Shaders::MeshVisualizer2D for 2D mesh visualization +- Tangent space visualization in @ref Shaders::MeshVisualizer3D - Texture coordinate transformation in @ref Shaders::DistanceFieldVector, @ref Shaders::Flat, @ref Shaders::Phong and @ref Shaders::Vector - New attribute definitions and an location allocation scheme in @@ -502,6 +503,10 @@ See also: @ref MeshTools::subdivideInPlace() overloads instead - @cpp Shaders::MeshVisualizer @ce is deprecated as the shader can now handle both 2D and 3D, use @ref Shaders::MeshVisualizer3D instead +- @cpp Shaders::MeshVisualizer::setTransformationProjectionMatrix() @ce is + deprecated on the 3D variant, use separate + @ref Shaders::MeshVisualizer3D::setTransformationMatrix() and + @ref Shaders::MeshVisualizer3D::setProjectionMatrix() instead @subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs diff --git a/doc/generated/primitives.cpp b/doc/generated/primitives.cpp index 3f4832e26..166253984 100644 --- a/doc/generated/primitives.cpp +++ b/doc/generated/primitives.cpp @@ -312,7 +312,8 @@ int PrimitiveVisualizer::exec() { .setWireframeColor(OutlineColor) .setWireframeWidth(2.0f) .setViewportSize(Vector2{ImageSize}) - .setTransformationProjectionMatrix(Projection3D*Transformation3D); + .setTransformationMatrix(Transformation3D) + .setProjectionMatrix(Projection3D); { Shaders::Phong phong; diff --git a/doc/generated/shaders.cpp b/doc/generated/shaders.cpp index 5413bf63b..1e55aaeeb 100644 --- a/doc/generated/shaders.cpp +++ b/doc/generated/shaders.cpp @@ -186,7 +186,7 @@ std::string ShaderVisualizer::meshVisualizer2D() { } std::string ShaderVisualizer::meshVisualizer3D() { - const Matrix4 projection = Projection*Transformation* + const Matrix4 transformation = Transformation* Matrix4::rotationZ(13.7_degf)* Matrix4::rotationX(-12.6_degf); @@ -195,7 +195,8 @@ std::string ShaderVisualizer::meshVisualizer3D() { .setWireframeColor(OutlineColor) .setWireframeWidth(2.0f) .setViewportSize(Vector2{ImageSize}) - .setTransformationProjectionMatrix(projection) + .setTransformationMatrix(transformation) + .setProjectionMatrix(Projection) .draw(MeshTools::compile(Primitives::icosphereSolid(1))); return "meshvisualizer3d.png"; diff --git a/doc/snippets/MagnumShaders.cpp b/doc/snippets/MagnumShaders.cpp index 7d63b07fe..13a152faf 100644 --- a/doc/snippets/MagnumShaders.cpp +++ b/doc/snippets/MagnumShaders.cpp @@ -100,7 +100,8 @@ visualizerShader .setColor(0x2f83cc_rgbf) .setWireframeColor(0xdcdcdc_rgbf) .setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()}) - .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) + .setTransformationMatrix(transformationMatrix) + .setProjectionMatrix(projectionMatrix) .draw(mesh); /* [shaders-meshvisualizer] */ } @@ -266,7 +267,8 @@ Shaders::MeshVisualizer3D shader{Shaders::MeshVisualizer3D::Flag::Wireframe}; shader.setColor(0x2f83cc_rgbf) .setWireframeColor(0xdcdcdc_rgbf) .setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()}) - .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) + .setTransformationMatrix(transformationMatrix) + .setProjectionMatrix(projectionMatrix) .draw(mesh); /* [MeshVisualizer-usage-geom2] */ @@ -282,6 +284,46 @@ mesh.addVertexBuffer(vertexIndices, 0, Shaders::MeshVisualizer3D::VertexIndex{}) } #endif +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +{ +/* [MeshVisualizer-usage-tbn1] */ +struct Vertex { + Vector3 position; + Vector4 tangent; + Vector3 normal; +}; +Vertex data[60]{ + // ... +}; + +GL::Buffer vertices; +vertices.setData(data); + +GL::Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::MeshVisualizer3D::Position{}, + Shaders::MeshVisualizer3D::Tangent4{}, + Shaders::MeshVisualizer3D::Normal{}); +/* [MeshVisualizer-usage-tbn1] */ + +/* [MeshVisualizer-usage-tbn2] */ +Matrix4 transformationMatrix, projectionMatrix; + +Shaders::MeshVisualizer3D shader{ + Shaders::MeshVisualizer3D::Flag::TangentDirection| + Shaders::MeshVisualizer3D::Flag::BitangentFromTangentDirection| + Shaders::MeshVisualizer3D::Flag::NormalDirection}; +shader.setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()}) + .setTransformationMatrix(transformationMatrix) + .setProjectionMatrix(projectionMatrix) + .setNormalMatrix(transformationMatrix.normalMatrix()) + .setLineLength(0.3f) + .draw(mesh); +/* [MeshVisualizer-usage-tbn2] */ + +} +#endif + { /* [MeshVisualizer-usage-no-geom1] */ Containers::StridedArrayView1D indices; @@ -305,7 +347,8 @@ Shaders::MeshVisualizer3D shader{ Shaders::MeshVisualizer3D::Flag::NoGeometryShader}; shader.setColor(0x2f83cc_rgbf) .setWireframeColor(0xdcdcdc_rgbf) - .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) + .setTransformationMatrix(transformationMatrix) + .setProjectionMatrix(projectionMatrix) .draw(mesh); /* [MeshVisualizer-usage-no-geom2] */ } diff --git a/src/Magnum/Shaders/MeshVisualizer.cpp b/src/Magnum/Shaders/MeshVisualizer.cpp index 7c8347b1d..172957077 100644 --- a/src/Magnum/Shaders/MeshVisualizer.cpp +++ b/src/Magnum/Shaders/MeshVisualizer.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "Magnum/Math/Color.h" @@ -94,14 +95,6 @@ GL::Version MeshVisualizerBase::setupShaders(GL::Shader& vert, GL::Shader& frag, return version; } -MeshVisualizerBase& MeshVisualizerBase::setViewportSize(const Vector2& size) { - /* Not asserting here, since the relation to wireframe is a bit vague. - Also it's an ugly hack that should be removed, ideally. */ - if(_flags & FlagBase::Wireframe && !(_flags & FlagBase::NoGeometryShader)) - setUniform(_viewportSizeUniform, size); - return *this; -} - MeshVisualizerBase& MeshVisualizerBase::setColor(const Color4& color) { setUniform(_colorUniform, color); return *this; @@ -121,15 +114,6 @@ MeshVisualizerBase& MeshVisualizerBase::setWireframeWidth(const Float width) { return *this; } -MeshVisualizerBase& MeshVisualizerBase::setSmoothness(const Float smoothness) { - /* This is a bit vaguely related too, but less vague than setViewportSize() - so asserting. */ - CORRADE_ASSERT(_flags & FlagBase::Wireframe, - "Shaders::MeshVisualizer::setSmoothness(): the shader was not created with wireframe enabled", *this); - setUniform(_smoothnessUniform, smoothness); - return *this; -} - } MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedByte(flags))} { @@ -148,7 +132,9 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua Containers::Optional geom; if(flags & Flag::Wireframe && !(flags & Flag::NoGeometryShader)) { geom = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Geometry); - geom->addSource(rs.get("MeshVisualizer.geom")); + (*geom) + .addSource("#define WIREFRAME_RENDERING\n#define MAX_VERTICES 3\n") + .addSource(rs.get("MeshVisualizer.geom")); } #else static_cast(version); @@ -214,28 +200,75 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua #endif } +MeshVisualizer2D& MeshVisualizer2D::setViewportSize(const Vector2& size) { + /* Not asserting here, since the relation to wireframe is a bit vague. + Also it's an ugly hack that should be removed, ideally. */ + if(flags() & Flag::Wireframe && !(flags() & Flag::NoGeometryShader)) + setUniform(_viewportSizeUniform, size); + return *this; +} + MeshVisualizer2D& MeshVisualizer2D::setTransformationProjectionMatrix(const Matrix3& matrix) { setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } +MeshVisualizer2D& MeshVisualizer2D::setSmoothness(const Float smoothness) { + /* This is a bit vaguely related but less vague than setViewportSize() so + asserting in this case. */ + CORRADE_ASSERT(flags() & Flag::Wireframe, + "Shaders::MeshVisualizer2D::setSmoothness(): the shader was not created with wireframe enabled", *this); + setUniform(_smoothnessUniform, smoothness); + return *this; +} + MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedByte(flags))} { + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + CORRADE_ASSERT(!(flags & Flag::NoGeometryShader && flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)), + "Shaders::MeshVisualizer3D: geometry shader has to be enabled when rendering TBN direction", ); + CORRADE_ASSERT(!(flags & Flag::BitangentDirection && flags & Flag::BitangentFromTangentDirection), + "Shaders::MeshVisualizer3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive", ); + #endif + Utility::Resource rs{"MagnumShaders"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; const GL::Version version = setupShaders(vert, frag, rs); vert.addSource("#define THREE_DIMENSIONS\n") + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + .addSource(flags & Flag::TangentDirection ? "#define TANGENT_DIRECTION\n" : "") + .addSource(flags & Flag::BitangentFromTangentDirection ? "#define BITANGENT_FROM_TANGENT_DIRECTION\n" : "") + .addSource(flags & Flag::BitangentDirection ? "#define BITANGENT_DIRECTION\n" : "") + .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : "") + #endif .addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.vert")); - frag.addSource(rs.get("generic.glsl")) + frag + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + .addSource(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) ? "#define TBN_DIRECTION\n" : "") + #endif + .addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.frag")); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) Containers::Optional geom; - if(flags & Flag::Wireframe && !(flags & Flag::NoGeometryShader)) { + if(flags & (Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) && !(flags & Flag::NoGeometryShader)) { + Int maxVertices = 0; + if(flags & Flag::Wireframe) maxVertices += 3; + if(flags & Flag::TangentDirection) maxVertices += 3*6; + if(flags & (Flag::BitangentDirection|Flag::BitangentFromTangentDirection)) + maxVertices += 3*6; + if(flags & Flag::NormalDirection) maxVertices += 3*6; + geom = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Geometry); - geom->addSource(rs.get("MeshVisualizer.geom")); + (*geom) + .addSource(Utility::formatString("#define MAX_VERTICES {}\n", maxVertices)) + .addSource(flags & Flag::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") + .addSource(flags & Flag::TangentDirection ? "#define TANGENT_DIRECTION\n" : "") + .addSource(flags & (Flag::BitangentDirection|Flag::BitangentFromTangentDirection) ? "#define BITANGENT_DIRECTION\n" : "") + .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : "") + .addSource(rs.get("MeshVisualizer.geom")); } #else static_cast(version); @@ -260,6 +293,17 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua { bindAttributeLocation(Position::Location, "position"); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(flags & Flag::TangentDirection || + flags & Flag::BitangentFromTangentDirection) + bindAttributeLocation(Tangent4::Location, "tangent"); + if(flags & Flag::BitangentDirection) + bindAttributeLocation(Bitangent::Location, "bitangent"); + if(flags & Flag::NormalDirection || + flags & Flag::BitangentFromTangentDirection) + bindAttributeLocation(Normal::Location, "normal"); + #endif + #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isVersionSupported(GL::Version::GL310)) @@ -277,32 +321,118 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua if(!GL::Context::current().isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + _transformationMatrixUniform = uniformLocation("transformationMatrix"); + _projectionMatrixUniform = uniformLocation("projectionMatrix"); _colorUniform = uniformLocation("color"); if(flags & Flag::Wireframe) { _wireframeColorUniform = uniformLocation("wireframeColor"); _wireframeWidthUniform = uniformLocation("wireframeWidth"); + } + if(flags & (Flag::Wireframe + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection + #endif + )) { _smoothnessUniform = uniformLocation("smoothness"); if(!(flags & Flag::NoGeometryShader)) _viewportSizeUniform = uniformLocation("viewportSize"); } + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { + _normalMatrixUniform = uniformLocation("normalMatrix"); + _lineWidthUniform = uniformLocation("lineWidth"); + _lineLengthUniform = uniformLocation("lineLength"); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix({}); + setTransformationMatrix({}); + setProjectionMatrix({}); setColor(Color3(1.0f)); if(flags & Flag::Wireframe) { /* Viewport size is zero by default */ setWireframeColor(Color3{0.0f}); setWireframeWidth(1.0f); + } + if(flags & (Flag::Wireframe + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection + #endif + )) { setSmoothness(2.0f); } + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { + setNormalMatrix({}); + setLineWidth(1.0f); + setLineLength(1.0f); + } + #endif #endif } -MeshVisualizer3D& MeshVisualizer3D::setTransformationProjectionMatrix(const Matrix4& matrix) { - setUniform(_transformationProjectionMatrixUniform, matrix); +MeshVisualizer3D& MeshVisualizer3D::setTransformationMatrix(const Matrix4& matrix) { + setUniform(_transformationMatrixUniform, matrix); + return *this; +} + +MeshVisualizer3D& MeshVisualizer3D::setProjectionMatrix(const Matrix4& matrix) { + setUniform(_projectionMatrixUniform, matrix); + return *this; +} + +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +MeshVisualizer3D& MeshVisualizer3D::setNormalMatrix(const Matrix3x3& matrix) { + CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), + "Shaders::MeshVisualizer3D::setNormalMatrix(): the shader was not created with TBN direction enabled", *this); + setUniform(_normalMatrixUniform, matrix); + return *this; +} +#endif + +MeshVisualizer3D& MeshVisualizer3D::setViewportSize(const Vector2& size) { + /* Not asserting here, since the relation to wireframe is a bit vague. + Also it's an ugly hack that should be removed, ideally. */ + if((flags() & Flag::Wireframe && !(flags() & Flag::NoGeometryShader)) + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + || flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) + #endif + ) + setUniform(_viewportSizeUniform, size); + return *this; +} + +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +MeshVisualizer3D& MeshVisualizer3D::setLineWidth(const Float width) { + CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), + "Shaders::MeshVisualizer3D::setLineWidth(): the shader was not created with TBN direction enabled", *this); + setUniform(_lineWidthUniform, width); + return *this; +} + +MeshVisualizer3D& MeshVisualizer3D::setLineLength(const Float length) { + CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), + "Shaders::MeshVisualizer3D::setLineLength(): the shader was not created with TBN direction enabled", *this); + setUniform(_lineLengthUniform, length); + return *this; +} +#endif + +MeshVisualizer3D& MeshVisualizer3D::setSmoothness(const Float smoothness) { + #ifndef CORRADE_NO_ASSERT + /* This is a bit vaguely related but less vague than setViewportSize() so + asserting in this case. */ + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + constexpr Flags allowed = Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection; + #else + constexpr Flags allowed = Flag::Wireframe; + #endif + CORRADE_ASSERT(flags() & allowed, + "Shaders::MeshVisualizer3D::setSmoothness(): the shader was not created with wireframe or TBN direction enabled", *this); + #endif + setUniform(_smoothnessUniform, smoothness); return *this; } @@ -329,6 +459,12 @@ Debug& operator<<(Debug& debug, const MeshVisualizer3D::Flag value) { #define _c(v) case MeshVisualizer3D::Flag::v: return debug << "::" #v; _c(NoGeometryShader) _c(Wireframe) + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + _c(TangentDirection) + _c(BitangentFromTangentDirection) + _c(BitangentDirection) + _c(NormalDirection) + #endif #undef _c /* LCOV_EXCL_STOP */ } diff --git a/src/Magnum/Shaders/MeshVisualizer.frag b/src/Magnum/Shaders/MeshVisualizer.frag index 7f454ae88..8b72d6307 100644 --- a/src/Magnum/Shaders/MeshVisualizer.frag +++ b/src/Magnum/Shaders/MeshVisualizer.frag @@ -44,6 +44,7 @@ #extension GL_NV_shader_noperspective_interpolation: require #endif +#ifndef TBN_DIRECTION #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) #endif @@ -53,7 +54,7 @@ uniform lowp vec4 color #endif ; -#ifdef WIREFRAME_RENDERING +#if defined(WIREFRAME_RENDERING) && !defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 2) #endif @@ -62,7 +63,10 @@ uniform lowp vec4 wireframeColor = vec4(0.0, 0.0, 0.0, 1.0) #endif ; +#endif +#endif +#ifdef WIREFRAME_RENDERING #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 3) #endif @@ -71,7 +75,18 @@ uniform lowp float wireframeWidth = 1.0 #endif ; +#elif defined(TBN_DIRECTION) +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 8) +#endif +uniform lowp float lineWidth + #ifndef GL_ES + = 1.0 + #endif + ; +#endif +#if defined(WIREFRAME_RENDERING) || defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 4) #endif @@ -91,6 +106,11 @@ in lowp vec3 barycentric; #endif #endif +#ifdef TBN_DIRECTION +in lowp vec4 backgroundColor; +in lowp vec4 lineColor; +#endif + #ifdef NEW_GLSL #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = COLOR_OUTPUT_ATTRIBUTE_LOCATION) @@ -99,13 +119,63 @@ out lowp vec4 fragmentColor; #endif void main() { - #ifdef WIREFRAME_RENDERING + /* 1. For wireframe the line is on the triangle edges, thus dist = 0 at + vertices and dist = w (= wireframeWidth) at center of smoothed edge. + 2. For antialiased TBN (drawn alone) the line is in the center of the + bar, thus dist = 0 at the center, dist = ±w (= lineWidth) at center + of the smoothed edge, and dist = ±S = w + s (= smoothness) at edge of + the primitive. + 3. For non-antialised TBN (drawn together with wireframe) the bar width + is without the extra padding for smoothness. + + 0 _____________ 0 -S_-w_0__w_S -w_0__w + \ _______ / | | | | | | + \ w w / | | /| | | /| + \ \ / / | | / | | | / | + \ \w/ / | | / | | | / | + \ v / | | / | | | / | + \ / | |/ | | |/ | + v |_|_____|_| |_____| + 0 -S -w 0 w S -w 0 w + */ + #if defined(WIREFRAME_RENDERING) || defined(TBN_DIRECTION) #ifndef NO_GEOMETRY_SHADER - /* Distance to nearest side */ + /* Distance to nearest side. If signed distance is involved when rendering + TBN alone, we need to find an absolute distance */ + #if defined(TBN_DIRECTION) && !defined(WIREFRAME_RENDERING) + lowp const float nearest = min(min(abs(dist.x), abs(dist.y)), abs(dist.z)); + #else lowp const float nearest = min(min(dist.x, dist.y), dist.z); + #endif - /* Smooth step between face color and wireframe color based on distance */ - fragmentColor = mix(wireframeColor, color, smoothstep(wireframeWidth-smoothness, wireframeWidth+smoothness, nearest)); + /* Smooth step between two colors based on distance. + + 1. For wireframe alone the width and colors are supplied directly from + wireframeWidth, wireframeColor, color uniforms + 2. For TBN alone the width is supplied directly from the lineWidth + uniform and colors from the GS + 3. For TBN and wireframe together wireframeWidth is used to draw the + wireframe, and since `nearest` is always 0 for the TBN bars, there it + doesn't matter. + */ + fragmentColor = mix( + #if defined(WIREFRAME_RENDERING) && !defined(TBN_DIRECTION) + wireframeColor, color, + #else + lineColor, backgroundColor, + #endif + #ifdef WIREFRAME_RENDERING + smoothstep(wireframeWidth - smoothness, + wireframeWidth + smoothness, nearest) + #elif defined(TBN_DIRECTION) + smoothstep(lineWidth - smoothness, + lineWidth + smoothness, nearest) + #else + #error + #endif + ); + + /* Wireframe rendering without a GS. No TBN in this case */ #else const lowp vec3 d = fwidth(barycentric); const lowp vec3 factor = smoothstep(vec3(0.0), d*1.5, barycentric); @@ -113,6 +183,7 @@ void main() { fragmentColor = mix(wireframeColor, color, nearest); #endif + /* Plain color rendering */ #else fragmentColor = color; #endif diff --git a/src/Magnum/Shaders/MeshVisualizer.geom b/src/Magnum/Shaders/MeshVisualizer.geom index 32989140b..ffbfcc718 100644 --- a/src/Magnum/Shaders/MeshVisualizer.geom +++ b/src/Magnum/Shaders/MeshVisualizer.geom @@ -41,7 +41,55 @@ uniform lowp vec2 viewportSize; /* defaults to zero */ layout(triangles) in; -layout(triangle_strip, max_vertices = 3) out; +#if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 1) +#endif +uniform lowp vec4 color + #ifndef GL_ES + = vec4(1.0, 1.0, 1.0, 1.0) + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 2) +#endif +uniform lowp vec4 wireframeColor + #ifndef GL_ES + = vec4(0.0, 0.0, 0.0, 1.0) + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 8) +#endif +uniform lowp float lineWidth + #ifndef GL_ES + = 1.0 + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 4) +#endif +uniform lowp float smoothness + #ifndef GL_ES + = 2.0 + #endif + ; +#endif + +#ifdef TANGENT_DIRECTION +in highp vec4 tangentEndpoint[]; +#endif +#ifdef BITANGENT_DIRECTION +in highp vec4 bitangentEndpoint[]; +#endif +#ifdef NORMAL_DIRECTION +in highp vec4 normalEndpoint[]; +#endif + +layout(triangle_strip, max_vertices = MAX_VERTICES) out; /* Interpolate in screen space (if possible) */ #if !defined(GL_ES) || defined(GL_NV_shader_noperspective_interpolation) @@ -49,12 +97,89 @@ noperspective #endif out lowp vec3 dist; +#if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) +out lowp vec4 backgroundColor; +out lowp vec4 lineColor; + +void emitQuad(vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointScreen) { + /* Calculate screen-space locations for the bar vertices and form two + triangles out of them. In case TBN is rendered alone, half bar width is + lineWidth + smoothness to allow for antialiasing, in case it's rendered + together with wireframe, it's just lineWidth, as antialiasing would + cause depth order issues. + + 3 - e - 1 -S_-w_0__w_S -w_0__w + | /| | | | | | | + | / | | | /| | | /| + | / | | | / | | | / | + | / | | | / | | | / | + | / | | | / | | | / | + | / | | |/ | | |/ | + |/ | |_|_____|_| |_____| + 2 - p - 0 -S -w 0 w S -w 0 w + + The edgeDistance is set to a signed half-width for the antialiased case + and to 0 otherwise. See the fragment shader for details. + */ + vec2 direction = normalize(endpointScreen - positionScreen); + #ifdef WIREFRAME_RENDERING + float edgeDistance = 0.0; + vec2 halfSide = lineWidth*vec2(-direction.y, direction.x); + #else + float edgeDistance = lineWidth + smoothness; + vec2 halfSide = edgeDistance*vec2(-direction.y, direction.x); + #endif + + /* 0 */ + dist = vec3(-edgeDistance); + gl_Position = vec4((positionScreen - halfSide)*position.w/viewportSize, position.zw); + EmitVertex(); + + /* 1 */ + dist = vec3(-edgeDistance); + gl_Position = vec4((endpointScreen - halfSide)*endpoint.w/viewportSize, endpoint.zw); + EmitVertex(); + + /* 2 */ + dist = vec3(edgeDistance); + gl_Position = vec4((positionScreen + halfSide)*position.w/viewportSize, position.zw); + EmitVertex(); + + /* 3 */ + dist = vec3(edgeDistance); + gl_Position = vec4((endpointScreen + halfSide)*endpoint.w/viewportSize, endpoint.zw); + EmitVertex(); + + EndPrimitive(); +} +#endif + void main() { /* Screen position of each vertex */ vec2 p[3]; - for(int i = 0; i != 3; ++i) + #ifdef TANGENT_DIRECTION + vec2 t[3]; + #endif + #ifdef BITANGENT_DIRECTION + vec2 b[3]; + #endif + #ifdef NORMAL_DIRECTION + vec2 n[3]; + #endif + for(int i = 0; i != 3; ++i) { p[i] = viewportSize*gl_in[i].gl_Position.xy/gl_in[i].gl_Position.w; + #ifdef TANGENT_DIRECTION + t[i] = viewportSize*tangentEndpoint[i].xy/tangentEndpoint[i].w; + #endif + #ifdef BITANGENT_DIRECTION + b[i] = viewportSize*bitangentEndpoint[i].xy/bitangentEndpoint[i].w; + #endif + #ifdef NORMAL_DIRECTION + n[i] = viewportSize*normalEndpoint[i].xy/normalEndpoint[i].w; + #endif + } + #ifdef WIREFRAME_RENDERING /* Vector of each triangle side */ const vec2 v[3] = vec2[3]( p[2]-p[1], @@ -69,9 +194,34 @@ void main() { for(int i = 0; i != 3; ++i) { dist = vec3(0.0, 0.0, 0.0); dist[i] = area/length(v[i]); + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) + backgroundColor = color; + lineColor = wireframeColor; + #endif gl_Position = gl_in[i].gl_Position; EmitVertex(); } EndPrimitive(); + #endif + + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) + backgroundColor = vec4(0.0, 0.0, 0.0, 0.0); + for(int i = 0; i != 3; ++i) { + #ifdef TANGENT_DIRECTION + lineColor = vec4(1.0, 0.0, 0.0, 1.0); + emitQuad(gl_in[i].gl_Position, p[i], tangentEndpoint[i], t[i]); + #endif + + #ifdef BITANGENT_DIRECTION + lineColor = vec4(0.0, 1.0, 0.0, 1.0); + emitQuad(gl_in[i].gl_Position, p[i], bitangentEndpoint[i], b[i]); + #endif + + #ifdef NORMAL_DIRECTION + lineColor = vec4(0.0, 0.0, 1.0, 1.0); + emitQuad(gl_in[i].gl_Position, p[i], normalEndpoint[i], n[i]); + #endif + } + #endif } diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index e4cc505f6..80a4c2a0c 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -59,11 +59,9 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerBase: public GL::AbstractShaderProgram MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const; - MeshVisualizerBase& setViewportSize(const Vector2& size); MeshVisualizerBase& setColor(const Color4& color); MeshVisualizerBase& setWireframeColor(const Color4& color); MeshVisualizerBase& setWireframeWidth(Float width); - MeshVisualizerBase& setSmoothness(Float smoothness); /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES @@ -205,9 +203,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali * shaders are used, otherwise it does nothing. Initial value is a zero * vector. */ - MeshVisualizer2D& setViewportSize(const Vector2& size) { - return static_cast(Implementation::MeshVisualizerBase::setViewportSize(size)); - } + MeshVisualizer2D& setViewportSize(const Vector2& size); /** * @brief Set base object color @@ -250,9 +246,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali * initial value is @cpp 2.0f @ce. Expects that @ref Flag::Wireframe is * enabled. */ - MeshVisualizer2D& setSmoothness(Float smoothness) { - return static_cast(Implementation::MeshVisualizerBase::setSmoothness(smoothness)); - } + MeshVisualizer2D& setSmoothness(Float smoothness); private: Int _transformationProjectionMatrixUniform{0}; @@ -290,7 +284,34 @@ have OpenGL < 3.1 or OpenGL ES 2.0, you need to provide also the wireframe rendering without geometry shaders. If using geometry shaders on OpenGL ES, @gl_extension{NV,shader_noperspective_interpolation} -is optionally used for improving line appearance. +is optionally used for improving line appearance. On desktop OpenGL this is +done implicitly. + +@section Shaders-MeshVisualizer-tbn Tangent space visualization + +On platforms with geometry shaders (desktop GL, OpenGL ES 3.2), the shader is +able to visualize tangents, bitangent and normal direction via colored lines +coming out of vertices (red, green and blue for tangent, bitangent and normal, respectively). This can be enabled together with wireframe visualization, +however note that when both are enabled, the lines are not antialiased to avoid +depth ordering artifacts. + +For tangents and normals, you need to provide the @ref Tangent and @ref Normal +attributes and enable @ref Flag::TangentDirection and +@ref Flag::NormalDirection, respectively. If any of the attributes isn't +present, its data are implicitly zero and thus the direction isn't shown --- +which means you don't need to worry about having two active variants of the +shader and switching between either depending on whether tangents are present +or not. + +For bitangents however, there are two possible representations --- the more +efficient one is via a fourth component in the tangent attribute that +indicates tangent space handedness, in which case you'll be using the +@ref Tangent4 attribute instead of @ref Tangent, and enable +@ref Flag::BitangentFromTangentDirection. The other, more obvious but less +efficient representation, is a dedicated @ref Bitangent attribute (in which +case you'll enable @ref Flag::BitangentDirection). Note that these two are +mutually exclusive, so you need to choose either of them based on what given +mesh contains. @section Shaders-MeshVisualizer-usage Example usage @@ -304,6 +325,18 @@ Common rendering setup: @snippet MagnumShaders.cpp MeshVisualizer-usage-geom2 +@subsection Shaders-MeshVisualizer-usage-wireframe-tbn Tangent space visualization with a geometry shader (desktop GL, OpenGL ES 3.2) + +Setup for a mesh that contains four-component tangents +(@ref Shaders-MeshVisualizer-tbn "see above" if you have a dedicated bitangent +attribute instead): + +@snippet MagnumShaders.cpp MeshVisualizer-usage-tbn1 + +Rendering setup: + +@snippet MagnumShaders.cpp MeshVisualizer-usage-tbn2 + @subsection Shaders-MeshVisualizer-usage-wireframe-no-geom Wireframe visualization of indexed meshes without a geometry shader The vertices have to be converted to a non-indexed array first. Mesh setup: @@ -336,6 +369,47 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali */ typedef typename Generic3D::Position Position; + /** + * @brief Tangent direction + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector3 "Vector3". Use either this or the @ref Tangent4 + * attribute. Used only if @ref Flag::TangentDirection is enabled. + */ + typedef typename Generic3D::Tangent Tangent; + + /** + * @brief Tangent direction with a bitangent sign + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector4 "Vector4". Use either this or the @ref Tangent + * attribute. Used only if @ref Flag::TangentDirection or + * @ref Flag::BitangentFromTangentDirection is enabled. + */ + typedef typename Generic3D::Tangent4 Tangent4; + + /** + * @brief Bitangent direction + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector4 "Vector4". Use either this or the @ref Tangent4 + * attribute. Used only if @ref Flag::BitangentDirection is enabled. + */ + typedef typename Generic3D::Bitangent Bitangent; + + /** + * @brief Normal direction + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector3 "Vector3". Used only if + * @ref Flag::NormalDirection is enabled. + */ + typedef typename Generic3D::Normal Normal; + /** * @brief Vertex index * @@ -384,8 +458,78 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * enabled, you might need to provide also the @ref VertexIndex * attribute in the mesh. In OpenGL ES 2.0 enabled alongside * @ref Flag::Wireframe. + * + * Mutually exclusive with @ref Flag::TangentDirection, + * @ref Flag::BitangentFromTangentDirection, + * @ref Flag::BitangentDirection and @ref Flag::NormalDirection --- + * those need a geometry shader always. */ - NoGeometryShader = 1 << 1 + NoGeometryShader = 1 << 1, + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /** + * Visualize tangent direction with red lines pointing out of + * vertices. You need to provide the @ref Tangent or @ref Tangent4 + * attribute in the mesh. Mutually exclusive with + * @ref Flag::NoGeometryShader (as this needs a geometry shader + * always). + * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / + * @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + * @m_since_latest + */ + TangentDirection = 1 << 2, + + /** + * Visualize bitangent direction with green lines pointing out of + * vertices. You need to provide both @ref Normal and @ref Tangent4 + * attributes in the mesh, alternatively you can provide the + * @ref Bitangent attribute and enable + * @ref Flag::BitangentDirection instead. Mutually exclusive with + * @ref Flag::NoGeometryShader (as this needs a geometry shader + * always). + * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / + * @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + * @m_since_latest + */ + BitangentFromTangentDirection = 1 << 3, + + /** + * Visualize bitangent direction with green lines pointing out of + * vertices. You need to provide the @ref Bitangent attribute in + * the mesh, alternatively you can provide both @ref Normal and + * @ref Tangent4 attributes and enable + * @ref Flag::BitangentFromTangentDirection instead. Mutually + * exclusive with @ref Flag::NoGeometryShader (as this needs a + * geometry shader always). + * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / + * @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + * @m_since_latest + */ + BitangentDirection = 1 << 4, + + /** + * Visualize normal direction with blue lines pointing out of + * vertices. You need to provide the @ref Normal attribute in the + * mesh. Mutually exclusive with @ref Flag::NoGeometryShader (as + * this needs a geometry shader always). + * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / + * @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + * @m_since_latest + */ + NormalDirection = 1 << 5 + #endif }; /** @brief Flags */ @@ -428,24 +572,67 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali return reinterpret_cast(Implementation::MeshVisualizerBase::_flags); } + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Set transformation and projection matrix + * @m_deprecated_since_latest Use @ref setTransformationMatrix() and + * @ref setProjectionMatrix() instead. + */ + CORRADE_DEPRECATED("use setTransformationMatrix() and setProjectionMatrix() instead") MeshVisualizer3D& setTransformationProjectionMatrix(const Matrix4& matrix) { + /* Keep projection at identity, which should still work for + wireframe (but of course not for TBN visualization) */ + return setTransformationMatrix(matrix); + } + #endif + + /** + * @brief Set transformation matrix * @return Reference to self (for method chaining) * * Initial value is an identity matrix. */ - MeshVisualizer3D& setTransformationProjectionMatrix(const Matrix4& matrix); + MeshVisualizer3D& setTransformationMatrix(const Matrix4& matrix); + + /** + * @brief Set projection matrix + * @return Reference to self (for method chaining) + * + * Initial value is an identity matrix. (i.e., an orthographic + * projection of the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ + * cube). + */ + MeshVisualizer3D& setProjectionMatrix(const Matrix4& matrix); + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /** + * @brief Set transformation matrix + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::TangentDirection, + * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is + * enabled. The matrix doesn't need to be normalized, as + * renormalization is done per-fragment anyway. + * Initial value is an identity matrix. + * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / + * @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + */ + MeshVisualizer3D& setNormalMatrix(const Matrix3x3& matrix); + #endif /** * @brief Set viewport size * @return Reference to self (for method chaining) * * Has effect only if @ref Flag::Wireframe is enabled and geometry - * shaders are used. Initial value is a zero vector. + * shaders are used; or if @ref Flag::TangentDirection, + * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is + * enabled, otherwise it does nothing. Initial value is a zero vector. */ - MeshVisualizer3D& setViewportSize(const Vector2& size) { - return static_cast(Implementation::MeshVisualizerBase::setViewportSize(size)); - } + MeshVisualizer3D& setViewportSize(const Vector2& size); /** * @brief Set base object color @@ -472,27 +659,74 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @brief Set wireframe width * @return Reference to self (for method chaining) * - * Initial value is @cpp 1.0f @ce. Has effect only if - * @ref Flag::Wireframe is enabled. + * Value is in screen space (depending on @ref setViewportSize()), + * initial value is @cpp 1.0f @ce. Expects that @ref Flag::Wireframe is + * enabled. */ MeshVisualizer3D& setWireframeWidth(Float width) { return static_cast(Implementation::MeshVisualizerBase::setWireframeWidth(width)); } + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /** + * @brief Set line width + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Value is in screen space (depending on @ref setViewportSize()), + * initial value is @cpp 1.0f @ce. Expects that + * @ref Flag::TangentDirection, + * @ref Flag::BitangentFromTangentDirection, + * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is + * enabled. + * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / + * @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + */ + MeshVisualizer3D& setLineWidth(Float width); + + /** + * @brief Set line length + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Value is in object space, initial value is @cpp 1.0f @ce. Expects + * that @ref Flag::TangentDirection, + * @ref Flag::BitangentFromTangentDirection, + * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is + * enabled. + * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / + * @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + */ + MeshVisualizer3D& setLineLength(Float length); + #endif + /** * @brief Set line smoothness * @return Reference to self (for method chaining) * * Value is in screen space (depending on @ref setViewportSize()), - * initial value is @cpp 2.0f @ce. Expects that @ref Flag::Wireframe - * is enabled. + * initial value is @cpp 2.0f @ce. Expects that @ref Flag::Wireframe, + * @ref Flag::TangentDirection, + * @ref Flag::BitangentFromTangentDirection, + * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is + * enabled. */ - MeshVisualizer3D& setSmoothness(Float smoothness) { - return static_cast(Implementation::MeshVisualizerBase::setSmoothness(smoothness)); - } + MeshVisualizer3D& setSmoothness(Float smoothness); private: - Int _transformationProjectionMatrixUniform{0}; + Int _transformationMatrixUniform{0}, + _projectionMatrixUniform{6}; + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + Int _normalMatrixUniform{7}, + _lineWidthUniform{8}, + _lineLengthUniform{9}; + #endif }; #ifdef MAGNUM_BUILD_DEPRECATED diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index c38a0a40b..63352b9d1 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -28,17 +28,28 @@ #define out varying #endif +#ifdef TWO_DIMENSIONS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif -#ifdef TWO_DIMENSIONS uniform highp mat3 transformationProjectionMatrix #ifndef GL_ES = mat3(1.0) #endif ; #elif defined(THREE_DIMENSIONS) -uniform highp mat4 transformationProjectionMatrix +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp mat4 transformationMatrix + #ifndef GL_ES + = mat4(1.0) + #endif + ; +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 6) +#endif +uniform highp mat4 projectionMatrix #ifndef GL_ES = mat4(1.0) #endif @@ -47,6 +58,26 @@ uniform highp mat4 transformationProjectionMatrix #error #endif +#if defined(TANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 7) +#endif +uniform highp mat3 normalMatrix + #ifndef GL_ES + = mat3(1.0) + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 9) +#endif +uniform highp float lineLength + #ifndef GL_ES + = 1.0 + #endif + ; +#endif + #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = POSITION_ATTRIBUTE_LOCATION) #endif @@ -58,6 +89,27 @@ in highp vec4 position; #error #endif +#if defined(TANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = TANGENT_ATTRIBUTE_LOCATION) +#endif +in highp vec4 tangent; +#endif + +#ifdef BITANGENT_DIRECTION +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = BITANGENT_ATTRIBUTE_LOCATION) +#endif +in highp vec3 bitangent; +#endif + +#if defined(NORMAL_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = NORMAL_ATTRIBUTE_LOCATION) +#endif +in highp vec3 normal; +#endif + #if defined(WIREFRAME_RENDERING) && defined(NO_GEOMETRY_SHADER) #if (!defined(GL_ES) && __VERSION__ < 140) || (defined(GL_ES) && __VERSION__ < 300) #ifdef EXPLICIT_ATTRIB_LOCATION @@ -70,15 +122,38 @@ in lowp float vertexIndex; out vec3 barycentric; #endif +#ifdef TANGENT_DIRECTION +out highp vec4 tangentEndpoint; +#endif +#if defined(BITANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) +out highp vec4 bitangentEndpoint; +#endif +#ifdef NORMAL_DIRECTION +out highp vec4 normalEndpoint; +#endif + void main() { #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) - gl_Position = transformationProjectionMatrix*position; + gl_Position = projectionMatrix*transformationMatrix*position; #else #error #endif + #ifdef TANGENT_DIRECTION + tangentEndpoint = projectionMatrix*(transformationMatrix*position + vec4(normalize(normalMatrix*tangent.xyz)*lineLength, 0.0)); + #endif + #ifdef BITANGENT_FROM_TANGENT_DIRECTION + vec3 bitangent = cross(normal, tangent.xyz)*tangent.w; + #endif + #if defined(BITANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) + bitangentEndpoint = projectionMatrix*(transformationMatrix*position + vec4(normalize(normalMatrix*bitangent)*lineLength, 0.0)); + #endif + #ifdef NORMAL_DIRECTION + normalEndpoint = projectionMatrix*(transformationMatrix*position + vec4(normalize(normalMatrix*normal)*lineLength, 0.0)); + #endif + #if defined(WIREFRAME_RENDERING) && defined(NO_GEOMETRY_SHADER) barycentric = vec3(0.0); diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index c33306f9f..5363411d4 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -68,7 +68,10 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void constructWireframeGeometryShader2D(); - void constructWireframeGeometryShader3D(); + void constructGeometryShader3D(); + + void construct3DGeometryShaderDisabledButNeeded(); + void construct3DConflictingBitangentInput(); #endif void constructMove2D(); @@ -76,6 +79,9 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void setWireframeNotEnabled2D(); void setWireframeNotEnabled3D(); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + void setTangentBitangentNormalNotEnabled3D(); + #endif void renderSetup(); void renderTeardown(); @@ -85,6 +91,7 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void renderDefaultsWireframe2D(); void renderDefaultsWireframe3D(); + void renderDefaultsTangentBitangentNormal(); #endif void render2D(); void render3D(); @@ -92,13 +99,15 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void renderWireframe3D(); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void renderWireframe3DPerspective(); + void renderTangentBitangentNormal(); #endif private: PluginManager::Manager _manager{"nonexistent"}; std::string _testDir; - GL::Renderbuffer _color{NoCreate}; + GL::Renderbuffer _color{NoCreate}, + _depth{NoCreate}; #ifndef MAGNUM_TARGET_GLES2 GL::Renderbuffer _objectId{NoCreate}; #endif @@ -111,7 +120,7 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { - Mesa Intel - Mesa AMD - SwiftShader ES2/ES3 - - ARM Mali (Huawei P10) ES2/ES3 + - ARM Mali (Huawei P10) ES2/ES3 (except TBN visualization) - WebGL 1 / 2 (on Mesa Intel) - iPhone 6 w/ iOS 12.4 */ @@ -134,6 +143,22 @@ constexpr struct { {"wireframe w/o GS", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::NoGeometryShader} }; +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +constexpr struct { + const char* name; + MeshVisualizer3D::Flags flags; +} ConstructGeometryShaderData3D[] { + {"wireframe", MeshVisualizer3D::Flag::Wireframe}, + {"tangent direction", MeshVisualizer3D::Flag::TangentDirection}, + {"bitangent direction from tangent", MeshVisualizer3D::Flag::BitangentFromTangentDirection}, + {"bitangent direction", MeshVisualizer3D::Flag::BitangentDirection}, + {"normal direction", MeshVisualizer3D::Flag::NormalDirection}, + {"tbn direction", MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::BitangentDirection|MeshVisualizer3D::Flag::NormalDirection}, + {"tbn direction with bitangent from tangent", MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::BitangentFromTangentDirection|MeshVisualizer3D::Flag::NormalDirection}, + {"wireframe + t/n direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::NormalDirection} +}; +#endif + constexpr struct { const char* name; MeshVisualizer2D::Flags flags; @@ -174,6 +199,60 @@ constexpr struct { 3.0f, 1.0f, "wireframe-wide3D.tga", "wireframe-nogeo3D.tga"} }; +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +constexpr struct { + const char* name; + MeshVisualizer3D::Flags flags; + MeshVisualizer3D::Flags secondPassFlags; + bool skipBitagnentEvenIfEnabledInFlags; + Float smoothness; + Float lineWidth; + Float lineLength; + Float multiply; + const char* file; +} TangentBitangentNormalData[] { + {"", + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::BitangentDirection| + MeshVisualizer3D::Flag::NormalDirection, {}, + false, 2.0f, 1.0f, 0.6f, 1.0f, "tbn.tga"}, + {"bitangents from tangents", + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::BitangentFromTangentDirection| + MeshVisualizer3D::Flag::NormalDirection, {}, + false, 2.0f, 1.0f, 0.6f, 1.0f, "tbn.tga"}, + {"scaled data", + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::BitangentDirection| + MeshVisualizer3D::Flag::NormalDirection, {}, + false, 2.0f, 1.0f, 0.6f, 5.0f, "tbn.tga"}, + {"wide blurry lines", + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::BitangentDirection| + MeshVisualizer3D::Flag::NormalDirection, {}, + false, 5.0f, 5.0f, 0.8f, 1.0f, "tbn-wide.tga"}, + {"only bitangent from tangent", + MeshVisualizer3D::Flag::BitangentFromTangentDirection, {}, + false, 2.0f, 1.0f, 0.6f, 1.0f, "bitangents-from-tangents.tga"}, + {"wireframe + tangents + normals, single pass", + MeshVisualizer3D::Flag::Wireframe| + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::NormalDirection, {}, + false, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-tn.tga"}, + {"wireframe, rendering all, but only tangents + normals present", + MeshVisualizer3D::Flag::Wireframe| + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::BitangentDirection| + MeshVisualizer3D::Flag::NormalDirection, {}, + true, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-tn.tga"}, + {"wireframe + tangents + normals, two passes", + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::NormalDirection, + MeshVisualizer3D::Flag::Wireframe, + false, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-tn-smooth.tga"}, +}; +#endif + MeshVisualizerGLTest::MeshVisualizerGLTest() { addInstancedTests({&MeshVisualizerGLTest::construct2D}, Containers::arraySize(ConstructData2D)); @@ -181,23 +260,35 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { addInstancedTests({&MeshVisualizerGLTest::construct3D}, Containers::arraySize(ConstructData3D)); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + addTests({&MeshVisualizerGLTest::constructWireframeGeometryShader2D}); + + addInstancedTests({&MeshVisualizerGLTest::constructGeometryShader3D}, + Containers::arraySize(ConstructGeometryShaderData3D)); + #endif + addTests({ #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - &MeshVisualizerGLTest::constructWireframeGeometryShader2D, - &MeshVisualizerGLTest::constructWireframeGeometryShader3D, + &MeshVisualizerGLTest::construct3DGeometryShaderDisabledButNeeded, + &MeshVisualizerGLTest::construct3DConflictingBitangentInput, #endif &MeshVisualizerGLTest::constructMove2D, &MeshVisualizerGLTest::constructMove3D, &MeshVisualizerGLTest::setWireframeNotEnabled2D, - &MeshVisualizerGLTest::setWireframeNotEnabled3D}); + &MeshVisualizerGLTest::setWireframeNotEnabled3D, + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + &MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D, + #endif + }); addTests({&MeshVisualizerGLTest::renderDefaults2D, &MeshVisualizerGLTest::renderDefaults3D, #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) &MeshVisualizerGLTest::renderDefaultsWireframe2D, &MeshVisualizerGLTest::renderDefaultsWireframe3D, + &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal, #endif &MeshVisualizerGLTest::render2D, &MeshVisualizerGLTest::render3D}, @@ -218,6 +309,11 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { addTests({&MeshVisualizerGLTest::renderWireframe3DPerspective}, &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); + + addInstancedTests({&MeshVisualizerGLTest::renderTangentBitangentNormal}, + Containers::arraySize(TangentBitangentNormalData), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); #endif /* Load the plugins directly from the build tree. Otherwise they're either @@ -304,7 +400,10 @@ void MeshVisualizerGLTest::constructWireframeGeometryShader2D() { } } -void MeshVisualizerGLTest::constructWireframeGeometryShader3D() { +void MeshVisualizerGLTest::constructGeometryShader3D() { + auto&& data = ConstructGeometryShaderData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() + std::string(" is not supported")); @@ -318,8 +417,8 @@ void MeshVisualizerGLTest::constructWireframeGeometryShader3D() { Debug() << "Using" << GL::Extensions::NV::shader_noperspective_interpolation::string(); #endif - MeshVisualizer3D shader{MeshVisualizer3D::Flag::Wireframe}; - CORRADE_COMPARE(shader.flags(), MeshVisualizer3D::Flag::Wireframe); + MeshVisualizer3D shader{data.flags}; + CORRADE_COMPARE(shader.flags(), data.flags); { #ifdef CORRADE_TARGET_APPLE CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); @@ -330,6 +429,40 @@ void MeshVisualizerGLTest::constructWireframeGeometryShader3D() { } #endif +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +void MeshVisualizerGLTest::construct3DGeometryShaderDisabledButNeeded() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() + std::string(" is not supported")); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() + std::string(" is not supported")); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizer3D{MeshVisualizer3D::Flag::NoGeometryShader|MeshVisualizer3D::Flag::NormalDirection}; + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer3D: geometry shader has to be enabled when rendering TBN direction\n"); +} + +void MeshVisualizerGLTest::construct3DConflictingBitangentInput() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() + std::string(" is not supported")); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() + std::string(" is not supported")); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizer3D{MeshVisualizer3D::Flag::BitangentFromTangentDirection|MeshVisualizer3D::Flag::BitangentDirection}; + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive\n"); +} +#endif + void MeshVisualizerGLTest::constructMove2D() { MeshVisualizer2D a{MeshVisualizer2D::Flag::Wireframe|MeshVisualizer2D::Flag::NoGeometryShader}; const GLuint id = a.id(); @@ -380,7 +513,7 @@ void MeshVisualizerGLTest::setWireframeNotEnabled2D() { CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizer::setWireframeColor(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer::setWireframeWidth(): the shader was not created with wireframe enabled\n" - "Shaders::MeshVisualizer::setSmoothness(): the shader was not created with wireframe enabled\n"); + "Shaders::MeshVisualizer2D::setSmoothness(): the shader was not created with wireframe enabled\n"); } void MeshVisualizerGLTest::setWireframeNotEnabled3D() { @@ -395,9 +528,28 @@ void MeshVisualizerGLTest::setWireframeNotEnabled3D() { CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizer::setWireframeColor(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer::setWireframeWidth(): the shader was not created with wireframe enabled\n" - "Shaders::MeshVisualizer::setSmoothness(): the shader was not created with wireframe enabled\n"); + "Shaders::MeshVisualizer3D::setSmoothness(): the shader was not created with wireframe or TBN direction enabled\n"); } +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +void MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D() { + std::ostringstream out; + Error redirectError{&out}; + + MeshVisualizer3D shader; + shader.setNormalMatrix({}) + .setLineWidth({}) + .setLineLength({}) + .setSmoothness({}); + + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer3D::setNormalMatrix(): the shader was not created with TBN direction enabled\n" + "Shaders::MeshVisualizer3D::setLineWidth(): the shader was not created with TBN direction enabled\n" + "Shaders::MeshVisualizer3D::setLineLength(): the shader was not created with TBN direction enabled\n" + "Shaders::MeshVisualizer3D::setSmoothness(): the shader was not created with wireframe or TBN direction enabled\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void MeshVisualizerGLTest::renderSetup() { @@ -414,10 +566,19 @@ void MeshVisualizerGLTest::renderSetup() { GL::RenderbufferFormat::RGBA4, #endif RenderSize); + _depth = GL::Renderbuffer{}; + _depth.setStorage(GL::RenderbufferFormat::DepthComponent16, RenderSize); _framebuffer = GL::Framebuffer{{{}, RenderSize}}; - _framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _color) - .clear(GL::FramebufferClear::Color) + _framebuffer + .attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _color) + .attachRenderbuffer(GL::Framebuffer::BufferAttachment::Depth, _depth) + .clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth) .bind(); + + /* Disable depth test & blending by default, particular tests enable it if + needed */ + GL::Renderer::disable(GL::Renderer::Feature::DepthTest); + GL::Renderer::disable(GL::Renderer::Feature::Blending); } void MeshVisualizerGLTest::renderTeardown() { @@ -575,6 +736,18 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { /* AMD has off-by-one errors on edges compared to Intel */ (DebugTools::CompareImageToFile{_manager, 1.0f, 0.06f})); } + +void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() + std::string(" is not supported")); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() + std::string(" is not supported")); + #endif + + CORRADE_SKIP("Needs compile() and Primitives supporting TBN, which is not done yet."); +} #endif void MeshVisualizerGLTest::render2D() { @@ -610,11 +783,12 @@ void MeshVisualizerGLTest::render3D() { MeshVisualizer3D{} .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + .setTransformationMatrix( Matrix4::translation(Vector3::zAxis(-2.15f))* Matrix4::rotationY(-15.0_degf)* Matrix4::rotationX(15.0_degf)) + .setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) .draw(sphere); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -775,11 +949,11 @@ void MeshVisualizerGLTest::renderWireframe3D() { .setWireframeWidth(data.width) .setSmoothness(data.smoothness) .setViewportSize({80, 80}) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + .setTransformationMatrix( Matrix4::translation(Vector3::zAxis(-2.15f))* Matrix4::rotationY(-15.0_degf)* Matrix4::rotationX(15.0_degf)) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) .draw(sphere); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -845,11 +1019,11 @@ void MeshVisualizerGLTest::renderWireframe3DPerspective() { .setWireframeWidth(8.0f) .setWireframeColor(0xff0000_rgbf) .setViewportSize({80, 80}) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + .setTransformationMatrix( Matrix4::translation({0.0f, 0.5f, -3.5f})* Matrix4::rotationX(-60.0_degf)* Matrix4::scaling(Vector3::yScale(2.0f))) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) .draw(plane); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -870,6 +1044,143 @@ void MeshVisualizerGLTest::renderWireframe3DPerspective() { Utility::Directory::join(_testDir, "MeshVisualizerTestFiles/wireframe-perspective.tga"), (DebugTools::CompareImageToFile{_manager, 0.667f, 0.002f})); } + +void MeshVisualizerGLTest::renderTangentBitangentNormal() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() + std::string(" is not supported")); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() + std::string(" is not supported")); + #endif + + auto&& data = TangentBitangentNormalData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + GL::Renderer::enable(GL::Renderer::Feature::DepthTest); + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::One, GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + /* Creating a primitive from scratch because Primitives::planeSolid() is + too regular to test everything properly */ + struct Vertex { + Vector3 position; + Vector4 tangent; + Vector3 bitangent; + Vector3 normal; + } vertexData[] { + {{ 1.0f, -1.0f, 0.0f}, + Vector4{Vector3{1.25f, 0.0f, 0.25f}.normalized(), -1.0f}, {}, + Vector3{0.25f, 0.0f, -1.25f}.normalized()}, + {{ 1.0f, 1.0f, 0.0f}, + Vector4{Vector3{-1.0f, 0.25f, 0.0f}.normalized(), -1.0f}, {}, + Vector3{-0.25f, -1.0f, 0.0f}.normalized()}, + {{-1.0f, -1.0f, 0.0f}, + {1.0f, 0.0f, 0.0f, -1.0f}, {}, + {0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f, 0.0f}, + Vector4{Vector3{0.75f, 0.0f, -0.25f}.normalized(), 1.0f}, {}, + Vector3{0.25f, 0.0f, 0.75f}.normalized()} + }; + + /* Calculate bitangents from normal+tangent */ + for(Vertex& i: vertexData) + i.bitangent = Math::cross(i.normal, i.tangent.xyz())*i.tangent.w(); + + /* Verify the TBN is orthogonal */ + for(Vertex& i: vertexData) { + CORRADE_ITERATION(i.position); + CORRADE_VERIFY(i.tangent.xyz().isNormalized()); + CORRADE_VERIFY(i.bitangent.isNormalized()); + CORRADE_VERIFY(i.normal.isNormalized()); + CORRADE_COMPARE(dot(i.normal, i.tangent.xyz()), 0.0f); + CORRADE_COMPARE(dot(i.normal, i.bitangent), 0.0f); + CORRADE_COMPARE(dot(i.tangent.xyz(), i.bitangent), 0.0f); + } + + /* Apply scale to all */ + for(Vertex& i: vertexData) { + i.tangent *= data.multiply; + i.bitangent *= data.multiply; + i.normal *= data.multiply; + } + + GL::Buffer vertices{vertexData}; + GL::Mesh mesh{MeshPrimitive::TriangleStrip}; + mesh.setCount(4) + .addVertexBuffer(vertices, 0, + Shaders::MeshVisualizer3D::Position{}, + sizeof(Vector4), /* conditionally added below */ + sizeof(Vector3), /* conditionally added below */ + Shaders::MeshVisualizer3D::Normal{}); + if(data.flags & MeshVisualizer3D::Flag::BitangentFromTangentDirection && !data.skipBitagnentEvenIfEnabledInFlags) + mesh.addVertexBuffer(vertices, 0, + sizeof(Vector3), + Shaders::MeshVisualizer3D::Tangent4{}, + sizeof(Vector3), + sizeof(Vector3)); + else if(data.flags & MeshVisualizer3D::Flag::TangentDirection) + mesh.addVertexBuffer(vertices, 0, + sizeof(Vector3), + Shaders::MeshVisualizer3D::Tangent{}, sizeof(Float), sizeof(Vector3), + sizeof(Vector3)); + if(data.flags & MeshVisualizer3D::Flag::BitangentDirection && !data.skipBitagnentEvenIfEnabledInFlags) + mesh.addVertexBuffer(vertices, 0, + sizeof(Vector3), + sizeof(Vector4), + Shaders::MeshVisualizer3D::Bitangent{}, + sizeof(Vector3)); + + Matrix4 transformation = Matrix4::translation({0.0f, 0.5f, -3.5f})* + Matrix4::rotationX(-60.0_degf)* + Matrix4::scaling(Vector3::yScale(1.5f)); + + if(data.secondPassFlags) { + MeshVisualizer3D{data.secondPassFlags} + /** @todo make this unnecessary */ + .setViewportSize({80, 80}) + .setTransformationMatrix(transformation) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .draw(mesh); + } + + MeshVisualizer3D shader{data.flags}; + shader + /** @todo make this unnecessary */ + .setViewportSize({80, 80}) + .setTransformationMatrix(transformation) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setNormalMatrix(transformation.normalMatrix()*data.multiply) + .setSmoothness(data.smoothness) + .setLineLength(data.lineLength) + .setLineWidth(data.lineWidth); + + if(data.flags & MeshVisualizer3D::Flag::Wireframe) shader + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf); + + shader.draw(mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* Slight rasterization differences on AMD. If + GL_NV_shader_noperspective_interpolation is not supported, the artifacts + are bigger. */ + Float maxThreshold = 1.334f, meanThreshold = 0.008f; + #ifdef MAGNUM_TARGET_GLES + if(!(data.flags & MeshVisualizer3D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) { + maxThreshold = 39.0f; + meanThreshold = 1.207f; + } + #endif + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.file}), + (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); +} #endif }}}} diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/bitangents-from-tangents.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/bitangents-from-tangents.tga new file mode 100644 index 0000000000000000000000000000000000000000..92ae5a181554a1de786af345eb2f8f5278ac7bbc GIT binary patch literal 856 zcmbu7Noy8C5Qh8fp6;IR>D8!w9z;~cg@_;sdJzNzkZP*IX-G!Z-q z?s)JZxPoHDLqcK{muM70qW&A-8ZzGxFas~u_0%#wRl}HN&C|nrSeyOGInP%tFV5f5 zhq=R!Y)ih8aebV@k*mkb44S+!eD6Wp`xiS|` z?#k11494&A;;-;~JuwN<)0NEyqh6@ECVqk24~e)os+Y=~qx#PP0}yxnocJyLF5eJ= z3z-3dBQ=INODoeA*voW| z8{#-@%bv9ItsKJf1H}DyFMbMl#1VN9iL^ZFhq@doF}VhE+h^sNj2k2Mo1((7Efh0_ zJL;~&dsi1OOZtH>ua~)<8_28PN>0kKDGVcJvM$^jis_;{R*k@$s0)`SeNUG+%CsaA zq;4#14%cAkv;RBPCw9lzu(u(uo+{H%3$MMfC5S%)SG(23{caC}jnr{e;tMgLCf=g0 zo;TE%V@h&dx`U2_^a7sRNj+25iK;`8uJ3Al+7cOsTG&lFTsAJZr>wrOk#=(~fmg>X U{4|(Dhq(PtPRO*PCjT>ye@lR#aR2}S literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/tbn-wide.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/tbn-wide.tga new file mode 100644 index 0000000000000000000000000000000000000000..bf571fbb059359c99c1d26a38a6b01359a5a28ac GIT binary patch literal 4259 zcma)AdAyfX9zNT9miH|0`_}Ecw_Ur?sDvUUG)l@4(lC>TkzvX*1`Q%)Z(q>Hgt1ME zgk&t0GTD-;OpLN6g`py$dCvR0xB1uo+dBsJuXq9}gu z4=EZIuVX*S+GX)y>W}exll(RO$?SPH>yi~!#j5y8-=WK6 zUEU@yk72F1_t^KU5Z_UXldlPJtJcSR4~x;mg#1X@hfL0w>7UbZbjTa$i{pg|pjQYt z7_&kMIEnDy;QV&2f9X9!h$kiafzaMbR^a(Z(zlVh5$W33;3I30j}%eUr*z z#oyv|CykzOV)AjB{wWPdhP*a^CSG1u6i0+`wJ|RVv7OQ%2ziI|Nm`%eebdT$E&ndr z2Mp)wx0Jul)eL2a+Uyt6|EKFr^ax>;F)s-5O=)5+=To&F=v=cp6K1RtFABP?jA9$b z-{xlHPx5)Iyfo3UC=LtZiZp#bNxr7^M?%(dK3%C_IoG0M!r)Ha%vW=G5o6iN;8%KS z8hXZ97DY#3<&Y3APt)g;TIRS3yHbO`2Kmm zZ`IVIXf(UdSl2AOI87HN$rq)u0{0ZfVCPy@j9Gj}KiG4unyHXs^a;9!{fEiNq#oJE zcJC4z9X=(5p3$C%bDgtnM4B#4lC7n^5Dv{ykE{dk&GV*3fksAM8(3^dhlZmKA_YLttS|y z%8W|Wk-B0LE@Ol-$QWk~b)4EC$aG3~lu;Ko#eN67VSVJoJs5jGnxph9#y(?g6X*Zt ze6FF2*3o8vZW!sBb!LmX@u;ah1F0rD6#`Gca~Gr6n}%$9TY2s zpw8HcZ}i`8iaw+0#jSAw^|8(Tn2r*Fg&D()tf6ft(jooWv?{)AGUsoXR`Zs*@9WJN zlPpQ-=P@OmuVL&x#y+C>O#e&iPC_u=Fux~yX;A+`!QG`41_oAPdns!*TJ-HHFUj@o zlaZvE*p{K5p?gS2r1D`2b4Ux>MYNuz)GR3fbBe{V*ktyaeAs2uL-)HA`R?g=KTQY23tEUP5+pt zR~j=$>pPXYOUk*it?&ip0dibr%#w22ki7R9yHBQP+Xn4J&6GTEQeKd^$o&c4TxXKS z3ERSu6(mBuBJv#I&^pWZ%d*Y^;e}q__jB%4YtOE`0QdtG0}ulI(F}lN#5Z1R^dgSV zFP*3^!eJr3D3yPcd;^a~+(r0WuI^Lz_crUAm76dz7>9k}QJ%NVvUYX$+hy5V&fTjN zDg^IQ9<TbGU%LXXpKkjvUqwpw@X_@ea`+8Ze~t0f`xi(SJ|)damXwJKTnD0X3>O2LL?9xv5G4-mwon z111I4f!n}P^KqeZXE;4ki&q8xrfh;wS+XcGR~vtvN3CwI^Xq^tJJ;HWr2Me#D1*$E;E|ol;u+0p8g!vIGg`S|@=`QP#-4bs>CBPPd`o;Cw`T=A2OF7h9w8BDc zNn#Z5a(TbB7uaw}jG{b=>VXm=2Egi|XAIJ6&L?U;!29NT9)X)D&m`s=MwjO|m#=1F;>wFi~;TZY{l zY6WEba>;Zs2!a7G0Lt6s`SQ4avU8YQa4_5hfIxRZKtKk7$;F=Gc%NezwjC?jR>wY2BHOAF%gRZ;VKLWrXz%I-uH3t%rqTg%#BLj z9oGq_Wm+_Xj36%nNkH!cDFF`18oGNN7BN^b(iBVtO7i{$=YE~07aKEHshLtP5Mnv! z>&i@*tO5{0Tr0dOx0h%%y>rCh1S$QdD01yPN0qJsOnqoqbkn&O?R)IsnLnV7e z>L~Bp&e&$4J)%bJ@lp>ekn&L}=OxJk&|s20eJUS4o()<_JAO^lO2II5K$Mu?$Pw&e zDd8n1I#$?$@hTiRgxuSlu4C*|&esaD43z_8ql%~{DtZI=mTUM%(6|<&HK|8 z=g_Hh$dG@L_PdBgTgH}+E#qTwlx!o#LfZQxHtGOASIfuHSH|3c?4Z-UKOS9&-ix-! zAVga_@>3vRcCVgyX_qtRcWwM(El23=6DA*#_FM~Co{Y`$C&TnZYmPrcyw#fS7Gwue z%V>wuqW_WVydoX$-07GB6dv=C=Z(>DF?wz-bs!ToUmzm(Zvn zZHY0Ss}!hamQwe~+B@Va=kt3GryHusPZa72uoac;!BLn@s*5VB*=~)(C2jOmt@&0T z#R(+u{n8J$d50{&EtT34lzlE@sS8ehlNbQRyRElNvqcuc1(1FfUB6(~d}0Tv;aD?}eb z%0%hVHN)hC4dVj=s_K2F<|ucjLyJV8N=wKpU;y~GfiVm>LlZtp~Tna4cx`<}*N;Itkfyip16@y|`K!XbYHQt%e@5AtuiIYs;yXV|H%f07LmZ_{fy0WgUKD!h` z_`fp?m)zQeXIXAhOSWRfDm;hnj+$-d47<~H)N;@qtW99tp__^bdpEBkcV-reF8xbMC#q%Rv zz8-fv8(kI~bDFV9C70fan1zP?N$dif_%35c8Jm2aCzt@)EBO$Isc6Jj9xOVy)<_=K@<2>FfDpBejF zh`my-*Sg!<`6;MJsn3KMA8NOSP&!i^K+fU)Xj7*E|I(0j*e0NjNVe_S1N^aLouB|gupEzJ`%;w0vaH?BvqhVQPOKm zSI%6!d#}t)_a1ADBDhdS78!Gn(rMmTkX|Ww6-7-55s5X+CjuO0>^ZG}EF-g=yGqEt z&Q*|QS|8#Zw$m8_4B&I z-{Jj5N>S2@$EQT>6Y5n};ZaFLoWE4cXchKx(N{z=CO6+G<<6w%6s%S&v0va4yURR2 pTBjJp8zf$LYkbg9i531H=dKZQUn&2&dCag829pziW9|Qo{Wq)Z7&-s| literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn-smooth.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn-smooth.tga new file mode 100644 index 0000000000000000000000000000000000000000..405c4f59c65cdcf29c24e637530796a1cf38e4a6 GIT binary patch literal 2039 zcmb_dS8N2~78;oK#=+Hb^2PrL8jbMC$G-aBtNPUgH% zFc-`{#I>2G`G2ii__7?Z+oNo@0Mk5#od@|@mSZK!ANf(1&q&f%KhC$Zd`^(3d+4%6Hc0t;y^?>ijO z{qVLsVo%U)YOmXElcckZl~}Evg3!!Zt|Z0T?FSU)3y0$aQiwmva*`x1Bs)j*3I~fy z3x>x|t#5?KZfvIfxw$E4E-Wmjr<>+B!`P|oFF726in3Rh_t|VQqL?WN4F}=Fjw+#g zs46ynRaG_J-Sb&lJ=N9Ob8~Q#6L;?(pSZpbizi3Z3^0s`b$u6U<(GB6-D*Vzu)*W* z?RBoKv@|uXrld4CH{-txat`oOSV^INs;Z@oJ!u%kH_b~K85x%@Ar7J;CL$yLx$D<6 zDJ(3od!?2zo9(z~IH+JYlqccVEznMHY!r=+jTRS=7Z#Qd4+BgiF@8b$4b)$;ERP#T zA!BvR5^Yggg>VTUrVQ zNw{$Na${@ja%yUOLj$l26coQ;__W{2GR6=?M@Q$oYiiEizD-(&1LJU=b#>@ZeMiUN zSFfHYIm{p(1347Sf{DhY?~~=j*m329g9k|)f9FpA!w@#rYFCrJ)7NRc3WqJOO@8KQ{GfO^M45Kh{p8#k(ZdQcin zl52JrnZy7la};O-79QM0xS@Wr+o^B-bZ#zAcMLQE8R8{aaXMQY8&TmX3JV(;ernts zxKBP(RjNLZ3ZW;YrV+`SpT{^XWMW>jK@MOBDH`M$EDn=TFvD>Q$HzxXN+t>l zP*n^+IUzIAXz`Qz`N>zVL=YP@gAqsh1}ZA@rl#PNO2xwr5|Mu=0p4SrI1&~CmWKBB zrL?q;`ug<6#UsRq4GdzQ#NrU1PDpu!0Up@UKuMEFW}?yJks~HPn%D@91P05?Q5B?w z#OVFtVFroFe~_T5rR9F@Z3*k^I3Lm+E-i&moEv-?K_O}^7Khh^dQ9Vr%AtPK`}d=k zfXVEkj1?8dAv;UE+r1VPgdL&eKzQ0PF#JKbKwW>uG#{V=lLVnj5Na7KXDlB#rzj>% zQo?RAH1590veh)>B?;du>a3*eyA1=Mh@FP#VuAFl^%&;S4c literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn.tga new file mode 100644 index 0000000000000000000000000000000000000000..2aa934dd40c9a422afb94d4ca2209d12adf24f34 GIT binary patch literal 1676 zcmd6oS5H(?6ov0~W+L&8HxrDBi9d+n1X2a1i4KCGhypXzDfA9Q?|taK_cnCDWxz~G zOpNit2{-4=S!|AZUi##5s|G&J;IJwEO-{1!_L>;JY_PA0j%e0G%d z;`xbVthqVLV(~|CEXW45RaCT4Q880fQ#Ci2b$RK{xVbS)Ha0kpf)N2bnl%I_3!m|( zrq+>>_0rPOmX@-$HRvw;?X8&;2!O;v5M6#Kh^={StZ;X?x4V0(VZ@03bRHyDi*6A{3+FQ=zXg(HaI0ia8ILlWcRv5u5Ibn{pwtl*}kjh4F)G_4qVQ%~2;J3rWJPCmzB|?6s%*ZfS zYrWqeB~|9tm3MJ*s;+Ly<7t?j{Cswn%dy}$41|L%HN&Wznu-IqQha}BCx&1P=45gv zED4Ufq@vUUbH(K<*xQrDhSjc>6@~`$nFktSS0G?W!9rPTVl*klONd`JGc$CVURTe~ z@-cYDe1po-wexM6hUj%tf#F`@uny*z}*_#G=WD2G7zGcghqK7?Q~EB;bvs!ekIDNXNjy zw$nM--d?n|6)QFv3^CJr48p52!Vw!Rj z7?Q~ECFt($z4JOGI}o5g(wwfX|VWB7Hj-?KUMu-A7r*!_q5j61%I^>_Qf`492~p$|ea literal 0 HcmV?d00001