diff --git a/README.md b/README.md index e3315f1..3566415 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ keypresses: - Reload the config file: Press the "R" key. + - Switch between rendering modes: Press the "M" key. + - Quit the program: Close the window, or press the escape key. Configuring the L-System diff --git a/l_system_3d.c b/l_system_3d.c index 0bbddbd..af6bea7 100644 --- a/l_system_3d.c +++ b/l_system_3d.c @@ -16,6 +16,7 @@ #define DEFAULT_WINDOW_WIDTH (800) #define DEFAULT_WINDOW_HEIGHT (600) #define DEFAULT_FPS (60.0) +#define DEFAULT_GEOMETRY_THICKNESS (0.5) static ApplicationState* AllocateApplicationState(void) { ApplicationState *to_return = NULL; @@ -26,6 +27,7 @@ static ApplicationState* AllocateApplicationState(void) { to_return->aspect_ratio = ((float) to_return->window_width) / ((float) to_return->window_height); to_return->frame_duration = 1.0 / DEFAULT_FPS; + to_return->shared_uniforms.geometry_thickness = DEFAULT_GEOMETRY_THICKNESS; return to_return; } @@ -98,6 +100,7 @@ static int GenerateVertices(ApplicationState *s) { int result; uint32_t char_index, inst_index; uint8_t c; + float size_scale; ResetTurtle3D(t); for (char_index = 0; char_index < s->l_system_length; char_index++) { c = s->l_system_string[char_index]; @@ -117,10 +120,11 @@ static int GenerateVertices(ApplicationState *s) { return 0; } if (!SetTransformInfo(s->turtle, s->mesh->model, s->mesh->normal, - s->mesh->location_offset)) { + s->mesh->location_offset, &size_scale)) { printf("Failed getting transform matrices.\n"); return 0; } + s->shared_uniforms.size_scale = size_scale; return 1; } @@ -233,6 +237,10 @@ static int ProcessInputs(ApplicationState *s) { glfwSetWindowShouldClose(s->window, 1); return 1; } + // TODO: Replace this key-press logic with a single function that checks + // the state of key_pressed_tmp, setting or unsetting it if necessary and + // returning true on a "new" press. + // s->key_pressed_tmp is used to prevent counting one press multiple times, // and to prevent up and down from being pressed together. pressed = glfwGetKey(s->window, GLFW_KEY_UP) == GLFW_PRESS; @@ -265,6 +273,18 @@ static int ProcessInputs(ApplicationState *s) { // R pressed -> R released s->key_pressed_tmp = 0; } + pressed = glfwGetKey(s->window, GLFW_KEY_M) == GLFW_PRESS; + if (!s->key_pressed_tmp && pressed) { + // Nothing pressed -> M pressed + s->key_pressed_tmp = GLFW_KEY_M; + if (!SwitchRenderingModes(s->mesh)){ + printf("Failed switching rendering modes.\n"); + return 0; + } + } else if ((s->key_pressed_tmp == GLFW_KEY_M) && !pressed) { + // M pressed -> M released + s->key_pressed_tmp = 0; + } return 1; } @@ -303,8 +323,9 @@ static int WaitNextFrame(ApplicationState *s) { static int RunMainLoop(ApplicationState *s) { glEnable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); + // TODO: Re-enable face culling after getting the geometry shader working. + //glEnable(GL_CULL_FACE); + //glCullFace(GL_BACK); glClearColor(0, 0, 0, 1.0); while (!glfwWindowShouldClose(s->window)) { s->frame_start = glfwGetTime(); diff --git a/l_system_3d.h b/l_system_3d.h index f714afe..868572d 100644 --- a/l_system_3d.h +++ b/l_system_3d.h @@ -12,6 +12,9 @@ typedef struct { mat4 projection; mat4 view; vec4 camera_position; + float size_scale; + float geometry_thickness; + float pad[2]; } SharedUniforms; // Maintains global data about the running program. diff --git a/l_system_mesh.c b/l_system_mesh.c index 2e9d0a8..0414395 100644 --- a/l_system_mesh.c +++ b/l_system_mesh.c @@ -79,11 +79,13 @@ static GLuint LoadShader(const char *path, GLenum shader_type) { } // Links and returns the shader program from the given source file names. +// The geometry_src_file can be NULL if a geometry shader isn't used. A +// vertex and fragment shader are always required. static GLuint CreateShaderProgram(const char *vertex_src_file, - const char *fragment_src_file) { + const char *geometry_src_file, const char *fragment_src_file) { GLchar link_log[512]; GLint link_result = 0; - GLuint vertex_shader, fragment_shader, to_return; + GLuint vertex_shader, geometry_shader, fragment_shader, to_return; vertex_shader = LoadShader(vertex_src_file, GL_VERTEX_SHADER); if (!vertex_shader) { @@ -96,11 +98,28 @@ static GLuint CreateShaderProgram(const char *vertex_src_file, glDeleteShader(vertex_shader); return 0; } + + // Only load the geometry shader if requested. + geometry_shader = 0; + if (geometry_src_file != NULL) { + geometry_shader = LoadShader(geometry_src_file, GL_GEOMETRY_SHADER); + if (!geometry_shader) { + printf("Couldn't load geometry shader.\n"); + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + return 0; + } + } + to_return = glCreateProgram(); glAttachShader(to_return, vertex_shader); + if (geometry_shader) { + glAttachShader(to_return, geometry_shader); + } glAttachShader(to_return, fragment_shader); glLinkProgram(to_return); glDeleteShader(vertex_shader); + glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); glGetProgramiv(to_return, GL_LINK_STATUS, &link_result); memset(link_log, 0, sizeof(link_log)); @@ -131,11 +150,11 @@ static int UniformIndex(GLuint program, const char *name, GLint *index) { // Loads the shaders for the model, and looks up uniform indices. Returns 0 on // error. -static int SetupShaderProgram(LSystemMesh *m) { +static int SetupShaderProgram(LSystemMesh *m, const char *vertex_src, + const char *geometry_src, const char *fragment_src) { GLuint p; GLuint block_index; - p = CreateShaderProgram("./mesh_vertex_shader.vert", - "./mesh_fragment_shader.frag"); + p = CreateShaderProgram(vertex_src, geometry_src, fragment_src); if (!p) return 0; m->shader_program = p; if (!UniformIndex(p, "model", &(m->model_uniform_index))) return 0; @@ -154,6 +173,19 @@ static int SetupShaderProgram(LSystemMesh *m) { return CheckGLErrors(); } +int SwitchRenderingModes(LSystemMesh *m) { + glDeleteProgram(m->shader_program); + m->shader_program = 0; + if (m->using_geometry_shader) { + m->using_geometry_shader = 0; + return SetupShaderProgram(m, "simple_shader.vert", NULL, + "simple_shader.frag"); + } + m->using_geometry_shader = 1; + return SetupShaderProgram(m, "pipes_shader.vert", "pipes_shader.geom", + "pipes_shader.frag"); +} + LSystemMesh* CreateLSystemMesh(void) { LSystemMesh *m = NULL; @@ -186,7 +218,8 @@ LSystemMesh* CreateLSystemMesh(void) { DestroyLSystemMesh(m); return NULL; } - if (!SetupShaderProgram(m)) { + if (!SetupShaderProgram(m, "simple_shader.vert", NULL, + "simple_shader.frag")) { DestroyLSystemMesh(m); return NULL; } diff --git a/l_system_mesh.h b/l_system_mesh.h index 0388605..f84662b 100644 --- a/l_system_mesh.h +++ b/l_system_mesh.h @@ -30,6 +30,9 @@ typedef struct { uint32_t vertex_count; // OpenGL stuff needed for drawing this mesh. GLuint shader_program; + // If nonzero, the shader program is currently the more complex geometry + // version. If zero, we're just rendering the wireframe. + int using_geometry_shader; GLuint vao; GLuint vbo; // The model and normal matrices used when drawing this mesh. @@ -62,4 +65,8 @@ int SetMeshVertices(LSystemMesh *m, MeshVertex *vertices, uint32_t count); // Draws the mesh. Returns 0 on error, including any GL errors if they occur. int DrawMesh(LSystemMesh *m); +// Cycles between rendering modes (i.e. shader programs) that may be used to +// render the given mesh. Returns 0 on error. +int SwitchRenderingModes(LSystemMesh *m); + #endif // L_SYSTEM_MESH_H diff --git a/pipes_shader.frag b/pipes_shader.frag new file mode 100644 index 0000000..196b0c1 --- /dev/null +++ b/pipes_shader.frag @@ -0,0 +1,18 @@ +#version 330 core + +in GS_OUT { + vec3 frag_position; + vec3 forward; + vec3 up; + vec3 right; + vec4 color; +} fs_in; + + +out vec4 frag_color; + +//INCLUDE_SHARED_UNIFORMS + +void main() { + frag_color = fs_in.color; +} diff --git a/pipes_shader.geom b/pipes_shader.geom new file mode 100644 index 0000000..48707ff --- /dev/null +++ b/pipes_shader.geom @@ -0,0 +1,57 @@ +#version 330 core +layout (lines) in; +layout (triangle_strip, max_vertices = 4) out; + +in VS_OUT { + vec3 position; + vec3 forward; + vec3 up; + vec3 right; + vec4 color; +} gs_in[]; + +out GS_OUT { + vec3 frag_position; + vec3 forward; + vec3 up; + vec3 right; + vec4 color; +} gs_out; + +//INCLUDE_SHARED_UNIFORMS + +void main() { + vec3 right = gs_in[0].right; + vec3 forward = gs_in[0].forward; + vec3 bottom = gs_in[0].position; + vec3 top = gs_in[0].position; + // NOTE: verify this is in the correct order. + mat4 projView = shared_uniforms.projection * shared_uniforms.view; + // The normal vectors don't change. + gs_out.forward = forward; + gs_out.up = gs_in[0].up; + gs_out.right = right; + //gs_out.color = gs_in[1].color; + gs_out.color = vec4(0.5, 0.3, 1.0, 1.0); + + float half_rect_width = 0.5 * shared_uniforms.geometry_thickness * + shared_uniforms.size_scale; + + // Bottom left + gs_out.frag_position = bottom - (right * half_rect_width); + gl_Position = projView * vec4(gs_out.frag_position, 1.0); + EmitVertex(); + // Bottom right + gs_out.frag_position = bottom + (right * half_rect_width); + gl_Position = projView * vec4(gs_out.frag_position, 1.0); + EmitVertex(); + // Top right + gs_out.frag_position = top + (right * half_rect_width); + gl_Position = projView * vec4(gs_out.frag_position, 1.0); + EmitVertex(); + // Top left + gs_out.frag_position = top - (right * half_rect_width); + gl_Position = projView * vec4(gs_out.frag_position, 1.0); + EmitVertex(); + EndPrimitive(); +} diff --git a/pipes_shader.vert b/pipes_shader.vert new file mode 100644 index 0000000..0a46638 --- /dev/null +++ b/pipes_shader.vert @@ -0,0 +1,29 @@ +#version 330 core +layout (location = 0) in vec3 position_in; +layout (location = 1) in vec3 forward_in; +layout (location = 2) in vec3 up_in; +layout (location = 3) in vec4 color_in; + +// Added to the location of each vertex to center the overall mesh on 0,0,0. +uniform vec3 location_offset; + +uniform mat4 model; +uniform mat3 normal; + +out VS_OUT { + vec3 position; + vec3 forward; + vec3 up; + vec3 right; + vec4 color; +} vs_out; + +//INCLUDE_SHARED_UNIFORMS + +void main() { + vs_out.position = (model * vec4(position_in + location_offset, 1.0)).xyz; + vs_out.forward = normalize(normal * forward_in); + vs_out.up = normalize(normal * up_in); + vs_out.right = normalize(cross(vs_out.forward, vs_out.up)); + vs_out.color = color_in; +} diff --git a/shared_uniforms.glsl b/shared_uniforms.glsl index a7ccdfa..80c4d7c 100644 --- a/shared_uniforms.glsl +++ b/shared_uniforms.glsl @@ -2,5 +2,8 @@ layout(std140) uniform SharedUniforms { mat4 projection; mat4 view; vec4 camera_position; + float size_scale; + float geometry_thickness; + float pad[2]; } shared_uniforms; diff --git a/mesh_fragment_shader.frag b/simple_shader.frag similarity index 85% rename from mesh_fragment_shader.frag rename to simple_shader.frag index 6129ccc..c1514da 100644 --- a/mesh_fragment_shader.frag +++ b/simple_shader.frag @@ -2,6 +2,9 @@ in VS_OUT { vec3 frag_position; + vec3 forward; + vec3 up; + vec3 right; vec4 color; } fs_in; diff --git a/mesh_vertex_shader.vert b/simple_shader.vert similarity index 58% rename from mesh_vertex_shader.vert rename to simple_shader.vert index cce296d..4e106df 100644 --- a/mesh_vertex_shader.vert +++ b/simple_shader.vert @@ -12,6 +12,9 @@ uniform vec3 location_offset; out VS_OUT { vec3 frag_position; + vec3 forward; + vec3 up; + vec3 right; vec4 color; } vs_out; @@ -22,12 +25,15 @@ void main() { gl_Position = shared_uniforms.projection * shared_uniforms.view * model * vec4(position_in + location_offset, 1.0); vs_out.color = color_in; + vs_out.forward = normal * forward_in; + vs_out.up = normal * up_in; + vs_out.right = normalize(cross(vs_out.forward, vs_out.up)); - // Prevent optimizing out the normal uniform. - // TODO (eventually): Remove once we actually use the normal uniform. - if (normal[0].x < -1) { - vs_out.color[1] = 1; + // Used to prevent the normal matrix from being optimized out. (I don't want + // to have to ignore the error when looking for its uniform location.) + if (normal[0][0] == 0.0001) { + vs_out.color[0] *= 0.9999; } - vs_out.frag_position = vec3(model * vec4(position_in, 1.0)); + vs_out.frag_position = vec3(model * vec4(position_in + location_offset, 1.0)); } diff --git a/turtle_3d.c b/turtle_3d.c index c9b022f..596f039 100644 --- a/turtle_3d.c +++ b/turtle_3d.c @@ -128,8 +128,10 @@ static void ModelToNormalMatrix(mat4 model, mat3 normal) { glm_mat4_pick3(dst, normal); } -int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset) { +int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset, + float *size_scale) { float dx, dy, dz, max_axis; + *size_scale = 1.0; dx = t->max_bounds[0] - t->min_bounds[0]; dy = t->max_bounds[1] - t->min_bounds[1]; dz = t->max_bounds[2] - t->min_bounds[2]; @@ -145,7 +147,8 @@ int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset) { max_axis = Max3(dx, dy, dz); glm_mat4_identity(model); if (max_axis > 0) { - glm_scale_uni(model, MESH_CUBE_SIZE / max_axis); + *size_scale = MESH_CUBE_SIZE / max_axis; + glm_scale_uni(model, *size_scale); } ModelToNormalMatrix(model, normal); // TODO (eventually): If I get less stupid, combine the loc_offset diff --git a/turtle_3d.h b/turtle_3d.h index d9117c7..3b34fc6 100644 --- a/turtle_3d.h +++ b/turtle_3d.h @@ -78,8 +78,10 @@ void DestroyTurtle3D(Turtle3D *t); // Sets the model and normal matrices for the turtle's vertices, and the // location offset vector (to be added to each vertex location) that will -// center the mesh on 0, 0, 0. Returns 0 on error. -int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset); +// center the mesh on 0, 0, 0. Returns 0 on error. Also returns the linear +// scale by which the model matrix scales up or down. +int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset, + float *size_scale); // The type used by all turtle-movement instructions. Some instructions may not // use the floating-point parameter. All instructions must return 0 on error