libs/render/src/eu/render/renderer.cc
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "eu/render/renderer.h" | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | |||
| 5 | #include "eu/log/log.h" | ||
| 6 | |||
| 7 | #include "eu/render/camera.h" | ||
| 8 | #include "eu/render/fullscreen.h" | ||
| 9 | #include "eu/core/geom.builder.h" | ||
| 10 | #include "eu/core/geom.h" | ||
| 11 | #include "eu/render/opengl_utils.h" | ||
| 12 | #include "eu/render/renderer.pimpl.h" | ||
| 13 | #include "eu/render/state.h" | ||
| 14 | #include "eu/render/constants.h" | ||
| 15 | |||
| 16 | namespace eu::render | ||
| 17 | { | ||
| 18 | |||
| 19 | ✗ | Renderer::Renderer(State* states, Assets* default_assets, const RenderSettings& set) | |
| 20 | ✗ | : assets(default_assets) | |
| 21 | ✗ | , settings(set) | |
| 22 | ✗ | , pimpl(std::make_unique<RendererPimpl>(states, *assets, set, FullScreenGeom{})) | |
| 23 | { | ||
| 24 | ✗ | } | |
| 25 | |||
| 26 | ✗ | Renderer::~Renderer() = default; | |
| 27 | |||
| 28 | ✗ | Skybox Renderer::make_skybox(std::shared_ptr<TextureCubemap> texture) const | |
| 29 | { | ||
| 30 | ✗ | constexpr float size = 1.0f; | |
| 31 | |||
| 32 | ✗ | const auto layout = pimpl->shaders_resources.skybox_shader.geom_layout; | |
| 33 | |||
| 34 | ✗ | const auto triangle = core::geom::create_box(size, size, size, core::geom::NormalsFacing::In, colors::white).to_geom(); | |
| 35 | ✗ | auto geom = compile_geom(USE_DEBUG_LABEL_MANY("skybox") triangle, layout); | |
| 36 | |||
| 37 | ✗ | LOG_INFO("Created skybox"); | |
| 38 | ✗ | return {geom, std::move(texture)}; | |
| 39 | ✗ | } | |
| 40 | |||
| 41 | struct TransparentMesh | ||
| 42 | { | ||
| 43 | std::shared_ptr<MeshInstance> mesh; | ||
| 44 | float squared_distance_to_camera; | ||
| 45 | }; | ||
| 46 | |||
| 47 | ✗ | std::shared_ptr<UnlitMaterial> Renderer::make_unlit_material() const | |
| 48 | { | ||
| 49 | ✗ | return std::make_shared<UnlitMaterial>(pimpl->shaders_resources); | |
| 50 | } | ||
| 51 | |||
| 52 | ✗ | std::shared_ptr<DefaultMaterial> Renderer::make_default_material() const | |
| 53 | { | ||
| 54 | ✗ | return std::make_shared<DefaultMaterial>(pimpl->shaders_resources); | |
| 55 | } | ||
| 56 | |||
| 57 | ✗ | core::CompiledGeomVertexAttributes Renderer::unlit_geom_layout() const | |
| 58 | { | ||
| 59 | ✗ | return pimpl->shaders_resources.unlit_shader_container.geom_layout; | |
| 60 | } | ||
| 61 | |||
| 62 | ✗ | core::CompiledGeomVertexAttributes Renderer::default_geom_layout() const | |
| 63 | { | ||
| 64 | ✗ | return pimpl->shaders_resources.default_shader_container.geom_layout; | |
| 65 | } | ||
| 66 | |||
| 67 | ✗ | bool Renderer::is_loaded() const | |
| 68 | { | ||
| 69 | ✗ | return pimpl->shaders_resources.is_loaded() && pimpl->debug_drawer.is_loaded(); | |
| 70 | } | ||
| 71 | |||
| 72 | ✗ | m4 transform_from_rotation(const v3& position, const Ypr& ypr) | |
| 73 | { | ||
| 74 | ✗ | const auto translation = m4::from_translation(position); | |
| 75 | ✗ | const auto rotation = m4::from(Q::from(ypr)).value_or(m4_identity); | |
| 76 | ✗ | return translation * rotation; | |
| 77 | } | ||
| 78 | ✗ | m4 transform_from_billboard(const v3& position, Billboarding billboarding, const CompiledCamera& cc) | |
| 79 | { | ||
| 80 | ✗ | const auto calc_fixed_right = [](const n3& normal, const n3& up) | |
| 81 | { | ||
| 82 | ✗ | const auto right = normal.cross_norm(up).value_or(kk::right); | |
| 83 | ✗ | const auto new_up = right.cross_norm(normal).value_or(kk::up); | |
| 84 | ✗ | return m4::from_basis(right, new_up, normal); | |
| 85 | }; | ||
| 86 | |||
| 87 | ✗ | const auto calc_fixed_up = [](const n3& normal, const n3& up) | |
| 88 | { | ||
| 89 | ✗ | const auto right = normal.cross_norm(up).value_or(kk::right); | |
| 90 | ✗ | const auto new_normal = right.cross_norm(up).value_or(kk::up); | |
| 91 | ✗ | return m4::from_basis(right, up, new_normal); | |
| 92 | }; | ||
| 93 | |||
| 94 | ✗ | const auto translation = m4::from_translation(position); | |
| 95 | |||
| 96 | // todo(Gustav): verify that the billboards are oriented correctly, grass in example 3 is twosided... | ||
| 97 | ✗ | switch (billboarding) | |
| 98 | { | ||
| 99 | ✗ | case Billboarding::screen: | |
| 100 | { | ||
| 101 | ✗ | const auto rotation = calc_fixed_right(v3::from_to(cc.position, position).get_normalized().value_or(kk::in), kk::up); | |
| 102 | ✗ | return translation * rotation; | |
| 103 | } | ||
| 104 | ✗ | case Billboarding::screen_fast: | |
| 105 | { | ||
| 106 | // todo(Gustav): move to precalculated or remove? | ||
| 107 | ✗ | const auto rotation = calc_fixed_right(cc.in, kk::up); | |
| 108 | ✗ | return translation * rotation; | |
| 109 | } | ||
| 110 | ✗ | case Billboarding::axial_y: | |
| 111 | { | ||
| 112 | ✗ | const auto rotation = calc_fixed_up(v3::from_to(cc.position, position).get_normalized().value_or(kk::in), kk::up); | |
| 113 | ✗ | return translation * rotation; | |
| 114 | } | ||
| 115 | ✗ | case Billboarding::axial_y_fast: | |
| 116 | { | ||
| 117 | // todo(Gustav): move to precalculated or remove? | ||
| 118 | ✗ | const auto rotation = calc_fixed_up(cc.in, kk::up); | |
| 119 | ✗ | return translation * rotation; | |
| 120 | } | ||
| 121 | ✗ | default: | |
| 122 | { | ||
| 123 | ✗ | DIE("invalid billboarding"); | |
| 124 | ✗ | const auto rotation = calc_fixed_up(cc.in, kk::up); | |
| 125 | ✗ | return translation * rotation; | |
| 126 | } | ||
| 127 | |||
| 128 | } | ||
| 129 | }; | ||
| 130 | |||
| 131 | ✗ | void batch_lines(LineDrawer* drawer, const std::vector<DebugLine>& debug_lines, float gamma) | |
| 132 | { | ||
| 133 | ✗ | for (const auto& line: debug_lines) | |
| 134 | { | ||
| 135 | ✗ | drawer->line(line.from, line.to, linear_from_srgb(line.color, gamma)); | |
| 136 | } | ||
| 137 | ✗ | drawer->submit(); | |
| 138 | ✗ | } | |
| 139 | |||
| 140 | ✗ | void render_debug_lines(const std::vector<DebugLine>& debug_lines, State* states, LineDrawer* drawer, const CompiledCamera& compiled_camera, const Size& window_size, float gamma) | |
| 141 | { | ||
| 142 | ✗ | if (debug_lines.empty()) | |
| 143 | { | ||
| 144 | ✗ | return; | |
| 145 | } | ||
| 146 | |||
| 147 | ✗ | drawer->shader.use(); | |
| 148 | ✗ | drawer->set_camera(compiled_camera); | |
| 149 | |||
| 150 | ✗ | StateChanger{states}.depth_func(Compare::less_equal).depth_test(true); | |
| 151 | ✗ | drawer->set_line_to_solid(); | |
| 152 | ✗ | batch_lines(drawer, debug_lines, gamma); | |
| 153 | |||
| 154 | ✗ | StateChanger{states}.depth_func(Compare::greater).depth_test(true); | |
| 155 | ✗ | drawer->set_line_to_dash({float_from_int(window_size.width), float_from_int(window_size.height)}, 20.0f, 20.0f); | |
| 156 | ✗ | batch_lines(drawer, debug_lines, gamma); | |
| 157 | } | ||
| 158 | |||
| 159 | ✗ | void Renderer::render_world(const Size& window_size, const World& world, const CompiledCamera& compiled_camera, const ShadowContext& shadow_context) | |
| 160 | { | ||
| 161 | ✗ | SCOPED_DEBUG_GROUP("render world call"sv); | |
| 162 | ✗ | const auto has_outlined_meshes = std::ranges::any_of(world.meshes, | |
| 163 | ✗ | [](const auto& mesh) { return mesh->outline.has_value(); } | |
| 164 | ); | ||
| 165 | ✗ | StateChanger{pimpl->states} | |
| 166 | ✗ | .cull_face(true) | |
| 167 | ✗ | .cull_face_mode(CullFace::back) | |
| 168 | ✗ | .stencil_mask(0xFF) | |
| 169 | ✗ | .stencil_test(has_outlined_meshes) | |
| 170 | ✗ | .depth_test(true) | |
| 171 | ✗ | .depth_mask(true) | |
| 172 | ✗ | .stencil_op(StencilAction::keep, StencilAction::replace, StencilAction::replace) | |
| 173 | ✗ | .blend_mode(Blend::src_alpha, Blend::one_minus_src_alpha); | |
| 174 | |||
| 175 | ✗ | const auto clear_color = linear_from_srgb(world.clear_color, settings.gamma); | |
| 176 | ✗ | glClearColor(clear_color.r, clear_color.g, clear_color.b, 1.0f); | |
| 177 | ✗ | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| 178 | |||
| 179 | ✗ | auto bound_camera_buffer = BoundUniformBuffer{pimpl->camera_uniform_buffer.buffer.get()}; | |
| 180 | ✗ | pimpl->camera_uniform_buffer.set_props(compiled_camera); | |
| 181 | |||
| 182 | ✗ | std::vector<TransparentMesh> transparent_meshes; | |
| 183 | |||
| 184 | // render solids | ||
| 185 | { | ||
| 186 | ✗ | if (world.meshes.empty() == false) | |
| 187 | { | ||
| 188 | ✗ | SCOPED_DEBUG_GROUP("render basic geom"sv); | |
| 189 | ✗ | for (const auto& mesh: world.meshes) | |
| 190 | { | ||
| 191 | const auto not_transparent_context | ||
| 192 | ✗ | = RenderContext{TransformSource::Uniform, UseTransparency::no, settings.gamma, &shadow_context}; | |
| 193 | |||
| 194 | ✗ | if (mesh->material->is_transparent()) | |
| 195 | { | ||
| 196 | ✗ | transparent_meshes.emplace_back( | |
| 197 | ✗ | TransparentMesh{ | |
| 198 | .mesh = mesh, | ||
| 199 | ✗ | .squared_distance_to_camera = v3::from_to(compiled_camera.position, mesh->transform.get_translation()).get_length_squared() | |
| 200 | } | ||
| 201 | ); | ||
| 202 | |||
| 203 | ✗ | continue; | |
| 204 | } | ||
| 205 | ✗ | StateChanger{pimpl->states} | |
| 206 | ✗ | .depth_test(true) | |
| 207 | ✗ | .depth_mask(true) | |
| 208 | ✗ | .depth_func(Compare::less) | |
| 209 | ✗ | .blending(false) | |
| 210 | ✗ | .stencil_mask(0x0) | |
| 211 | ✗ | .stencil_func(Compare::always, 1, 0xFF); | |
| 212 | |||
| 213 | ✗ | if (mesh->outline) | |
| 214 | { | ||
| 215 | ✗ | StateChanger{pimpl->states}.stencil_func(Compare::always, 1, 0xFF).stencil_mask(0xFF); | |
| 216 | } | ||
| 217 | ✗ | mesh->material->use_shader(not_transparent_context); | |
| 218 | ✗ | mesh->material->set_uniforms( | |
| 219 | ✗ | not_transparent_context, compiled_camera, mesh->transform | |
| 220 | ); | ||
| 221 | ✗ | mesh->material->bind_textures(not_transparent_context, pimpl->states, assets); | |
| 222 | ✗ | mesh->material->apply_lights(not_transparent_context, world.lights, settings, pimpl->states, assets); | |
| 223 | |||
| 224 | ✗ | render_geom(*mesh->geom); | |
| 225 | } | ||
| 226 | ✗ | } | |
| 227 | |||
| 228 | ✗ | if (world.instances.empty() == false) | |
| 229 | { | ||
| 230 | ✗ | SCOPED_DEBUG_GROUP("render instances"sv); | |
| 231 | ✗ | for (const auto& instance: world.instances) | |
| 232 | { | ||
| 233 | ✗ | const auto not_transparent_context = RenderContext{TransformSource::Instanced_mat4, UseTransparency::no, settings.gamma, &shadow_context}; | |
| 234 | |||
| 235 | ✗ | StateChanger{pimpl->states} | |
| 236 | ✗ | .depth_test(true) | |
| 237 | ✗ | .depth_mask(true) | |
| 238 | ✗ | .depth_func(Compare::less) | |
| 239 | ✗ | .blending(false) | |
| 240 | ✗ | .stencil_mask(0x0) | |
| 241 | ✗ | .stencil_func(Compare::always, 1, 0xFF); | |
| 242 | ✗ | instance->material->use_shader(not_transparent_context); | |
| 243 | ✗ | instance->material->set_uniforms( | |
| 244 | // todo(Gustav): should we really set the model matrix for instanced meshes? | ||
| 245 | not_transparent_context, | ||
| 246 | compiled_camera, | ||
| 247 | std::nullopt | ||
| 248 | ); | ||
| 249 | ✗ | instance->material->bind_textures(not_transparent_context, pimpl->states, assets); | |
| 250 | ✗ | instance->material->apply_lights( | |
| 251 | ✗ | not_transparent_context, world.lights, settings, pimpl->states, assets | |
| 252 | ); | ||
| 253 | |||
| 254 | ✗ | render_geom_instanced(*instance); | |
| 255 | } | ||
| 256 | ✗ | } | |
| 257 | |||
| 258 | ✗ | if (debug.lines.empty() == false) | |
| 259 | { | ||
| 260 | ✗ | SCOPED_DEBUG_GROUP("render debug lines"sv); | |
| 261 | ✗ | render_debug_lines( | |
| 262 | ✗ | debug.lines, pimpl->states, &pimpl->debug_drawer, compiled_camera, window_size, settings.gamma | |
| 263 | ); | ||
| 264 | ✗ | } | |
| 265 | } | ||
| 266 | |||
| 267 | // todo(Gustav): should this be done at the start of the update so the renderer can be "const" | ||
| 268 | ✗ | debug.lines.clear(); | |
| 269 | |||
| 270 | // render skybox | ||
| 271 | ✗ | if (world.skybox && world.skybox->cubemap != nullptr && world.skybox->geom != nullptr) | |
| 272 | { | ||
| 273 | ✗ | SCOPED_DEBUG_GROUP("render skybox"sv); | |
| 274 | ✗ | StateChanger{pimpl->states} | |
| 275 | ✗ | .depth_test(true) | |
| 276 | ✗ | .depth_mask(false) | |
| 277 | ✗ | .depth_func(Compare::less_equal) | |
| 278 | ✗ | .blending(false) | |
| 279 | ✗ | .stencil_mask(0x0) | |
| 280 | ✗ | .stencil_func(Compare::always, 1, 0xFF); | |
| 281 | |||
| 282 | ✗ | auto& shader = pimpl->shaders_resources.skybox_shader; | |
| 283 | |||
| 284 | ✗ | shader.program->use(); | |
| 285 | ✗ | bind_texture_cubemap(pimpl->states, shader.tex_skybox_uniform, *world.skybox->cubemap); | |
| 286 | |||
| 287 | ✗ | render_geom(*world.skybox->geom); | |
| 288 | ✗ | } | |
| 289 | |||
| 290 | ✗ | std::sort( | |
| 291 | transparent_meshes.begin(), | ||
| 292 | transparent_meshes.end(), | ||
| 293 | ✗ | [](const auto& lhs, const auto& rhs) { return lhs.squared_distance_to_camera > rhs.squared_distance_to_camera; } | |
| 294 | ); | ||
| 295 | |||
| 296 | ✗ | if (transparent_meshes.empty() == false) | |
| 297 | { | ||
| 298 | ✗ | SCOPED_DEBUG_GROUP("render transparent meshes"sv); | |
| 299 | ✗ | for (auto& transparent_mesh: transparent_meshes) | |
| 300 | { | ||
| 301 | ✗ | const auto transparent_context = RenderContext{TransformSource::Uniform, UseTransparency::yes, settings.gamma, &shadow_context}; | |
| 302 | |||
| 303 | ✗ | const auto& mesh = transparent_mesh.mesh; | |
| 304 | ✗ | StateChanger{pimpl->states} | |
| 305 | ✗ | .depth_test(true) | |
| 306 | ✗ | .depth_mask(true) | |
| 307 | ✗ | .depth_func(Compare::less) | |
| 308 | ✗ | .blending(true) | |
| 309 | ✗ | .stencil_mask(0x0) | |
| 310 | ✗ | .stencil_func(Compare::always, 1, 0xFF); | |
| 311 | |||
| 312 | ✗ | if (mesh->outline) | |
| 313 | { | ||
| 314 | ✗ | StateChanger{pimpl->states}.stencil_func(Compare::always, 1, 0xFF).stencil_mask(0xFF); | |
| 315 | } | ||
| 316 | ✗ | mesh->material->use_shader(transparent_context); | |
| 317 | ✗ | mesh->material->set_uniforms(transparent_context, compiled_camera, mesh->transform); | |
| 318 | ✗ | mesh->material->bind_textures(transparent_context, pimpl->states, assets); | |
| 319 | ✗ | mesh->material->apply_lights(transparent_context, world.lights, settings, pimpl->states, assets); | |
| 320 | |||
| 321 | ✗ | render_geom(*mesh->geom); | |
| 322 | } | ||
| 323 | ✗ | } | |
| 324 | |||
| 325 | // render outline over all other meshes | ||
| 326 | ✗ | if (has_outlined_meshes) | |
| 327 | { | ||
| 328 | ✗ | SCOPED_DEBUG_GROUP("render outline meshes"sv); | |
| 329 | ✗ | for (const auto& mesh: world.meshes) | |
| 330 | { | ||
| 331 | ✗ | if (const auto& mesh_outline = mesh->outline) | |
| 332 | { | ||
| 333 | ✗ | StateChanger{pimpl->states} | |
| 334 | ✗ | .stencil_func(Compare::not_equal, 1, 0xFF) | |
| 335 | ✗ | .stencil_mask(0x00) | |
| 336 | ✗ | .depth_test(false); | |
| 337 | ✗ | const m4 small_scale_mat = m4::from_scale(v3{OUTLINE_SCALE, OUTLINE_SCALE, OUTLINE_SCALE}); | |
| 338 | |||
| 339 | ✗ | auto& shader = pimpl->shaders_resources.single_color_shader; | |
| 340 | ✗ | shader.program->use(); | |
| 341 | ✗ | shader.program->set_vec4(shader.tint_color_uni, v4{vec_from_lin(linear_from_srgb(*mesh_outline, settings.gamma)), 1}); | |
| 342 | |||
| 343 | ✗ | shader.program->set_mat(shader.world_from_local_uni, mesh->transform * small_scale_mat); | |
| 344 | |||
| 345 | ✗ | render_geom(*mesh->geom); | |
| 346 | } | ||
| 347 | } | ||
| 348 | ✗ | } | |
| 349 | ✗ | } | |
| 350 | |||
| 351 | ✗ | void Renderer::render_shadows(const Size& window_size, const World& world, const CompiledCamera& compiled_camera) const | |
| 352 | { | ||
| 353 | // todo(Gustav): bind less colors and use depth only shaders | ||
| 354 | ✗ | SCOPED_DEBUG_GROUP("render shadows call"sv); | |
| 355 | ✗ | StateChanger{pimpl->states} | |
| 356 | ✗ | .cull_face(true) | |
| 357 | ✗ | .cull_face_mode(CullFace::back) | |
| 358 | ✗ | .depth_test(true) | |
| 359 | ✗ | .depth_mask(true); | |
| 360 | |||
| 361 | ✗ | glClear(GL_DEPTH_BUFFER_BIT); | |
| 362 | |||
| 363 | ✗ | auto bound_camera_buffer = BoundUniformBuffer{pimpl->camera_uniform_buffer.buffer.get()}; | |
| 364 | ✗ | pimpl->camera_uniform_buffer.set_props(compiled_camera); | |
| 365 | |||
| 366 | // todo(Gustav): change shader to something that doesn't write color | ||
| 367 | |||
| 368 | ✗ | StateChanger{pimpl->states} | |
| 369 | ✗ | .depth_test(true) | |
| 370 | ✗ | .depth_mask(true) | |
| 371 | ✗ | .depth_func(Compare::less) | |
| 372 | ✗ | .blending(false) | |
| 373 | ✗ | .stencil_mask(0x0) | |
| 374 | ✗ | .stencil_func(Compare::always, 1, 0xFF); | |
| 375 | |||
| 376 | // render solids | ||
| 377 | { | ||
| 378 | ✗ | if (world.meshes.empty() == false) | |
| 379 | { | ||
| 380 | ✗ | SCOPED_DEBUG_GROUP("render basic geom"sv); | |
| 381 | ✗ | for (const auto& mesh: world.meshes) | |
| 382 | { | ||
| 383 | ✗ | if (mesh->material->is_transparent()) | |
| 384 | { | ||
| 385 | ✗ | continue; | |
| 386 | } | ||
| 387 | |||
| 388 | ✗ | auto& shader = pimpl->shaders_resources.depth_transform_uniform; | |
| 389 | ✗ | shader.program->use(); | |
| 390 | |||
| 391 | // todo(Gustav): the depth shader should not be a shared type between transform uniform and instanced, this should be verified at compile time with different types | ||
| 392 | ✗ | ASSERT(shader.world_from_local_uni.has_value()); | |
| 393 | ✗ | if (shader.world_from_local_uni) | |
| 394 | { | ||
| 395 | ✗ | shader.program->set_mat(*shader.world_from_local_uni, mesh->transform); | |
| 396 | } | ||
| 397 | |||
| 398 | ✗ | if (mesh->billboarding != Billboarding::none) | |
| 399 | { | ||
| 400 | ✗ | continue; | |
| 401 | } | ||
| 402 | ✗ | render_geom(*mesh->geom); | |
| 403 | } | ||
| 404 | ✗ | } | |
| 405 | |||
| 406 | ✗ | if (world.instances.empty() == false) | |
| 407 | { | ||
| 408 | ✗ | SCOPED_DEBUG_GROUP("render instances"sv); | |
| 409 | ✗ | for (const auto& instance: world.instances) | |
| 410 | { | ||
| 411 | ✗ | auto& shader = pimpl->shaders_resources.depth_transform_instanced_mat4; | |
| 412 | ✗ | shader.program->use(); | |
| 413 | ✗ | ASSERT(shader.world_from_local_uni.has_value() == false); | |
| 414 | |||
| 415 | ✗ | render_geom_instanced(*instance); | |
| 416 | } | ||
| 417 | ✗ | } | |
| 418 | |||
| 419 | ✗ | if (debug.lines.empty() == false) | |
| 420 | { | ||
| 421 | ✗ | SCOPED_DEBUG_GROUP("render debug lines"sv); | |
| 422 | ✗ | render_debug_lines( | |
| 423 | ✗ | debug.lines, pimpl->states, &pimpl->debug_drawer, compiled_camera, window_size, settings.gamma | |
| 424 | ); | ||
| 425 | ✗ | } | |
| 426 | } | ||
| 427 | ✗ | } | |
| 428 | |||
| 429 | } // namespace eu::render | ||
| 430 |