Skip to content

Commit

Permalink
Add depth sorting for node faces (minetest#11696)
Browse files Browse the repository at this point in the history
Use BSP tree to order transparent triangles
https://en.wikipedia.org/wiki/Binary_space_partitioning
  • Loading branch information
x2048 committed Apr 2, 2022
1 parent 26c046a commit b0b9732
Show file tree
Hide file tree
Showing 8 changed files with 629 additions and 93 deletions.
4 changes: 4 additions & 0 deletions builtin/settingtypes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,10 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# A restart is required after changing this.
show_entity_selectionbox (Show entity selection boxes) bool false

# Distance in nodes at which transparency depth sorting is enabled
# Use this to limit the performance impact of transparency depth sorting
transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128

[*Menus]

# Use a cloud animation for the main menu background.
Expand Down
244 changes: 171 additions & 73 deletions src/client/clientmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,32 @@ ClientMap::ClientMap(
m_cache_trilinear_filter = g_settings->getBool("trilinear_filter");
m_cache_bilinear_filter = g_settings->getBool("bilinear_filter");
m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");

}

void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
{
v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);

m_camera_position = pos;
m_camera_direction = dir;
m_camera_fov = fov;
m_camera_offset = offset;

v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);

// reorder the blocks when camera crosses block boundary
if (previous_block != current_block)
m_needs_update_drawlist = true;

// reorder transparent meshes when camera crosses node boundary
if (previous_node != current_node)
m_needs_update_transparent_meshes = true;
}

MapSector * ClientMap::emergeSector(v2s16 p2d)
{
// Check that it doesn't exist already
Expand Down Expand Up @@ -323,22 +346,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
u32 mesh_animate_count = 0;
//u32 mesh_animate_count_far = 0;

/*
Update transparent meshes
*/
if (is_transparent_pass)
updateTransparentMeshBuffers();

/*
Draw the selected MapBlocks
*/

MeshBufListList grouped_buffers;

struct DrawDescriptor {
v3s16 m_pos;
scene::IMeshBuffer *m_buffer;
bool m_reuse_material;

DrawDescriptor(const v3s16 &pos, scene::IMeshBuffer *buffer, bool reuse_material) :
m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material)
{}
};

std::vector<DrawDescriptor> draw_order;
video::SMaterial previous_material;

Expand Down Expand Up @@ -375,7 +393,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
/*
Get the meshbuffers of the block
*/
{
if (is_transparent_pass) {
// In transparent pass, the mesh will give us
// the partial buffers in the correct order
for (auto &buffer : block->mesh->getTransparentBuffers())
draw_order.emplace_back(block_pos, &buffer);
}
else {
// otherwise, group buffers across meshes
// using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);

Expand All @@ -389,35 +415,14 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)

video::SMaterial& material = buf->getMaterial();
video::IMaterialRenderer* rnd =
driver->getMaterialRenderer(material.MaterialType);
driver->getMaterialRenderer(material.MaterialType);
bool transparent = (rnd && rnd->isTransparent());
if (transparent == is_transparent_pass) {
if (!transparent) {
if (buf->getVertexCount() == 0)
errorstream << "Block [" << analyze_block(block)
<< "] contains an empty meshbuf" << std::endl;

material.setFlag(video::EMF_TRILINEAR_FILTER,
m_cache_trilinear_filter);
material.setFlag(video::EMF_BILINEAR_FILTER,
m_cache_bilinear_filter);
material.setFlag(video::EMF_ANISOTROPIC_FILTER,
m_cache_anistropic_filter);
material.setFlag(video::EMF_WIREFRAME,
m_control.show_wireframe);

if (is_transparent_pass) {
// Same comparison as in MeshBufListList
bool new_material = material.getTexture(0) != previous_material.getTexture(0) ||
material != previous_material;

draw_order.emplace_back(block_pos, buf, !new_material);

if (new_material)
previous_material = material;
}
else {
grouped_buffers.add(buf, block_pos, layer);
}
<< "] contains an empty meshbuf" << std::endl;

grouped_buffers.add(buf, block_pos, layer);
}
}
}
Expand All @@ -442,8 +447,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)

// Render all mesh buffers in order
drawcall_count += draw_order.size();

for (auto &descriptor : draw_order) {
scene::IMeshBuffer *buf = descriptor.m_buffer;
scene::IMeshBuffer *buf;

if (descriptor.m_use_partial_buffer) {
descriptor.m_partial_buffer->beforeDraw();
buf = descriptor.m_partial_buffer->getBuffer();
}
else {
buf = descriptor.m_buffer;
}

// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 2000) {
Expand All @@ -454,6 +468,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)

