#include "render.h" #include "state.h" #include "../platform.h" #include "../lib/math.h" #include "../debug/logger.h" #include "gl_helpers.h" #include "string.h" #include "../camera.h" // @Cleanup: remove dependency from camera r_state r_render_state; void r_init() { // OpenGL debug messages glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(glDebugOutput, nullptr); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); // Depth test glEnable(GL_DEPTH_TEST); // Face culling glEnable(GL_CULL_FACE); // Texture pixel alignment (default is 4, so rgb textures should have every pixel padded to 4 bytes. With this we can have 3 byte pixels with no padding between eachother) glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Blending glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // Cubemap glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Load shaders if(!r_shader_from_files(&r_render_state.shader_2d, "shaders/2d.vert", "shaders/2d.frag")) { LOG(LOG_ERROR, "Cannot load 2D shader."); } if(!r_shader_from_files(&r_render_state.shader_postprocessing, "shaders/postprocessing.vert", "shaders/postprocessing.frag")) { LOG(LOG_ERROR, "Cannot load postprocessing shader."); } if(!r_shader_from_files(&r_render_state.shader_pbr, "shaders/pbr.vert", "shaders/pbr.frag")) { LOG(LOG_ERROR, "Cannot load pbr shader."); } if(!r_shader_from_files(&r_render_state.shader_shadow_map, "shaders/shadow_map.vert", "shaders/shadow_map.frag")) { LOG(LOG_ERROR, "Cannot load shadow_map shader."); } if(!r_shader_from_files(&r_render_state.shader_environment_map, "shaders/environment_map.vert", "shaders/environment_map.frag")) { LOG(LOG_ERROR, "Cannot load environment_map shader."); } // Screen size u32 width, height; p_window_dimensions(&width, &height); glViewport(0, 0, width, height); /* We have to apply some post-processing effects only to the 3D world * and some effects to both the 3D world and the HUD. * * So we make 2 framebuffers: * 1. target for 3D world * 2. target for HUD * After rendering, we apply the correct post-processing effects and * merge them toghether. */ // Init framebuffers v2s size = {width, height}; r_render_state.framebuffer_SCREEN = {.gl_id = 0, .size = size, .flags = 0}; // This is a special framebuffer, because it's already there. We don't need to create it r_render_state.framebuffer_HUD = r_framebuffer_create(size, 0); r_render_state.framebuffer_3D = r_framebuffer_create(size, R_FRAMEBUFFER_DEPTH); // Default framebuffer r_framebuffer_select(&r_render_state.framebuffer_SCREEN); // Init fullscreen quad glGenVertexArrays(1, &r_render_state.gl_screen_quad_VAO); glBindVertexArray(r_render_state.gl_screen_quad_VAO); glGenBuffers(1, &r_render_state.gl_screen_quad_VBO); glBindBuffer(GL_ARRAY_BUFFER, r_render_state.gl_screen_quad_VBO); v2 quad_vertices[2*6] = { // position UV coords v2{-1, 1}, v2{0, 1}, v2{-1, -1}, v2{0, 0}, v2{ 1, -1}, v2{1, 0}, v2{-1, 1}, v2{0, 1}, v2{ 1, -1}, v2{1, 0}, v2{ 1, 1}, v2{1, 1} }; glBufferData(GL_ARRAY_BUFFER, 2*6 * sizeof(v2), quad_vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(v2), (void*)(0)); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2*sizeof(v2), (void*)(sizeof(v2))); // Render state r_size_update(width, height); } void r_deinit() { r_framebuffer_destroy(&r_render_state.framebuffer_HUD); r_framebuffer_destroy(&r_render_state.framebuffer_3D); glDeleteBuffers(1, &r_render_state.gl_screen_quad_VBO); glDeleteVertexArrays(1, &r_render_state.gl_screen_quad_VAO); } void r_size_update(u32 width, u32 height) { // Screen size r_render_state.width = width; r_render_state.height = height; // Update shaders glUseProgram(r_render_state.shader_2d.id); glUniform1i(r_render_state.shader_2d.width , width); glUniform1i(r_render_state.shader_2d.height, height); // Update framebuffers size v2s size = {width, height}; r_render_state.framebuffer_SCREEN.size = size; r_framebuffer_update_size(&r_render_state.framebuffer_HUD, size); r_framebuffer_update_size(&r_render_state.framebuffer_3D , size); } void r_time_update(f64 time) { r_render_state.time = time; } void r_clear(v4 color) { glClearColor(color.r, color.g, color.b, color.a); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void r_swap() { p_graphics_swap(); } r_scene r_scene_create() { r_scene scene; scene.view_matrix = m4_identity; scene.view_matrix_inverse = m4_identity; scene.projection_matrix = m4_identity; scene.projection_matrix_inverse = m4_identity; scene.objects = NULL; scene.objects_count = 0; // Init lights memory memset(&scene.lights, 0, sizeof(r_light_container)); glGenBuffers(1, &scene.gl_lights); glBindBuffer(GL_UNIFORM_BUFFER, scene.gl_lights); glBufferData(GL_UNIFORM_BUFFER, sizeof(r_light_container), &scene.lights, GL_STATIC_DRAW); glBindBufferBase(GL_UNIFORM_BUFFER, 0, scene.gl_lights); return scene; } void r_scene_destroy(r_scene *scene) { glDeleteBuffers(1, &scene->gl_lights); r_cubemap_destroy(&scene->environment_map); } void r_scene_set_view(r_scene *scene, m4 m) { scene->view_matrix = m; scene->view_matrix_inverse = inverse(m); } void r_scene_set_projection(r_scene *scene, m4 m) { scene->projection_matrix = m; scene->projection_matrix_inverse = inverse(m); } void r_update_lights(r_scene *scene) { glBindBuffer(GL_UNIFORM_BUFFER, scene->gl_lights); glBufferData(GL_UNIFORM_BUFFER, sizeof(r_light_container), &scene->lights, GL_STATIC_DRAW); } r_object build_basic_cube() { r_object object = r_object_allocate(1); object.meshes[0] = (r_mesh*)p_alloc(sizeof(r_mesh)); *object.meshes[0] = r_mesh_build_cube(); object.mesh_material[0] = (r_material*)p_alloc(sizeof(r_material)); memset(object.mesh_material[0], 0, sizeof(r_material)); object.mesh_material[0]->shader = &r_render_state.shader_pbr; object.mesh_material[0]->albedo_factor = {1,1,1,1}; object.mesh_material[0]->emissive_factor = {1,1,1,0}; return object; } void r_render_scene(r_scene *scene) { // Render shadow maps scene->shadow_map = r_build_shadow_map_sun(scene, &scene->lights.sun_lights[0]); // Render environment map glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); static r_object cube = build_basic_cube(); cube.position = extract_column(scene->view_matrix_inverse, 3).xyz; glUseProgram(r_render_state.shader_environment_map.id); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, scene->environment_map.gl_id); glUniform1i(r_render_state.shader_environment_map.has_environment_map, 1); glUniform1i(r_render_state.shader_environment_map.environment_map , 0); r_render_object(scene, &cube, &r_render_state.shader_environment_map); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); // Render objects glUseProgram(r_render_state.shader_pbr.id); // Set shadow map // @Cleanup: this should be in r_render_objects glActiveTexture(GL_TEXTURE5); glBindTexture(GL_TEXTURE_2D, scene->shadow_map.gl_depth_texture); glUniform1i (r_render_state.shader_pbr.has_shadow_map, 1); glUniform1i (r_render_state.shader_pbr.shadow_map , 5); glUniformMatrix4fv(r_render_state.shader_pbr.shadow_matrix , 1, GL_TRUE, (const f32 *)(scene->shadow_map.view_matrix.E)); // Environment map glActiveTexture(GL_TEXTURE6); glBindTexture(GL_TEXTURE_CUBE_MAP, scene->environment_map.gl_id); glUniform1i(r_render_state.shader_pbr.has_environment_map, 1); glUniform1i(r_render_state.shader_pbr.environment_map , 6); // Render objects for(u32 i = 0; i < scene->objects_count; i++) { r_render_object(scene, &scene->objects[i]); } glUniform1i(r_render_state.shader_pbr.has_shadow_map, 0); glUniform1i(r_render_state.shader_pbr.has_environment_map, 0); r_free_shadow_map(&scene->shadow_map); } // Single object rendering functions (batched funcs are below) void r_render_object(r_scene *scene, r_object *object, r_shader *shader_override) { // @Performance: Minimize draw calls by batching mesh rendering for(u32 i = 0; i < object->count; i++) { r_mesh *mesh = object->meshes[i]; r_material *material = object->mesh_material[i]; r_shader *shader = material->shader; if(shader_override) shader = shader_override; glUseProgram(shader->id); glUniform1f(shader->time , (f32)r_render_state.time ); glUniform1f(shader->width , (f32)r_render_state.width ); glUniform1f(shader->height, (f32)r_render_state.height); // Transform matrices m4 projview_matrix = scene->projection_matrix * scene->view_matrix; m4 projview_matrix_inverse = inverse(projview_matrix); glUniformMatrix4fv(shader->view_matrix, 1, GL_TRUE, (const f32 *)(&projview_matrix)); glUniformMatrix4fv(shader->view_matrix_inverse, 1, GL_TRUE, (const f32 *)(&projview_matrix_inverse)); m4 model_transform = r_object_mesh_transform_matrix(object, i); glUniformMatrix4fv(shader->model_matrix, 1, GL_TRUE, (const f32 *)(&model_transform)); // Textures // Albedo glUniform1i(shader->has_albedo_texture, material->albedo_texture != NULL); if(material->albedo_texture) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, material->albedo_texture->gl_id); glUniform1i(shader->albedo_texture, 0); } glUniform4f(shader->albedo_factor, material->albedo_factor.r, material->albedo_factor.g, material->albedo_factor.b, material->albedo_factor.a); // Metallic glUniform1i(shader->has_metallic_texture, material->metallic_texture != NULL); if(material->metallic_texture) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, material->metallic_texture->gl_id); glUniform1i(shader->metallic_texture, 1); } glUniform1f(shader->metallic_factor, material->metallic_factor); // Roughness glUniform1i(shader->has_roughness_texture, material->roughness_texture != NULL); if(material->roughness_texture) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, material->roughness_texture->gl_id); glUniform1i(shader->roughness_texture, 2); } glUniform1f(shader->roughness_factor, material->roughness_factor); // Normal glUniform1i(shader->has_normal_texture, material->normal_texture != NULL); if(material->normal_texture) { glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, material->normal_texture->gl_id); glUniform1i(shader->normal_texture, 3); } // Emissive glUniform1i(shader->has_emissive_texture, material->emissive_texture != NULL); if(material->emissive_texture) { glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_2D, material->emissive_texture->gl_id); glUniform1i(shader->emissive_texture, 4); } glUniform4f(shader->emissive_factor, material->emissive_factor.r, material->emissive_factor.g, material->emissive_factor.b, material->emissive_factor.a); // Draw call glBindVertexArray(mesh->gl_VAO); glDrawElements(GL_TRIANGLES, mesh->indices_count, GL_UNSIGNED_INT, (void*)(0)); } } void r_render_object_wireframe(r_scene *scene, r_object *object) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); r_render_object(scene, object); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } void r_render_line(r_scene *scene, v3 a, v3 b, v4 a_color, v4 b_color, f32 thickness) { m4 projview_matrix = scene->projection_matrix * scene->view_matrix; v4 a_trans = projview_matrix * V4(a, 1); v4 b_trans = projview_matrix * V4(b, 1); a_trans /= a_trans.w; b_trans /= b_trans.w; if(a_trans.z > 1 || b_trans.z > 1) return; v2 a_2d = a_trans.xy; v2 b_2d = b_trans.xy; a_2d = (v2{.5,.5} + v2{.5,-0.5} * a_2d) * v2{(f32)r_render_state.width, (f32)r_render_state.height}; b_2d = (v2{.5,.5} + v2{.5,-0.5} * b_2d) * v2{(f32)r_render_state.width, (f32)r_render_state.height}; r_2d_immediate_segment(a_2d, b_2d, a_color, b_color, thickness); // @Cleanup: This should be independent from the 2D drawing functions, probably. } // Batch render functions void r_render_objects(r_scene *scene, u64 count, r_object **objects) { // @Feature: implement } r_shadow_map r_build_shadow_map_sun(r_scene *scene, r_sun_light *sun) { glCullFace(GL_FRONT); r_shadow_map sm; u32 width = 2*1024; u32 height = 2*1024; // Init depth texture/framebuffer glGenTextures(1, &sm.gl_depth_texture); glBindTexture(GL_TEXTURE_2D, sm.gl_depth_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); glGenFramebuffers(1, &sm.gl_FBO); glBindFramebuffer(GL_FRAMEBUFFER, sm.gl_FBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, sm.gl_depth_texture, 0); glDrawBuffer(GL_NONE); glBindTexture(GL_TEXTURE_2D, 0); // Compute light frustum m4 projview_matrix = scene->projection_matrix * scene->view_matrix; m4 projview_matrix_inverse = inverse(projview_matrix); v4 center_point_4 = projview_matrix_inverse * v4{0, 0, 0, 1}; v3 center_point = center_point_4.xyz / center_point_4.w; f32 size = 1; v4 bottom = projview_matrix_inverse * v4{ 0,-1, 0, 1}; v4 top = projview_matrix_inverse * v4{ 0, 1, 0, 1}; v4 left = projview_matrix_inverse * v4{-1, 0, 0, 1}; v4 right = projview_matrix_inverse * v4{ 1, 0, 0, 1}; v4 front = projview_matrix_inverse * v4{ 0, 0,-1, 1}; v4 back = projview_matrix_inverse * v4{ 0, 0, 1, 1}; size = maximum(size, length(top.xyz/top.w - bottom.xyz/bottom.w)); size = maximum(size, length(right.xyz/right.w - left.xyz/left.w)); size = maximum(size, length(back.xyz/back.w - front.xyz/front.w)); size /= 2; // @Cleanup: hack to have better precision. There are multiple ways to do it right: smallest box around all objects, cascaded shadow maps v3 light_position = center_point - size * sun->direction; m4 light_camera_matrix = r_view_matrix(light_position, sun->direction, v3{0, 0, 1}); m4 projection_matrix = r_orthographic_matrix(-size/2, size/2, -size/2, size/2, 0.1, 100.0); sm.view_matrix = projection_matrix * light_camera_matrix; // Render shadow map glViewport(0, 0, width, height); glClear(GL_DEPTH_BUFFER_BIT); // @Feature @Cleanup: Some objects might not need to be drawn. Add a "hidden" flag. Maybe take the list of objects to render as an argument m4 old_view = scene->view_matrix; m4 old_proj = scene->projection_matrix; r_scene_set_view (scene, sm.view_matrix); r_scene_set_projection(scene, m4_identity); for(u32 i = 0; i < scene->objects_count; i++) { if(scene->objects[i].has_shadow) { r_render_object(scene, &scene->objects[i], &r_render_state.shader_shadow_map); } } r_scene_set_view (scene, old_view); r_scene_set_projection(scene, old_proj); // Restore framebuffer r_framebuffer_select(r_render_state.current_framebuffer); glCullFace(GL_BACK); return sm; } void r_free_shadow_map(r_shadow_map *sm) { glDeleteTextures(1, &sm->gl_depth_texture); glDeleteFramebuffers(1, &sm->gl_FBO); } void r_merge_and_postprocess() { glDisable(GL_DEPTH_TEST); glEnable(GL_FRAMEBUFFER_SRGB); glUseProgram(r_render_state.shader_postprocessing.id); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, r_render_state.framebuffer_3D.color_texture.gl_id); glUniform1i(r_render_state.shader_postprocessing.texture[0], 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, r_render_state.framebuffer_HUD.color_texture.gl_id); glUniform1i(r_render_state.shader_postprocessing.texture[1], 1); glBindVertexArray(r_render_state.gl_screen_quad_VAO); glDrawArrays(GL_TRIANGLES, 0, 6); glEnable(GL_DEPTH_TEST); glDisable(GL_FRAMEBUFFER_SRGB); } void r_framebuffer_select(r_framebuffer *fb) { glBindFramebuffer(GL_FRAMEBUFFER, fb->gl_id); glViewport(0, 0, fb->size.x, fb->size.y); glUseProgram(r_render_state.shader_2d.id); glUniform1i(r_render_state.shader_2d.width , fb->size.x); glUniform1i(r_render_state.shader_2d.height, fb->size.y); r_render_state.current_framebuffer = fb; }