sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English

Show/Hide search bar
black cat logo variable logo
[27 Oct 2012]

Visualization of normals and edges with geometry shaders in OpenGL and GLSL

This tutorial shows how to visualize normals, tangents, bitangnets and edges of a polygonal mesh with a GLSL geometry shader. Following image depicts what we will get.
GLSL geometry shader can create new primitives from original primitives. Such shader is executed after a vertex shader and before a fragment shader. The vertex shader prepares data for the geometry shader, and the geometry shader from input data generates new primitives that will be rasterized and handled by the fragment shader. Type of input and output primitives for the geometry shader can be points, lines (two points) or triangles (three points). Input primitives can also contain information about adjacent vertices. Types of input and output primitives may be different. For example, in this tutorial input primitive type for the geometry shader is triangle (group of three vertices) and output primitive type is array of lines - line strip (line between each pair of consecutive vertices).
Normals, tangents, bitangents and edges are created with the geometry shader
It's simple. If input primitive type of the geometry shader is triangle, then the shader has access to three vertices of the triangle. Each vertex has access to all data passed for this vertex from the vertex shader. To visualize normals at the vertices you have to create one line for each vertex. First point of the line has to be equal to position of the vertex. Second point has to be equal to to position of the vertex shifted along the normal at the vertex (the normal is passed from vertex shader). You can control length of new normals with uniform variable that represents length multiplier. On the image the normals at the vertices are rendered as green lines.
The tangents and the bitangents are visualized similarly to the normals at the vertices. They are represented with blue and red lines on the image.
To visualize the edges of the polygonal mesh you have to add three lines for each triangle. We are using line strip as output primitive type for the geometry shader. The line strip is an array of vertices where each previous vertex is connected to the next with a line. In such case we have to add only 4 vertices instead of 6 vertices (pair of vertices for each of three lines). For first new vertex use position of the first initial vertex, then second, third, and then again first for fourth new vertex (to close loop of lines). On the image the edges are depicted with gray lines.
The last step is to visualize a normal for each triangle. From three vertices of the triangle build two vectors: from the first vertex to the second, and from the first to the third. Then with help of cross product find vector that is perpendicular to these two vectors. This new vector is normal of the triangle. The first point of the new line is equal to arithmetic average of positions of the three vertices of the triangle. The second point of the line is calculated as shift from the first point along normal of the triangle. The normals of the triangles are yellow on the image.
It is worth noting, that input and output primitive types, and maximum number of output vertices cannot be changed dynamically during shader runtime.
Following sections contain the vertex, the geometry and the fragment shaders for visualization of normals, tangents, bitangents and edges of polygonal meshes.
Vertex shader
#version 330

// attributes
layout(location = 0) in vec3 i_position; // xyz - position
layout(location = 1) in vec3 i_normal; // xyz - normal
layout(location = 2) in vec4 i_tangent; // xyz - tangent, w - handedness

// matrices
uniform mat4 u_model_mat;
uniform mat3 u_normal_mat;

// data to geometry shader
out vec3 o_normal;
out vec3 o_tangent;
out vec3 o_bitangent;

///////////////////////////////////////////////////////////////////

void main(void)
{
   // position to world coordinates
   gl_Position = u_model_mat * vec4(i_position, 1.0);

   // normal, tangent and bitangent in world coordinates
   o_normal = normalize(u_normal_mat * i_normal);
   o_tangent = normalize(u_normal_mat * i_tangent.xyz);
   o_bitangent = cross(o_normal, o_tangent) * i_tangent.w;
}
Geometry shader
#version 330

// type of input primitives
layout(triangles) in;

// type of output primitives and maximum number of new vertices
// each line requires two vertices
// vertex normal, tangent and bitangent for each vertex in triangle = 18
// three lines for edges = 4 (due to usage of line strip output primitive)
// one line for normal of the triangle = 2

layout(line_strip, max_vertices = 24) out;

///////////////////////////////////////////////////////////////////

// data from vertex shader for three vertices of the triangle
in vec3 o_normal[];
in vec3 o_tangent[];
in vec3 o_bitangent[];

