Magnum::Shaders::MeshVisualizer3D class

3D mesh visualization shader

Visualizes wireframe, per-vertex/per-instance object ID, primitive ID or tangent space of 3D meshes. You need to provide the Position attribute in your triangle mesh at the very least. Use setTransformationProjectionMatrix(), setColor() and others to configure the shader.

The shader expects that you enable wireframe visualization, tangent space visualization or object/primitive ID visualization by passing an appropriate Flag to the constructor — there's no default behavior with nothing enabled.

Wireframe visualization

Wireframe visualization is done by enabling Flag::Wireframe. It is done either using geometry shaders or with help of additional vertex information. If you have geometry shaders available, you don't need to do anything else except calling setViewportSize() to correctly size the wireframe — without this, the mesh will be rendered in a single color.

If you don't have geometry shaders, you need to enable Flag::NoGeometryShader (done by default in OpenGL ES 2.0) and use only non-indexed triangle meshes (see MeshTools::duplicate() for a possible solution). Additionaly, if you have OpenGL < 3.1 or OpenGL ES 2.0, you need to provide also the VertexIndex attribute.

If using geometry shaders on OpenGL ES, NV_shader_noperspective_interpolation is optionally used for improving line appearance. On desktop OpenGL this is done implicitly.

If you want to render just the wireframe on top of an existing mesh, call setColor() with 0x00000000_rgbaf. Alpha / transparency is supported by the shader implicitly, but to have it working on the framebuffer, you need to enable GL::Renderer::Feature::Blending and set up the blending function. See GL::Renderer::setBlendFunction() for details.

Example setup with a geometry shader (desktop GL, OpenGL ES 3.2)

Common mesh setup:

struct Vertex {
    Vector3 position;
};
Vertex data[60]{
    // ...
};

GL::Buffer vertices;
vertices.setData(data, GL::BufferUsage::StaticDraw);

GL::Mesh mesh;
mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer3D::Position{});

Common rendering setup:

Matrix4 transformationMatrix = Matrix4::translation(Vector3::zAxis(-5.0f));
Matrix4 projectionMatrix =
    Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f);

Shaders::MeshVisualizer3D shader{Shaders::MeshVisualizer3D::Flag::Wireframe};
shader.setColor(0x2f83cc_rgbf)
    .setWireframeColor(0xdcdcdc_rgbf)
    .setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()})
    .setTransformationMatrix(transformationMatrix)
    .setProjectionMatrix(projectionMatrix)
    .draw(mesh);

Example setup for indexed meshes without a geometry shader

The vertices have to be converted to a non-indexed array first. Mesh setup:

Containers::StridedArrayView1D<const UnsignedInt> indices;
Containers::StridedArrayView1D<const Vector3> indexedPositions;

/* De-indexing the position array */
GL::Buffer vertices{MeshTools::duplicate(indices, indexedPositions)};

GL::Mesh mesh;
mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer3D::Position{});

Rendering setup:

Matrix4 transformationMatrix, projectionMatrix;

Shaders::MeshVisualizer3D shader{
    Shaders::MeshVisualizer3D::Flag::Wireframe|
    Shaders::MeshVisualizer3D::Flag::NoGeometryShader};
shader.setColor(0x2f83cc_rgbf)
    .setWireframeColor(0xdcdcdc_rgbf)
    .setTransformationMatrix(transformationMatrix)
    .setProjectionMatrix(projectionMatrix)
    .draw(mesh);

Wireframe visualization of non-indexed meshes without a geometry shader on older hardware

You need to provide also the VertexIndex attribute. Mesh setup in addition to the above:

Containers::Array<Float> vertexIndex{Containers::arraySize(data)};
std::iota(vertexIndex.begin(), vertexIndex.end(), 0.0f);

GL::Buffer vertexIndices;
vertexIndices.setData(vertexIndex, GL::BufferUsage::StaticDraw);

mesh.addVertexBuffer(vertexIndices, 0, Shaders::MeshVisualizer3D::VertexIndex{});

