#include "renderer.hpp" #include #include #include "gl.hpp" #include "gfx/shader_sources.hpp" gfx::Renderer::Renderer() { ShaderSources::MakeShader(sector_shader_, SS_SECTOR_MESH_VERT, SS_SECTOR_MESH_FRAG); ShaderSources::MakeShader(portal_shader_, SS_PORTAL_VERT, SS_PORTAL_FRAG); proj_ = glm::mat4(1.0f); // Initialize projection matrix to identity SetupPortalVAO(); } void gfx::Renderer::SetupPortalVAO() { const glm::vec3 portal_verts[4] = { { -0.5f, 0.0f, 0.5f }, // Top-left { -0.5f, 0.0f, -0.5f }, // Bottom-left { 0.5f, 0.0f, -0.5f }, // Bottom-right { 0.5f, 0.0f, 0.5f }, // Top-right }; portal_vao_ = std::make_shared(VA_POSITION, 0); portal_vao_->SetVBOData(portal_verts, sizeof(portal_verts)); } void gfx::Renderer::Begin(size_t width, size_t height) { glViewport(0, 0, width, height); glClearStencil(0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } void gfx::Renderer::DrawWorld( const game::World& world, size_t sector_idx, const glm::vec3& eye, const glm::vec3& dir, const glm::vec3& up, float aspect, float fov) { proj_ = glm::perspective( glm::radians(fov), // FOV aspect, // Aspect ratio 0.1f, // Near plane 1000.0f // Far plane ); glm::mat4 view = glm::lookAt( eye, // Camera position eye + dir, // Look at point (camera position + look direction) up // Up vector ); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0, 0xFF); // Always pass stencil test glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glStencilMask(0xFF); const game::Sector& sector = world.GetSector(sector_idx); DrawSectorParams params; params.sector = §or; params.entry_portal = nullptr; // No entry portal for the first sector params.clip_plane = glm::vec4(0.0f, 0.0f, 1.0f, 1000.0f); // Default clip plane params.screen_aabb = collision::AABB2(glm::vec2(-1.0f), glm::vec2(1.0f)); // Full screen params.recursion = 0; // Start with recursion level 0 params.view = view; params.view_proj = proj_ * view; params.eye = eye; DrawSector(params); //glDisable(GL_STENCIL_TEST); //glDisable(GL_CULL_FACE); //glDisable(GL_DEPTH_TEST); } void gfx::Renderer::DrawSector(const DrawSectorParams& params) { assets::Mesh& mesh = *params.sector->GetMesh(); glm::mat4 model = glm::mat4(1.0f); // Identity matrix for model // #ifndef PG_GLES // glEnable(GL_CLIP_DISTANCE0); // #else // glEnable(GL_CLIP_DISTANCE0_EXT); // #endif glUseProgram(sector_shader_->GetId()); glUniformMatrix4fv(sector_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]); glUniformMatrix4fv(sector_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &model[0][0]); glUniform4fv(sector_shader_->U(gfx::SU_CLIP_PLANE), 1, ¶ms.clip_plane[0]); glBindVertexArray(mesh.GetVA().GetVAOId()); for (auto mesh_materials = mesh.GetMaterials(); const auto& mat : mesh_materials) { glActiveTexture(GL_TEXTURE0); if (mat.texture) { glBindTexture(GL_TEXTURE_2D, mat.texture->GetId()); glUniform1i(sector_shader_->U(gfx::SU_TEX), 0); // Bind texture to sampler } else { glBindTexture(GL_TEXTURE_2D, 0); // No texture } glDrawElements(GL_TRIANGLES, mat.num_tris * 3, GL_UNSIGNED_INT, (void*)(mat.first_tri * 3 * sizeof(uint32_t))); } glBindVertexArray(0); for (size_t i = 0; i < params.sector->GetNumPortals(); ++i) { const game::Portal& portal = params.sector->GetPortal(i); DrawPortal(params, portal); } } void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Portal& portal) { if (params.recursion > 10) { return; // Recursion limit reached } if (&portal == params.entry_portal) { return; // Skip the portal this sector was rendered through // This should NEVER happen due to portal plane check, but would cause cyclic traversal if it did. } if (!portal.link) { return; // Portal not linked, skip } // Signed distance of eye to the portal plane float eye_plane_sd = glm::dot(glm::vec3(portal.plane), params.eye) + portal.plane.w; if (eye_plane_sd < 0.0f) { return; // Eye is behind the portal plane, skip } collision::AABB2 portal_aabb; if (!ComputePortalScreenAABB(portal, params.view_proj, portal_aabb)) { return; // Portal is fully behind the camera, skip } if (!portal_aabb.CollidesWith(params.screen_aabb)) { return; // Portal AABB does not intersect with the screen AABB, skip } // Open portal glStencilFunc(GL_EQUAL, params.recursion, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); DrawPortalPlane(params, portal, false); // Mark the portal plane in stencil buffer glStencilFunc(GL_EQUAL, params.recursion + 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glDepthFunc(GL_ALWAYS); DrawPortalPlane(params, portal, true); // Clear the depth buffer for nested sector glDepthFunc(GL_LESS); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); const game::Portal& other_portal = *portal.link; const game::Sector& other_sector = *other_portal.sector; DrawSectorParams new_params; new_params.sector = &other_sector; new_params.entry_portal = &other_portal; new_params.clip_plane = other_portal.plane; // Use the portal plane as the clip plane // Compute the new screen AABB based on the portal AABB and the current screen AABB new_params.screen_aabb = params.screen_aabb.Intersection(portal_aabb); new_params.recursion = params.recursion + 1; new_params.view = params.view * other_portal.link_trans; new_params.view_proj = proj_ * new_params.view; new_params.eye = portal.link_trans * glm::vec4(params.eye, 1.0f); // Draw the sector through the portal DrawSector(new_params); // Close portal glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glStencilFunc(GL_EQUAL, params.recursion + 1, 0xFF); DrawPortalPlane(params, portal, false); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } bool gfx::Renderer::ComputePortalScreenAABB(const game::Portal& portal, const glm::mat4 view_proj, collision::AABB2& aabb) { size_t num_behind = 0; for (size_t i = 0; i < 4; ++i) { glm::vec4 vert = glm::vec4(portal.verts[i], 1.0f); glm::vec4 clip = view_proj * vert; if (clip.w <= 0.0f) { num_behind++; continue; // Skip vertices behind the camera } glm::vec2 ndc = clip; ndc /= clip.w; aabb.AddPoint(ndc, 0.0f); } if (num_behind == 4) { return false; // All vertices are behind the camera, no visible portal } if (num_behind > 0) { // Some vertices are behind the camera, cannot use the AABB aabb = collision::AABB2(glm::vec2(-1.0f), glm::vec2(1.0f)); // Full screen AABB } return true; } void gfx::Renderer::DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, bool clear_depth) { if (!clear_depth) // first draw { glUseProgram(portal_shader_->GetId()); glUniformMatrix4fv(portal_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]); glUniformMatrix4fv(portal_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &portal.trans[0][0]); glUniform4fv(portal_shader_->U(gfx::SU_CLIP_PLANE), 1, ¶ms.clip_plane[0]); glUniform2fv(portal_shader_->U(gfx::SU_PORTAL_SIZE), 1, &portal.def->size[0]); glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 0); glBindVertexArray(portal_vao_->GetVAOId()); } else // assume uniforms are already set from previous draw { glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 1); } glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Draw the portal as a quad }