Add report, in its current unfinished state

master
Peder Bergebakken Sundt 2019-04-02 23:51:16 +02:00
parent 1402a0cb90
commit 9c889c2d9a
38 changed files with 546 additions and 0 deletions

3
.gitignore vendored
View File

@ -32,3 +32,6 @@
*.app
build/
report/pd-images/
report/report_combined.md
report/report_combined_out.pdf

50
report/build_report.sh Executable file
View File

@ -0,0 +1,50 @@
#/usr/bin/env bash
here="$(pwd)"
echo Building report_combined.md...
(
cat report_part1_intro.md; echo;
cat report_part2_hills.md; echo;
cat report_part3_models.md; echo;
cat report_part4_optimizations.md; echo;
cat report_part5_scene.md; echo;
cat report_part6_effect.md; echo;
cat report_part7_daylight.md; echo;
) | sed -e "s/ i / I /g" | sed -e "s/ i'm / I'm /g" > report_combined.md
#ENGINE=pdflatex
#ENGINE=lualatex
ENGINE=xelatex
VARIABLES="$VARIABLES --filter pandoc-imagine"
#VARIABLES="$VARIABLES --variable classoption=twocolumn"
VARIABLES="$VARIABLES --variable papersize=a4paper"
VARIABLES="$VARIABLES --table-of-contents"
VARIABLES="$VARIABLES --number-sections"
#VARIABLES="$VARIABLES --number-offset=0,0"
VARIABLES="$VARIABLES --variable links-as-notes=true"
VARIABLES="$VARIABLES --highlight-style=pygments" # the default
#VARIABLES="$VARIABLES --highlight-style=haddock" # kinda nice for python at least
#VARIABLES="$VARIABLES --highlight-style=tango"
#VARIABLES="$VARIABLES --highlight-style=espresso"
#VARIABLES="$VARIABLES --highlight-style=zenburn"
#VARIABLES="$VARIABLES --highlight-style=kate"
#VARIABLES="$VARIABLES --highlight-style=monochrome"
#VARIABLES="$VARIABLES --highlight-style=breezedark"
ls -1 *.md | grep -v "part" |
( while read source; do
(
base="$(basename $source)"
dest="$(echo $base | rev | cut -c4- | rev)_out"
cd "$(dirname $source)"
echo "Converting $source into $(dirname $source)/${dest}.pdf ..."
pandoc "$base" --pdf-engine="$ENGINE" $VARIABLES -o "$dest.pdf"
) &
done
wait )

BIN
report/images/0-base.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
report/images/13-tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

BIN
report/images/24-noise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

BIN
report/images/26-day.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

5
report/install_dependencies.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
sudo pacman -S pandoc
sudo pacman -S graphviz
pip install --user pandoc-imagine

View File

@ -0,0 +1,26 @@
% TDT4230 - Final assignment
% Peder Berbebakken Sundt
% insert date here
\newpage
```{.shebang im_out="stdout"}
#!/usr/bin/env bash
printf "time for some intricate graphics surgery!\n" | cowsay -f surgery | head -n -1 | sed -e "s/^/ /"
```
# the task
todo
# Getting started
First I had to clean out all the cruft from the glowbox scene in the program. while at it, I removed a lot of the unneccesary code and made a lot of helpers which keeps track of VAO IDs buffer IDs. Now, these are generated and cached automatically causing me a lot less of hassle.
I then went though and added the ability to use multiple shaders. The each sceneNode can specify a shader.
If the shader is a `nullptr`, then it is inherited from the parent.
When changing shader I had to make sure all the uniforms are passed properly. This was done through the use of a few helper structs and great miss-use of the c++ preprocessor to make all the uniforms named. Now the program simply uses the uniform name instead of hardcoding all the location numbers.
When support for multiple shaders was in place, I revamped the camera transforms to make sure I'm able to move the camera intuitively should I so choose to do so.
This was done with the help of `glm::lookAt()`. While at it I changed the coordinate system to make Z point skyward, the only sensible way.

View File

