Added smooth_meshing option to VoxelTerrain and handle padding differences it introduces

This commit is contained in:
Marc Gilleron 2019-04-28 01:32:23 +01:00
parent fe37ed3674
commit d6bb354bd9
7 changed files with 127 additions and 63 deletions

View File

@ -1326,7 +1326,12 @@ Array VoxelMesherDMC::build(const VoxelBuffer &voxels) {
_stats = { 0 };
int padding = 1;
if (voxels.is_uniform(VoxelBuffer::CHANNEL_ISOLEVEL)) {
// That won't produce any polygon
return Array();
}
int padding = 2;
const Vector3i buffer_size = voxels.get_size();
// Taking previous power of two because the algorithm uses an integer cubic octree, and data should be padded
int chunk_size = previous_power_of_2(MIN(MIN(buffer_size.x, buffer_size.y), buffer_size.z));

View File

@ -12,9 +12,11 @@ VoxelMeshUpdater::VoxelMeshUpdater(Ref<VoxelLibrary> library, MeshingParams para
_blocky_mesher->set_occlusion_enabled(params.baked_ao);
_blocky_mesher->set_occlusion_darkness(params.baked_ao_darkness);
_dmc_mesher.instance();
_dmc_mesher->set_geometric_error(0.05);
_dmc_mesher->set_octree_mode(VoxelMesherDMC::OCTREE_NONE);
if (params.smooth_surface) {
_dmc_mesher.instance();
_dmc_mesher->set_geometric_error(0.05);
_dmc_mesher->set_octree_mode(VoxelMesherDMC::OCTREE_NONE);
}
_input_mutex = Mutex::create();
_output_mutex = Mutex::create();
@ -177,10 +179,18 @@ void VoxelMeshUpdater::process_block(const InputBlock &block, OutputBlock &outpu
CRASH_COND(block.voxels.is_null());
int padding = 1;
if (_dmc_mesher.is_valid()) {
padding = 2;
}
// Build cubic parts of the mesh
output.model_surfaces = _blocky_mesher->build(**block.voxels, Voxel::CHANNEL_TYPE, Vector3i(0, 0, 0), block.voxels->get_size() - Vector3(1, 1, 1));
// Build smooth parts of the mesh
output.smooth_surfaces = _dmc_mesher->build(**block.voxels);
output.model_surfaces = _blocky_mesher->build(**block.voxels, Voxel::CHANNEL_TYPE, padding);
if (_dmc_mesher.is_valid()) {
// Build smooth parts of the mesh
output.smooth_surfaces = _dmc_mesher->build(**block.voxels);
}
output.position = block.position;
}

View File

@ -52,10 +52,12 @@ public:
struct MeshingParams {
bool baked_ao;
float baked_ao_darkness;
bool smooth_surface;
MeshingParams() :
baked_ao(true),
baked_ao_darkness(0.75) {}
baked_ao_darkness(0.75),
smooth_surface(false) {}
};
VoxelMeshUpdater(Ref<VoxelLibrary> library, MeshingParams params);

View File

@ -55,7 +55,7 @@ Ref<ArrayMesh> VoxelMesher::build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int
ERR_FAIL_COND_V(buffer_ref.is_null(), Ref<ArrayMesh>());
VoxelBuffer &buffer = **buffer_ref;
Array surfaces = build(buffer, channel, Vector3i(), buffer.get_size());
Array surfaces = build(buffer, channel, MINIMUM_PADDING);
if (mesh.is_null())
mesh.instance();
@ -75,11 +75,12 @@ Ref<ArrayMesh> VoxelMesher::build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int
return mesh;
}
Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector3i min, Vector3i max) {
uint64_t time_before = OS::get_singleton()->get_ticks_usec();
Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, int padding) {
//uint64_t time_before = OS::get_singleton()->get_ticks_usec();
ERR_FAIL_COND_V(_library.is_null(), Array());
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());
ERR_FAIL_COND_V(padding < MINIMUM_PADDING, Array());
const VoxelLibrary &library = **_library;
@ -105,10 +106,8 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector
// => Could be implemented in a separate class?
// Data must be padded, hence the off-by-one
Vector3i::sort_min_max(min, max);
const Vector3i pad(1, 1, 1);
min.clamp_to(pad, max);
max.clamp_to(min, buffer.get_size() - pad);
Vector3i min = Vector3i(padding);
Vector3i max = buffer.get_size() - Vector3i(padding);
int index_offset = 0;
@ -176,8 +175,8 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector
corner_neighbor_lut[Cube::CORNER_TOP_FRONT_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT];
corner_neighbor_lut[Cube::CORNER_TOP_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT];
uint64_t time_prep = OS::get_singleton()->get_ticks_usec() - time_before;
time_before = OS::get_singleton()->get_ticks_usec();
//uint64_t time_prep = OS::get_singleton()->get_ticks_usec() - time_before;
//time_before = OS::get_singleton()->get_ticks_usec();
for (unsigned int z = min.z; z < max.z; ++z) {
for (unsigned int x = min.x; x < max.x; ++x) {
@ -360,8 +359,8 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector
}
}
uint64_t time_meshing = OS::get_singleton()->get_ticks_usec() - time_before;
time_before = OS::get_singleton()->get_ticks_usec();
//uint64_t time_meshing = OS::get_singleton()->get_ticks_usec() - time_before;
//time_before = OS::get_singleton()->get_ticks_usec();
// Commit mesh
@ -412,7 +411,7 @@ Array VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channel, Vector
}
}
uint64_t time_commit = OS::get_singleton()->get_ticks_usec() - time_before;
//uint64_t time_commit = OS::get_singleton()->get_ticks_usec() - time_before;
//print_line(String("P: {0}, M: {1}, C: {2}").format(varray(time_prep, time_meshing, time_commit)));

