Search code examples
openglglslshader

OpenGL shader to shade each face similar to MeshLab's visualizer


I have very basic OpenGL knowledge, but I'm trying to replicate the shading effect that MeshLab's visualizer has.

If you load up a mesh in MeshLab, you'll realize that if a face is facing the camera, it is completely lit and as you rotate the model, the lighting changes as the face that faces the camera changes. I loaded a simple unit cube with 12 faces in MeshLab and captured these screenshots to make my point clear:

  • Model loaded up (notice how the face is completely gray):
    Loaded up

  • Model slightly rotated (notice how the faces are a bit darker): Rotated

  • More rotation (notice how all faces are now darker):
    Rotated

Off the top of my head, I think the way it works is that it is somehow assigning colors per face in the shader. If the angle between the face normal and camera is zero, then the face is fully lit (according to the color of the face), otherwise it is lit proportional to the dot product between the normal vector and the camera vector.

I already have the code to draw meshes with shaders/VBO's. I can even assign per-vertex colors. However, I don't know how I can achieve a similar effect. As far as I know, fragment shaders work on vertices. A quick search revealed questions like this. But I got confused when the answers talked about duplicate vertices.

If it makes any difference, in my application I load *.ply files which contain vertex position, triangle indices and per-vertex colors.

Results after the answer by @DietrichEpp

I created the duplicate vertices array and used the following shaders to achieve the desired lighting effect. As can be seen in the posted screenshot, the similarity is uncanny :)

The vertex shader:

#version 330 core

uniform mat4 projection_matrix;
uniform mat4 model_matrix;
uniform mat4 view_matrix;

in vec3 in_position;    // The vertex position
in vec3 in_normal;      // The computed vertex normal
in vec4 in_color;       // The vertex color

out vec4 color;     // The vertex color (pass-through)

void main(void)
{
    gl_Position = projection_matrix * view_matrix * model_matrix * vec4(in_position, 1);

    // Compute the vertex's normal in camera space
    vec3 normal_cameraspace = normalize(( view_matrix * model_matrix * vec4(in_normal,0)).xyz); 
    // Vector from the vertex (in camera space) to the camera (which is at the origin)
    vec3 cameraVector = normalize(vec3(0, 0, 0) - (view_matrix * model_matrix * vec4(in_position, 1)).xyz);

    // Compute the angle between the two vectors
    float cosTheta = clamp( dot( normal_cameraspace, cameraVector ), 0,1 );

    // The coefficient will create a nice looking shining effect.
    // Also, we shouldn't modify the alpha channel value.
    color = vec4(0.3 * in_color.rgb + cosTheta * in_color.rgb, in_color.a);
}

The fragment shader:

#version 330 core

in vec4 color;

out vec4 out_frag_color;

void main(void)
{
    out_frag_color = color;
}

The uncanny results with the unit cube:

Result 1

Result 2


Solution

  • It looks like the effect is a simple lighting effect with per-face normals. There are a few different ways you can achieve per-face normals:

    • You can create a VBO with a normal attribute, and then duplicate vertex position data for faces which don't have the same normal. For example, a cube would have 24 vertexes instead of 8, because the "duplicates" would have different normals.

    • You can use a geometry shader which calculates a per-face normal.

    • You can use dFdx() and dFdy() in the fragment shader to approximate the normal.

    I recommend the first approach, because it is simple. You can simply calculate the normals ahead of time in your program, and then use them to calculate the face colors in your vertex shader.