🍏 if this homework runs slow, please reduce your window size to speed things up 🚨 please pull a fresh copy of the codebase before starting this homework!--it has a few new functions we'll need 🗣️ you are highly encouraged to collaborate on homework provided you follow the spirit of the 50 ft rule
goals. - learn GLSL - understand fragment shaders - have fun
reading. note: as you read, please cross-reference the docs for cow's shader API
a. (tutorial) get familiar with cow's shader API
let's start with a simple app that mesh_draw's a two triangle mesh of a square - if you don't know how to "fold code" to hide stuff in between curly braces, please let me know!
void hw6a() {
IndexedTriangleMesh3D mesh = {}; {
mesh.num_triangles = 2;
mesh.triangle_indices = (int3 *) malloc(mesh.num_triangles * sizeof(int3)); {
int k = 0;
mesh.triangle_indices[k++] = { 0, 1, 2 };
mesh.triangle_indices[k++] = { 0, 2, 3 };
}
mesh.num_vertices = 4;
mesh.vertex_positions = (vec3 *) malloc(mesh.num_vertices * sizeof(vec3)); {
int k = 0;
mesh.vertex_positions[k++] = { 0.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 1.0, 0.0 };
mesh.vertex_positions[k++] = { 0.0, 1.0, 0.0 };
}
}
Camera3D camera = { 3.0, RAD(45) };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
mat4 M = globals.Identity;
mesh.draw(P, V, M);
}
}
let's make a shader - note: we aren't actually doing anything with it yet
void hw6a() {
Shader shader = {}; {
char *vertex_shader_source = R""(
#version 330 core
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
layout (location = 0) in vec3 p_model;
void main() {
gl_Position = P * V * M * vec4(p_model, 1.0);
}
)"";
char *fragment_shader_source = R""(
#version 330 core
out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
)"";
shader = shader_create(vertex_shader_source, 1, fragment_shader_source);
}
IndexedTriangleMesh3D mesh = {}; {
mesh.num_triangles = 2;
mesh.triangle_indices = (int3 *) malloc(mesh.num_triangles * sizeof(int3)); {
int k = 0;
mesh.triangle_indices[k++] = { 0, 1, 2 };
mesh.triangle_indices[k++] = { 0, 2, 3 };
}
mesh.num_vertices = 4;
mesh.vertex_positions = (vec3 *) malloc(mesh.num_vertices * sizeof(vec3)); {
int k = 0;
mesh.vertex_positions[k++] = { 0.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 1.0, 0.0 };
mesh.vertex_positions[k++] = { 0.0, 1.0, 0.0 };
}
}
Camera2D camera = { 3.0 };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
mat4 M = globals.Identity;
mesh.draw(P, V, M);
}
}
let's draw the mesh with our shader
void hw6a() {
Shader shader = {}; {
char *vertex_shader_source = R""(
#version 330 core
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
layout (location = 0) in vec3 p_model;
void main() {
gl_Position = P * V * M * vec4(p_model, 1.0);
}
)"";
char *fragment_shader_source = R""(
#version 330 core
out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
)"";
shader = shader_create(vertex_shader_source, 1, fragment_shader_source);
}
IndexedTriangleMesh3D mesh = {}; {
mesh.num_triangles = 2;
mesh.triangle_indices = (int3 *) malloc(mesh.num_triangles * sizeof(int3)); {
int k = 0;
mesh.triangle_indices[k++] = { 0, 1, 2 };
mesh.triangle_indices[k++] = { 0, 2, 3 };
}
mesh.num_vertices = 4;
mesh.vertex_positions = (vec3 *) malloc(mesh.num_vertices * sizeof(vec3)); {
int k = 0;
mesh.vertex_positions[k++] = { 0.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 1.0, 0.0 };
mesh.vertex_positions[k++] = { 0.0, 1.0, 0.0 };
}
}
Camera2D camera = { 3.0 };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
mat4 M = globals.Identity;
shader_set_uniform(&shader, "P", P);
shader_set_uniform(&shader, "V", V);
shader_set_uniform(&shader, "M", M);
shader_pass_vertex_attribute(&shader, mesh.num_vertices, mesh.vertex_positions);
shader_draw(&shader, mesh.num_triangles, mesh.triangle_indices);
}
}
let's pass the vertex positions to our fragment shader, and use them to determine pixel color
void hw6a() {
Shader shader = {}; {
char *vertex_shader_source = R""(
#version 330 core
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
layout (location = 0) in vec3 p_model;
out vec2 uv;
void main() {
uv = p_model.xy;
gl_Position = P * V * M * vec4(p_model, 1.0);
}
)"";
char *fragment_shader_source = R""(
#version 330 core
in vec2 uv;
out vec4 fragColor;
void main() {
fragColor = vec4(uv.x, uv.y, 0.0, 1.0);
}
)"";
shader = shader_create(vertex_shader_source, 1, fragment_shader_source);
}
IndexedTriangleMesh3D mesh = {}; {
mesh.num_triangles = 2;
mesh.triangle_indices = (int3 *) malloc(mesh.num_triangles * sizeof(int3)); {
int k = 0;
mesh.triangle_indices[k++] = { 0, 1, 2 };
mesh.triangle_indices[k++] = { 0, 2, 3 };
}
mesh.num_vertices = 4;
mesh.vertex_positions = (vec3 *) malloc(mesh.num_vertices * sizeof(vec3)); {
int k = 0;
mesh.vertex_positions[k++] = { 0.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 1.0, 0.0 };
mesh.vertex_positions[k++] = { 0.0, 1.0, 0.0 };
}
}
Camera2D camera = { 3.0 };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
mat4 M = globals.Identity;
shader_set_uniform(&shader, "P", P);
shader_set_uniform(&shader, "V", V);
shader_set_uniform(&shader, "M", M);
shader_pass_vertex_attribute(&shader, mesh.num_vertices, mesh.vertex_positions);
shader_draw(&shader, mesh.num_triangles, mesh.triangle_indices);
}
}
let's mess about in the fragment shader
void hw6a() {
Shader shader = {}; {
char *vertex_shader_source = R""(
#version 330 core
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
layout (location = 0) in vec3 p_model;
out vec2 uv;
void main() {
uv = p_model.xy;
gl_Position = P * V * M * vec4(p_model, 1.0);
}
)"";
char *fragment_shader_source = R""(
#version 330 core
in vec2 uv;
out vec4 fragColor;
void main() {
float TAU = 6.283185307179586;
fragColor = vec4(sin(uv.x * 10.0 * TAU), sin(uv.y * 10.0 * TAU), 0.2, 1.0);
}
)"";
shader = shader_create(vertex_shader_source, 1, fragment_shader_source);
}
IndexedTriangleMesh3D mesh = {}; {
mesh.num_triangles = 2;
mesh.triangle_indices = (int3 *) malloc(mesh.num_triangles * sizeof(int3)); {
int k = 0;
mesh.triangle_indices[k++] = { 0, 1, 2 };
mesh.triangle_indices[k++] = { 0, 2, 3 };
}
mesh.num_vertices = 4;
mesh.vertex_positions = (vec3 *) malloc(mesh.num_vertices * sizeof(vec3)); {
int k = 0;
mesh.vertex_positions[k++] = { 0.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 1.0, 0.0 };
mesh.vertex_positions[k++] = { 0.0, 1.0, 0.0 };
}
}
Camera2D camera = { 3.0 };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
mat4 M = globals.Identity;
shader_set_uniform(&shader, "P", P);
shader_set_uniform(&shader, "V", V);
shader_set_uniform(&shader, "M", M);
shader_pass_vertex_attribute(&shader, mesh.num_vertices, mesh.vertex_positions);
shader_draw(&shader, mesh.num_triangles, mesh.triangle_indices);
}
}
let's add a uniform variable for time
void hw6a() {
Shader shader = {}; {
char *vertex_shader_source = R""(
#version 330 core
uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
layout (location = 0) in vec3 p_model;
out vec2 uv;
void main() {
uv = p_model.xy;
gl_Position = P * V * M * vec4(p_model, 1.0);
}
)"";
char *fragment_shader_source = R""(
#version 330 core
uniform float time;
in vec2 uv;
out vec4 fragColor;
void main() {
float TAU = 6.283185307179586;
fragColor = vec4(sin(-10.0 * time + uv.x * 10.0 * TAU), sin(uv.y * 10.0 * TAU), 0.2, 1.0);
}
)"";
shader = shader_create(vertex_shader_source, 1, fragment_shader_source);
}
IndexedTriangleMesh3D mesh = {}; {
mesh.num_triangles = 2;
mesh.triangle_indices = (int3 *) malloc(mesh.num_triangles * sizeof(int3)); {
int k = 0;
mesh.triangle_indices[k++] = { 0, 1, 2 };
mesh.triangle_indices[k++] = { 0, 2, 3 };
}
mesh.num_vertices = 4;
mesh.vertex_positions = (vec3 *) malloc(mesh.num_vertices * sizeof(vec3)); {
int k = 0;
mesh.vertex_positions[k++] = { 0.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 0.0, 0.0 };
mesh.vertex_positions[k++] = { 1.0, 1.0, 0.0 };
mesh.vertex_positions[k++] = { 0.0, 1.0, 0.0 };
}
}
Camera2D camera = { 3.0 };
real time = 0.0;
while (cow_begin_frame()) {
time += 0.0167;
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
mat4 M = globals.Identity;
shader_set_uniform(&shader, "P", P);
shader_set_uniform(&shader, "V", V);
shader_set_uniform(&shader, "M", M);
shader_set_uniform(&shader, "time", time);
shader_pass_vertex_attribute(&shader, mesh.num_vertices, mesh.vertex_positions);
shader_draw(&shader, mesh.num_triangles, mesh.triangle_indices);
}
}
b. render the mandelbrodt set using a fragment shader i. color it using just two colors; e.g., black for points in the set and white for points NOT in the set - HINT:
uv
(as computed in the previous (tutorial) question) has both uv.x
and uv.y
in the interval $[0.0, 1.0]$; as per Wikipedia, you will need to make $x_0 \in [-2.00, 0.47]$ and $y_0 \in [-1.12, 1.12]$; because we're coding in a shader, we don't have access to LINEAR_REMAP(...)
so you'll have to do it yourself :)
- HINT: in shaders, we use float
instead of real
- HINT: if iteration
is an int but you need it as a float, you can cast like float(iteration)
ii. color points NOT in the set according to escape time
- you may want a color map :)
(optional) iii. upgrade your viewer (choose 0+ of the following or equivalent)
- fancy time-dependent infinite zooming colors (ooh!)
- use smooth iteration count
- add anti-aliasing; see iq's approach (average over AA * AA subpixels)
-- note: anti-aliasing will only make the jagged bits smoother/blurrier; it will not make the hard steps between colors into a smooth gradient (that's the job of the smooth iteration count)
-- note: you will need to pass some additional uniforms to the fragment shader
--- you will need window_get_size()
from cow, which returns the windows width and height in pixels as a vec2
--- you will also need camera.screen_height_World
- make it zoom into an interesting region (automatically) -- feel free to do this on the GPU (shader) or the CPU (Camera2D)
for inspo, here is my solution; you do NOT need to replicate it
c. world-space phong or blinn-phong lighting - (optional) add a checkbox to render with toon shading - (optional) add a checkbox to distort the bunny into a corkscrew using the vertex shader (see here) - (optional) implement attenuation
recommended starter code (everything except part of the fragment shader's main() is set up for you)
void hw6c() {
Shader shader; {
char *vertex_shader_source = R""(
#version 330 core
layout (location = 0) in vec3 _p_model;
layout (location = 1) in vec3 _n_model;
uniform mat4 P, V, M;
out vec3 p_world;
out vec3 _n_world;
void main() {
p_world = (M * vec4(_p_model, 1.0)).xyz;
_n_world = mat3(transpose(inverse(M))) * _n_model;
gl_Position = P * V * vec4(p_world, 1.0);
}
)"";
char *fragment_shader_source = R""(
#version 330 core
uniform vec3 o_camera_world; // camera position
uniform int num_lights;
uniform vec3 light_positions_world[16];
uniform vec3 light_colors[16];
uniform float ambientStrength;
uniform float diffuseStrength;
uniform float specularStrength;
uniform float shininess;
in vec3 p_world; // fragment position
in vec3 _n_world;
out vec4 fragColor;
void main() {
vec3 n_world = normalize(_n_world); // fragment normal
vec3 color = vec3(ambientStrength);
for (int i = 0; i < num_lights; ++i) {
// TODO add diffuse contribution
// TODO add specular contribution
}
fragColor = vec4(color, 1);
}
)"";
shader = shader_create(vertex_shader_source, 2, fragment_shader_source);
}
#define MAX_NUM_LIGHTS 6
int num_lights = 1;
vec3 light_positions_world[MAX_NUM_LIGHTS] = {};
vec3 light_colors[MAX_NUM_LIGHTS] = { monokai.red, monokai.orange, monokai.yellow, monokai.green, monokai.blue, monokai.purple };
{
int k = 0;
light_positions_world[k++] = { 0.0, 0.0, 3.0 };
light_positions_world[k++] = { 0.0, 0.0, -3.0 };
light_positions_world[k++] = { 0.0, 3.0, 0.0 };
light_positions_world[k++] = { 0.0, -3.0, 0.0 };
light_positions_world[k++] = { 3.0, 0.0, 0.0 };
light_positions_world[k++] = { -3.0, 0.0, 0.0 };
}
IndexedTriangleMesh3D mesh = library.meshes.bunny;
real ambientStrength = 0.1;
real diffuseStrength = 0.6;
real specularStrength = 1.0;
real shininess = 12.0;
Camera3D camera = { 10.0, RAD(45) };
while (cow_begin_frame()) {
camera_move(&camera);
mat4 P = camera_get_P(&camera);
mat4 V = camera_get_V(&camera);
mat4 M = globals.Identity;
mat4 PV = P * V;
gui_slider("num_lights", &num_lights, 0, MAX_NUM_LIGHTS, 'j', 'k');
soup_draw(PV, SOUP_POINTS, num_lights, light_positions_world, light_colors);
_widget_translate_3D(P * V, num_lights, light_positions_world, light_colors);
gui_printf("");
gui_slider("ambientStrength", &ambientStrength, 0.0, 2.0);
gui_slider("diffuseStrength", &diffuseStrength, 0.0, 2.0);
gui_slider("specularStrength", &specularStrength, 0.0, 2.0);
gui_slider("shininess", &shininess, 0.0, 256.0);
shader_set_uniform(&shader, "P", P);
shader_set_uniform(&shader, "V", V);
shader_set_uniform(&shader, "M", M);
shader_set_uniform(&shader, "o_camera_world", camera_get_origin(&camera));
shader_set_uniform(&shader, "num_lights", num_lights);
shader_set_uniform(&shader, "light_positions_world", num_lights, light_positions_world);
shader_set_uniform(&shader, "light_colors", num_lights, light_colors);
shader_set_uniform(&shader, "ambientStrength", ambientStrength);
shader_set_uniform(&shader, "diffuseStrength", diffuseStrength);
shader_set_uniform(&shader, "specularStrength", specularStrength);
shader_set_uniform(&shader, "shininess", shininess);
shader_pass_vertex_attribute(&shader, mesh.num_vertices, mesh.vertex_positions);
shader_pass_vertex_attribute(&shader, mesh.num_vertices, mesh.vertex_normals);
shader_draw(&shader, mesh.num_triangles, mesh.triangle_indices);
}
}
(optional) 🐰. use custom shaders to make a scene of your choosing