@ -0,0 +1,104 @@
# Creating the hills
To make my plane of grass I first needed a plane. I added a generator function in `glutils.cpp` which makes a segmented plane for me with as many vertices and faces as specified. This was needed since I plan to add a displacement map capability, but only do so in the vertex shader. I thus need a lot of vertexes. The other route would be to make a texel shader which divides the faces into smaller faces and use it on the plane, which would save memory bandwith. But making it from the start is the easier option.
I added the plane to the scene and set the cobble texture on it.
It didn't look right, so I went through the shader again to make sure the lighting was correct. I failed to pass the specular shininess factor properly as a uniform. I also had failed to account for the specular component being negative (before raising it to the power of the shininess). I added this check and now the lighting looks correct.
![](images/0-base.png)
While at it I added the ability to specify all the color components and the attenuation per light source. With this I'm able to create a sun far away without being bothered by attenuation.
Now for the displacement of the plane. I created a `PNGImage` generator function which generates a perlin noise texture with the amount of layers and scales specified.
This I registered as the displacement texture for the plane. In the vertex shader I added a `isDisplacementMapped` uniform shader flag which adds the normal vector multiplied by the displacement to each vertex.
![](images/1-perlin-displacement.png)
```{.shebang im_out="stdout"}
#!/usr/bin/env bash
echo A M A S I N G | boxes -d unicornsay -a c -p h10
```
I've yet to modify the normals from the displacement, so the hill currently won't cast shadows. I currently don't have a plan on how to fix this. Perhaps use the bitangents to calculate the slope of the displacement in the vertex shader?
At this point I went online a found myself a grass texture and normal map.
![](images/2-wrong-handedness.png)
```{.shebang im_out="stdout"}
#!/usr/bin/env bash
printf " Something's wrong...! \n" | cowsay -f head-in | sed -e "s/^/ /"
```
Apparently, the direction of the normal map colors aren't the same everywhere. I therefore added a flag to the image loader function which will flip the handedness. (inverting the R and G channels).
![](images/3-flipped-handedness.png)
*Much better*
Now we'll up the granularity by decreasing the UV step per vertex along the plane, and enable the displacement map:
![](images/4-fine-plane.png)
## Scrolling the field
Now, how can we scroll this plane?
I decided the easies way would be to add a uniform variable called `uvOffset` to the vertex shader. Now I can simply scroll the plane by adding to this offset to all the UV coordinates in the vertex shader before passing it to the fragment shader:
```c++
/*vec2*/plainNode->uvOffset += /*vec2*/speed * timeDelta;
```
The code above works since I added in some operator overloads for `vec2`, `vec3`, and `vec4` with scalars.
Now we unfortunately see steep cuts where the perlin noise texture repeats. This we simply fix by mirroring the texture on repeat with `GL_MIRRORED_REPEAT`:
![](images/5-gl-mirror.jpg)
An another solution to making the perlin noise repeatable is to pass the repeat size into the `glm::gtx::perlin` function as I create the texture. But as of now I chose the quick and dirty solution. It also has the added effect of creating a less repeating texture. It repeats from 0-2 instead of 0-1.
At this point I was stuck with a bug where the coordinates of the lights were doubled. After two days of debugging I found the line where I update pass the light position into the uniform in the fragment shader:
```c++
lights[id].position = vec3(node->MV * vec4(node->position, 1.0));
```
Which *should* have been
```c++
lights[id].position = vec3(node->MV * vec4(vec3(0.0), 1.0));
```
*...yeah.*
## Normal mapping the displacement
After that goober, I moved on to try to rotate the normals according the displacement map. After some playing around I landed on this solution in glsl:
\small
```c++
if (isDisplacementMapped) {
float o = texture(displaceTex, UV).r * 2 - 1;
float u = (texture(displaceTex, UV + vec2(0.0001, 0)).r*2-1 - o) / 0.0004; // magic numbers!
float v = (texture(displaceTex, UV + vec2(0, 0.0001)).r*2-1 - o) / 0.0004; // magic numbers!
TBN = mat3(
normalize(tangent + normal*u),
normalize(bitangent + normal*v),
normalize(cross(tangent + normal*u, bitangent + normal*v))
);
}
```
\normalsize
Here I find the slope along the displacement map along the `U` and `V` direction. `o` is the 'origin', and `u` and `v` are the tangent and bitangent slope cosines *(derived with the help of a few magic numbers which should be made into uniforms really,s ince they only apply for the plane as of now)*. Using these cosines I can simply add the normal vector multiplied by the cosine to the tangent and the bitangent and normalize them, giving me the new tangents. I can from here derive the new normal vector by simply computing the cross product of the two tangents.
This did however give me a pretty coarse image, so I moved the computation of the TBN matrix from the vertex shader to the fragement shader. This will give me a slight performance penalty, but I can undo the change in a simplified shader should I need the performance boost later. Here we can see how the displacement affects the normals along the displaced plane:
![](images/6-displacement-normals.png)
```{.shebang im_out="stdout"}
#!/usr/bin/env bash
echo Windows XP background incoming? | boxes -d whirly -a c -p h15
```

