1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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.target = { Format::Swapchain, Format::D32 };
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.target = { Format::Swapchain, Format::D32 };
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.target = { Format::Swapchain, Format::D32 };
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);
}
}
}
|