Files
fan/src/renderer.cpp

217 lines
7.8 KiB
C++

#include <iostream>
#include <algorithm>
#include <string>
#include <unordered_map>
#include <glad/gl.h>
#include "renderer.hpp"
namespace Fan
{
Rc<UniformBuffer> Renderer::s_camera_ubo = nullptr;
Rc<UniformBuffer> Renderer::s_light_ubo = nullptr;
void Renderer::init()
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
using bp = Shader::BindingPoint;
s_camera_ubo = UniformBuffer::create(sizeof(CameraData), bp::Camera);
s_light_ubo = UniformBuffer::create(sizeof(LightEnvironmentData), bp::LightEnvironment);
}
void Renderer::set_viewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
void Renderer::set_clear_color(const glm::vec4& color)
{
glClearColor(color.r, color.g, color.b, color.a);
}
void Renderer::set_wireframe(bool enabled)
{
glPolygonMode(GL_FRONT_AND_BACK, enabled ? GL_LINE : GL_FILL);
}
void Renderer::clear()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void Renderer::draw_scene(const Scene& scene, const Camera& camera,
int screen_width, int screen_height)
{
update_global_data(scene, camera, screen_width, screen_height);
struct DrawCommand
{
const Transform* transform;
const Mesh* mesh;
GLuint index_offset;
GLuint index_count;
};
// Sort Entities: Shader -> Material -> SubMesh Draw Commands
using RenderQueue = std::unordered_map<Shader*, std::unordered_map<Material*, std::vector<DrawCommand>>>;
RenderQueue render_queue;
for (const auto& entity : scene.get_entities()) {
if (!entity.mesh) { continue; }
// If Entity has an explicit material override
if (entity.material) {
if (!entity.material->shader) { continue; }
Shader* shader_ptr = entity.material->shader.get();
Material* material_ptr = entity.material.get();
for (const auto& submesh : entity.mesh->get_submeshes()) {
render_queue[shader_ptr][material_ptr].push_back({
&entity.transform,
entity.mesh.get(),
submesh.index_offset,
submesh.index_count
});
}
// Otherwise use SubMesh's native material
} else {
for (const auto& submesh : entity.mesh->get_submeshes()) {
if (!submesh.material || !submesh.material->shader) { continue; }
Shader* shader_ptr = submesh.material->shader.get();
Material* material_ptr = submesh.material.get();
render_queue[shader_ptr][material_ptr].push_back({
&entity.transform,
entity.mesh.get(),
submesh.index_offset,
submesh.index_count
});
}
}
}
// Execute sorted queue for minimal state switching
for (const auto& [shader, materials] : render_queue) {
shader->bind();
for (const auto& [material, draw_command] : materials) {
material->bind();
for (const auto& cmd: draw_command) {
shader->set_mat4("u_model", cmd.transform->get_model_matrix());
cmd.mesh->draw_submesh(cmd.index_offset, cmd.index_count);
}
}
}
}
void Renderer::draw_debug_wireframes(const Scene& scene, const Camera& camera,
int screen_width, int screen_height,
const Rc<Shader>& debug_shader,
const Rc<Mesh>& default_mesh)
{
debug_shader->bind();
debug_shader->set_mat4("u_view_projection", camera.get_view_projection_matrix(screen_width, screen_height));
set_wireframe(true);
// Draw all standard entities (green)
debug_shader->set_vec3("u_color", glm::vec3(0.0f, 0.7f, 0.0f));
for (const Entity& entity : scene.get_entities()) {
// If an entity has no mesh, use the default debug mesh
auto mesh = entity.mesh ? entity.mesh : default_mesh;
debug_shader->set_mat4("u_model", entity.transform.get_model_matrix());
mesh->draw();
}
// Draw point lights (yellow)
debug_shader->set_vec3("u_color", glm::vec3(0.7f, 0.7f, 0.0f));
for (const PointLight& light : scene.light_environment.point_lights) {
glm::mat4 model = glm::translate(glm::mat4(1.0f), light.position);
model = glm::scale(model, glm::vec3(0.2f));
debug_shader->set_mat4("u_model", model);
default_mesh->draw();
}
// Draw spot lights (cyan)
debug_shader->set_vec3("u_color", glm::vec3(0.0f, 0.7f, 0.7f));
for (const SpotLight& light : scene.light_environment.spot_lights) {
glm::mat4 model = glm::translate(glm::mat4(1.0f), light.position);
model = glm::scale(model, glm::vec3(0.2f));
debug_shader->set_mat4("u_model", model);
default_mesh->draw();
}
// Restore state
set_wireframe(false);
}
void Renderer::update_global_data(const Scene& scene, const Camera& camera, int screen_width, int screen_height)
{
// Update camera UBO
CameraData cam_data{};
cam_data.view = camera.get_view_matrix();
cam_data.projection = camera.get_projection_matrix(screen_width, screen_height);
s_camera_ubo->update(&cam_data, sizeof(CameraData));
// Update light UBO
LightEnvironmentData light_data{};
const LightEnvironment& env = scene.light_environment;
// Map DirectionalLight
const auto& dir_src = env.directional_light;
light_data.dir_light.direction = glm::vec4(dir_src.direction, 0.0f);
light_data.dir_light.ambient = glm::vec4(dir_src.ambient, 0.0f);
light_data.dir_light.diffuse = glm::vec4(dir_src.diffuse, 0.0f);
light_data.dir_light.specular = glm::vec4(dir_src.specular, 0.0f);
// Map PointLights
if (light_data.active_point_lights > MAX_POINT_LIGHTS) {
std::cerr << "WARNING: RENDERER: Maximum number of point lights reached"
<< " (" << MAX_POINT_LIGHTS << ")" << std::endl;
}
light_data.active_point_lights = std::min(static_cast<int>(env.point_lights.size()), MAX_POINT_LIGHTS);
for (int i = 0; i < light_data.active_point_lights; ++i) {
const auto& src = env.point_lights[i];
light_data.point_lights[i].position = glm::vec4(src.position, 1.0f);
light_data.point_lights[i].ambient = glm::vec4(src.ambient, 1.0f);
light_data.point_lights[i].diffuse = glm::vec4(src.diffuse, 1.0f);
light_data.point_lights[i].specular = glm::vec4(src.specular, 1.0f);
light_data.point_lights[i].attenuation = glm::vec4(src.constant, src.linear, src.quadratic, 0.0f);
}
// Map SpotLights
if (light_data.active_spot_lights > MAX_SPOT_LIGHTS) {
std::cerr << "WARNING: RENDERER: Maximum number of spot lights reached"
<< " (" << MAX_SPOT_LIGHTS << ")" << std::endl;
}
light_data.active_spot_lights = std::min(static_cast<int>(env.spot_lights.size()), MAX_SPOT_LIGHTS);
for (int i = 0; i < light_data.active_spot_lights; ++i) {
const auto& src = env.spot_lights[i];
light_data.spot_lights[i].position = glm::vec4(src.position, 1.0f);
light_data.spot_lights[i].direction = glm::vec4(src.direction, 0.0f);
light_data.spot_lights[i].ambient = glm::vec4(src.ambient, 1.0f);
light_data.spot_lights[i].diffuse = glm::vec4(src.diffuse, 1.0f);
light_data.spot_lights[i].specular = glm::vec4(src.specular, 1.0f);
light_data.spot_lights[i].attenuation = glm::vec4(src.constant, src.linear, src.quadratic, 0.0f);
light_data.spot_lights[i].cut_offs = glm::vec4(src.cut_off, src.outer_cut_off, 0.0f, 0.0f);
}
s_light_ubo->update(&light_data, sizeof(LightEnvironmentData));
}
} // namespace Fan