// matrices
uniform mat4 u_view_mat;
uniform mat4 u_proj_mat;

// modifier for size of the normals, tangents and bitangents
uniform float u_normalScale;

// colors for different type of new lines
uniform vec4 u_edgeColor;
uniform vec4 u_faceNormalColor;
uniform vec4 u_normalColor;
uniform vec4 u_tangentColor;
uniform vec4 u_bitangentColor;

// color to fragment shader
out vec4 o_color;

///////////////////////////////////////////////////////////////////

void main()
{
   mat4 viewProjection = u_proj_mat * u_view_mat;

   // normals of each vertex of the triangle
   vec3 nor[3];
   nor[0] = o_normal[0].xyz;
   nor[1] = o_normal[1].xyz;
   nor[2] = o_normal[2].xyz;

   // positions of each vertex of the triangle
   // shifted a bit along normal
   // so there won't be Z fighting when rendered over the mesh

   vec4 pos[3];
   pos[0] = viewProjection * vec4(gl_in[0].gl_Position.xyz + nor[0] * 0.01, 1.0);
   pos[1] = viewProjection * vec4(gl_in[1].gl_Position.xyz + nor[1] * 0.01, 1.0);
   pos[2] = viewProjection * vec4(gl_in[2].gl_Position.xyz + nor[2] * 0.01, 1.0);

   // output normals, tangents and bitangents for each vertex of the triangle
   for(int i=0; i < gl_in.length(); i++)
   {
      // get position of the vertex
      vec3 P = gl_in[i].gl_Position.xyz;

      // create normal for vertex
      o_color = u_normalColor;
      gl_Position = pos[i];
      EmitVertex();
      gl_Position = viewProjection * vec4(P + o_normal[i].xyz
                        * u_normalScale, 1.0);
      EmitVertex();
      EndPrimitive();

      // create tangent for vertex
      o_color = u_tangentColor;
      gl_Position = pos[i];
      EmitVertex();
      gl_Position = viewProjection * vec4(P + o_tangent[i].xyz * u_normalScale, 1.0);
      EmitVertex();
      EndPrimitive();

      // create bitangent for vertex
      o_color = u_bitangentColor;
      gl_Position = pos[i];
      EmitVertex();
      gl_Position = viewProjection * vec4(P +
                              o_bitangent[i].xyz * u_normalScale, 1.0);
      EmitVertex();
      EndPrimitive();
   }

   // create edges for triangle
   o_color = u_edgeColor;
   gl_Position = pos[0];
   EmitVertex();
   gl_Position = pos[1];
   EmitVertex();
   gl_Position = pos[2];
   EmitVertex();
   gl_Position = pos[0];
   EmitVertex();
   // end line strip after four added vertices, so we will get three lines
   EndPrimitive();

   // create normal for triangle
   o_color = u_faceNormalColor;
   // form two vectors from triangle
   vec3 V0 = gl_in[0].gl_Position.xyz - gl_in[1].gl_Position.xyz;
   vec3 V1 = gl_in[2].gl_Position.xyz - gl_in[1].gl_Position.xyz;
   // calculate normal as perpendicular to two vectors of the triangle
   vec3 N = normalize(cross(V1, V0));
   // position as arithmetic average
   vec3 P = (gl_in[0].gl_Position.xyz + gl_in[1].gl_Position.xyz
                        + gl_in[2].gl_Position.xyz) / 3.0;
   gl_Position = viewProjection * vec4(P, 1.0);
   EmitVertex();
   gl_Position = viewProjection * vec4(P + N * u_normalScale, 1.0);
   EmitVertex();
   EndPrimitive();
}
Fragment shader
#version 330

// color from geometry shader
in vec4 o_color;

// color to the framebuffer
out vec4 resultingColor;

///////////////////////////////////////////////////////////////////

void main(void)
{
   // output color to the framebuffer
   resultingColor = o_color;
}



Sun and Black Cat- Igor Dykhta (igor dykhta email) 2007-2014