263 lines
7.5 KiB
C++
263 lines
7.5 KiB
C++
#include "renderer.hpp"
|
|
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
#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<VertexArray>(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
|
|
}
|
|
|