mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
perf(render): batch model rendering with instancing
Replaces per-entity draw calls with instanced rendering for both main and shadow passes. Each model's node hierarchy is flattened once and instance matrices are updated per frame, reducing draw calls and CPU overhead.
This commit is contained in:
@@ -18,77 +18,112 @@ float swing_angle(const WalkPose& pose, float speed, float amp_deg) {
|
||||
|
||||
ModelRender::ModelRender(Renderer& renderer) : m_renderer(renderer) {}
|
||||
|
||||
void ModelRender::render_model(ModelID id, const glm::vec3& pos, float yaw,
|
||||
Camera& camera, const WalkPose& pose) {
|
||||
void ModelRender::render_instance(ModelID id,
|
||||
std::span<const InstanceData> instances,
|
||||
const Camera& camera, bool shadow) {
|
||||
if (instances.empty()) {
|
||||
return;
|
||||
}
|
||||
auto& root = ModelManager::model(id).node;
|
||||
glm::vec3 bob_pos = pos;
|
||||
if (pose.gait != Gait::STOP &&
|
||||
root.anim.has_role(NodeAnimRule::Role::LEG)) {
|
||||
bob_pos.y += std::abs(glm::sin(pose.walk_time * root.anim.walk_speed)) *
|
||||
root.anim.body_bob;
|
||||
auto& batch = get_batch(id, root);
|
||||
const size_t PER_INSTANCE_SIZE = batch.node_count * sizeof(glm::mat4);
|
||||
if (instances.size() > batch.capacity) {
|
||||
batch.capacity = instances.size() * 2;
|
||||
batch.instance_matrices.resize(batch.capacity * batch.node_count);
|
||||
batch.instance_vbo = std::make_unique<VertexBuffer>();
|
||||
batch.instance_vbo->buffer_data(batch.instance_matrices.data(),
|
||||
batch.instance_matrices.size() *
|
||||
sizeof(glm::mat4),
|
||||
BufferUsage::DYNAMIC_DRAW);
|
||||
for (const auto& entry : batch.entries) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
entry.mesh->vao->attribute(
|
||||
3 + col, 4, GL_FLOAT, PER_INSTANCE_SIZE,
|
||||
(void*)(entry.node_slot * sizeof(glm::mat4) +
|
||||
col * sizeof(glm::vec4)));
|
||||
entry.mesh->vao->divisor(3 + col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 transform = glm::translate(glm::mat4(1.0f), bob_pos) *
|
||||
glm::rotate(glm::mat4(1.0f), yaw, {0, 1, 0});
|
||||
auto& shader = m_renderer.get_shader("model_shader");
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
glm::vec3 bob_pos = instances[i].pos;
|
||||
if (instances[i].pose.gait != Gait::STOP &&
|
||||
root.anim.has_role(NodeAnimRule::Role::LEG)) {
|
||||
float speed = instances[i].pose.gait == Gait::RUN
|
||||
? root.anim.run_speed
|
||||
: root.anim.walk_speed;
|
||||
bob_pos.y +=
|
||||
std::abs(glm::sin(instances[i].pose.walk_time * speed)) *
|
||||
root.anim.body_bob;
|
||||
}
|
||||
|
||||
glm::mat4 transform =
|
||||
glm::translate(glm::mat4(1.0f), bob_pos) *
|
||||
glm::rotate(glm::mat4(1.0f), instances[i].yaw, {0, 1, 0});
|
||||
collect_matrices(root, transform, instances[i].pose, root.anim,
|
||||
batch.instance_matrices, i * batch.node_count);
|
||||
}
|
||||
|
||||
batch.instance_vbo->buffer_sub_data(
|
||||
batch.instance_matrices.data(),
|
||||
instances.size() * batch.node_count * sizeof(glm::mat4), 0);
|
||||
|
||||
auto& shader = shadow ? m_renderer.get_shader("depth_model_instance")
|
||||
: m_renderer.get_shader("model_instance");
|
||||
glm::mat4 view = camera.get_camera_lookat();
|
||||
shader.set_loc("proj_matrix", m_renderer.p_mat());
|
||||
render_node(root, transform, view, shader, false, pose, root.anim);
|
||||
}
|
||||
|
||||
void ModelRender::shadow_pass(ModelID id, const glm::vec3& pos, float yaw,
|
||||
Camera& camera, const WalkPose& pose) {
|
||||
auto& root = ModelManager::model(id).node;
|
||||
glm::vec3 bob_pos = pos;
|
||||
if (pose.gait != Gait::STOP &&
|
||||
root.anim.has_role(NodeAnimRule::Role::LEG)) {
|
||||
bob_pos.y += std::abs(glm::sin(pose.walk_time * root.anim.walk_speed)) *
|
||||
root.anim.body_bob;
|
||||
}
|
||||
|
||||
glm::mat4 transform = glm::translate(glm::mat4(1.0f), bob_pos) *
|
||||
glm::rotate(glm::mat4(1.0f), yaw, {0, 1, 0});
|
||||
|
||||
auto& shader = m_renderer.get_shader("depth_model");
|
||||
glm::mat4 view = camera.get_camera_lookat();
|
||||
|
||||
render_node(root, transform, view, shader, true, pose, root.anim);
|
||||
}
|
||||
|
||||
void ModelRender::render_node(const ModelNode& node, const glm::mat4& parent,
|
||||
const glm::mat4& view, const Shader& shader,
|
||||
bool shadow, const WalkPose& pose,
|
||||
const ModelAnimConfig& cfg) {
|
||||
|
||||
glm::mat4 transform = parent * pose_node(node, cfg, pose);
|
||||
if (shadow) {
|
||||
shader.set_loc("modelMatrix", transform);
|
||||
} else {
|
||||
glm::mat4 mv_matrix = view * transform;
|
||||
shader.set_loc("modelMatrix", transform);
|
||||
shader.set_loc("mv_matrix", mv_matrix);
|
||||
shader.set_loc("norm_matrix", glm::transpose(glm::inverse(mv_matrix)));
|
||||
shader.set_loc("view_matrix", view);
|
||||
}
|
||||
|
||||
for (auto& mesh : node.meshes) {
|
||||
render_mesh(mesh, shadow);
|
||||
}
|
||||
|
||||
for (auto& child : node.children) {
|
||||
render_node(child, transform, view, shader, shadow, pose, cfg);
|
||||
for (const auto& entry : batch.entries) {
|
||||
if (entry.mesh->texture) {
|
||||
entry.mesh->texture->bind(1);
|
||||
}
|
||||
entry.mesh->vao->bind();
|
||||
glDrawElementsInstanced(GL_TRIANGLES, entry.mesh->indices.size(),
|
||||
GL_UNSIGNED_INT, 0, instances.size());
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRender::render_mesh(const Mesh& mesh, bool) {
|
||||
mesh.vao->bind();
|
||||
if (mesh.texture) {
|
||||
mesh.texture->bind(1);
|
||||
} else {
|
||||
Logger::error("Model Texture Not Find");
|
||||
size_t ModelRender::collect_matrices(const ModelNode& node,
|
||||
const glm::mat4& parent,
|
||||
const WalkPose& pose,
|
||||
const ModelAnimConfig& cfg,
|
||||
std::vector<glm::mat4>& out, size_t slot) {
|
||||
glm::mat4 transform = parent * pose_node(node, cfg, pose);
|
||||
out[slot] = transform;
|
||||
size_t next = slot + 1;
|
||||
for (const auto& clild : node.children) {
|
||||
next = collect_matrices(clild, transform, pose, cfg, out, next);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
ModelRender::ModelBatch& ModelRender::get_batch(ModelID id,
|
||||
const ModelNode& root) {
|
||||
auto it = m_batches.find(id);
|
||||
if (it != m_batches.end()) {
|
||||
return it->second;
|
||||
}
|
||||
ModelBatch batch;
|
||||
batch.node_count = flatten_nodes(root, 0, batch);
|
||||
return m_batches.try_emplace(id, std::move(batch)).first->second;
|
||||
}
|
||||
|
||||
size_t ModelRender::flatten_nodes(const ModelNode& node, size_t slot,
|
||||
ModelBatch& batch) {
|
||||
size_t node_slot = slot++;
|
||||
for (const auto& mesh : node.meshes) {
|
||||
batch.entries.emplace_back(&mesh, node_slot);
|
||||
}
|
||||
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()),
|
||||
GL_UNSIGNED_INT, 0);
|
||||
for (const auto& child : node.children) {
|
||||
slot = flatten_nodes(child, slot, batch);
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
glm::mat4 ModelRender::pose_node(const ModelNode& node,
|
||||
|
||||
@@ -35,6 +35,11 @@ void ShaderManager::init() {
|
||||
"shaders/rect_f_shader.glsl");
|
||||
register_shader("model_shader", "shaders/model_vert.glsl",
|
||||
"shaders/model_frag.glsl");
|
||||
register_shader("model_instance", "shaders/model_instance_vert.glsl",
|
||||
"shaders/model_frag.glsl");
|
||||
register_shader("depth_model_instance",
|
||||
"shaders/depth_model_instance_vert.glsl",
|
||||
"shaders/depth_model_frag.glsl");
|
||||
}
|
||||
|
||||
void ShaderManager::register_shader(const std::string& name,
|
||||
|
||||
@@ -35,5 +35,8 @@ void VertexArray::attribute(GLuint index, GLint size, GLenum type,
|
||||
stride, ptr);
|
||||
glEnableVertexAttribArray(index);
|
||||
}
|
||||
|
||||
void VertexArray::divisor(GLuint index, GLuint divisor) {
|
||||
bind();
|
||||
glVertexAttribDivisor(index, divisor);
|
||||
}
|
||||
} // namespace Cubed
|
||||
@@ -48,5 +48,10 @@ void VertexBuffer::buffer_data(const void* data, GLsizeiptr size,
|
||||
|
||||
glBufferData(target, size, data, std::to_underlying(usage));
|
||||
}
|
||||
void VertexBuffer::buffer_sub_data(const void* data, GLsizeiptr size,
|
||||
GLintptr offset) const {
|
||||
bind();
|
||||
glBufferSubData(get_buffer_target(), offset, size, data);
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -11,7 +11,37 @@
|
||||
#include "Cubed/tools/math_tools.hpp"
|
||||
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <unordered_map>
|
||||
namespace Cubed {
|
||||
|
||||
namespace {
|
||||
std::unordered_map<ModelID, std::vector<ModelRender::InstanceData>>
|
||||
get_instances_data_map(ClientWorld& world) {
|
||||
std::unordered_map<ModelID, std::vector<ModelRender::InstanceData>>
|
||||
instances_data_map;
|
||||
auto& registry = world.entity_manager().get_registry();
|
||||
auto view = registry.view<BaseClientCreature, RenderTransform>();
|
||||
for (auto entity : view) {
|
||||
auto& creature = view.get<BaseClientCreature>(entity);
|
||||
auto pos = creature.transform.position.value;
|
||||
if (!world.is_render(pos)) {
|
||||
continue;
|
||||
}
|
||||
auto& t = view.get<RenderTransform>(entity);
|
||||
float yaw = std::atan2(t.direction.value.x, t.direction.value.z);
|
||||
ModelRender::InstanceData data;
|
||||
data.pos = t.position.value;
|
||||
data.yaw = yaw;
|
||||
data.pose = creature.pose;
|
||||
instances_data_map[creature.model].emplace_back(std::move(data));
|
||||
// m_renderer.model_renderer().shadow_pass(
|
||||
// creature.model, t.position.value, yaw,
|
||||
// world.world_scene().camera(), creature.pose);
|
||||
}
|
||||
return instances_data_map;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
WorldRenderer::WorldRenderer(Renderer& renderer)
|
||||
: m_renderer(renderer), m_player_renderer(renderer),
|
||||
m_texture_manager(renderer.texture_mamger()) {}
|
||||
@@ -292,26 +322,24 @@ void WorldRenderer::render_outline(ClientWorld& world) {
|
||||
|
||||
void WorldRenderer::shadow_entity(ClientWorld& world,
|
||||
const glm::mat4& light_matrix) {
|
||||
auto& shader = m_renderer.get_shader("depth_model");
|
||||
shader.use();
|
||||
shader.set_loc("lightSpaceMatrix", light_matrix);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
auto& registry = world.entity_manager().get_registry();
|
||||
auto view = registry.view<BaseClientCreature, RenderTransform>();
|
||||
for (auto entity : view) {
|
||||
auto& creature = view.get<BaseClientCreature>(entity);
|
||||
auto pos = creature.transform.position.value;
|
||||
if (!world.is_render(pos)) {
|
||||
continue;
|
||||
{
|
||||
auto& shader = m_renderer.get_shader("depth_model_instance");
|
||||
shader.use();
|
||||
shader.set_loc("lightSpaceMatrix", light_matrix);
|
||||
auto instances_data_map = get_instances_data_map(world);
|
||||
for (auto& [id, data] : instances_data_map) {
|
||||
m_renderer.model_renderer().render_instance(
|
||||
id, data, world.world_scene().camera(), true);
|
||||
}
|
||||
auto& t = view.get<RenderTransform>(entity);
|
||||
float yaw = std::atan2(t.direction.value.x, t.direction.value.z);
|
||||
|
||||
m_renderer.model_renderer().shadow_pass(
|
||||
creature.model, t.position.value, yaw, world.world_scene().camera(),
|
||||
creature.pose);
|
||||
}
|
||||
m_player_renderer.render(shader, world, true);
|
||||
|
||||
{
|
||||
auto& shader = m_renderer.get_shader("depth_model");
|
||||
shader.use();
|
||||
shader.set_loc("lightSpaceMatrix", light_matrix);
|
||||
m_player_renderer.render(shader, world, true);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::shadow_map_generate(ClientWorld& world) {
|
||||
@@ -700,46 +728,57 @@ void WorldRenderer::render_transparent_block(const glm::mat4& mv_mat,
|
||||
}
|
||||
|
||||
void WorldRenderer::render_entity(ClientWorld& world) {
|
||||
auto& shader = m_renderer.get_shader("model_shader");
|
||||
shader.use();
|
||||
glm::vec3 light_dir_view =
|
||||
glm::normalize(glm::mat3(view_matrix) * m_parallel_light.lightdir);
|
||||
|
||||
shader.set_loc("lightSpaceMatrix", m_parallel_light.light_space_matrix);
|
||||
shader.set_loc("ambientStrength", m_ambient_strength);
|
||||
shader.set_loc("sunlightColor", m_parallel_light.directional_light_color);
|
||||
shader.set_loc("ambientColor", m_parallel_light.finnal_ambient_color);
|
||||
shader.set_loc("sunlightDir", light_dir_view);
|
||||
shader.set_loc("shadowMode", m_shadow_mode);
|
||||
shader.set_loc("shader_on", m_shader_on);
|
||||
shader.set_loc("lightSizeUV", static_cast<float>(m_light_size_uv));
|
||||
shader.set_loc("minRadius", m_min_radius);
|
||||
shader.set_loc("maxRadius", m_max_radius);
|
||||
shader.set_loc("samples", m_samples);
|
||||
|
||||
// shader.set_loc("renderDistance", m_world.rendering_distance());
|
||||
// shader.set_loc("skyColor", m_sky_uniform.sky_top);
|
||||
|
||||
m_depth_map_texture->bind(0);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
{
|
||||
auto& model = m_renderer.get_shader("model_instance");
|
||||
model.use();
|
||||
glm::vec3 light_dir_view =
|
||||
glm::normalize(glm::mat3(view_matrix) * m_parallel_light.lightdir);
|
||||
|
||||
auto& registry = world.entity_manager().get_registry();
|
||||
auto view = registry.view<BaseClientCreature, RenderTransform>();
|
||||
for (auto entity : view) {
|
||||
auto& creature = view.get<BaseClientCreature>(entity);
|
||||
auto pos = creature.transform.position.value;
|
||||
if (!world.is_render(pos)) {
|
||||
continue;
|
||||
model.set_loc("lightSpaceMatrix", m_parallel_light.light_space_matrix);
|
||||
model.set_loc("ambientStrength", m_ambient_strength);
|
||||
model.set_loc("sunlightColor",
|
||||
m_parallel_light.directional_light_color);
|
||||
model.set_loc("ambientColor", m_parallel_light.finnal_ambient_color);
|
||||
model.set_loc("sunlightDir", light_dir_view);
|
||||
model.set_loc("shadowMode", m_shadow_mode);
|
||||
model.set_loc("shader_on", m_shader_on);
|
||||
model.set_loc("lightSizeUV", static_cast<float>(m_light_size_uv));
|
||||
model.set_loc("minRadius", m_min_radius);
|
||||
model.set_loc("maxRadius", m_max_radius);
|
||||
model.set_loc("samples", m_samples);
|
||||
m_depth_map_texture->bind(0);
|
||||
|
||||
auto instances_data_map = get_instances_data_map(world);
|
||||
for (auto& [id, data] : instances_data_map) {
|
||||
m_renderer.model_renderer().render_instance(
|
||||
id, data, world.world_scene().camera(), false);
|
||||
}
|
||||
auto& t = view.get<RenderTransform>(entity);
|
||||
float yaw = std::atan2(t.direction.value.x, t.direction.value.z);
|
||||
m_renderer.model_renderer().render_model(
|
||||
creature.model, t.position.value, yaw, world.world_scene().camera(),
|
||||
creature.pose);
|
||||
}
|
||||
|
||||
m_player_renderer.render(shader, world, false);
|
||||
{
|
||||
auto& model = m_renderer.get_shader("model_shader");
|
||||
model.use();
|
||||
glm::vec3 light_dir_view =
|
||||
glm::normalize(glm::mat3(view_matrix) * m_parallel_light.lightdir);
|
||||
|
||||
model.set_loc("lightSpaceMatrix", m_parallel_light.light_space_matrix);
|
||||
model.set_loc("ambientStrength", m_ambient_strength);
|
||||
model.set_loc("sunlightColor",
|
||||
m_parallel_light.directional_light_color);
|
||||
model.set_loc("ambientColor", m_parallel_light.finnal_ambient_color);
|
||||
model.set_loc("sunlightDir", light_dir_view);
|
||||
model.set_loc("shadowMode", m_shadow_mode);
|
||||
model.set_loc("shader_on", m_shader_on);
|
||||
model.set_loc("lightSizeUV", static_cast<float>(m_light_size_uv));
|
||||
model.set_loc("minRadius", m_min_radius);
|
||||
model.set_loc("maxRadius", m_max_radius);
|
||||
model.set_loc("samples", m_samples);
|
||||
m_player_renderer.render(model, world, false);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 WorldRenderer::quantize_sun_direction(const glm::vec3& lightdir,
|
||||
|
||||
Reference in New Issue
Block a user