View File

@ -14,6 +14,7 @@ class VoxelMesher : public Reference {
public:
static const unsigned int MAX_MATERIALS = 8; // Arbitrary. Tweak if needed.
static const int MINIMUM_PADDING = 1;
VoxelMesher();
@ -26,7 +27,7 @@ public:
void set_occlusion_enabled(bool enable);
bool get_occlusion_enabled() const { return _bake_occlusion; }
Array build(const VoxelBuffer &buffer_ref, unsigned int channel, Vector3i min, Vector3i max);
Array build(const VoxelBuffer &buffer_ref, unsigned int channel, int padding);
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Array materials, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
protected:

View File

@ -10,9 +10,7 @@
#include <core/os/os.h>
#include <scene/3d/mesh_instance.h>
VoxelTerrain::VoxelTerrain() :
Spatial(),
_generate_collisions(true) {
VoxelTerrain::VoxelTerrain() {
_map = Ref<VoxelMap>(memnew(VoxelMap));
@ -24,6 +22,7 @@ VoxelTerrain::VoxelTerrain() :
_generate_collisions = false;
_run_in_editor = false;
_smooth_meshing_enabled = false;
}
VoxelTerrain::~VoxelTerrain() {
@ -108,15 +107,7 @@ void VoxelTerrain::set_voxel_library(Ref<VoxelLibrary> library) {
#endif
_library = library;
if (_block_updater) {
memdelete(_block_updater);
_block_updater = NULL;
}
// TODO Thread-safe way to change those parameters
VoxelMeshUpdater::MeshingParams params;
_block_updater = memnew(VoxelMeshUpdater(_library, params));
reset_updater();
// Voxel appearance might completely change
make_all_view_dirty_deferred();
@ -169,6 +160,18 @@ Ref<Material> VoxelTerrain::get_material(int id) const {
return _materials[id];
}
bool VoxelTerrain::is_smooth_meshing_enabled() const {
return _smooth_meshing_enabled;
}
void VoxelTerrain::set_smooth_meshing_enabled(bool enabled) {
if (_smooth_meshing_enabled != enabled) {
_smooth_meshing_enabled = enabled;
reset_updater();
make_all_view_dirty_deferred();
}
}
void VoxelTerrain::make_block_dirty(Vector3i bpos) {
// TODO Immediate update viewer distance?
@ -269,6 +272,29 @@ void VoxelTerrain::make_all_view_dirty_deferred() {
// make_blocks_dirty(-radius, 2*radius);
}
void VoxelTerrain::reset_updater() {
if (_block_updater) {
memdelete(_block_updater);
_block_updater = NULL;
}
// TODO Thread-safe way to change those parameters
VoxelMeshUpdater::MeshingParams params;
params.smooth_surface = _smooth_meshing_enabled;
_block_updater = memnew(VoxelMeshUpdater(_library, params));
}
int VoxelTerrain::get_block_padding() const {
// How many neighbor voxels we should pad for mesh updates to be seamless
// TODO Generalize padding retrieval, or split terrain systems because blocky and smooth are two different beasts
// - Blocky needs padding of 1
// - Transvoxel needs padding of 2
// - DMC needs padding of 2
return _smooth_meshing_enabled ? 2 : 1;
}
inline int get_border_index(int x, int max) {
return x == 0 ? 0 : x != max ? 1 : 2;
}
@ -688,44 +714,54 @@ void VoxelTerrain::_process() {
for (int i = 0; i < _blocks_pending_update.size(); ++i) {
Vector3i block_pos = _blocks_pending_update[i];
VoxelBlock *block = _map->get_block(block_pos);
if (block == NULL) {
continue;
}
// Check if the block is worth meshing
// Smooth meshing works on more neighbors, so checking a single block isn't enough to ignore it,
// but that will slow down meshing a lot.
// TODO This is one reason to separate terrain systems between blocky and smooth (other reason is LOD)
if (!_smooth_meshing_enabled) {
VoxelBlock *block = _map->get_block(block_pos);
if (block == NULL) {
continue;
} else {
CRASH_COND(block->voxels.is_null());
CRASH_COND(block->voxels.is_null());
int air_type = 0;
if (
block->voxels->is_uniform(Voxel::CHANNEL_TYPE) &&
block->voxels->is_uniform(Voxel::CHANNEL_ISOLEVEL) &&
block->voxels->get_voxel(0, 0, 0, Voxel::CHANNEL_TYPE) == air_type) {
VoxelTerrain::BlockDirtyState *block_state = _dirty_blocks.getptr(block_pos);
CRASH_COND(block_state == NULL);
CRASH_COND(*block_state != BLOCK_UPDATE_NOT_SENT);
// The block contains empty voxels
block->set_mesh(Ref<Mesh>(), Ref<World>());
_dirty_blocks.erase(block_pos);
// Optional, but I guess it might spare some memory
block->voxels->clear_channel(Voxel::CHANNEL_TYPE, air_type);
continue;
}
}
}
VoxelTerrain::BlockDirtyState *block_state = _dirty_blocks.getptr(block_pos);
CRASH_COND(block_state == NULL);
CRASH_COND(*block_state != BLOCK_UPDATE_NOT_SENT);
int air_type = 0;
if (
block->voxels->is_uniform(Voxel::CHANNEL_TYPE) &&
block->voxels->is_uniform(Voxel::CHANNEL_ISOLEVEL) &&
block->voxels->get_voxel(0, 0, 0, Voxel::CHANNEL_TYPE) == air_type) {
// The block contains empty voxels
block->set_mesh(Ref<Mesh>(), Ref<World>());
_dirty_blocks.erase(block_pos);
// Optional, but I guess it might spare some memory
block->voxels->clear_channel(Voxel::CHANNEL_TYPE, air_type);
continue;
}
// Create buffer padded with neighbor voxels
Ref<VoxelBuffer> nbuffer;
nbuffer.instance();
// TODO Make the buffer re-usable
// TODO Padding set to 3 at the moment because Transvoxel works on 2x2 cells.
// It should change for a smarter padding (if smooth isn't used for example).
unsigned int block_size = _map->get_block_size();
//nbuffer->create(block_size + 3, block_size + 3, block_size + 3);
nbuffer->create(block_size + 2, block_size + 2, block_size + 2);
_map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(1, 1, 1), **nbuffer, 0x3);
// TODO Make the buffer re-usable
unsigned int block_size = _map->get_block_size();
unsigned int padding = get_block_padding();
nbuffer->create(block_size + 2 * padding, block_size + 2 * padding, block_size + 2 * padding);
unsigned int channels_mask = (1 << VoxelBuffer::CHANNEL_TYPE) | (1 << VoxelBuffer::CHANNEL_ISOLEVEL);
_map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(padding), **nbuffer, channels_mask);
VoxelMeshUpdater::InputBlock iblock;
iblock.voxels = nbuffer;
@ -932,6 +968,9 @@ void VoxelTerrain::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_viewer_path"), &VoxelTerrain::get_viewer_path);
ClassDB::bind_method(D_METHOD("set_viewer_path", "path"), &VoxelTerrain::set_viewer_path);
ClassDB::bind_method(D_METHOD("is_smooth_meshing_enabled"), &VoxelTerrain::is_smooth_meshing_enabled);
ClassDB::bind_method(D_METHOD("set_smooth_meshing_enabled", "enabled"), &VoxelTerrain::set_smooth_meshing_enabled);
ClassDB::bind_method(D_METHOD("get_storage"), &VoxelTerrain::get_map);
ClassDB::bind_method(D_METHOD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding);
@ -950,6 +989,7 @@ void VoxelTerrain::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "view_distance"), "set_view_distance", "get_view_distance");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewer_path"), "set_viewer_path", "get_viewer_path");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_meshing_enabled"), "set_smooth_meshing_enabled", "is_smooth_meshing_enabled");
BIND_ENUM_CONSTANT(BLOCK_NONE);
BIND_ENUM_CONSTANT(BLOCK_LOAD);

View File

@ -13,8 +13,9 @@
class VoxelMap;
class VoxelLibrary;
// Infinite static terrain made of voxels.
// It is loaded around VoxelTerrainStreamers.
// Infinite paged terrain made of voxel blocks.
// Voxels are polygonized around the viewer.
// Data is streamed using a VoxelProvider.
class VoxelTerrain : public Spatial {
GDCLASS(VoxelTerrain, Spatial)
public:
@ -53,6 +54,9 @@ public:
void set_material(int id, Ref<Material> material);
Ref<Material> get_material(int id) const;
bool is_smooth_meshing_enabled() const;
void set_smooth_meshing_enabled(bool enabled);
Ref<VoxelMap> get_map() { return _map; }
struct Stats {
@ -93,6 +97,8 @@ private:
void _process();
void make_all_view_dirty_deferred();
void reset_updater();
int get_block_padding() const;
Spatial *get_viewer(NodePath path) const;
@ -142,6 +148,7 @@ private:
bool _generate_collisions;
bool _run_in_editor;
bool _smooth_meshing_enabled;
Ref<Material> _materials[VoxelMesher::MAX_MATERIALS];