View File

@ -0,0 +1,124 @@
# Loading models
Now I want to load the car model I found online. After downloading the car model from `sketchfab.com`, I added `assimp` as a submodule to the project repository. I then linked in the library using cmake. From here I had to make a few helper functions to read the models and parse them into the already established structs and classes.
*This took some time.*
Here I finally loaded all the vertices and faces:
![](images/7-car-meshes.png)
Here I applied all the model transformations for each sub mesh:
![](images/8-car-transformations.png)
Here I accounted for the difference in coordinate systems. I prefer to have the Z component point skyward.
![](images/9-car-coordinate-system.png)
## The material system
Now it's time to load the properties from the models. To do this I had to implement a material system. Here I loaded all the materials and loaded the material diffuse color and the color vertices. To make this work I had to expand the shader to accept colors per vertex.
![](images/10-car-materials.png)
Formalizing the materials as a struct allows me to apply it recursively to a `SceneNode` and its children should I wish to do so. I also need to expand the model loader to read textures, but I'll cross that bridge when I get to it.
But for now, it's time for a revamp of the lighting system, yet again!
Instead of having a diffuse, specular and emissive color component per light source, I'll have a single color per light and instead store the color components per `SceneNode`. This allows me to set material properties per node in the scene. The lights will now only store the basecolor and attenuation coefficients.
![](images/11-material-colors.png)
In the image above the shader uses the emissive and specular colors per node in the scene. Seems like the car model is expecting a metal effect on top. The emissive colors are pretty bright.
This is a good excuse to create a reflection map shader!
![](images/12-reflection.png)
This car looks *nice*.
I opted to implement the reflection shader by reflecting the vector from the camera to the fragment by the normal vector. I then map the reflection vector into UV coordinates as if it covers a sphere. I the found some 360 images online to use as the reflection map texture:
![](../res/textures/reflection_field.png)
The color extracted from the reflection map can then be mixed with the diffuse and emissive colors in two ways in my shader:
```cpp
basecolor = (reflexiveness < 0)
? basecolor * mix(vec3(0.0), reflection, -reflexiveness)
: mix(basecolor, reflection, reflexiveness);
```
A `reflexiveness` value between 0 and -1 will multiply the reflection with the base color, after having mixed the reflection with white using `-reflexiveness` as the interpolation factor.
A `reflexiveness` value between 0 and 1 will instead mix the reflection with the basecolor directly using `reflexiveness` as the interpolation factor.
I've yet to define *'basecolor'*. The basecolor is the emissive and diffuse parts of the phong model. This is roughly how I compute them. $a(m)$ is the attenuation:
$a(m) = x + y\cdot |L_{m}| + z\cdot |L_{m}|^2 \\
basecolor_{frag} =
k_e + k_d * \sum_{m\in lights}{\frac{\hat{L}_m \cdot \hat{N}}{a(m)}} \\
finalcolor_{frag} =
reflection(basecolor) + \sum{\frac{k_s(\hat{R}_m\cdot \hat{V})^\alpha}{a(m)}}$
The specular component is added in after having mixed with the reflection.
## Textures
The scene is supposed to have both trees and grass loaded as well, let's try loading in the tree model I found online:
![](images/13-tree.png)
Textures aren't loaded yet, I knew this day would come sooner or later. Thanks to all the work that went into the material system, loading models was a quick fix. *(after recieving a thourough beating from `assimp`)*
![](images/14-tree-alpha.png)
## Transparancy
Seems like the transparent textures are rendered before the field, making the field fail the depth test. Seems like I have to sort and render all nodes with any partial opacity and render them last. Sorted by distance from camera. Hang on a minute...
![](images/15-tree-sorted.png)
Huh, it *kinda* works, since all transparent objects now are rendered at last in sorted order. But within a single mesh we still have depth priority issues. The topmost leaves on the tree pictured above was rendered before the lower ones.
One idea I tried out was disabling the depth test while rendering the transparent objects. For this scene, it looked ok, but if we where to have an opaque object in front of the tree, we would see the leaves on top.
No opaque object, being rendered earlier than the transparent ones, would be able to occlude it.
After some reading and searching around I found this neat option in OpenGL:
```c++
glDepthMask(GL_FALSE);
```
This will make the depth buffer read only, locking the depth buffer in place. If I first render all opaque objects while the depth buffer is writeable, then lock the depth buffer and render the opaque objects in sorted order, I get a nice compromise. Opaque objects will be able to occlude transparent ones, due to the depth test, but transparent ones don't affect the depth test. Since the transparent objects are sorted by the distance from the camera, they will be rendered on top of another in the correct order anyway. The only issue is with transparent meshes which overlap. There is still some weirdness in the tree leaves since it is a single mesh, but nothing too noticeable in leaves and bushes.
![](images/16-tree-depth-readonly.png)
Neato!
The current rendering pipeline:
```dot
digraph asd {
//rankdir=LR;
dpi=600;
ratio=0.7;
node [fontname=arial, shape=rectangle, style=filled, fillcolor="#dddddd"]
null [ label="updateNodes(rootNode)" ]
0 [ label="renderNodes(rootNode, only_opaque=true)" ]
1 [ label="std::sort(transparent_nodes)" ]
2 [ label="glDepthMask(GL_FALSE)" ]
3 [ label="for (Node* n : transparent_nodes)\l renderNodes(n, no_recursion=true)\l" ]
4 [ label="glDepthMask(GL_TRUE)" ]
5 [ label="renderNodes(hudNode)" ]
null->0
0->1 [label="create vector of the\lskipped transparent nodes"]
1->2->3->4->5
}
```
Now we are rendering something which looks *kinda* nice. I then had the model loader load my grass model, and duplicated it roughly a hundred times. This model has a single grass mesh which it uses 64 times in a "cluster" of grass. All of these uses transparent textures. Since I decided to add quite a few of these into the scene graph I started to notice some issues with performance. After I added a few helpful print statement, I was delighted to learn that I'm trying to render **5455** nodes with geometry.
![](images/17-low-fps.png)