Rendering setup the same as above.

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 Tangent and Normal attributes and enable Flag::TangentDirection and 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 Tangent4 attribute instead of Tangent, and enable Flag::BitangentFromTangentDirection. The other, more obvious but less efficient representation, is a dedicated Bitangent attribute (in which case you'll enable Flag::BitangentDirection). Note that these two are mutually exclusive, so you need to choose either of them based on what given mesh contains. Example for the first case:

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{});

Rendering setup:

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);

Object, vertex and primitive ID visualization

If the mesh contains a per-vertex (or instanced) ObjectId, it can be visualized by enabling Flag::InstancedObjectId. For the actual visualization you need to provide a color map using bindColorMapTexture() and use setColorMapTransformation() to map given range of discrete IDs to the $ [0, 1] $ texture range. Various colormap presets are in the DebugTools::ColorMap namespace. Example usage:

const auto map = DebugTools::ColorMap::turbo();
const Vector2i size{Int(map.size()), 1};

GL::Texture2D colorMapTexture;
colorMapTexture
    .setMinificationFilter(SamplerFilter::Linear)
    .setMagnificationFilter(SamplerFilter::Linear)
    .setWrapping(SamplerWrapping::ClampToEdge)
    .setStorage(1, GL::TextureFormat::RGBA8, size)
    .setSubImage(0, {}, ImageView2D{PixelFormat::RGB8Srgb, size, map});

Shaders::MeshVisualizer3D shader{
    Shaders::MeshVisualizer3D::Flag::InstancedObjectId};
shader.setColorMapTransformation(0.0f, 1.0f/Math::max(objectIds))
    .setTransformationMatrix(transformationMatrix)
    .setProjectionMatrix(projectionMatrix)
    .bindColorMapTexture(colorMapTexture)
    .draw(mesh);

If you enable 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 gl_VertexID GLSL builtin.

The 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 gl_PrimitiveID GLSL builtin; with Flag::PrimitiveIdFromVertexId it's emulated using gl_VertexID, expecting you to draw a non-indexed triangle mesh. You can use MeshTools::duplicate() (and potentially MeshTools::generateIndices()) to conveniently convert the mesh to a non-indexed MeshPrimitive::Triangles.

Public types

enum (anonymous): UnsignedInt { ColorOutput = Generic3D::ColorOutput }
enum class Flag: UnsignedShort { Wireframe = 1 << 0, NoGeometryShader = 1 << 1, InstancedObjectId = 1 << 2 new in 2020.06, VertexId = 1 << 3 new in 2020.06, PrimitiveId = 1 << 4 new in 2020.06, PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId new in 2020.06, TangentDirection = 1 << 6 new in 2020.06, BitangentFromTangentDirection = 1 << 7 new in 2020.06, BitangentDirection = 1 << 8 new in 2020.06, NormalDirection = 1 << 9 new in 2020.06 }
Flag.
using Position = Generic3D::Position
Vertex position.
using Tangent = Generic3D::Tangent new in 2020.06
Tangent direction.
using Tangent4 = Generic3D::Tangent4 new in 2020.06
Tangent direction with a bitangent sign.
using Bitangent = Generic3D::Bitangent new in 2020.06
Bitangent direction.
using Normal = Generic3D::Normal new in 2020.06
Normal direction.
using VertexIndex = GL::Attribute<4, Float>
Vertex index.
using ObjectId = Generic3D::ObjectId new in 2020.06
(Instanced) object ID
using Flags = Containers::EnumSet<Flag>
Flags.

Constructors, destructors, conversion operators

MeshVisualizer3D(Flags flags) explicit
Constructor.
MeshVisualizer3D() deprecated in 2020.06 explicit
Constructor.
MeshVisualizer3D(NoCreateT) explicit noexcept
Construct without creating the underlying OpenGL object.
MeshVisualizer3D(const MeshVisualizer3D&) deleted
Copying is not allowed.
MeshVisualizer3D(MeshVisualizer3D&&) defaulted noexcept
Move constructor.

