draw_instanced3d() vẽ cả một khúc bộ đệm bằng một lệnh, nhưng card đồ họa vẫn xử lý từng instance trong khúc đó, kể cả những cái nằm sau lưng camera. Với một khu rừng hay một thành phố hàng chục nghìn vật mà camera chỉ thấy một góc, phần lớn công sức ấy bỏ phí. njin_spatial_batch.h giải quyết bằng cách chia mặt đất thành ô: xếp instance trong bộ đệm theo ô một lần, rồi mỗi frame chọn những ô cần vẽ và nhận lại các khúc bộ đệm tương ứng. Cũng cách đó chọn được mức chi tiết: ô gần vẽ hình mịn, ô xa vẽ hình ít mặt.
#include <njin.h>
#include <algorithm>
#include <cmath>
#include <vector>
namespace {
using namespace njin;
constexpr i32 tree_count = 40000;
constexpr i32 floats = 16;
const batch_grid2d grid{.origin = {-1000.0f, -1000.0f}, .cell_size = {40.0f, 40.0f}, .cols = 50, .rows = 50};
std::vector<u32> offsets;
std::vector<u8> near_cells, far_cells;
void load(context &ctx) {
std::vector<f32> unsorted(tree_count * floats, 0.0f);
std::vector<i32>
cell(tree_count);
offsets.assign(grid.
count() + 1, 0);
for (
i32 i = 0; i < tree_count; i++) {
f32 *t = &unsorted[i * floats];
t[0] = r.
range(-1000.0f, 1000.0f);
t[2] = r.
range(-1000.0f, 1000.0f);
t[5] = r.
range(0.35f, 0.6f);
t[4] = t[6] = 0.2f;
t[7] = 1.0f;
t[12] = t[14] = 0.5f;
t[13] = r.
range(4.0f, 8.0f);
}
offsets[c + 1] += offsets[c];
std::vector<u32> next(offsets.begin(), offsets.end() - 1);
std::vector<f32> sorted(unsorted.size());
for (
i32 i = 0; i < tree_count; i++)
std::copy_n(&unsorted[i * floats], floats, &sorted[next[
cell[i]]++ * floats]);
near_cells.resize(grid.
count());
far_cells.resize(grid.
count());
}
void render(context &ctx) {
const vec3 target{std::cos(a) * 500.0f, 0.0f, std::sin(a) * 500.0f};
const camera3d camera{.position = {target.
x - 120.0f, 80.0f, target.
z - 120.0f}, .target = target};
.hi = {target.
x + 400.0f, target.
z + 400.0f},
.overhang = 0.5f};
for (
i32 c = 0; c < grid.
count(); c++) {
near_cells[c] = seen && close;
far_cells[c] = seen && !close;
}
draw_plane3d(ctx, {0.0f, 0.0f, 0.0f}, {2000.0f, 2000.0f}, {0.45f, 0.5f, 0.3f, 1.0f});
}
void setup(context &ctx) {
}
}
mod_desc spatial_batch_module() {
return {.name =
"spatial_batch", .setup = setup}; }
void begin_3d(context &ctx, const camera3d &camera)
Bắt đầu vẽ 3D qua camera.
void draw_plane3d(const context &ctx, vec3 center, vec2 size, rgba color)
Vẽ mặt phẳng nằm ngang (song song mặt xz), mặt trên hướng +y.
void end_3d(context &ctx)
Kết thúc vẽ 3D và quay về vẽ 2D trong không gian thế giới.
void draw_instanced3d(const context &ctx, mesh3d_kind mesh, instance_buffer_handle buffer, u32 first, u32 count, shader_handle shader={})
Vẽ count bản của một hình có sẵn bằng một lệnh vẽ, mỗi bản đặt theo dữ liệu của nó trong một bộ đệm i...
@ mesh3d_cylinder
Trụ bán kính 1, đáy ở gốc, cao 1 theo +y.
Definition njin_3d.h:927
@ mesh3d_cylinder_low
Như mesh3d_cylinder nhưng 8 cạnh thay vì 48, cùng lý do.
Definition njin_3d.h:933
bool batch_cell_detailed(const batch_grid2d &grid, const batch_view2d &view, i32 cell, f32 radius)
Ô cell có ở gần không: khoảng cách từ view.focus đến hình chữ nhật của ô nhỏ hơn radius.
std::vector< instance_range > batch_instance_ranges(std::span< const u32 > offsets, std::span< const u8 > wanted, std::span< const instance_range > excluded={})
Các đoạn instance cần vẽ, từ những ô được chọn.
bool batch_cell_visible(const batch_grid2d &grid, const batch_view2d &view, i32 cell)
Ô cell có lọt vào vùng nhìn không: hình chữ nhật của ô, nới thêm overhang, có chạm hình chữ nhật lo .
void instance_buffer_upload(context &ctx, instance_buffer_handle handle, const f32 *data, u32 count)
Ghi count instance vào bộ đệm, thay toàn bộ nội dung cũ.
instance_buffer_handle instance_buffer_create(context &ctx, u32 floats_per_instance)
Tạo một bộ đệm instance trên GPU: mỗi instance là floats_per_instance số thực, do game tự quyết định ...
void ecs_register(context &ctx, sys_phase phase, sys_fnc fnc, const char *name=nullptr)
Thêm một system vào lịch chạy của một phase.
@ phase_startup
Một lần, trước frame đầu tiên.
Definition _mod.h:32
@ phase_render
Mỗi frame, vẽ trong không gian thế giới.
Definition _mod.h:38
rng & random(context &ctx)
Bộ sinh số ngẫu nhiên dùng chung của engine.
f32 elapsed(const context &ctx)
Thời gian đã trôi qua kể từ lúc mở cửa sổ, tính bằng giây.
std::int32_t i32
Số nguyên có dấu 32 bit.
Definition _types.h:14
float f32
Số thực 32 bit.
Definition _types.h:28
Lưới chia mặt đất thành ô, để nhóm instance theo ô chứa tâm của chúng.
Definition njin_spatial_batch.h:17
i32 count() const
Số ô, cols * rows.
i32 cell_at(vec2 p) const
Ô chứa điểm p: hàng * cols + cột.
Phần mặt phẳng một camera thấy, do game dựng từ camera của mình mỗi frame.
Definition njin_spatial_batch.h:37
Camera phối cảnh cho begin_3d().
Definition njin_3d.h:15
vec3 position
Vị trí mắt.
Definition njin_3d.h:16
Vị trí một ô trong lưới. Có thể âm.
Definition _tilemap.h:43
Định danh của một bộ đệm instance, tạo bởi instance_buffer_create().
Definition _types.h:109
Mô tả một module: một nhóm system có tên.
Definition _mod.h:67
Bộ sinh số ngẫu nhiên PCG32: nhanh, chất lượng tốt, và lặp lại được: cùng hạt giống thì cùng dãy số.
Definition _random.h:13
f32 range(f32 lo, f32 hi)
Số thực trong [lo, hi).
Definition _random.h:50
Vector 3 chiều.
Definition _types.h:47
f32 x
Thành phần thứ nhất.
Definition _types.h:48
f32 z
Thành phần thứ ba.
Definition _types.h:50
Ô liền nhau được chọn thì gộp thành một khúc, nên một vùng nhìn thấy thường chỉ tốn vài lệnh vẽ. Ô rỗng không sao.
Kích thước ô là sự đánh đổi: ô nhỏ thì vẽ sát vùng nhìn hơn nhưng nhiều khúc hơn (nhiều lệnh vẽ); ô to thì ngược lại. Bắt đầu với ô cỡ vài lần vật lớn nhất, rồi đo.
batch_cell_visible() cho biết ô có chạm vùng nhìn không. batch_cell_detailed() cho biết ô có cách focus dưới một bán kính không, và không xét vùng nhìn: kết hợp hai hàm như ví dụ để có hai mức chi tiết, mỗi mức một mảng wanted.
Đây là phép lọc thô theo hình chữ nhật, không phải kiểm tra frustum 3D hay che khuất: vật trong hình chữ nhật mà nằm ngoài khung hình vẫn được vẽ. Mỗi camera (màn hình chính, minimap, gương) giữ một vùng nhìn riêng và dùng chung bộ đệm.
Các khúc bỏ có thể chồng nhau và không cần sắp xếp.
Các hàm chỉ đọc dữ liệu vào và không giữ gì giữa các lần gọi, nên gọi được cho nhiều camera, và trên thread khác miễn là không ai sửa dữ liệu vào cùng lúc. Giữ mảng wanted qua các frame để khỏi cấp phát lại.