View File

@ -0,0 +1,68 @@
# Optimizations
So at this point I started to look into some optimizations. I tried resizing my window to see if I was fragment bound or not. This didn't make a significant difference. I therefore had to be either vertex bound or bandwidth bound. Being vertexbound but not fragment bound with this little geometry makes little sense, so I started to look into reducing the amount of bandwidth between the cpu and the gpu, and the amount of data the shader uses (since I'm running on an integrated graphics processor using the same ram as the cpu).
After some searching through the code I came over the part where I upload the uniforms for each draw call to gl:
\small
```c++
glUniformMatrix4fv(s->location("MVP") , 1, GL_FALSE, glm::value_ptr(node->MVP));
glUniformMatrix4fv(s->location("MV") , 1, GL_FALSE, glm::value_ptr(node->MV));
glUniformMatrix4fv(s->location("MVnormal"), 1, GL_FALSE, glm::value_ptr(node->MVnormal));
glUniform2fv(s->location("uvOffset") , 1, glm::value_ptr(node->uvOffset));
glUniform3fv(s->location("diffuse_color") , 1, glm::value_ptr(node->diffuse_color));
glUniform3fv(s->location("emissive_color"), 1, glm::value_ptr(node->emissive_color));
glUniform3fv(s->location("specular_color"), 1, glm::value_ptr(node->specular_color));
glUniform1f( s->location("opacity"), node->opacity);
glUniform1f( s->location("shininess"), node->shininess);
glUniform1f( s->location("reflexiveness"), node->reflexiveness);
glUniform1f( s->location("displacementCoefficient"), node->displacementCoefficient);
glUniform1ui(s->location("isTextured"), node->isTextured);
glUniform1ui(s->location("isVertexColored"), node->isVertexColored);
glUniform1ui(s->location("isNormalMapped"), node->isNormalMapped);
glUniform1ui(s->location("isDisplacementMapped"), node->isDisplacementMapped);
glUniform1ui(s->location("isReflectionMapped"), node->isReflectionMapped);
glUniform1ui(s->location("isIlluminated"), node->isIlluminated);
glUniform1ui(s->location("isInverted"), node->isInverted);
```
\normalsize
*Yeah...* I think I could optimize this. The `s->location` function is a uniform name string to location GLint ID lookup. I believe calling GL for this lookup is costly, so I'll cache the results per shader and make the compiler inline the caching lookup function (not possible with the gl functions, since code is dynamically linked, my code on the other hand is subject to compile-time optimizations). The cached function is shown below. The commented line is the old implementation:
```c++
GLint inline Shader::location(std::string const& name) {
//return glGetUniformLocation(mProgram, name.c_str());
auto it = cache.find(name);
if (it == cache.end())
return cache[name] = glGetUniformLocation(mProgram, name.c_str());
return it->second;
}
```
This boosted me from 11 FPS up to around 14 FPS, pretty neat!
Next up: avoiding reuploading unchanged information to the shader time and time again for every object in the scene.
Most of the time, objects are pretty similar, only differing in tranformation/position. Most of the uniforms remain unchanged between draw calls.
I replaced all the `glUniformX` methods with these defines shown below, which performs caching for me using static cache variables:
\small
```c++
bool shader_changed = s != prev_s;
#define cache(x) static decltype(node->x) cached_ ## x; \
if (shader_changed || cached_ ## x != node->x) \
{ cached_ ## x = node->x;
#define um4fv(x) cache(x) glUniformMatrix4fv(s->location(#x), 1, GL_FALSE, glm::value_ptr(node->x)); }
#define u2fv(x) cache(x) glUniform2fv( s->location(#x), 1, glm::value_ptr(node->x)); }
#define u3fv(x) cache(x) glUniform3fv( s->location(#x), 1, glm::value_ptr(node->x)); }
#define u1f(x) cache(x) glUniform1f( s->location(#x), node->x); }
#define u1ui(x) cache(x) glUniform1ui( s->location(#x), node->x); }
```
\normalsize
This is a bigger CPU workload, but it optimizes communication with the graphics processor, which is well suited for high bandwidth streaming but not for smaller back and forth communication.
This caching bumped me up from 14 FPS to 21 on my less-than-ideal integrated graphics chip. The precomputed `shader_changed` bool value alone was responsible for 2 FPS. `s` and `prev_s` are static, making the compiler unable to optimize it with reuse like I do here.
Caching the `glBindTextureUnit` calls done when selecting textures further sped up the rendering by an another 4.3 FPS.
Perhaps restructuring the `renderNode` function to use iteration instead of recursion would be the next logical improvement. I don't need that kind of performance as of now. I believe I'm currently bandwidth bound anyway, since disabling the tangents and bitangents give a 8 FPS improvement. That's integrated graphics for you.