Public functions

auto operator=(const MeshVisualizer3D&) -> MeshVisualizer3D& deleted
Copying is not allowed.
auto operator=(MeshVisualizer3D&&) -> MeshVisualizer3D& defaulted noexcept
Move assignment.
auto flags() const -> Flags
Flags.
auto setTransformationProjectionMatrix(const Matrix4& matrix) -> MeshVisualizer3D& deprecated in 2020.06
Set transformation and projection matrix.
auto setTransformationMatrix(const Matrix4& matrix) -> MeshVisualizer3D&
Set transformation matrix.
auto setProjectionMatrix(const Matrix4& matrix) -> MeshVisualizer3D&
Set projection matrix.
auto setNormalMatrix(const Matrix3x3& matrix) -> MeshVisualizer3D& new in 2020.06
Set transformation matrix.
auto setViewportSize(const Vector2& size) -> MeshVisualizer3D&
Set viewport size.
auto setColor(const Color4& color) -> MeshVisualizer3D&
Set base object color.
auto setWireframeColor(const Color4& color) -> MeshVisualizer3D&
Set wireframe color.
auto setWireframeWidth(Float width) -> MeshVisualizer3D&
Set wireframe width.
auto setColorMapTransformation(Float offset, Float scale) -> MeshVisualizer3D& new in 2020.06
Set color map transformation.
auto bindColorMapTexture(GL::Texture2D& texture) -> MeshVisualizer3D& new in 2020.06
Bind a color map texture.
auto setLineWidth(Float width) -> MeshVisualizer3D& new in 2020.06
Set line width.
auto setLineLength(Float length) -> MeshVisualizer3D& new in 2020.06
Set line length.
auto setSmoothness(Float smoothness) -> MeshVisualizer3D&
Set line smoothness.

Enum documentation

enum Magnum::Shaders::MeshVisualizer3D::(anonymous): UnsignedInt

Enumerators
ColorOutput

Color shader output. Generic output, present always. Expects three- or four-component floating-point or normalized buffer attachment.

enum class Magnum::Shaders::MeshVisualizer3D::Flag: UnsignedShort

Flag.

Enumerators
Wireframe

Visualize wireframe. On OpenGL ES 2.0 and WebGL this also enables Flag::NoGeometryShader.

NoGeometryShader

Don't use a geometry shader for wireframe visualization. If enabled, you might need to provide also the VertexIndex attribute in the mesh. On OpenGL ES 2.0 and WebGL enabled alongside Flag::Wireframe.

Mutually exclusive with Flag::TangentDirection, Flag::BitangentFromTangentDirection, Flag::BitangentDirection and Flag::NormalDirection — those need a geometry shader always.

InstancedObjectId new in 2020.06

Visualize instanced object ID. You need to provide the ObjectId attribute in the mesh. Mutually exclusive with Flag::VertexId and Flag::PrimitiveId.

VertexId new in 2020.06

Visualize vertex ID (gl_VertexID). 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 Flag::InstancedObjectId and Flag::PrimitiveId.

PrimitiveId new in 2020.06

Visualize primitive ID (gl_PrimitiveID). Useful for visualizing how well is the mesh optimized for a post-transform vertex cache. Mutually exclusive with Flag::InstancedObjectId and Flag::VertexId. See also Flag::PrimitiveIdFromVertexId.

PrimitiveIdFromVertexId new in 2020.06

Visualize primitive ID on a non-indexed triangle mesh using gl_VertexID/3. Implicitly enables Flag::PrimitiveId, mutually exclusive with Flag::InstancedObjectId. Usable on OpenGL < 3.2, OpenGL ES < 3.2 and WebGL where gl_PrimitiveID is not available.

TangentDirection new in 2020.06

Visualize tangent direction with red lines pointing out of vertices. You need to provide the Tangent or Tangent4 attribute in the mesh. Mutually exclusive with Flag::NoGeometryShader (as this needs a geometry shader always).

BitangentFromTangentDirection new in 2020.06

