These exercises are taken from the 2016/17 lecture "Computer Graphics" by Filip Sadlo. While there were many more exercises, we picked those involving OpenGL and shader programming.
The goal of this exercise was to create billboard effect, meaning that planar objects should always face the camera and thus creating a 3D impression. As the fragment shader simply samples from a texture we will focus on the vertex shader. The complete shader code can be found here.
First, we have to understand why objects using the default vertex shader "rotate away" from the camera in order to create the impression that it's actually the camera that moves.
This rotation is done with the view
matrix. If we want the object not to rotate on a certain axis, we can simply set the corresponding entries in the matrix to "no rotation"
(this "no rotation" value is mathematically equivalent to the unit vector).
So for our application, we want to disable the rotation along the x-axis and the z-axis. We still want these objects to rotate around the y-axis. Note that this explanation might seem the wrong way around, but keep in mind that an enabled rotation will seem like it's disabled — since it's correctly following the camera's movements and rotations — and the other way round.
1 void main()
2 {
3 mat4 view = global.view;
4
5 view[0][0] = 1;
6 view[0][1] = 0;
7 view[0][2] = 0;
8
9 view[2][0] = 0;
10 view[2][1] = 0;
11 view[2][2] = 1;
12
13 gl_Position = view * gl_Position;
14 gl_Position = global.projection * gl_Position;
15 }
See the demo video here
In this exercise we had to dynamically adjust the level of detail of a tessellated terrain depending on the distance to the camera. The vertex, fragment and tessellation evaluation shaders are fairly standard, so we won't cover them here. The interesting things happen inside the tessellation control shader (full code here). Most of the code has been taken from this tutorial. Check it out, if you want to read more on that subject, especially how to stitch the terrain patches together.
At first we implemented a level
function that looks up a tessellation level for a given point in 3D space based on the camera distance: the further away, the smaller the tessellation level.
A continuous scaling between distance and tessellation proved to create flickering whenever the camera was moved. However, with these discrete steps the LOD "swaps" occur less frequent.
1 float level(vec4 eye, vec4 point)
2 {
3 float d = distance(eye, point);
4 if(d < 10) return 64;
5 if(d < 50) return 32;
6 if(d < 100) return 16;
7 if(d < 200) return 8;
8 if(d < 400) return 4;
9 if(d < 800) return 2;
10 return 1;
11 }
The general structure is again a standard tessellation control shader. In the variables d1
, d2
, d3
we
calculate the middle points of the three triangle edges. If the level depended on the distance of the vertices, you could obtain different values for adjacent triangles. The result of that incorrect approach
will be tears in the terrain and wrongly placed textures.
1 void main()
2 {
3 if(gl_InvocationID == 0)
4 {
5 vec4 eye = vec4(global.camera, 1);
6
7 mat4 mat = local.model;
8 vec4 v0 = mat * gl_in[0].gl_Position / 2;
9 vec4 v1 = mat * gl_in[1].gl_Position / 2;
10 vec4 v2 = mat * gl_in[2].gl_Position / 2;
11
12 vec3 d1 = v1.xyz + (v2.xyz - v1.xyz) / 2;
13 vec3 d2 = v0.xyz + (v2.xyz - v0.xyz) / 2;
14 vec3 d3 = v0.xyz + (v1.xyz - v0.xyz) / 2;
15
16 float e0 = level(vec4(d1, 1), eye);
17 float e1 = level(vec4(d2, 1), eye);
18 float e2 = level(vec4(d3, 1), eye);
19 float mine = min(e0, min(e1, e2));
20 float maxe = max(e0, max(e1, e2));
21
22 gl_TessLevelInner[0] = floor((mine + maxe) / 2);
23 gl_TessLevelOuter[0] = e0;
24 gl_TessLevelOuter[1] = e1;
25 gl_TessLevelOuter[2] = e2;
26 }
27
28 gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
29 outTexCoord[gl_InvocationID] = inTexCoord[gl_InvocationID];
30 }
See the demo video here