View File

@ -0,0 +1,15 @@
# Creating the scene
For now, I decided to change the scene into what I imagine the end result to be like. The idea is to have a car drive along the moving field, moving along the slope of the field. The car should have some headlights and backlights as well.
To be able to do this, I had to implement bilinear filtering on the PNGImage struct, to be able to read the displacement map where the car is at, so i can calculate the slope.
I then made the trees and the grass scroll along the movement of the offset displacement map, wrapping around when leaving the edge of the field.
I then added a check to see if any of the trees or grass which wrapped around is in the path of the car or not. If it is, then I make them invisible. This ensures there ar no obstacles in the path of the car, avoiding unseemly clipping.
I then changed the lightning to be at night time. To have this make sense, I tweaked the shader so that the color of the first light is multiplied by the emissive component of the objects. This allows me to use the first light to set the "ambient tone" of the scene. Will be handy.
The scene is pretty dark now, so I added in some spot lights attached to the front lights of the card, which are transformed along with the car.
They look a bit mechanical alone, so I added in four point lights aswell, two attached to the front lights, and two attached to the back lights with a red hue. This blends together pretty well.
![](images/18-night-scene-lights.png)

View File

@ -0,0 +1,119 @@
# Adding effects
The car however blends into the backround now. To make it pop a bit more, I added support for a rim light color in the shader.
I don't know how these are usually done, but I simply opted for the intuitive implementation I thought out.
The more the surface normal points away from the camera, the more it should be lit up. When the surface is pointing 90 degrees away from the camera it should be at maximum brightness, and it should decrease the more it points toward the camera. Using the dot product between the normalize position vector and the normalize normal vector in MV space gives me a cosine. Now I have a value of 1 when pointing away from the camera, 0 when pointing 90 degrees to the side and a value of -1 when pointing towards the camera. Adding a "strength" uniform value to this will skew it towards the camera. I then divide it by this same strength value and clamp it between 0 and 1 to have it be 0 when pointing towards the camera and 1 when pointing to the side. This value is multiplied by a rim light color component.
```c++
c.rgb += backlight_color * clamp((dot(normalize(vertex), normal)
+ strength) / strength, 0, 1);
```
Below you can see an exaggerated use of the effect:
![](images/19-rim-lights.png)
Setting the rim light color to a weak white color makes the car pop just so slightly more from the surroundings.
## Post processing
Now is the time to add in the post-processing step to the rendering pipeline.
The idea is to render the scene not directly to the window, but to an internal frame buffer. Then I can, using two triangles, render this framebuffer to the window as a texture using a separate shader, henceforth refered to as the post shader. The post shader has the ability to combine the color and depth buffer values of not just a single pixel, but also its neighbors. This allows me to implement effects such as blur and depth of field.
This took quite a lot of time. GL isn't the most verbose debugging companion to work with, and I was blessed with a lot of issues and weird behaviors with the framebuffers. After a few days I got it working again.
```c++
#version 430 core
layout(binding = 0) uniform sampler2D framebuffer;
layout(binding = 1) uniform sampler2D depthbuffer;
layout(location = 0) out vec4 color_out;
uniform uint windowWidth;
uniform uint windowHeight;
void main() {
vec2 dx = vec2(1,0) * 1.0/windowWidth;
vec2 dy = vec2(0,1) * 1.0/windowHeight;
vec2 UV = gl_FragCoord.xy / vec2(windowWidth, windowHeight);
color_out = texture(framebuffer, UV);
}
```
This simple fragmentshader allows me to make a depth of field effect. Here is a visualization of the depthbuffer, after transforming it to be 0 around the focal distance where the car is at, and tend towards a value of 1 otherwise. This "focus value" is stored as `z` in the shader, should you start wondering in the code further below:
![](images/20-depth-map.png)
Here we can see the transparent objects (grass and leaves), which was rendered with the depth buffer in read only mode, doesn't show up on the depth buffer. Due to this I'm unable to use this depth buffer as fog since it would look weird. But if I want fog I can do that in the normal shader anyway. This depth map will suffice in my dark scene for effects such as depth of field.
## Depth of Field
Depth of field is a blur dependent on distance from the focal point. I planned on computing a weighted average for each pixel, where the weight per neighbor is their depth. This computation proved expensive however, due to all the access to the depthbuffer. Therefore I arrived at this simplified model instead:
```c++
int radius = int(5*z); // z=0 in focus, otherwise -> 1
vec3 color = vec3(0);
for (int x = -radius; x <= radius; x++)
for (int y = -radius; y <= radius; y++)
color += texture(framebuffer, UV + x*dx + y*dy).rgb;
color /= pow(2*radius+1, 2);
color_out.rgb = color;
```
Resulting in:
![](images/21-depth-of-field.png)
A weakness with this depth of field model effect though is that the edges of objects don't blur, only the surfaces. At a distance it looks okay, but elements in the foreground; in front of the point of focus, will look weird.
I could try replacing the trees with non-transparent ones for a more detailed effect.
## Vignette
Next up I wanted to try adding a simple vignette to the mix. Simply computing the euclidian distance from the center of the screen is doable with the UV coordinates:
```c++
color_out = vec4(color.rgb * (1-pow(length((UV-0.5)*1.2), 3)), color.a);
```
When `color.rgb` is all white, we get the output:
![](images/22-vingette.png)
Which demonstrates the effect of the vignette quite clearly.
## Chromatic Aberration
Chromatic aberation is the effect of the different frequencies of light refracting differently. This is demonstrated in the figure below:
![](images/23.5-what-is.jpg)
As we can see, the further away we are from the focal point, the more aberration we ought to have. The aberration also gets worse the closer to the edge of the lens we move. I therefore added in the aberration as a modification to the depth of field effect, where I read the color values from the framebuffer anyway. I now scale the UV vector into the framebuffer according the depthbuffer and the aberration factor per color component:
```c++
for (int x = -radius; x <= radius; x++)
for (int y = -radius; y <= radius; y++){
vec2 p = UV + x*dx + y*dy;
color.r += texture(framebuffer, (p-0.5)*(1+z*chomatic_aberration_r) + 0.5).r;
color.g += texture(framebuffer, (p-0.5)*(1+z*chomatic_aberration_g) + 0.5).g;
color.b += texture(framebuffer, (p-0.5)*(1+z*chomatic_aberration_b) + 0.5).b;
}
```
An exaggerated example:
![](images/23-chromatic-aberration.png)
## Grain
Next up I wanted to add some grain. GLSL doesn't have a random number generation function built in, but I found one online which was based around using a UV vector. I further modified it to use a time uniform as well making it vary per frame.
![](images/24-noise.png)
The grain in the middle of the screen however doesn't look good, so here again I opted to add more grain where the scene is out of focus using the depth buffer. I could also make it a part of the vignette, but I think using the depth buffer value looks better for this scene. I simply do this:
```c++
color += (random(UV)-0.5) * z * 0.2;
```
All these effects comes together into this final product:
![](images/25-all-effects.png)