Visualize bitangent direction with green lines pointing out of vertices. You need to provide both Normal and Tangent4 attributes in the mesh, alternatively you can provide the Bitangent attribute and enable Flag::BitangentDirection instead. Mutually exclusive with Flag::NoGeometryShader (as this needs a geometry shader always).

BitangentDirection new in 2020.06

Visualize bitangent direction with green lines pointing out of vertices. You need to provide the Bitangent attribute in the mesh, alternatively you can provide both Normal and Tangent4 attributes and enable Flag::BitangentFromTangentDirection instead. Mutually exclusive with Flag::NoGeometryShader (as this needs a geometry shader always).

NormalDirection new in 2020.06

Visualize normal direction with blue lines pointing out of vertices. You need to provide the Normal attribute in the mesh. Mutually exclusive with Flag::NoGeometryShader (as this needs a geometry shader always).

Typedef documentation

typedef Generic3D::Position Magnum::Shaders::MeshVisualizer3D::Position

Vertex position.

Generic attribute, Vector3.

typedef Generic3D::Tangent Magnum::Shaders::MeshVisualizer3D::Tangent new in 2020.06

Tangent direction.

Generic attribute, Vector3. Use either this or the Tangent4 attribute. Used only if Flag::TangentDirection is enabled.

typedef Generic3D::Tangent4 Magnum::Shaders::MeshVisualizer3D::Tangent4 new in 2020.06

Tangent direction with a bitangent sign.

Generic attribute, Vector4. Use either this or the Tangent attribute. Used only if Flag::TangentDirection or Flag::BitangentFromTangentDirection is enabled.

typedef Generic3D::Bitangent Magnum::Shaders::MeshVisualizer3D::Bitangent new in 2020.06

Bitangent direction.

Generic attribute, Vector3. Use either this or the Tangent4 attribute. Used only if Flag::BitangentDirection is enabled.

typedef Generic3D::Normal Magnum::Shaders::MeshVisualizer3D::Normal new in 2020.06

Normal direction.

Generic attribute, Vector3. Used only if Flag::NormalDirection is enabled.

typedef GL::Attribute<4, Float> Magnum::Shaders::MeshVisualizer3D::VertexIndex

Vertex index.

Float, used only in OpenGL < 3.1 and OpenGL ES 2.0 if Flag::Wireframe is enabled. This attribute (modulo 3) specifies index of given vertex in triangle, i.e. 0.0f for first, 1.0f for second, 2.0f for third. In OpenGL 3.1, OpenGL ES 3.0 and newer this value is provided via the gl_VertexID shader builtin, so the attribute is not needed.

typedef Generic3D::ObjectId Magnum::Shaders::MeshVisualizer3D::ObjectId new in 2020.06

(Instanced) object ID

Generic attribute, Magnum::UnsignedInt. Used only if Flag::InstancedObjectId is set.

Function documentation

Magnum::Shaders::MeshVisualizer3D::MeshVisualizer3D(Flags flags) explicit

Constructor.

Parameters
flags Flags

At least Flag::Wireframe or one of Flag::TangentDirection, Flag::BitangentFromTangentDirection, Flag::BitangentDirection, Flag::NormalDirection is expected to be enabled.

Magnum::Shaders::MeshVisualizer3D::MeshVisualizer3D() explicit

Constructor.

Magnum::Shaders::MeshVisualizer3D::MeshVisualizer3D(NoCreateT) explicit noexcept

Construct without creating the underlying OpenGL object.

The constructed instance is equivalent to a moved-from state. Useful in cases where you will overwrite the instance later anyway. Move another object over it to make it useful.

This function can be safely used for constructing (and later destructing) objects even without any OpenGL context being active. However note that this is a low-level and a potentially dangerous API, see the documentation of NoCreate for alternatives.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setTransformationProjectionMatrix(const Matrix4& matrix)

Set transformation and projection matrix.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setTransformationMatrix(const Matrix4& matrix)

Set transformation matrix.

Returns Reference to self (for method chaining)

