From 062055d20dbae253a28c74e6725b786f1d9a9b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 3 May 2020 17:54:33 +0200 Subject: [PATCH] Shaders: add vertex ID visualization to MeshVisualizer. Helps visualizing face connectivity (or the lack of it). --- doc/changelog.dox | 4 +- src/Magnum/Shaders/MeshVisualizer.cpp | 39 +++-- src/Magnum/Shaders/MeshVisualizer.frag | 52 ++++-- src/Magnum/Shaders/MeshVisualizer.geom | 12 +- src/Magnum/Shaders/MeshVisualizer.h | 74 ++++++--- src/Magnum/Shaders/MeshVisualizer.vert | 29 ++++ src/Magnum/Shaders/Test/CMakeLists.txt | 6 + .../Shaders/Test/MeshVisualizerGLTest.cpp | 149 ++++++++++++++---- .../defaults-vertexid2D.tga | Bin 0 -> 12102 bytes .../defaults-vertexid3D.tga | Bin 0 -> 9981 bytes .../MeshVisualizerTestFiles/vertexid2D.tga | Bin 0 -> 12932 bytes .../MeshVisualizerTestFiles/vertexid3D.tga | Bin 0 -> 12015 bytes .../wireframe-vertexid2D.tga | Bin 0 -> 13488 bytes .../wireframe-vertexid3D.tga | Bin 0 -> 12019 bytes 14 files changed, 277 insertions(+), 88 deletions(-) create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-vertexid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-vertexid3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/vertexid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/vertexid3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-vertexid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-vertexid3D.tga diff --git a/doc/changelog.dox b/doc/changelog.dox index 20584cba7..c1fe68d91 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -247,8 +247,8 @@ See also: - New @ref Shaders::MeshVisualizer2D for 2D mesh visualization - Tangent space visualization in @ref Shaders::MeshVisualizer3D -- Object and primitive ID visualization in @ref Shaders::MeshVisualizer2D - and @ref Shaders::MeshVisualizer3D +- Object, vertex and primitive ID visualization in + @ref Shaders::MeshVisualizer2D and @ref Shaders::MeshVisualizer3D - Texture coordinate transformation in @ref Shaders::DistanceFieldVector, @ref Shaders::Flat, @ref Shaders::Phong and @ref Shaders::Vector - Ability to render Per-instance / per-vertex object ID in @ref Shaders::Flat diff --git a/src/Magnum/Shaders/MeshVisualizer.cpp b/src/Magnum/Shaders/MeshVisualizer.cpp index 03c78a1ba..72f886ea5 100644 --- a/src/Magnum/Shaders/MeshVisualizer.cpp +++ b/src/Magnum/Shaders/MeshVisualizer.cpp @@ -57,10 +57,11 @@ MeshVisualizerBase::MeshVisualizerBase(FlagsBase flags): _flags{flags} { #ifndef CORRADE_NO_ASSERT Int countMutuallyExclusive = 0; if(flags & FlagBase::InstancedObjectId) ++countMutuallyExclusive; + if(flags & FlagBase::VertexId) ++countMutuallyExclusive; if(flags & FlagBase::PrimitiveIdFromVertexId) ++countMutuallyExclusive; #endif CORRADE_ASSERT(countMutuallyExclusive <= 1, - "Shaders::MeshVisualizer: Flag::InstancedObjectId and Flag::PrimitiveId are mutually exclusive", ); + "Shaders::MeshVisualizer: Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive", ); #endif #ifndef MAGNUM_TARGET_GLES2 @@ -113,6 +114,7 @@ GL::Version MeshVisualizerBase::setupShaders(GL::Shader& vert, GL::Shader& frag, vert.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") #endif #ifdef MAGNUM_TARGET_WEBGL @@ -126,6 +128,7 @@ GL::Version MeshVisualizerBase::setupShaders(GL::Shader& vert, GL::Shader& frag, .addSource(_flags & FlagBase::NoGeometryShader ? "#define NO_GEOMETRY_SHADER\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : @@ -138,8 +141,8 @@ GL::Version MeshVisualizerBase::setupShaders(GL::Shader& vert, GL::Shader& frag, MeshVisualizerBase& MeshVisualizerBase::setColor(const Color4& color) { #ifndef MAGNUM_TARGET_GLES2 - CORRADE_ASSERT(_flags & (FlagBase::Wireframe|FlagBase::InstancedObjectId|FlagBase::PrimitiveId), - "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/primitive ID enabled", *this); + CORRADE_ASSERT(_flags & (FlagBase::Wireframe|FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/vertex/primitive ID enabled", *this); #else CORRADE_ASSERT(_flags & FlagBase::Wireframe, "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled", *this); @@ -164,15 +167,15 @@ MeshVisualizerBase& MeshVisualizerBase::setWireframeWidth(const Float width) { #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerBase& MeshVisualizerBase::setColorMapTransformation(const Float offset, const Float scale) { - CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::PrimitiveId), - "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/primitive ID enabled", *this); + CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), + "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/vertex/primitive ID enabled", *this); setUniform(_colorMapOffsetScaleUniform, Vector2{offset, scale}); return *this; } MeshVisualizerBase& MeshVisualizerBase::bindColorMapTexture(GL::Texture2D& texture) { - CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::PrimitiveId), - "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/primitive ID enabled", *this); + CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), + "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/vertex/primitive ID enabled", *this); texture.bind(ColorMapTextureUnit); return *this; } @@ -182,7 +185,7 @@ MeshVisualizerBase& MeshVisualizerBase::bindColorMapTexture(GL::Texture2D& textu MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedShort(flags))} { #ifndef MAGNUM_TARGET_GLES2 - CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizer2D: at least one visualization feature has to be enabled", ); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), @@ -212,6 +215,7 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua (*geom) .addSource("#define WIREFRAME_RENDERING\n#define MAX_VERTICES 3\n") .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : @@ -264,7 +268,7 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); if(flags & (Flag::Wireframe #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId #endif )) _colorUniform = uniformLocation("color"); @@ -276,7 +280,7 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua _viewportSizeUniform = uniformLocation("viewportSize"); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) { + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); } @@ -299,7 +303,7 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua setSmoothness(2.0f); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif #endif @@ -329,7 +333,7 @@ MeshVisualizer2D& MeshVisualizer2D::setSmoothness(const Float smoothness) { MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedShort(flags))} { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizer3D: at least one visualization feature has to be enabled", ); 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", ); @@ -394,6 +398,7 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua .addSource(Utility::formatString("#define MAX_VERTICES {}\n", maxVertices)) .addSource(flags & Flag::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : @@ -461,7 +466,7 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua _projectionMatrixUniform = uniformLocation("projectionMatrix"); if(flags & (Flag::Wireframe #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId #endif )) _colorUniform = uniformLocation("color"); @@ -479,7 +484,7 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua _viewportSizeUniform = uniformLocation("viewportSize"); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) { + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); } @@ -516,7 +521,7 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua setSmoothness(2.0f); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -602,6 +607,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizer2D::Flag value) { _c(Wireframe) #ifndef MAGNUM_TARGET_GLES2 _c(InstancedObjectId) + _c(VertexId) #ifndef MAGNUM_TARGET_WEBGL _c(PrimitiveId) #endif @@ -630,6 +636,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizer3D::Flag value) { #endif #ifndef MAGNUM_TARGET_GLES2 _c(InstancedObjectId) + _c(VertexId) #ifndef MAGNUM_TARGET_WEBGL _c(PrimitiveId) #endif @@ -649,6 +656,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizer2D::Flags value) { MeshVisualizer2D::Flag::NoGeometryShader, #ifndef MAGNUM_TARGET_GLES2 MeshVisualizer2D::Flag::InstancedObjectId, + MeshVisualizer2D::Flag::VertexId, MeshVisualizer2D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL MeshVisualizer2D::Flag::PrimitiveId @@ -670,6 +678,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizer3D::Flags value) { #endif #ifndef MAGNUM_TARGET_GLES2 MeshVisualizer3D::Flag::InstancedObjectId, + MeshVisualizer3D::Flag::VertexId, MeshVisualizer3D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL MeshVisualizer3D::Flag::PrimitiveId diff --git a/src/Magnum/Shaders/MeshVisualizer.frag b/src/Magnum/Shaders/MeshVisualizer.frag index 483d3ab58..0d301f1b1 100644 --- a/src/Magnum/Shaders/MeshVisualizer.frag +++ b/src/Magnum/Shaders/MeshVisualizer.frag @@ -44,7 +44,7 @@ #extension GL_NV_shader_noperspective_interpolation: require #endif -#if (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) +#if (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) #endif @@ -95,12 +95,14 @@ uniform lowp float smoothness ; #endif -#if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) +#if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) #ifdef EXPLICIT_TEXTURE_LAYER layout(binding = 4) #endif uniform lowp sampler2D colorMapTexture; +#endif +#if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 6) #endif @@ -127,6 +129,9 @@ in lowp vec3 barycentric; #ifdef INSTANCED_OBJECT_ID flat in highp uint interpolatedInstanceObjectId; #endif +#ifdef VERTEX_ID +in highp float interpolatedMappedVertexId; +#endif #ifdef PRIMITIVE_ID_FROM_VERTEX_ID flat in highp uint interpolatedPrimitiveId; #endif @@ -144,21 +149,34 @@ out lowp vec4 fragmentColor; #endif void main() { - /* Map object/primitive ID to a color. Will be either combined with the - wireframe background color (if wireframe is enabled), ignored (if + /* Map object/vertex/primitive ID to a color. Will be either combined with + the wireframe background color (if wireframe is enabled), ignored (if rendering TBN direction) or used as-is if nothing else is enabled */ - #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) - lowp vec4 faceColor = texture(colorMapTexture, vec2(colorMapOffset + float( - #ifdef INSTANCED_OBJECT_ID - interpolatedInstanceObjectId - #elif defined(PRIMITIVE_ID) - gl_PrimitiveID - #elif defined(PRIMITIVE_ID_FROM_VERTEX_ID) - interpolatedPrimitiveId + #if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + lowp vec4 faceColor = texture(colorMapTexture, vec2( + /* Object/primitive IDs are constant across the whole primitive so we + do the offset/scale mapping here */ + #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + colorMapOffset + float( + #ifdef INSTANCED_OBJECT_ID + interpolatedInstanceObjectId + #elif defined(PRIMITIVE_ID) + gl_PrimitiveID + #elif defined(PRIMITIVE_ID_FROM_VERTEX_ID) + interpolatedPrimitiveId + #else + #error mosra messed up + #endif + )*colorMapScale + + /* Vertex ID changes, so the offset/scale mapping was done already in + a VS/GS and we just pass the interpolated result to a texture */ + #elif defined(VERTEX_ID) + interpolatedMappedVertexId #else - #error mosra messed up + #error mosra messed up, again #endif - )*colorMapScale, 0.0)); + , 0.0)); #endif /* 1. For wireframe the line is on the triangle edges, thus dist = 0 at @@ -195,7 +213,7 @@ void main() { #else fragmentColor = backgroundColor; #endif - #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + #if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) fragmentColor *= faceColor; #endif @@ -244,8 +262,8 @@ void main() { fragmentColor = mix(wireframeColor, fragmentColor, nearest); #endif - /* Object / Primitive ID visualization using a colormap */ - #elif defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + /* Object / Vertex / Primitive ID visualization using a colormap */ + #elif defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) fragmentColor = color*faceColor; #else diff --git a/src/Magnum/Shaders/MeshVisualizer.geom b/src/Magnum/Shaders/MeshVisualizer.geom index 2bf1cd751..8c638e3cb 100644 --- a/src/Magnum/Shaders/MeshVisualizer.geom +++ b/src/Magnum/Shaders/MeshVisualizer.geom @@ -103,6 +103,10 @@ out lowp vec3 dist; flat in highp uint interpolatedVsInstanceObjectId[]; flat out highp uint interpolatedInstanceObjectId; #endif +#ifdef VERTEX_ID +in highp float interpolatedVsMappedVertexId[]; +out highp float interpolatedMappedVertexId; +#endif #ifdef PRIMITIVE_ID_FROM_VERTEX_ID flat in highp uint interpolatedVsPrimitiveId[]; flat out highp uint interpolatedPrimitiveId; @@ -180,7 +184,7 @@ void main() { gl_PrimitiveID = gl_PrimitiveIDIn; #endif - /* Screen position of each vertex */ + /* Screen position of each vertex + TBN direction */ vec2 p[3]; #ifdef TANGENT_DIRECTION vec2 t[3]; @@ -216,7 +220,8 @@ void main() { const float area = abs(dot(vec2(-v[1].y, v[1].x), v[2])); /* If wireframe is enabled, add distance to opposite side to each vertex. - Otherwise make all distances the same to avoid any lines being drawn. */ + Otherwise make all distances the same to avoid any lines being drawn. + Propagate also mapped vertex ID, since that changes per-vertex also. */ for(int i = 0; i != 3; ++i) { dist = vec3(0.0, 0.0, 0.0); #ifdef WIREFRAME_RENDERING @@ -224,6 +229,9 @@ void main() { #else dist = vec3(area/length(v[i])); #endif + #ifdef VERTEX_ID + interpolatedMappedVertexId = interpolatedVsMappedVertexId[i]; + #endif #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) backgroundColor = color; lineColor = wireframeColor; diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index bd4cffad0..9c5393be0 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -51,8 +51,9 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerBase: public GL::AbstractShaderProgram NoGeometryShader = 1 << 1, #ifndef MAGNUM_TARGET_GLES2 InstancedObjectId = 1 << 2, - PrimitiveId = 1 << 3, - PrimitiveIdFromVertexId = (1 << 4)|PrimitiveId + VertexId = 1 << 3, + PrimitiveId = 1 << 4, + PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId #endif }; typedef Containers::EnumSet FlagsBase; @@ -189,16 +190,19 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali /** @copydoc MeshVisualizer3D::Flag::InstancedObjectId */ InstancedObjectId = 1 << 2, + /** @copydoc MeshVisualizer3D::Flag::VertexId */ + VertexId = 1 << 3, + #ifndef MAGNUM_TARGET_WEBGL /** @copydoc MeshVisualizer3D::Flag::PrimitiveId */ - PrimitiveId = 1 << 3, + PrimitiveId = 1 << 4, #endif /** @copydoc MeshVisualizer3D::Flag::PrimitiveIdFromVertexId */ #ifndef MAGNUM_TARGET_WEBGL - PrimitiveIdFromVertexId = (1 << 4)|PrimitiveId + PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId #else - PrimitiveIdFromVertexId = (1 << 4)|(1 << 3) + PrimitiveIdFromVertexId = (1 << 5)|(1 << 4) #endif #endif }; @@ -447,7 +451,7 @@ Rendering setup: @snippet MagnumShaders.cpp MeshVisualizer-usage-tbn2 -@section Shaders-MeshVisualizer-object-id Object and primitive ID visualization +@section Shaders-MeshVisualizer-object-id Object, vertex and primitive ID visualization If the mesh contains a per-vertex (or instanced) @ref ObjectId, it can be visualized by enabling @ref Flag::InstancedObjectId. For the actual @@ -458,14 +462,19 @@ the @f$ [0, 1] @f$ texture range. Various colormap presets are in the @snippet MagnumShaders.cpp MeshVisualizer-usage-object-id -If you enable @ref Flag::PrimitiveId instead, the shader will use the color map -to visualize the order in which primitives are drawn. That's useful for example -to see how well is the mesh optimized for a post-transform vertex cache. This -by default relies on the @glsl gl_PrimitiveID @ce GLSL builtin; with -@ref Flag::PrimitiveIdFromVertexId it's emulated using @glsl gl_VertexID @ce, -expecting you to draw a non-indexed triangle mesh. You can use -@ref MeshTools::duplicate() (and potentially @ref MeshTools::generateIndices()) -to conveniently convert the mesh to a non-indexed @ref MeshPrimitive::Triangles. +If you enable @ref Flag::VertexId, the shader will use the color map to +visualize how are vertices shared among primitives. That's useful for +inspecting mesh connectivity --- primitives sharing vertices will have a smooth +color map transition while duplicated vertices will cause a sharp edge. This +relies on the @glsl gl_VertexID @ce GLSL builtin. + +The @ref Flag::PrimitiveId then visualizes the order in which primitives are +drawn. That's useful for example to see to see how well is the mesh optimized +for a post-transform vertex cache. This by default relies on the @glsl gl_PrimitiveID @ce GLSL builtin; with @ref Flag::PrimitiveIdFromVertexId it's +emulated using @glsl gl_VertexID @ce, expecting you to draw a non-indexed +triangle mesh. You can use @ref MeshTools::duplicate() (and potentially +@ref MeshTools::generateIndices()) to conveniently convert the mesh to a +non-indexed @ref MeshPrimitive::Triangles. @requires_gl32 The `gl_PrimitiveID` shader variable is not available on OpenGL 3.1 and lower. @@ -606,7 +615,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali /** * Visualize instanced object ID. You need to provide the * @ref ObjectId attribute in the mesh. Mutually exclusive with - * @ref Flag::PrimitiveId. + * @ref Flag::VertexId and @ref Flag::PrimitiveId. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL @@ -615,10 +624,27 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali */ InstancedObjectId = 1 << 2, + /** + * Visualize vertex ID (@cpp gl_VertexID @ce). Useful for + * visualizing mesh connectivity --- primitives sharing vertices + * will have a smooth color map transition while duplicated + * vertices will cause a sharp edge. Mutually exclusive with + * @ref Flag::InstancedObjectId and @ref Flag::PrimitiveId. + * @requires_gl30 The `gl_VertexID` shader variable is not + * available on OpenGL 2.1. + * @requires_gles30 The `gl_VertexID` shader variable is not + * available on OpenGL ES 2.0. + * @requires_webgl20 `gl_VertexID` is not available in WebGL 1.0. + * @m_since_latest + */ + VertexId = 1 << 3, + #ifndef MAGNUM_TARGET_WEBGL /** - * Visualize primitive ID (@cpp gl_PrimitiveID @ce). Mutually - * exclusive with @ref Flag::InstancedObjectId. See also + * Visualize primitive ID (@cpp gl_PrimitiveID @ce). Useful for + * visualizing how well is the mesh optimized for a post-transform + * vertex cache. Mutually exclusive with + * @ref Flag::InstancedObjectId and @ref Flag::VertexId. See also * @ref Flag::PrimitiveIdFromVertexId. * @requires_gl32 The `gl_PrimitiveID` shader variable is not * available on OpenGL 3.1 and lower. @@ -628,7 +654,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles `gl_PrimitiveID` is not available in WebGL. * @m_since_latest */ - PrimitiveId = 1 << 3, + PrimitiveId = 1 << 4, #endif /** @@ -646,9 +672,9 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @m_since_latest */ #ifndef MAGNUM_TARGET_WEBGL - PrimitiveIdFromVertexId = (1 << 4)|PrimitiveId, + PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId, #else - PrimitiveIdFromVertexId = (1 << 4)|(1 << 3), + PrimitiveIdFromVertexId = (1 << 5)|(1 << 4), #endif #endif @@ -666,7 +692,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - TangentDirection = 1 << 5, + TangentDirection = 1 << 6, /** * Visualize bitangent direction with green lines pointing out of @@ -683,7 +709,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - BitangentFromTangentDirection = 1 << 6, + BitangentFromTangentDirection = 1 << 7, /** * Visualize bitangent direction with green lines pointing out of @@ -700,7 +726,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - BitangentDirection = 1 << 7, + BitangentDirection = 1 << 8, /** * Visualize normal direction with blue lines pointing out of @@ -714,7 +740,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - NormalDirection = 1 << 8 + NormalDirection = 1 << 9 #endif }; diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index 4443dd8c7..6a4085e98 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -62,6 +62,19 @@ uniform highp mat4 projectionMatrix #error #endif +#ifdef VERTEX_ID +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 6) +#endif +uniform lowp vec2 colorMapOffsetScale + #ifndef GL_ES + = vec2(1.0/512.0, 1.0/256.0) + #endif + ; +#define colorMapOffset colorMapOffsetScale.x +#define colorMapScale colorMapOffsetScale.y +#endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 8) @@ -139,6 +152,14 @@ flat out highp uint interpolatedVsInstanceObjectId; #endif #endif +#ifdef VERTEX_ID +#ifdef NO_GEOMETRY_SHADER +out highp float interpolatedMappedVertexId; +#else +out highp float interpolatedVsMappedVertexId; +#endif +#endif + #ifdef PRIMITIVE_ID_FROM_VERTEX_ID #ifdef NO_GEOMETRY_SHADER flat out highp uint interpolatedPrimitiveId; @@ -206,6 +227,14 @@ void main() { #endif = instanceObjectId; #endif + #ifdef VERTEX_ID + #ifdef NO_GEOMETRY_SHADER + interpolatedMappedVertexId + #else + interpolatedVsMappedVertexId + #endif + = colorMapOffset + float(gl_VertexID)*colorMapScale; + #endif #ifdef PRIMITIVE_ID_FROM_VERTEX_ID #ifdef NO_GEOMETRY_SHADER interpolatedPrimitiveId diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index a062724b2..d42a759fc 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -159,6 +159,8 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/defaults-primitiveid2D.tga MeshVisualizerTestFiles/defaults-primitiveid3D.tga MeshVisualizerTestFiles/defaults-tbn.tga + MeshVisualizerTestFiles/defaults-vertexid2D.tga + MeshVisualizerTestFiles/defaults-vertexid3D.tga MeshVisualizerTestFiles/defaults-wireframe2D.tga MeshVisualizerTestFiles/defaults-wireframe3D.tga MeshVisualizerTestFiles/objectid2D.tga @@ -168,6 +170,8 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/primitiveid3D.tga MeshVisualizerTestFiles/tbn-wide.tga MeshVisualizerTestFiles/tbn.tga + MeshVisualizerTestFiles/vertexid2D.tga + MeshVisualizerTestFiles/vertexid3D.tga MeshVisualizerTestFiles/wireframe-nogeo2D.tga MeshVisualizerTestFiles/wireframe-nogeo3D.tga MeshVisualizerTestFiles/wireframe-nogeo-objectid2D.tga @@ -175,6 +179,8 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/wireframe-perspective.tga MeshVisualizerTestFiles/wireframe-primitiveid-tn.tga MeshVisualizerTestFiles/wireframe-tn-smooth.tga + MeshVisualizerTestFiles/wireframe-vertexid2D.tga + MeshVisualizerTestFiles/wireframe-vertexid3D.tga MeshVisualizerTestFiles/wireframe-wide2D.tga MeshVisualizerTestFiles/wireframe-wide3D.tga MeshVisualizerTestFiles/wireframe2D.tga diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index c65c88e7b..1dfbcc486 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -106,6 +106,8 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { #ifndef MAGNUM_TARGET_GLES2 void renderDefaultsObjectId2D(); void renderDefaultsObjectId3D(); + void renderDefaultsVertexId2D(); + void renderDefaultsVertexId3D(); void renderDefaultsPrimitiveId2D(); void renderDefaultsPrimitiveId3D(); #endif @@ -115,8 +117,8 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void renderWireframe2D(); void renderWireframe3D(); #ifndef MAGNUM_TARGET_GLES2 - void renderObjectPrimitiveId2D(); - void renderObjectPrimitiveId3D(); + void renderObjectVertexPrimitiveId2D(); + void renderObjectVertexPrimitiveId3D(); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void renderWireframe3DPerspective(); @@ -140,10 +142,11 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { - Mesa Intel - Mesa AMD + . Mesa llvmpipe - SwiftShader ES2/ES3 - ARM Mali (Huawei P10) ES2/ES3 (except TBN visualization) - - WebGL 1 / 2 (on Mesa Intel) (except primitive/object ID) - - iPhone 6 w/ iOS 12.4 (except primitive/object ID) + - WebGL 1 / 2 (on Mesa Intel) (except primitive/vertex/object ID) + - iPhone 6 w/ iOS 12.4 (except primitive/vertex/object ID) */ using namespace Math::Literals; @@ -155,6 +158,7 @@ constexpr struct { {"wireframe w/o GS", MeshVisualizer2D::Flag::Wireframe|MeshVisualizer2D::Flag::NoGeometryShader}, #ifndef MAGNUM_TARGET_GLES2 {"object ID", MeshVisualizer2D::Flag::InstancedObjectId}, + {"vertex ID", MeshVisualizer2D::Flag::VertexId}, #ifndef MAGNUM_TARGET_WEBGL {"primitive ID", MeshVisualizer2D::Flag::PrimitiveId}, #endif @@ -169,6 +173,7 @@ constexpr struct { {"wireframe w/o GS", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::NoGeometryShader}, #ifndef MAGNUM_TARGET_GLES2 {"object ID", MeshVisualizer3D::Flag::InstancedObjectId}, + {"vertex ID", MeshVisualizer3D::Flag::VertexId}, #ifndef MAGNUM_TARGET_WEBGL {"primitive ID", MeshVisualizer3D::Flag::InstancedObjectId}, #endif @@ -188,8 +193,10 @@ constexpr struct { {"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 + vertex id", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::VertexId}, {"wireframe + t/n direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::NormalDirection}, - {"wireframe + object id + t/n direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::NormalDirection} + {"wireframe + object id + t/n direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::NormalDirection}, + {"wireframe + vertex id + t/b direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::VertexId|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::BitangentDirection} }; #endif @@ -209,7 +216,10 @@ constexpr struct { #ifndef MAGNUM_TARGET_GLES2 {"both object and primitive id", MeshVisualizer2D::Flag::InstancedObjectId|MeshVisualizer2D::Flag::PrimitiveIdFromVertexId, - ": Flag::InstancedObjectId and Flag::PrimitiveId are mutually exclusive"} + ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, + {"both object and vertex id", + MeshVisualizer2D::Flag::InstancedObjectId|MeshVisualizer2D::Flag::VertexId, + ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"} #endif }; @@ -229,7 +239,10 @@ constexpr struct { #ifndef MAGNUM_TARGET_GLES2 {"both object and primitive id", MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::PrimitiveIdFromVertexId, - ": Flag::InstancedObjectId and Flag::PrimitiveId are mutually exclusive"} + ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, + {"both vertex and primitive id", + MeshVisualizer3D::Flag::VertexId|MeshVisualizer3D::Flag::PrimitiveIdFromVertexId, + ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"} #endif }; @@ -293,11 +306,15 @@ constexpr struct { MeshVisualizer3D::Flags flags3D; const char* file2D; const char* file3D; -} ObjectPrimitiveIdData[] { +} ObjectVertexPrimitiveIdData[] { {"object ID", MeshVisualizer2D::Flag::InstancedObjectId, MeshVisualizer3D::Flag::InstancedObjectId, "objectid2D.tga", "objectid3D.tga"}, + {"vertex ID", + MeshVisualizer2D::Flag::VertexId, + MeshVisualizer3D::Flag::VertexId, + "vertexid2D.tga", "vertexid3D.tga"}, #ifndef MAGNUM_TARGET_WEBGL {"primitive ID", MeshVisualizer2D::Flag::PrimitiveId, @@ -317,7 +334,11 @@ constexpr struct { MeshVisualizer2D::Flag::NoGeometryShader, MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::Wireframe| MeshVisualizer3D::Flag::NoGeometryShader, - "wireframe-nogeo-objectid2D.tga", "wireframe-nogeo-objectid3D.tga"} + "wireframe-nogeo-objectid2D.tga", "wireframe-nogeo-objectid3D.tga"}, + {"wireframe + vertex ID", + MeshVisualizer2D::Flag::VertexId|MeshVisualizer2D::Flag::Wireframe, + MeshVisualizer3D::Flag::VertexId|MeshVisualizer3D::Flag::Wireframe, + "wireframe-vertexid2D.tga", "wireframe-vertexid3D.tga"} }; #endif @@ -438,6 +459,8 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { #ifndef MAGNUM_TARGET_GLES2 addTests({ + &MeshVisualizerGLTest::renderDefaultsVertexId2D, + &MeshVisualizerGLTest::renderDefaultsVertexId3D, &MeshVisualizerGLTest::renderDefaultsPrimitiveId2D, &MeshVisualizerGLTest::renderDefaultsPrimitiveId3D, #ifndef MAGNUM_TARGET_WEBGL @@ -459,9 +482,9 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderTeardown); #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&MeshVisualizerGLTest::renderObjectPrimitiveId2D, - &MeshVisualizerGLTest::renderObjectPrimitiveId3D}, - Containers::arraySize(ObjectPrimitiveIdData), + addInstancedTests({&MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D, + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D}, + Containers::arraySize(ObjectVertexPrimitiveIdData), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif @@ -778,7 +801,7 @@ void MeshVisualizerGLTest::setWireframeNotEnabled2D() { #ifndef MAGNUM_TARGET_GLES2 CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/primitive ID enabled\n"); + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/vertex/primitive ID enabled\n"); #else CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled\n"); @@ -813,7 +836,7 @@ void MeshVisualizerGLTest::setWireframeNotEnabled3D() { #ifndef MAGNUM_TARGET_GLES2 CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/primitive ID enabled\n"); + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/vertex/primitive ID enabled\n"); #else CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled\n"); @@ -846,8 +869,8 @@ void MeshVisualizerGLTest::setColorMapNotEnabled2D() { .bindColorMapTexture(texture); CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/primitive ID enabled\n" - "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/primitive ID enabled\n"); + "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/vertex/primitive ID enabled\n" + "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/vertex/primitive ID enabled\n"); } void MeshVisualizerGLTest::setColorMapNotEnabled3D() { @@ -864,8 +887,8 @@ void MeshVisualizerGLTest::setColorMapNotEnabled3D() { .bindColorMapTexture(texture); CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/primitive ID enabled\n" - "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/primitive ID enabled\n"); + "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/vertex/primitive ID enabled\n" + "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/vertex/primitive ID enabled\n"); } #endif @@ -1133,6 +1156,61 @@ void MeshVisualizerGLTest::renderDefaultsObjectId3D() { (DebugTools::CompareImageToFile{_manager, 150.67f, 0.165f})); } +void MeshVisualizerGLTest::renderDefaultsVertexId2D() { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if( + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + MeshVisualizer2D{MeshVisualizer2D::Flag::VertexId} + .bindColorMapTexture(_colorMapTexture) + .draw(MeshTools::compile(Primitives::circle2DSolid(16))); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + 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/defaults-vertexid2D.tga"), + (DebugTools::CompareImageToFile{_manager, 1.0f, 0.017f})); +} + +void MeshVisualizerGLTest::renderDefaultsVertexId3D() { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if( + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + MeshVisualizer2D{MeshVisualizer2D::Flag::VertexId} + .bindColorMapTexture(_colorMapTexture) + .draw(MeshTools::compile(Primitives::icosphereSolid(0))); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + 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/defaults-vertexid3D.tga"), + (DebugTools::CompareImageToFile{_manager, 1.0f, 0.012f})); +} void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -1461,8 +1539,8 @@ void MeshVisualizerGLTest::renderWireframe3D() { } #ifndef MAGNUM_TARGET_GLES2 -void MeshVisualizerGLTest::renderObjectPrimitiveId2D() { - auto&& data = ObjectPrimitiveIdData[testCaseInstanceId()]; +void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { + auto&& data = ObjectVertexPrimitiveIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); #ifndef MAGNUM_TARGET_GLES @@ -1519,15 +1597,23 @@ void MeshVisualizerGLTest::renderObjectPrimitiveId2D() { /* Shouldn't assert (nor warn) when wireframe is not enabled */ .setViewportSize({80, 80}) .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - /* Should cover the first half of the colormap, in reverse order; for - primitive ID the whole colormap due to the repeat wrapping */ - .setColorMapTransformation(0.5f, -1.0f/16.0f) .bindColorMapTexture(_colorMapTexture); /* OTOH the wireframe color should stay at full channels, not mixed */ if(data.flags3D & MeshVisualizer3D::Flag::Wireframe) shader.setWireframeColor(0xffffff_rgbf); + /* For vertex ID we don't want any repeat/wraparound as that causes + disruptions in the gradient and test failures. There's 17 vertices + also. */ + if(data.flags2D & MeshVisualizer2D::Flag::VertexId) + shader.setColorMapTransformation(1.0f, -1.0f/17.0f); + /* For object/primitive ID there's no gradient so a wraparound is okay. + This should cover the first half of the colormap, in reverse order; for + primitive ID the whole colormap due to the repeat wrapping */ + else + shader.setColorMapTransformation(0.5f, -1.0f/16.0f); + shader.draw(circle); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1545,8 +1631,8 @@ void MeshVisualizerGLTest::renderObjectPrimitiveId2D() { (DebugTools::CompareImageToFile{_manager, 4.0f, 0.141f})); } -void MeshVisualizerGLTest::renderObjectPrimitiveId3D() { - auto&& data = ObjectPrimitiveIdData[testCaseInstanceId()]; +void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { + auto&& data = ObjectVertexPrimitiveIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); #ifndef MAGNUM_TARGET_GLES @@ -1605,15 +1691,22 @@ void MeshVisualizerGLTest::renderObjectPrimitiveId3D() { Matrix4::rotationY(-15.0_degf)* Matrix4::rotationX(15.0_degf)) .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - /* Should cover the first half of the colormap, in reverse order; for - primitive ID the whole colormap due to the repeat wrapping */ - .setColorMapTransformation(0.5f, -1.0f/40.0f) .bindColorMapTexture(_colorMapTexture); /* OTOH the wireframe color should stay at full channels, not mixed */ if(data.flags2D & MeshVisualizer2D::Flag::Wireframe) shader.setWireframeColor(0xffffff_rgbf); + /* For vertex ID we don't want any repeat/wraparound as that causes + disruptions in the gradient and test failures. There's 42 vertices also. */ + if(data.flags2D & MeshVisualizer2D::Flag::VertexId) + shader.setColorMapTransformation(1.0f, -1.0f/42.0f); + /* For object/primitive ID there's no gradient so a wraparound is okay. + This should cover the first half of the colormap, in reverse order; for + primitive ID the whole colormap due to the repeat wrapping */ + else + shader.setColorMapTransformation(0.5f, -1.0f/40.0f); + shader.draw(circle); MAGNUM_VERIFY_NO_GL_ERROR(); diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-vertexid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-vertexid2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..f75b1b7a65d48ac37bf6b14b892aa689a5acf919 GIT binary patch literal 12102 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 literal 0 HcmV?d00001