<- back
hw06 help hours | codebase | docs | notes | glow

🍏 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