Virtual world
Goal: In this lab, you will expand your view to a richer virtual world, including a custom model and a skybox. You will also implement the diffuse shading from the CPU.
If you run into problems, you can either look in the textbook, or visit https://registry.khronos.org/vulkan/specs/latest/man/html/. There you will find all the functions and structs and enums used in Vulkan.
1) Ground plane
Add a "ground" as a fairly large textured polygon. You can do that with a few vectors:
float groundSize = 100.0f;
std::vector<vec3> vertices = {
vec3{-groundSize, 0.0f, -groundSize},
vec3{-groundSize, 0.0f, groundSize},
vec3{groundSize, -0.0f, -groundSize},
vec3{groundSize, -0.0f, groundSize}
};
std::vector<vec3> vertexnormals = {
vec3{0.0f, 1.0f, 0.0f},
vec3{0.0f, 1.0f, 0.0f},
vec3{0.0f, 1.0f, 0.0f},
vec3{0.0f, 1.0f, 0.0f}
};
std::vector<uint32_t> indices = {
0, 1, 2, 1, 3, 2
};
and pass this data in the function svk.loadCustomModel().
2) Populate the world (the TSBK11 town?)
Put several objects into the scene, most of them stationary.
There are several assets available in the assets folder for lab 3 and even more in the all_assets folder.
You can start with a few, then do part 3. After that works, this part will be more fun!
Question:
How many objects did you put into your scene?
3) Manual viewing controls
The lookAt() function is useful for more than placing the camera in some fixed place. Write controls for moving the camera. You can use keyboard only, or mouse and keyboard.
If you want to use the mouse, you can call svk.bindMouseMotionFunction() with a function returning void and having 2 int32_t as parameters. It can look something like this:
void mouse(int32_t xRel, int32_t yRel)
{
// some code
}
svk.bindMouseMotionFunction(mouse);
This allows you to modify variables by moving the mouse.
For keyboard controls, you can use the function svk.isKeyDown(). For example:
if (svk.isKeyDown("W"))
{
// move forward
}
Note: Each frame, the program checks if a key is down (provided you have called isKeyDown() somewhere). Compare this with the mouse motion, where the program doesn't check every frame but instead receives a signal when the mouse has moved. This means is that if you have a variable that is updated by the mouse moving, that update operation should not be modified by delta.
The manual controls should allow you to move around in the “world".
Questions:
What kind of control did you implement?
4) Skybox
Finally, add a "skybox". For this purpose, a skybox model and skybox texture are provided in the asset folder for lab 3. The skybox should follow the camera and seem to be drawn at the back. To do this, you should draw the skybox first, with the depth buffer turned off (This means the skybox needs a separate pipeline with depth test turned off). Culling should also be turned off since you always want to draw the skybox (and the model is not designed for culling). The skybox should be rotated by the world to view matrix but not translated. You can do this with a copy of the world to view matrix where you zero out the translation component.
Questions:
How did you handle the camera matrix for the skybox?
How did you represent the objects? Is this a good way to manage a scene or would you do it differently for a "real" application?
What special considerations are needed when rendering a skybox?
5) Diffuse shading, external light source
We did diffuse light before. We will modify that a little bit by making the light direction come from the CPU, and also verify that it doesn’t follow the camera.
The light source
You should use one directional light source, given by the main program like this:
vec4 lightSourceColor = vec4(1.0f, 0.0f, 0.0f); // Red light
vec4 lightSourceDirection = vec4(0.7f, 0.0f, 0.7f); // Light along diagonal
Add them to the push constant, then pass them along to the fragment shader. The reason they're vec4 instead of vec3 despite the fact that we're not concerned with the alpha is because Vulkan likes padding of 16 bytes between each variable. This means that if each variable is a multiple of 16 (which mat4 and vec4 are) everything will work fine. But if you're using a vec3, you will need to add a float for padding between the vec3s. As such, it's simpler to just a vec4 when uploading them to the vertex shader and then casting them to vec3 when transferring them over to the fragment shader.
You need to do the proper transformation of normal vectors in order to make the light stationary when you move around.
Questions:
In what coordinate system do you calculate the light?
6) Specular shading (non-mandatory)
Add the specular component to your shading. It is given (per object) like this:
float specularExponent = 100.0; // Should be defined per object
Add it to the push constant and pass it along to the fragment shader.
This will require you to calculate the reflected vector as well as the direction towards the camera.
For more detail, look in the specular shading part for TSBK07.
Note that the specularExponent can (should) be different for each object.
Questions (non-mandatory part):
Why do we get a white area in the distance for the specular light but not for the diffuse?
How do you generate a vector from the surface to the eye?
Which vectors need renormalization in the fragment shader?