if (!descriptor.m_reuse_material) {
auto &material = buf->getMaterial();

// Apply filter settings
material.setFlag(video::EMF_TRILINEAR_FILTER,
m_cache_trilinear_filter);
material.setFlag(video::EMF_BILINEAR_FILTER,
m_cache_bilinear_filter);
material.setFlag(video::EMF_ANISOTROPIC_FILTER,
m_cache_anistropic_filter);
material.setFlag(video::EMF_WIREFRAME,
m_control.show_wireframe);

// pass the shadow map texture to the buffer texture
ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
if (shadow && shadow->is_active()) {
Expand All @@ -475,7 +500,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)

driver->setTransform(video::ETS_WORLD, m);
driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount();
vertex_count += buf->getIndexCount();
}

g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
Expand Down Expand Up @@ -698,7 +723,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
u32 drawcall_count = 0;
u32 vertex_count = 0;

MeshBufListList drawbufs;
MeshBufListList grouped_buffers;
std::vector<DrawDescriptor> draw_order;


int count = 0;
int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
Expand Down Expand Up @@ -727,7 +754,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
/*
Get the meshbuffers of the block
*/
{
if (is_transparent_pass) {
// In transparent pass, the mesh will give us
// the partial buffers in the correct order
for (auto &buffer : block->mesh->getTransparentBuffers())
draw_order.emplace_back(block_pos, &buffer);
}
else {
// otherwise, group buffers across meshes
// using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);

Expand All @@ -742,49 +777,74 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
video::SMaterial &mat = buf->getMaterial();
auto rnd = driver->getMaterialRenderer(mat.MaterialType);
bool transparent = rnd && rnd->isTransparent();
if (transparent == is_transparent_pass)
drawbufs.add(buf, block_pos, layer);
if (!transparent)
grouped_buffers.add(buf, block_pos, layer);
}
}
}
}

u32 buffer_count = 0;
for (auto &lists : grouped_buffers.lists)
for (MeshBufList &list : lists)
buffer_count += list.bufs.size();

draw_order.reserve(draw_order.size() + buffer_count);

// Capture draw order for all solid meshes
for (auto &lists : grouped_buffers.lists) {
for (MeshBufList &list : lists) {
// iterate in reverse to draw closest blocks first
for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
}
}

TimeTaker draw("Drawing shadow mesh buffers");

core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
u32 material_swaps = 0;

// Render all layers in order
for (auto &lists : drawbufs.lists) {
for (MeshBufList &list : lists) {
// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 1000) {
infostream << "ClientMap::renderMapShadows(): Rendering "
"took >1s, returning." << std::endl;
break;
}
for (auto &pair : list.bufs) {
scene::IMeshBuffer *buf = pair.second;

// override some material properties
video::SMaterial local_material = buf->getMaterial();
local_material.MaterialType = material.MaterialType;
local_material.BackfaceCulling = material.BackfaceCulling;
local_material.FrontfaceCulling = material.FrontfaceCulling;
local_material.BlendOperation = material.BlendOperation;
local_material.Lighting = false;
driver->setMaterial(local_material);

v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset);

driver->setTransform(video::ETS_WORLD, m);
driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount();
}
// Render all mesh buffers in order
drawcall_count += draw_order.size();

for (auto &descriptor : draw_order) {
scene::IMeshBuffer *buf;

if (descriptor.m_use_partial_buffer) {
descriptor.m_partial_buffer->beforeDraw();
buf = descriptor.m_partial_buffer->getBuffer();
}
else {
buf = descriptor.m_buffer;
}

drawcall_count += list.bufs.size();
// Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 1000) {
infostream << "ClientMap::renderMapShadows(): Rendering "
"took >1s, returning." << std::endl;
break;
}

if (!descriptor.m_reuse_material) {
// override some material properties
video::SMaterial local_material = buf->getMaterial();
local_material.MaterialType = material.MaterialType;
local_material.BackfaceCulling = material.BackfaceCulling;
local_material.FrontfaceCulling = material.FrontfaceCulling;
local_material.BlendOperation = material.BlendOperation;
local_material.Lighting = false;
driver->setMaterial(local_material);
++material_swaps;
}

v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset);

driver->setTransform(video::ETS_WORLD, m);
driver->drawMeshBuffer(buf);
vertex_count += buf->getIndexCount();
}

// restore the driver material state
Expand All @@ -796,6 +856,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}

/*
Expand Down Expand Up @@ -891,3 +952,40 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
}

void ClientMap::updateTransparentMeshBuffers()
{
ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
u32 sorted_blocks = 0;
u32 unsorted_blocks = 0;
f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);


// Update the order of transparent mesh buffers in each mesh
for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
MapBlock* block = it->second;
if (!block->mesh)
continue;

if (m_needs_update_transparent_meshes ||
block->mesh->getTransparentBuffers().size() == 0) {

v3s16 block_pos = block->getPos();
v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
if (distance <= sorting_distance_sq) {
block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
++sorted_blocks;
}
else {
block->mesh->consolidateTransparentBuffers();
++unsorted_blocks;
}
}
}

g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
m_needs_update_transparent_meshes = false;
}

Loading

0 comments on commit b0b9732

Please sign in to comment.