View File

@ -0,0 +1,32 @@
# Day-night cycle
```{.shebang im_out="stdout"}
#!/usr/bin/env bash
echo At this point I\'m only playing around, but it\'s all for that sick-ass demo, am I right? | cowsay -f cheese | sed -e "s/^/ /"
```
Now that all the ground-work is in place, i can start adding timestamped events to happen in the scene:
Camera movement, change of lightning, etc. I started by adding a day-night cycle. I then expanded these timestamps to control the car headlights:
```cpp
struct seq_t { double t; vec3 light_c; vec3 bg_c; bool has_headlights; };
static const vector<seq_t> sequence = {
{ 0, vec3(0.2 , 0.2 , 0.7), vec3(0.05, 0.1 , 0.15), 1}, // night
{ 9, vec3(0.4 , 0.4 , 0.8), vec3(0.15, 0.15, 0.35), 1}, // dusk
{10, vec3(1.0 , 0.6 , 0.4), vec3(0.8 , 0.4 , 0.2 ), 1}, // sunrise
{11, vec3(0.9 , 0.7 , 0.5), vec3(0.8 , 0.6 , 0.2 ), 1}, // sunrise2
{12, vec3(0.85, 0.85, 0.9), vec3(0.3 , 0.5 , 0.8 ), 0}, // morning
{18, vec3(1.0 , 1.0 , 1.0), vec3(0.35, 0.6 , 0.9 ), 0}, // noon
{24, vec3(0.7 , 0.9 , 1.0), vec3(0.3 , 0.5 , 0.8 ), 0}, // evening
{25, vec3(0.9 , 0.7 , 0.5), vec3(0.8 , 0.6 , 0.2 ), 0}, // sundown
{26, vec3(1.0 , 0.6 , 0.4), vec3(0.8 , 0.4 , 0.2 ), 1}, // sunset
{27, vec3(0.5 , 0.5 , 0.8), vec3(0.35, 0.15, 0.35), 1}, // dusk
{36, vec3(0.2 , 0.2 , 0.7), vec3(0.05, 0.1 , 0.15), 1}, // night
};
```
Interpolating between these points per frame made it look quite nice:
![](images/26-day.png)
Now where there once again shines light, we can see the post-processing effects that much better!