Initial value is an identity matrix.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setProjectionMatrix(const Matrix4& matrix)

Set projection matrix.

Returns Reference to self (for method chaining)

Initial value is an identity matrix. (i.e., an orthographic projection of the default $ [ -\boldsymbol{1} ; \boldsymbol{1} ] $ cube).

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setNormalMatrix(const Matrix3x3& matrix) new in 2020.06

Set transformation matrix.

Returns Reference to self (for method chaining)

Expects that Flag::TangentDirection, Flag::BitangentDirection or 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.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setViewportSize(const Vector2& size)

Set viewport size.

Returns Reference to self (for method chaining)

Has effect only if Flag::Wireframe is enabled and geometry shaders are used; or if Flag::TangentDirection, Flag::BitangentDirection or Flag::NormalDirection is enabled, otherwise it does nothing. Initial value is a zero vector.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setColor(const Color4& color)

Set base object color.

Returns Reference to self (for method chaining)

Initial value is 0xffffffff_rgbaf. Expects that either Flag::Wireframe or Flag::InstancedObjectId / Flag::PrimitiveId is enabled. In case of the latter, the color is multiplied with the color map coming from bindColorMapTexture().

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setWireframeColor(const Color4& color)

Set wireframe color.

Returns Reference to self (for method chaining)

Initial value is 0x000000ff_rgbaf. Expects that Flag::Wireframe is enabled.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setWireframeWidth(Float width)

Set wireframe width.

Returns Reference to self (for method chaining)

Value is in screen space (depending on setViewportSize()), initial value is 1.0f. Expects that Flag::Wireframe is enabled.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setColorMapTransformation(Float offset, Float scale) new in 2020.06

Set color map transformation.

Returns Reference to self (for method chaining)

Offset and scale applied to the input value coming either from the ObjectId attribute or gl_PrimitiveID, resulting value is then used to fetch a color from a color map bound with bindColorMapTexture(). Initial value is 1.0f/512.0f and 1.0/256.0f, meaning that for a 256-entry colormap the first 256 values get an exact color from it and the next values will be either clamped to last color or repeated depending on the color map texture wrapping mode. Expects that either Flag::InstancedObjectId or Flag::PrimitiveId / Flag::PrimitiveIdFromVertexId is enabled.

Note that this shader doesn't directly offer a setObjectId() uniform that's used to offset the per-vertex / per-instance ID. Instead, you need to encode the base offset into the offset parameter.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::bindColorMapTexture(GL::Texture2D& texture) new in 2020.06

Bind a color map texture.

Returns Reference to self (for method chaining)

See also setColorMapTransformation(). Expects that either Flag::InstancedObjectId or Flag::PrimitiveId / Flag::PrimitiveIdFromVertexId is enabled.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setLineWidth(Float width) new in 2020.06

Set line width.

Returns Reference to self (for method chaining)

Value is in screen space (depending on setViewportSize()), initial value is 1.0f. Expects that Flag::TangentDirection, Flag::BitangentFromTangentDirection, Flag::BitangentDirection or Flag::NormalDirection is enabled.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setLineLength(Float length) new in 2020.06

Set line length.

Returns Reference to self (for method chaining)

Value is in object space, initial value is 1.0f. Expects that Flag::TangentDirection, Flag::BitangentFromTangentDirection, Flag::BitangentDirection or Flag::NormalDirection is enabled.

MeshVisualizer3D& Magnum::Shaders::MeshVisualizer3D::setSmoothness(Float smoothness)

Set line smoothness.

Returns Reference to self (for method chaining)

Value is in screen space (depending on setViewportSize()), initial value is 2.0f. Expects that Flag::Wireframe, Flag::TangentDirection, Flag::BitangentFromTangentDirection, Flag::BitangentDirection or Flag::NormalDirection is enabled.

Debug& operator<<(Debug& debug, MeshVisualizer3D::Flag value)

Debug output operator.

Debug& operator<<(Debug& debug, MeshVisualizer3D::Flags value)

Debug output operator.