aboutsummaryrefslogtreecommitdiff
path: root/src/rendering/scene_renderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rendering/scene_renderer.cpp')
-rw-r--r--src/rendering/scene_renderer.cpp217
1 files changed, 217 insertions, 0 deletions
diff --git a/src/rendering/scene_renderer.cpp b/src/rendering/scene_renderer.cpp
new file mode 100644
index 0000000..e276074
--- /dev/null
+++ b/src/rendering/scene_renderer.cpp
@@ -0,0 +1,217 @@
+#include "scene_renderer.h"
+
+#include "core/log.h"
+
+#include <glm/gtc/matrix_transform.hpp>
+#include <algorithm>
+#include <cmath>
+#include <numbers>
+#include <vector>
+
+namespace Donut
+{
+ using namespace RHI;
+
+ namespace
+ {
+ // std140 layouts, matching the Slang ConstantBuffer structs exactly. glm's
+ // column-major matrices upload directly (the shaders decorate the members
+ // row_major and use mul(M,v), so the same bytes read correctly on every
+ // backend, with no transpose).
+ struct GridUBO {
+ glm::mat4 view_projection; // 0
+ glm::mat4 transform; // 64
+ float grid_size; float p0[3]; // 128
+ glm::vec3 grid_color; // 144
+ float grid_alpha; // 156
+ glm::vec3 camera_pos; // 160
+ float p1; // 172
+ };
+ static_assert(sizeof(GridUBO) == 176, "GridUBO std140 layout mismatch");
+
+ struct SphereUBO {
+ glm::mat4 view_projection; // 0
+ glm::mat4 transform; // 64
+ glm::vec3 color; float specular; // 128, 140
+ float emission; float p0[3]; // 144
+ glm::vec3 light_pos; float p1; // 160, 172
+ glm::vec3 camera_pos; int is_selected; // 176, 188
+ glm::vec3 outline_color; float outline_width; // 192, 204
+ };
+ static_assert(sizeof(SphereUBO) == 208, "SphereUBO std140 layout mismatch");
+
+ struct SkyboxUBO { glm::mat4 projection; glm::mat4 view; };
+ static_assert(sizeof(SkyboxUBO) == 128, "SkyboxUBO std140 layout mismatch");
+ }
+
+ auto SceneRenderer::init(RHI::Device& device) -> bool
+ {
+ m_device = &device;
+
+ // Reference grid: line list on the XZ plane (+/-50, 1-unit cells). Grid.slang
+ // scales by u_GridSize/50, so u_GridSize = 50 keeps it 1:1.
+ {
+ std::vector<glm::vec3> lines;
+ const int N = 50;
+ for (int i = -N; i <= N; ++i)
+ {
+ lines.push_back({ (float)i, 0.0f, (float)-N });
+ lines.push_back({ (float)i, 0.0f, (float) N });
+ lines.push_back({ (float)-N, 0.0f, (float)i });
+ lines.push_back({ (float) N, 0.0f, (float)i });
+ }
+ m_grid_vertex_count = (int)lines.size();
+ m_grid_vb = device.create_buffer(BufferType::Vertex, lines.size() * sizeof(glm::vec3), lines.data());
+ m_grid_ubo = device.create_buffer(BufferType::Uniform, sizeof(GridUBO));
+
+ PipelineDesc d;
+ d.shader = "grid";
+ d.vertex_layout = { sizeof(glm::vec3), { { 0, 3, 0 } } };
+ d.resources = { { ResourceKind::UniformBuffer, 0, "GridU" } };
+ d.topology = Topology::Lines;
+ d.blend = BlendMode::AlphaBlend;
+ d.depth_test = true; d.depth_write = false; d.depth_op = CompareOp::LessEqual;
+ d.has_depth = true;
+ m_grid_pipeline = device.create_pipeline(d);
+ }
+
+ // Lit sphere: unit UV-sphere (pos + normal), placed/scaled per object.
+ {
+ std::vector<float> sv; std::vector<uint32_t> si;
+ const int RINGS = 24, SECTORS = 48;
+ for (int r = 0; r <= RINGS; ++r)
+ {
+ float phi = (float)std::numbers::pi * r / RINGS;
+ for (int s = 0; s <= SECTORS; ++s)
+ {
+ float theta = 2.0f * (float)std::numbers::pi * s / SECTORS;
+ float x = sinf(phi) * cosf(theta), y = cosf(phi), z = sinf(phi) * sinf(theta);
+ sv.push_back(x); sv.push_back(y); sv.push_back(z); // position (unit)
+ sv.push_back(x); sv.push_back(y); sv.push_back(z); // normal == position
+ }
+ }
+ for (int r = 0; r < RINGS; ++r)
+ for (int s = 0; s < SECTORS; ++s)
+ {
+ uint32_t a = r * (SECTORS + 1) + s, b = a + SECTORS + 1;
+ si.push_back(a); si.push_back(b); si.push_back(a + 1);
+ si.push_back(b); si.push_back(b + 1); si.push_back(a + 1);
+ }
+ m_sphere_index_count = (int)si.size();
+ m_sphere_vb = device.create_buffer(BufferType::Vertex, sv.size() * sizeof(float), sv.data());
+ m_sphere_ib = device.create_buffer(BufferType::Index, si.size() * sizeof(uint32_t), si.data());
+
+ PipelineDesc d;
+ d.shader = "sphere";
+ d.vertex_layout = { 6 * sizeof(float), { { 0, 3, 0 }, { 1, 3, 3 * sizeof(float) } } };
+ d.resources = { { ResourceKind::UniformBuffer, 0, "SphereU" },
+ { ResourceKind::Texture, 1, "u_HDRIEnvironment" } };
+ d.topology = Topology::Triangles;
+ d.depth_test = true; d.depth_write = true; d.depth_op = CompareOp::Less;
+ d.has_depth = true;
+ m_sphere_pipeline = device.create_pipeline(d);
+ }
+
+ // Skybox: a unit cube (36 verts) sampling the HDRI cubemap; the vertex
+ // shader forces depth 1 (pos.xyww) so it sits behind all scene geometry.
+ {
+ const float cube[] = {
+ -1,-1,-1, 1,-1,-1, 1, 1,-1, 1, 1,-1, -1, 1,-1, -1,-1,-1,
+ -1,-1, 1, 1,-1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1,-1, 1,
+ -1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1,-1, -1,-1, 1, -1, 1, 1,
+ 1, 1, 1, 1, 1,-1, 1,-1,-1, 1,-1,-1, 1,-1, 1, 1, 1, 1,
+ -1,-1,-1, 1,-1,-1, 1,-1, 1, 1,-1, 1, -1,-1, 1, -1,-1,-1,
+ -1, 1,-1, 1, 1,-1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1,-1
+ };
+ m_skybox_vb = device.create_buffer(BufferType::Vertex, sizeof(cube), cube);
+ m_skybox_ubo = device.create_buffer(BufferType::Uniform, sizeof(SkyboxUBO));
+
+ PipelineDesc d;
+ d.shader = "skybox";
+ d.vertex_layout = { 3 * sizeof(float), { { 0, 3, 0 } } };
+ d.resources = { { ResourceKind::UniformBuffer, 0, "SkyboxU" },
+ { ResourceKind::Texture, 1, "u_Skybox" } };
+ d.topology = Topology::Triangles;
+ d.depth_test = false; d.depth_write = false;
+ d.has_depth = true;
+ m_skybox_pipeline = device.create_pipeline(d);
+ }
+
+ DONUT_INFO("SceneRenderer ready ({} grid verts, {} sphere indices, skybox)",
+ m_grid_vertex_count, m_sphere_index_count);
+ return true;
+ }
+
+ auto SceneRenderer::render(RHI::CommandList& cmd, const CameraView& cam,
+ const std::vector<SceneObject>& objects, int selected,
+ RHI::Texture* cubemap) -> void
+ {
+ // Full-window viewport; flip_y lets the backend match the shared top-left
+ // orientation (Vulkan flips via a negative-height viewport, GL is a no-op).
+ cmd.set_viewport(0, 0, cam.fb_width, cam.fb_height, true);
+
+ glm::mat4 vp = cam.projection * cam.view;
+
+ // Skybox background first (depth 1, no depth test/write).
+ {
+ SkyboxUBO sky{};
+ sky.projection = cam.projection;
+ sky.view = glm::mat4(glm::mat3(cam.view)); // strip translation
+ m_skybox_ubo->update(&sky, sizeof(sky));
+ cmd.bind_pipeline(m_skybox_pipeline.get());
+ cmd.bind_uniform(0, m_skybox_ubo.get());
+ cmd.bind_texture(1, cubemap);
+ cmd.bind_vertex_buffer(m_skybox_vb.get());
+ cmd.draw(36);
+ }
+
+ // Opaque spheres (write depth). One UBO per object slot avoids aliasing the
+ // per-draw uniforms across the deferred command stream.
+ {
+ int count = std::min((int)objects.size(), MAX_OBJECTS);
+ while ((int)m_sphere_ubos.size() < count)
+ m_sphere_ubos.push_back(m_device->create_buffer(BufferType::Uniform, sizeof(SphereUBO)));
+
+ cmd.bind_pipeline(m_sphere_pipeline.get());
+ for (int i = 0; i < count; ++i)
+ {
+ const SceneObject& o = objects[i];
+ SphereUBO s{};
+ s.view_projection = vp;
+ s.transform = glm::translate(glm::mat4(1.0f), o.position)
+ * glm::scale(glm::mat4(1.0f), glm::vec3(o.radius));
+ s.color = o.color;
+ s.specular = 0.6f;
+ s.emission = 0.0f;
+ s.light_pos = glm::vec3(10.0f, 20.0f, 10.0f);
+ s.camera_pos = cam.position;
+ s.is_selected = (i == selected) ? 1 : 0;
+ s.outline_color = glm::vec3(1.0f, 1.0f, 0.0f);
+ s.outline_width = 0.15f;
+ m_sphere_ubos[i]->update(&s, sizeof(s));
+
+ cmd.bind_uniform(0, m_sphere_ubos[i].get());
+ cmd.bind_texture(1, cubemap);
+ cmd.bind_vertex_buffer(m_sphere_vb.get());
+ cmd.bind_index_buffer(m_sphere_ib.get());
+ cmd.draw_indexed(m_sphere_index_count);
+ }
+ }
+
+ // Transparent grid on top (tests depth, doesn't write).
+ {
+ GridUBO g{};
+ g.view_projection = vp;
+ g.transform = glm::mat4(1.0f);
+ g.grid_size = 50.0f;
+ g.grid_color = glm::vec3(0.55f, 0.55f, 0.6f);
+ g.grid_alpha = 0.75f;
+ g.camera_pos = cam.position;
+ m_grid_ubo->update(&g, sizeof(g));
+ cmd.bind_pipeline(m_grid_pipeline.get());
+ cmd.bind_uniform(0, m_grid_ubo.get());
+ cmd.bind_vertex_buffer(m_grid_vb.get());
+ cmd.draw(m_grid_vertex_count);
+ }
+ }
+}