Separate normalmap baking from meshing, so meshes don't have to wait.
- The option for having it separate is hardcoded and both code paths still exist. Not sure if that will be exposed or removed entirely. - Still need to limit the frequency at which normalmaps are updated - Would be nice to make this mesher-independent, Transvoxel is just a fastpath - Task priority might need improvement, it's a bit messy. Maybe use bands?
This commit is contained in:
parent
3411a9e1cc
commit
dbbab6a81d
constants
doc/source
engine
meshers
transvoxel
distance_normalmaps.cppdistance_normalmaps.hgenerate_distance_normalmap_task.cppgenerate_distance_normalmap_task.hvoxel_mesher_transvoxel.cppvoxel_mesher_transvoxel.h
voxel_mesher.cppvoxel_mesher.hterrain/variable_lod
util/godot
@ -45,6 +45,7 @@ VoxelStringNames::VoxelStringNames() {
|
||||
u_voxel_cell_lookup = StaticCString::create("u_voxel_cell_lookup");
|
||||
u_voxel_cell_size = StaticCString::create("u_voxel_cell_size");
|
||||
u_voxel_block_size = StaticCString::create("u_voxel_block_size");
|
||||
u_voxel_virtual_texture_fade = StaticCString::create("u_voxel_virtual_texture_fade");
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
_voxel_debug_vt_position = StaticCString::create("_voxel_debug_vt_position");
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
StringName u_voxel_cell_lookup;
|
||||
StringName u_voxel_cell_size;
|
||||
StringName u_voxel_block_size;
|
||||
StringName u_voxel_virtual_texture_fade;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
StringName _voxel_debug_vt_position;
|
||||
|
@ -343,15 +343,16 @@ Shader API reference
|
||||
|
||||
If you use a `ShaderMaterial` on a voxel node, the module may exploit some uniform (shader parameter) names to provide extra information. Some are necessary for features to work.
|
||||
|
||||
Parameter name | Type | Description
|
||||
---------------------------|------------------|-----------------------
|
||||
`u_lod_fade` | `vec2` | Information for progressive fading between levels of detail. Only available with `VoxelLodTerrain`. See [Lod fading](#lod-fading-experimental)
|
||||
`u_block_local_transform` | `mat4` | Transform of the rendered block, local to the whole volume, as they may be rendered with multiple meshes. Useful if the volume is moving, to fix triplanar mapping. Only available with `VoxelLodTerrain` at the moment.
|
||||
`u_voxel_cell_lookup` | `usampler2D` | 3D texture where each pixel contains a cell index packed in bytes of `RG` (`r + (g << 8)`), and an axis index in `B`. The position to use for indexing this texture is relative to the origin of the mesh. The texture is 2D and square, so coordinates may be computed knowing the size of the mesh in voxels. Will only be assigned in meshes using virtual texturing of [normalmaps](#distance-normals).
|
||||
`u_voxel_normalmap_atlas` | `sampler2DArray` | Texture array where each layer is a tile containing a model-space normalmap. The layer index may be computed from `u_voxel_cell_lookup`. UVs are similar to triplanar mapping, but the axis is known from the information in `u_voxel_cell_lookup`. Will only be assigned in meshes using virtual texturing of [normalmaps](#distance-normals).
|
||||
`u_voxel_cell_size` | `float` | Size of one cubic cell in the mesh, in model space units. Will be > 0 in voxel meshes having [normalmaps](#distance-normals).
|
||||
`u_voxel_block_size` | `int` | Size of the cubic block of voxels that the mesh represents, in voxels.
|
||||
`u_transition_mask` | `int` | When using `VoxelMesherTransvoxel`, this is a bitmask storing informations about neighboring meshes of different levels of detail. If one of the 6 sides of the mesh has a lower-resolution neighbor, the corresponding bit will be `1`. Side indices are in order `-X`, `X`, `-Y`, `Y`, `-Z`, `Z`. See [smooth stitches in vertex shaders](#smooth-stitches-in-vertex-shader).
|
||||
Parameter name | Type | Description
|
||||
-------------------------------|------------------|------------------------------
|
||||
`u_lod_fade` | `vec2` | Information for progressive fading between levels of detail. Only available with `VoxelLodTerrain`. See [Lod fading](#lod-fading-experimental)
|
||||
`u_block_local_transform` | `mat4` | Transform of the rendered block, local to the whole volume, as they may be rendered with multiple meshes. Useful if the volume is moving, to fix triplanar mapping. Only available with `VoxelLodTerrain` at the moment.
|
||||
`u_voxel_cell_lookup` | `usampler2D` | 3D texture where each pixel contains a cell index packed in bytes of `RG` (`r + (g << 8)`), and an axis index in `B`. The position to use for indexing this texture is relative to the origin of the mesh. The texture is 2D and square, so coordinates may be computed knowing the size of the mesh in voxels. Will only be assigned in meshes using virtual texturing of [normalmaps](#distance-normals).
|
||||
`u_voxel_normalmap_atlas` | `sampler2DArray` | Texture array where each layer is a tile containing a model-space normalmap. The layer index may be computed from `u_voxel_cell_lookup`. UVs are similar to triplanar mapping, but the axis is known from the information in `u_voxel_cell_lookup`. Will only be assigned in meshes using virtual texturing of [normalmaps](#distance-normals).
|
||||
`u_voxel_cell_size` | `float` | Size of one cubic cell in the mesh, in model space units. Will be > 0 in voxel meshes having [normalmaps](#distance-normals).
|
||||
`u_voxel_block_size` | `int` | Size of the cubic block of voxels that the mesh represents, in voxels.
|
||||
`u_voxel_virtual_texture_fade` | `float` | When LOD fading is enabled, this will be a value between 0 and 1 for how much to mix in virtual textures such as `u_voxel_normalmap_atlas`. They take time to update so this allows them to appear smoothly. The value is 1 if fading is not enabled, or 0 if the mesh has no virtual textures.
|
||||
`u_transition_mask` | `int` | When using `VoxelMesherTransvoxel`, this is a bitmask storing informations about neighboring meshes of different levels of detail. If one of the 6 sides of the mesh has a lower-resolution neighbor, the corresponding bit will be `1`. Side indices are in order `-X`, `X`, `-Y`, `Y`, `-Z`, `Z`. See [smooth stitches in vertex shaders](#smooth-stitches-in-vertex-shader).
|
||||
|
||||
|
||||
Level of detail (LOD)
|
||||
@ -492,6 +493,8 @@ The number of polygons is the same:
|
||||
|
||||
![Landscape wireframe](images/distance_normals_wireframe.webp)
|
||||
|
||||
TODO: showcase an example with overhangs to emphasize that it works too
|
||||
|
||||
This can be turned on in the inspector when using `VoxelMesherTransvoxel`. The cost is slower mesh generation and more memory usage to store normalmap textures.
|
||||
|
||||
This feature is only available in `VoxelLodTerrain`. It also works best with data streaming turned off (`full_load_mode_enabled`), because being able to see all details from far away requires to not unload edited blocks. It will still use the generator if data streaming is on, but you won't see edited regions.
|
||||
|
@ -1,11 +1,14 @@
|
||||
#include "mesh_block_task.h"
|
||||
#include "../meshers/transvoxel/distance_normalmaps.h"
|
||||
#include "../meshers/transvoxel/generate_distance_normalmap_task.h"
|
||||
#include "../meshers/transvoxel/voxel_mesher_transvoxel.h"
|
||||
#include "../storage/voxel_data_map.h"
|
||||
#include "../terrain/voxel_mesh_block.h"
|
||||
#include "../util/dstack.h"
|
||||
#include "../util/godot/funcs.h"
|
||||
#include "../util/log.h"
|
||||
#include "../util/profiling.h"
|
||||
//#include "../util/string_funcs.h" // Debug
|
||||
#include "voxel_engine.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
@ -299,25 +302,73 @@ void MeshBlockTask::run(zylann::ThreadedTaskContext ctx) {
|
||||
const Vector3i origin_in_voxels = position * (int(data_block_size) << lod_index);
|
||||
|
||||
const VoxelMesher::Input input = { voxels, meshing_dependency->generator.ptr(), data.get(), origin_in_voxels,
|
||||
lod_index, collision_hint, lod_hint };
|
||||
lod_index, collision_hint, lod_hint, true };
|
||||
mesher->build(_surfaces_output, input);
|
||||
|
||||
const bool mesh_is_empty = VoxelMesher::is_mesh_empty(_surfaces_output.surfaces);
|
||||
|
||||
Ref<VoxelMesherTransvoxel> transvoxel_mesher = mesher;
|
||||
if (transvoxel_mesher.is_valid() && transvoxel_mesher->is_normalmap_enabled() && input.defer_virtual_texture &&
|
||||
!mesh_is_empty && lod_index >= transvoxel_mesher->get_normalmap_begin_lod_index()) {
|
||||
ZN_PROFILE_SCOPE_NAMED("Schedule virtual render");
|
||||
const transvoxel::MeshArrays &mesh_arrays = VoxelMesherTransvoxel::get_mesh_cache_from_current_thread();
|
||||
|
||||
const Span<const transvoxel::CellInfo> cell_infos = VoxelMesherTransvoxel::get_cell_info_from_current_thread();
|
||||
std::vector<transvoxel::CellInfo> cell_infos_copy;
|
||||
cell_infos_copy.resize(cell_infos.size());
|
||||
for (unsigned int i = 0; i < cell_infos.size(); ++i) {
|
||||
cell_infos_copy[i] = cell_infos[i];
|
||||
}
|
||||
|
||||
ZN_ASSERT(cell_infos.size() > 0 && mesh_arrays.vertices.size() > 0);
|
||||
|
||||
const Vector3i block_size =
|
||||
voxels.get_size() - Vector3iUtil::create(mesher->get_minimum_padding() + mesher->get_maximum_padding());
|
||||
|
||||
ZN_ASSERT(_surfaces_output.virtual_textures == nullptr);
|
||||
std::shared_ptr<VoxelMesher::VirtualTextureOutput> virtual_textures =
|
||||
make_shared_instance<VoxelMesher::VirtualTextureOutput>();
|
||||
virtual_textures->valid = false;
|
||||
// This is stored here in case virtual texture rendering completes before the output of the current task gets
|
||||
// dequeued in the main thread, since it runs in a separate asynchronous task
|
||||
_surfaces_output.virtual_textures = virtual_textures;
|
||||
|
||||
GenerateDistanceNormalmapTask *nm_task = ZN_NEW(GenerateDistanceNormalmapTask);
|
||||
nm_task->cell_infos = std::move(cell_infos_copy);
|
||||
nm_task->mesh_vertices = mesh_arrays.vertices;
|
||||
nm_task->mesh_normals = mesh_arrays.normals;
|
||||
nm_task->mesh_indices = mesh_arrays.indices;
|
||||
nm_task->generator = meshing_dependency->generator;
|
||||
nm_task->voxel_data = data;
|
||||
nm_task->origin_in_voxels = origin_in_voxels;
|
||||
nm_task->block_size = block_size;
|
||||
nm_task->tile_resolution = transvoxel_mesher->get_virtual_texture_tile_resolution_for_lod(lod_index);
|
||||
nm_task->lod_index = lod_index;
|
||||
nm_task->octahedral_encoding = transvoxel_mesher->get_octahedral_normal_encoding();
|
||||
nm_task->block_position = position;
|
||||
nm_task->volume_id = volume_id;
|
||||
nm_task->virtual_textures = virtual_textures;
|
||||
|
||||
VoxelEngine::get_singleton().push_async_task(nm_task);
|
||||
}
|
||||
|
||||
if (VoxelEngine::get_singleton().is_threaded_mesh_resource_building_enabled()) {
|
||||
// This shall only run if Godot supports building meshes from multiple threads
|
||||
_mesh = build_mesh(to_span(_surfaces_output.surfaces), _surfaces_output.primitive_type,
|
||||
_surfaces_output.mesh_flags, _mesh_material_indices);
|
||||
_has_mesh_resource = true;
|
||||
|
||||
if (_surfaces_output.cell_lookup_image.is_valid()) {
|
||||
if (!input.defer_virtual_texture && _surfaces_output.virtual_textures != nullptr) {
|
||||
ZN_ASSERT(_surfaces_output.virtual_textures->valid);
|
||||
NormalMapImages images;
|
||||
images.atlas_images = _surfaces_output.normalmap_atlas_images;
|
||||
images.lookup_image = _surfaces_output.cell_lookup_image;
|
||||
images.atlas_images = _surfaces_output.virtual_textures->normalmap_atlas_images;
|
||||
images.lookup_image = _surfaces_output.virtual_textures->cell_lookup_image;
|
||||
NormalMapTextures textures = store_normalmap_data_to_textures(images);
|
||||
_surfaces_output.normalmap_atlas = textures.atlas;
|
||||
_surfaces_output.cell_lookup = textures.lookup;
|
||||
_surfaces_output.virtual_textures->normalmap_atlas = textures.atlas;
|
||||
_surfaces_output.virtual_textures->cell_lookup = textures.lookup;
|
||||
// No longer needed
|
||||
_surfaces_output.atlas_image.unref();
|
||||
_surfaces_output.cell_lookup_image.unref();
|
||||
_surfaces_output.virtual_textures->normalmap_atlas_images.clear();
|
||||
_surfaces_output.virtual_textures->cell_lookup_image.unref();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -33,6 +33,7 @@ public:
|
||||
uint8_t data_block_size = 0;
|
||||
bool collision_hint = false;
|
||||
bool lod_hint = false;
|
||||
//bool require_virtual_texture = false; // TODO Use this when tweaking frequency of updates
|
||||
PriorityDependency priority_dependency;
|
||||
std::shared_ptr<MeshingDependency> meshing_dependency;
|
||||
std::shared_ptr<VoxelDataLodMap> data;
|
||||
|
@ -61,14 +61,22 @@ public:
|
||||
bool initial_load;
|
||||
};
|
||||
|
||||
struct BlockVirtualTextureOutput {
|
||||
std::shared_ptr<VoxelMesher::VirtualTextureOutput> virtual_textures;
|
||||
Vector3i position;
|
||||
uint32_t lod_index;
|
||||
};
|
||||
|
||||
struct VolumeCallbacks {
|
||||
void (*mesh_output_callback)(void *, BlockMeshOutput &) = nullptr;
|
||||
void (*data_output_callback)(void *, BlockDataOutput &) = nullptr;
|
||||
void (*virtual_texture_output_callback)(void *, BlockVirtualTextureOutput &) = nullptr;
|
||||
void *data = nullptr;
|
||||
|
||||
inline bool check_callbacks() const {
|
||||
ZN_ASSERT_RETURN_V(mesh_output_callback != nullptr, false);
|
||||
ZN_ASSERT_RETURN_V(data_output_callback != nullptr, false);
|
||||
//ZN_ASSERT_RETURN_V(normalmap_output_callback != nullptr, false);
|
||||
ZN_ASSERT_RETURN_V(data != nullptr, false);
|
||||
return true;
|
||||
}
|
||||
@ -138,8 +146,10 @@ public:
|
||||
|
||||
// Allows/disallows building Mesh resources from inside threads. Depends on Godot's efficiency at doing so, and
|
||||
// which renderer is used. For example, the OpenGL renderer does not support this well, but the Vulkan one should.
|
||||
// TODO Rename `set_threaded_gpu_resource_building_enabled`, it applies to textures too
|
||||
void set_threaded_mesh_resource_building_enabled(bool enable);
|
||||
// This should be fast and safe to access from multiple threads.
|
||||
// TODO Rename `is_threaded_gpu_resource_building_enabled`, it applies to textures too
|
||||
bool is_threaded_mesh_resource_building_enabled() const;
|
||||
|
||||
void push_main_thread_progressive_task(IProgressiveTask *task);
|
||||
|
@ -58,18 +58,18 @@ static void dilate_normalmap(Span<Vector3f> normals, Vector2i size) {
|
||||
}
|
||||
}
|
||||
|
||||
NormalMapData::Tile compute_tile_info(
|
||||
const transvoxel::CellInfo cell_info, const transvoxel::MeshArrays &mesh, unsigned int first_index) {
|
||||
NormalMapData::Tile compute_tile_info(const transvoxel::CellInfo cell_info, Span<const Vector3f> mesh_normals,
|
||||
Span<const int> mesh_indices, unsigned int first_index) {
|
||||
Vector3f normal_sum;
|
||||
unsigned int ii = first_index;
|
||||
for (unsigned int triangle_index = 0; triangle_index < cell_info.triangle_count; ++triangle_index) {
|
||||
const unsigned vi0 = mesh.indices[ii];
|
||||
const unsigned vi1 = mesh.indices[ii + 1];
|
||||
const unsigned vi2 = mesh.indices[ii + 2];
|
||||
const unsigned vi0 = mesh_indices[ii];
|
||||
const unsigned vi1 = mesh_indices[ii + 1];
|
||||
const unsigned vi2 = mesh_indices[ii + 2];
|
||||
ii += 3;
|
||||
const Vector3f normal0 = mesh.normals[vi0];
|
||||
const Vector3f normal1 = mesh.normals[vi1];
|
||||
const Vector3f normal2 = mesh.normals[vi2];
|
||||
const Vector3f normal0 = mesh_normals[vi0];
|
||||
const Vector3f normal1 = mesh_normals[vi1];
|
||||
const Vector3f normal2 = mesh_normals[vi2];
|
||||
normal_sum += normal0;
|
||||
normal_sum += normal1;
|
||||
normal_sum += normal2;
|
||||
@ -116,21 +116,21 @@ void get_axis_indices(Vector3f::Axis axis, unsigned int &ax, unsigned int &ay, u
|
||||
typedef FixedArray<math::BakedIntersectionTriangleForFixedDirection, transvoxel::MAX_TRIANGLES_PER_CELL> CellTriangles;
|
||||
|
||||
unsigned int prepare_triangles(unsigned int first_index, const transvoxel::CellInfo cell_info, const Vector3f direction,
|
||||
CellTriangles &baked_triangles, const transvoxel::MeshArrays &mesh) {
|
||||
CellTriangles &baked_triangles, Span<const Vector3f> mesh_vertices, Span<const int> mesh_indices) {
|
||||
unsigned int triangle_count = 0;
|
||||
|
||||
unsigned int ii = first_index;
|
||||
for (unsigned int ti = 0; ti < cell_info.triangle_count; ++ti) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
ZN_ASSERT(ii + 2 < mesh.indices.size());
|
||||
ZN_ASSERT(ii + 2 < mesh_indices.size());
|
||||
#endif
|
||||
const unsigned vi0 = mesh.indices[ii];
|
||||
const unsigned vi1 = mesh.indices[ii + 1];
|
||||
const unsigned vi2 = mesh.indices[ii + 2];
|
||||
const unsigned vi0 = mesh_indices[ii];
|
||||
const unsigned vi1 = mesh_indices[ii + 1];
|
||||
const unsigned vi2 = mesh_indices[ii + 2];
|
||||
ii += 3;
|
||||
const Vector3f a = mesh.vertices[vi0];
|
||||
const Vector3f b = mesh.vertices[vi1];
|
||||
const Vector3f c = mesh.vertices[vi2];
|
||||
const Vector3f a = mesh_vertices[vi0];
|
||||
const Vector3f b = mesh_vertices[vi1];
|
||||
const Vector3f c = mesh_vertices[vi2];
|
||||
math::BakedIntersectionTriangleForFixedDirection baked_triangle;
|
||||
// The triangle can be parallel to the direction
|
||||
if (baked_triangle.bake(a, b, c, direction)) {
|
||||
@ -325,10 +325,11 @@ inline void query_sdf(VoxelGenerator &generator, const VoxelDataLodMap *voxel_da
|
||||
|
||||
// For each non-empty cell of the mesh, choose an axis-aligned projection based on triangle normals in the cell.
|
||||
// Sample voxels inside the cell to compute a tile of world space normals from the SDF.
|
||||
void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, const transvoxel::MeshArrays &mesh,
|
||||
NormalMapData &normal_map_data, unsigned int tile_resolution, VoxelGenerator &generator,
|
||||
const VoxelDataLodMap *voxel_data, Vector3i origin_in_voxels, unsigned int lod_index,
|
||||
bool octahedral_encoding) {
|
||||
// TODO Take an ICellIterator interface so we can make this independent from Transvoxel. Transvoxel is just a fastpath
|
||||
void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, Span<const Vector3f> mesh_vertices,
|
||||
Span<const Vector3f> mesh_normals, Span<const int> mesh_indices, NormalMapData &normal_map_data,
|
||||
unsigned int tile_resolution, VoxelGenerator &generator, const VoxelDataLodMap *voxel_data,
|
||||
Vector3i origin_in_voxels, unsigned int lod_index, bool octahedral_encoding) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
ZN_ASSERT_RETURN(generator.supports_series_generation());
|
||||
@ -346,7 +347,7 @@ void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, const transv
|
||||
for (unsigned int cell_index = 0; cell_index < cell_infos.size(); ++cell_index) {
|
||||
const transvoxel::CellInfo cell_info = cell_infos[cell_index];
|
||||
|
||||
const NormalMapData::Tile tile = compute_tile_info(cell_info, mesh, first_index);
|
||||
const NormalMapData::Tile tile = compute_tile_info(cell_info, mesh_normals, mesh_indices, first_index);
|
||||
normal_map_data.tiles.push_back(tile);
|
||||
|
||||
const Vector3f cell_origin_world = to_vec3f(origin_in_voxels + cell_info.position * cell_size);
|
||||
@ -388,7 +389,8 @@ void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, const transv
|
||||
|
||||
// Optimize triangles
|
||||
CellTriangles baked_triangles;
|
||||
unsigned int triangle_count = prepare_triangles(first_index, cell_info, direction, baked_triangles, mesh);
|
||||
unsigned int triangle_count =
|
||||
prepare_triangles(first_index, cell_info, direction, baked_triangles, mesh_vertices, mesh_indices);
|
||||
|
||||
// Fill query buffers
|
||||
{
|
||||
|
@ -16,7 +16,10 @@ namespace zylann::voxel {
|
||||
class VoxelGenerator;
|
||||
struct VoxelDataLodMap;
|
||||
|
||||
// TODO This system could be extended to more than just normals (texturing)
|
||||
// TODO This system could be extended to more than just normals
|
||||
// - Texturing data
|
||||
// - Color
|
||||
// - Some kind of depth (could be useful to fake water from far away)
|
||||
|
||||
// UV-mapping a voxel mesh is not trivial, but if mapping is required, an alternative is to subdivide the mesh into a
|
||||
// grid of cells (we can use Transvoxel cells). In each cell, pick an axis-aligned projection working best with
|
||||
@ -42,9 +45,10 @@ struct NormalMapData {
|
||||
|
||||
// For each non-empty cell of the mesh, choose an axis-aligned projection based on triangle normals in the cell.
|
||||
// Sample voxels inside the cell to compute a tile of world space normals from the SDF.
|
||||
void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, const transvoxel::MeshArrays &mesh,
|
||||
NormalMapData &normal_map_data, unsigned int tile_resolution, VoxelGenerator &generator,
|
||||
const VoxelDataLodMap *voxel_data, Vector3i origin_in_voxels, unsigned int lod_index, bool octahedral_encoding);
|
||||
void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, Span<const Vector3f> mesh_vertices,
|
||||
Span<const Vector3f> mesh_normals, Span<const int> mesh_indices, NormalMapData &normal_map_data,
|
||||
unsigned int tile_resolution, VoxelGenerator &generator, const VoxelDataLodMap *voxel_data,
|
||||
Vector3i origin_in_voxels, unsigned int lod_index, bool octahedral_encoding);
|
||||
|
||||
struct NormalMapImages {
|
||||
Vector<Ref<Image>> atlas_images;
|
||||
|
66
meshers/transvoxel/generate_distance_normalmap_task.cpp
Normal file
66
meshers/transvoxel/generate_distance_normalmap_task.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#include "generate_distance_normalmap_task.h"
|
||||
#include "../../engine/voxel_engine.h"
|
||||
#include "../../util/profiling.h"
|
||||
//#include "../../util/string_funcs.h" // Debug
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
void GenerateDistanceNormalmapTask::run(ThreadedTaskContext ctx) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
ZN_ASSERT_RETURN(generator.is_valid());
|
||||
ZN_ASSERT_RETURN(virtual_textures != nullptr);
|
||||
ZN_ASSERT_RETURN(virtual_textures->valid == false);
|
||||
|
||||
static thread_local NormalMapData tls_normalmap_data;
|
||||
tls_normalmap_data.clear();
|
||||
|
||||
compute_normalmap(to_span(cell_infos), to_span(mesh_vertices), to_span(mesh_normals), to_span(mesh_indices),
|
||||
tls_normalmap_data, tile_resolution, **generator, voxel_data.get(), origin_in_voxels, lod_index,
|
||||
octahedral_encoding);
|
||||
|
||||
NormalMapImages images =
|
||||
store_normalmap_data_to_images(tls_normalmap_data, tile_resolution, block_size, octahedral_encoding);
|
||||
|
||||
if (VoxelEngine::get_singleton().is_threaded_mesh_resource_building_enabled()) {
|
||||
NormalMapTextures textures = store_normalmap_data_to_textures(images);
|
||||
virtual_textures->normalmap_atlas = textures.atlas;
|
||||
virtual_textures->cell_lookup = textures.lookup;
|
||||
} else {
|
||||
virtual_textures->normalmap_atlas_images = images.atlas_images;
|
||||
virtual_textures->cell_lookup_image = images.lookup_image;
|
||||
}
|
||||
|
||||
virtual_textures->valid = true;
|
||||
}
|
||||
|
||||
void GenerateDistanceNormalmapTask::apply_result() {
|
||||
if (!VoxelEngine::get_singleton().is_volume_valid(volume_id)) {
|
||||
// This can happen if the user removes the volume while requests are still about to return
|
||||
ZN_PRINT_VERBOSE("Normalmap task completed but volume wasn't found");
|
||||
return;
|
||||
}
|
||||
|
||||
VoxelEngine::BlockVirtualTextureOutput o;
|
||||
// TODO Check for invalidation due to property changes
|
||||
|
||||
o.position = block_position;
|
||||
o.lod_index = lod_index;
|
||||
o.virtual_textures = virtual_textures;
|
||||
|
||||
VoxelEngine::VolumeCallbacks callbacks = VoxelEngine::get_singleton().get_volume_callbacks(volume_id);
|
||||
ZN_ASSERT_RETURN(callbacks.mesh_output_callback != nullptr);
|
||||
ZN_ASSERT_RETURN(callbacks.data != nullptr);
|
||||
callbacks.virtual_texture_output_callback(callbacks.data, o);
|
||||
}
|
||||
|
||||
int GenerateDistanceNormalmapTask::get_priority() {
|
||||
// TODO Give a proper priority, we just want this task to be done last relative to meshing
|
||||
return 99999999;
|
||||
}
|
||||
|
||||
bool GenerateDistanceNormalmapTask::is_cancelled() {
|
||||
// TODO Cancel if too far?
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace zylann::voxel
|
48
meshers/transvoxel/generate_distance_normalmap_task.h
Normal file
48
meshers/transvoxel/generate_distance_normalmap_task.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef VOXEL_GENERATE_DISTANCE_NORMALMAP_TASK_H
|
||||
#define VOXEL_GENERATE_DISTANCE_NORMALMAP_TASK_H
|
||||
|
||||
#include "../../util/tasks/threaded_task.h"
|
||||
#include "../voxel_mesher.h"
|
||||
#include "distance_normalmaps.h"
|
||||
|
||||
namespace zylann::voxel {
|
||||
|
||||
// Renders textures providing extra details to far away voxel meshes.
|
||||
// This is separate from the meshing task because it takes significantly longer to complete. It has different priority
|
||||
// so most of the time we can get the mesh earlier and affine later with the results.
|
||||
class GenerateDistanceNormalmapTask : public IThreadedTask {
|
||||
public:
|
||||
// Input
|
||||
|
||||
std::vector<transvoxel::CellInfo> cell_infos;
|
||||
// TODO Optimize: perhaps we could find a way to not copy mesh data? The only reason is because Godot wants a
|
||||
// slightly different data structure potentially taking unnecessary doubles because it uses `Vector3`...
|
||||
std::vector<Vector3f> mesh_vertices;
|
||||
std::vector<Vector3f> mesh_normals;
|
||||
std::vector<int> mesh_indices;
|
||||
Ref<VoxelGenerator> generator;
|
||||
std::shared_ptr<VoxelDataLodMap> voxel_data;
|
||||
Vector3i origin_in_voxels;
|
||||
Vector3i block_size;
|
||||
uint8_t tile_resolution;
|
||||
uint8_t lod_index;
|
||||
bool octahedral_encoding;
|
||||
|
||||
// Output (to be assigned so it can be populated)
|
||||
std::shared_ptr<VoxelMesher::VirtualTextureOutput> virtual_textures;
|
||||
|
||||
// Identification
|
||||
Vector3i block_position;
|
||||
uint32_t volume_id;
|
||||
|
||||
void run(ThreadedTaskContext ctx) override;
|
||||
void apply_result() override;
|
||||
int get_priority() override;
|
||||
bool is_cancelled() override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
||||
#endif // VOXEL_GENERATE_DISTANCE_NORMALMAP_TASK_H
|
@ -15,6 +15,19 @@ namespace zylann::voxel {
|
||||
|
||||
namespace {
|
||||
Ref<ShaderMaterial> g_minimal_shader_material;
|
||||
} //namespace
|
||||
|
||||
namespace transvoxel {
|
||||
thread_local MeshArrays tls_mesh_arrays;
|
||||
thread_local std::vector<CellInfo> tls_cell_infos;
|
||||
} //namespace transvoxel
|
||||
|
||||
const transvoxel::MeshArrays &VoxelMesherTransvoxel::get_mesh_cache_from_current_thread() {
|
||||
return transvoxel::tls_mesh_arrays;
|
||||
}
|
||||
|
||||
const Span<const transvoxel::CellInfo> VoxelMesherTransvoxel::get_cell_info_from_current_thread() {
|
||||
return to_span(transvoxel::tls_cell_infos);
|
||||
}
|
||||
|
||||
void VoxelMesherTransvoxel::load_static_resources() {
|
||||
@ -218,7 +231,6 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
static thread_local transvoxel::Cache tls_cache;
|
||||
static thread_local transvoxel::MeshArrays tls_mesh_arrays;
|
||||
// static thread_local FixedArray<transvoxel::MeshArrays, Cube::SIDE_COUNT> tls_transition_mesh_arrays;
|
||||
static thread_local transvoxel::MeshArrays tls_simplified_mesh_arrays;
|
||||
|
||||
@ -228,7 +240,8 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
|
||||
// These vectors are re-used.
|
||||
// We don't know in advance how much geometry we are going to produce.
|
||||
// Once capacity is big enough, no more memory should be allocated
|
||||
tls_mesh_arrays.clear();
|
||||
transvoxel::MeshArrays &mesh_arrays = transvoxel::tls_mesh_arrays;
|
||||
mesh_arrays.clear();
|
||||
|
||||
const VoxelBufferInternal &voxels = input.voxels;
|
||||
if (voxels.is_uniform(sdf_channel)) {
|
||||
@ -239,11 +252,10 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
|
||||
// const uint64_t time_before = Time::get_singleton()->get_ticks_usec();
|
||||
|
||||
transvoxel::DefaultTextureIndicesData default_texture_indices_data;
|
||||
static thread_local std::vector<transvoxel::CellInfo> tls_cell_infos;
|
||||
std::vector<transvoxel::CellInfo> *cell_infos = nullptr;
|
||||
if (_normalmap_settings.enabled && input.generator != nullptr && input.lod >= _normalmap_settings.begin_lod_index) {
|
||||
tls_cell_infos.clear();
|
||||
cell_infos = &tls_cell_infos;
|
||||
transvoxel::tls_cell_infos.clear();
|
||||
cell_infos = &transvoxel::tls_cell_infos;
|
||||
}
|
||||
|
||||
if (_deep_sampling_enabled && input.generator != nullptr && input.data != nullptr && input.lod > 0) {
|
||||
@ -253,24 +265,22 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
|
||||
// `generate_single` in between, knowing they will all be done within the specified area.
|
||||
|
||||
default_texture_indices_data = transvoxel::build_regular_mesh(voxels, sdf_channel, input.lod,
|
||||
static_cast<transvoxel::TexturingMode>(_texture_mode), tls_cache, tls_mesh_arrays, &ds, cell_infos);
|
||||
static_cast<transvoxel::TexturingMode>(_texture_mode), tls_cache, mesh_arrays, &ds, cell_infos);
|
||||
} else {
|
||||
default_texture_indices_data = transvoxel::build_regular_mesh(voxels, sdf_channel, input.lod,
|
||||
static_cast<transvoxel::TexturingMode>(_texture_mode), tls_cache, tls_mesh_arrays, nullptr, cell_infos);
|
||||
static_cast<transvoxel::TexturingMode>(_texture_mode), tls_cache, mesh_arrays, nullptr, cell_infos);
|
||||
}
|
||||
|
||||
if (tls_mesh_arrays.vertices.size() == 0) {
|
||||
if (mesh_arrays.vertices.size() == 0) {
|
||||
// The mesh can be empty
|
||||
return;
|
||||
}
|
||||
|
||||
if (cell_infos != nullptr) {
|
||||
if (cell_infos != nullptr && !input.defer_virtual_texture) {
|
||||
ZN_PROFILE_SCOPE_NAMED("Normalmap");
|
||||
const NormalMapSettings &settings = _normalmap_settings;
|
||||
|
||||
const unsigned int relative_lod_index = input.lod - settings.begin_lod_index;
|
||||
const unsigned int tile_resolution = math::clamp(int(settings.tile_resolution_min << relative_lod_index),
|
||||
int(settings.tile_resolution_min), int(settings.tile_resolution_max));
|
||||
const unsigned int tile_resolution = get_virtual_texture_tile_resolution_for_lod(input.lod);
|
||||
|
||||
static thread_local NormalMapData tls_normalmap_data;
|
||||
tls_normalmap_data.clear();
|
||||
@ -280,8 +290,9 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
|
||||
// TODO Allow to defer this to a different task?
|
||||
// - So the mesh can be obtained sooner
|
||||
// - And we can prevent it from updating as frequently as the mesh if repeatedly modified
|
||||
compute_normalmap(to_span(*cell_infos), tls_mesh_arrays, tls_normalmap_data, tile_resolution, *input.generator,
|
||||
input.data, input.origin_in_voxels + offset, input.lod, settings.octahedral_encoding_enabled);
|
||||
compute_normalmap(to_span(*cell_infos), to_span(mesh_arrays.vertices), to_span(mesh_arrays.normals),
|
||||
to_span(mesh_arrays.indices), tls_normalmap_data, tile_resolution, *input.generator, input.data,
|
||||
input.origin_in_voxels + offset, input.lod, settings.octahedral_encoding_enabled);
|
||||
|
||||
const Vector3i block_size =
|
||||
input.voxels.get_size() - Vector3iUtil::create(get_minimum_padding() + get_maximum_padding());
|
||||
@ -293,16 +304,19 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
|
||||
// .format(varray(input.lod, input.origin_in_voxels.x, input.origin_in_voxels.y,
|
||||
// input.origin_in_voxels.z)));
|
||||
|
||||
output.normalmap_atlas_images = images.atlas_images;
|
||||
output.cell_lookup_image = images.lookup_image;
|
||||
std::shared_ptr<VirtualTextureOutput> virtual_textures = make_shared_instance<VirtualTextureOutput>();
|
||||
virtual_textures->normalmap_atlas_images = images.atlas_images;
|
||||
virtual_textures->cell_lookup_image = images.lookup_image;
|
||||
virtual_textures->valid = true;
|
||||
output.virtual_textures = virtual_textures;
|
||||
}
|
||||
|
||||
transvoxel::MeshArrays *combined_mesh_arrays = &tls_mesh_arrays;
|
||||
transvoxel::MeshArrays *combined_mesh_arrays = &mesh_arrays;
|
||||
if (_mesh_optimization_params.enabled) {
|
||||
// TODO When voxel texturing is enabled, this will decrease quality a lot.
|
||||
// There is no support yet for taking textures into account when simplifying.
|
||||
// See https://github.com/zeux/meshoptimizer/issues/158
|
||||
simplify(tls_mesh_arrays, tls_simplified_mesh_arrays, _mesh_optimization_params.target_ratio,
|
||||
simplify(mesh_arrays, tls_simplified_mesh_arrays, _mesh_optimization_params.target_ratio,
|
||||
_mesh_optimization_params.error_threshold);
|
||||
|
||||
combined_mesh_arrays = &tls_simplified_mesh_arrays;
|
||||
|
@ -67,9 +67,23 @@ public:
|
||||
|
||||
Ref<ShaderMaterial> get_default_lod_material() const override;
|
||||
|
||||
// Internal
|
||||
|
||||
static void load_static_resources();
|
||||
static void free_static_resources();
|
||||
|
||||
// Exposed for a fast-path. Return values are only valid until the next invocation of build() in the calling thread.
|
||||
static const transvoxel::MeshArrays &get_mesh_cache_from_current_thread();
|
||||
static const Span<const transvoxel::CellInfo> get_cell_info_from_current_thread();
|
||||
|
||||
inline unsigned int get_virtual_texture_tile_resolution_for_lod(unsigned int lod_index) {
|
||||
const unsigned int relative_lod_index = lod_index - _normalmap_settings.begin_lod_index;
|
||||
const unsigned int tile_resolution =
|
||||
math::clamp(int(_normalmap_settings.tile_resolution_min << relative_lod_index),
|
||||
int(_normalmap_settings.tile_resolution_min), int(_normalmap_settings.tile_resolution_max));
|
||||
return tile_resolution;
|
||||
}
|
||||
|
||||
// Not sure if that's necessary, currently transitions are either combined or not generated
|
||||
// enum TransitionMode {
|
||||
// // No transition meshes will be generated
|
||||
|
@ -65,10 +65,10 @@ Ref<Mesh> VoxelMesher::build_mesh(
|
||||
++gd_surface_index;
|
||||
}
|
||||
|
||||
if (output.cell_lookup_image.is_valid()) {
|
||||
if (output.virtual_textures != nullptr && output.virtual_textures->valid) {
|
||||
NormalMapImages images;
|
||||
images.atlas_images = output.normalmap_atlas_images;
|
||||
images.lookup_image = output.cell_lookup_image;
|
||||
images.atlas_images = output.virtual_textures->normalmap_atlas_images;
|
||||
images.lookup_image = output.virtual_textures->cell_lookup_image;
|
||||
const NormalMapTextures textures = store_normalmap_data_to_textures(images);
|
||||
// That should be in return value, but for now I just want this for testing with GDScript, so it gotta go
|
||||
// somewhere
|
||||
@ -103,6 +103,18 @@ Ref<Material> VoxelMesher::get_material_by_index(unsigned int i) const {
|
||||
return Ref<Material>();
|
||||
}
|
||||
|
||||
bool VoxelMesher::is_mesh_empty(const std::vector<Output::Surface> &surfaces) {
|
||||
if (surfaces.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
for (const Output::Surface &surface : surfaces) {
|
||||
if (is_surface_triangulated(surface.arrays)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VoxelMesher::_bind_methods() {
|
||||
// Shortcut if you want to generate a mesh directly from a fixed grid of voxels.
|
||||
// Useful for testing the different meshers.
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "../constants/cube_tables.h"
|
||||
#include "../util/fixed_array.h"
|
||||
#include "../util/span.h"
|
||||
|
||||
#include <scene/resources/mesh.h>
|
||||
#include <vector>
|
||||
@ -40,6 +41,22 @@ public:
|
||||
// If true, the mesher is told that the mesh will be used in a context with variable level of detail.
|
||||
// For example, transition meshes will or will not be generated based on this (overriding mesher settings).
|
||||
bool lod_hint = false;
|
||||
// If true, virtual textures won't be generated immediately from the mesher
|
||||
// TODO I dont like this, there might be some refactoring to do
|
||||
bool defer_virtual_texture = false;
|
||||
};
|
||||
|
||||
struct VirtualTextureOutput {
|
||||
// Normalmap atlas used for smooth voxels.
|
||||
// If textures can't be created from threads, images are returned instead.
|
||||
// TODO Find a better organization to pass this around, this struct is getting quite big
|
||||
// TODO Might move out with the option of generating these separately
|
||||
Ref<Texture2DArray> normalmap_atlas;
|
||||
Vector<Ref<Image>> normalmap_atlas_images;
|
||||
Ref<Texture2D> cell_lookup;
|
||||
Ref<Image> cell_lookup_image;
|
||||
// Can be false if textures are computed asynchronously. Will become true when it's done (and not change after).
|
||||
std::atomic_bool valid;
|
||||
};
|
||||
|
||||
struct Output {
|
||||
@ -68,16 +85,12 @@ public:
|
||||
// (currently used only by the cubes mesher when baking colors)
|
||||
Ref<Image> atlas_image;
|
||||
|
||||
// Normalmap atlas used for smooth voxels.
|
||||
// If textures can't be created from threads, images are returned instead.
|
||||
// TODO Find a better organization to pass this around, this struct is getting quite big
|
||||
// TODO Might move out with the option of generating these separately
|
||||
Ref<Texture2DArray> normalmap_atlas;
|
||||
Vector<Ref<Image>> normalmap_atlas_images;
|
||||
Ref<Texture2D> cell_lookup;
|
||||
Ref<Image> cell_lookup_image;
|
||||
// Can be null.
|
||||
std::shared_ptr<VirtualTextureOutput> virtual_textures;
|
||||
};
|
||||
|
||||
static bool is_mesh_empty(const std::vector<Output::Surface> &surfaces);
|
||||
|
||||
// This can be called from multiple threads at once. Make sure member vars are protected or thread-local.
|
||||
virtual void build(Output &output, const Input &voxels);
|
||||
|
||||
|
@ -58,6 +58,7 @@ inline void recycle_shader_material(std::vector<Ref<ShaderMaterial>> &pool, Ref<
|
||||
// TODO Would be nice if we repurposed `u_transition_mask` to store extra flags.
|
||||
// Here we exploit cell_size==0 as "there is no virtual normalmaps on this block"
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_cell_size, 0.f);
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_virtual_texture_fade, 0.f);
|
||||
pool.push_back(material);
|
||||
}
|
||||
|
||||
@ -180,6 +181,10 @@ VoxelLodTerrain::VoxelLodTerrain() {
|
||||
VoxelLodTerrain *self = reinterpret_cast<VoxelLodTerrain *>(cb_data);
|
||||
self->apply_data_block_response(ob);
|
||||
};
|
||||
callbacks.virtual_texture_output_callback = [](void *cb_data, VoxelEngine::BlockVirtualTextureOutput &ob) {
|
||||
VoxelLodTerrain *self = reinterpret_cast<VoxelLodTerrain *>(cb_data);
|
||||
self->apply_virtual_texture_update(ob);
|
||||
};
|
||||
|
||||
_volume_id = VoxelEngine::get_singleton().add_volume(callbacks);
|
||||
// VoxelEngine::get_singleton().set_volume_octree_lod_distance(_volume_id, get_lod_distance());
|
||||
@ -1665,6 +1670,8 @@ void VoxelLodTerrain::apply_mesh_update(VoxelEngine::BlockMeshOutput &ob) {
|
||||
_instancer->on_mesh_block_exit(ob.position, ob.lod);
|
||||
}
|
||||
}
|
||||
// ZN_PRINT_VERBOSE(format("Empty block pos {} lod {} time {}", ob.position, int(ob.lod),
|
||||
// Time::get_singleton()->get_ticks_msec()));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1673,6 +1680,8 @@ void VoxelLodTerrain::apply_mesh_update(VoxelEngine::BlockMeshOutput &ob) {
|
||||
block->active = active;
|
||||
block->set_visible(active);
|
||||
mesh_map.set_block(ob.position, block);
|
||||
// ZN_PRINT_VERBOSE(format("Created block pos {} lod {} time {}", ob.position, int(ob.lod),
|
||||
// Time::get_singleton()->get_ticks_msec()));
|
||||
}
|
||||
|
||||
bool has_collision = get_generate_collisions();
|
||||
@ -1768,29 +1777,8 @@ void VoxelLodTerrain::apply_mesh_update(VoxelEngine::BlockMeshOutput &ob) {
|
||||
|
||||
block->set_parent_transform(get_global_transform());
|
||||
|
||||
// Distance normals
|
||||
if (ob.surfaces.cell_lookup_image.is_valid() || ob.surfaces.cell_lookup.is_valid()) {
|
||||
NormalMapTextures normalmap_textures;
|
||||
normalmap_textures.atlas = ob.surfaces.normalmap_atlas;
|
||||
normalmap_textures.lookup = ob.surfaces.cell_lookup;
|
||||
|
||||
if (normalmap_textures.lookup.is_null()) {
|
||||
NormalMapImages normalmap_images;
|
||||
normalmap_images.atlas_images = ob.surfaces.normalmap_atlas_images;
|
||||
normalmap_images.lookup_image = ob.surfaces.cell_lookup_image;
|
||||
normalmap_textures = store_normalmap_data_to_textures(normalmap_images);
|
||||
}
|
||||
|
||||
Ref<ShaderMaterial> material = block->get_shader_material();
|
||||
if (material.is_valid()) {
|
||||
material->set_shader_uniform(
|
||||
VoxelStringNames::get_singleton().u_voxel_normalmap_atlas, normalmap_textures.atlas);
|
||||
material->set_shader_uniform(
|
||||
VoxelStringNames::get_singleton().u_voxel_cell_lookup, normalmap_textures.lookup);
|
||||
const int cell_size = 1 << ob.lod;
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_cell_size, cell_size);
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_block_size, get_mesh_block_size());
|
||||
}
|
||||
if (ob.surfaces.virtual_textures != nullptr && ob.surfaces.virtual_textures->valid) {
|
||||
apply_virtual_texture_update_to_block(*block, *ob.surfaces.virtual_textures, ob.lod);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
@ -1800,6 +1788,68 @@ void VoxelLodTerrain::apply_mesh_update(VoxelEngine::BlockMeshOutput &ob) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::apply_virtual_texture_update(VoxelEngine::BlockVirtualTextureOutput &ob) {
|
||||
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[ob.lod_index];
|
||||
VoxelMeshBlockVLT *block = mesh_map.get_block(ob.position);
|
||||
|
||||
// This can happen if:
|
||||
// - Virtual texture rendering results are handled before the first meshing results which that have created the
|
||||
// block. In this case it will be applied when meshing results get handled, since the data is shared with it.
|
||||
// - The block was indeed unloaded early.
|
||||
if (block == nullptr) {
|
||||
// ZN_PRINT_VERBOSE(format("Ignored virtual texture update, block not found. pos {} lod {} time {}",
|
||||
// ob.position, ob.lod_index, Time::get_singleton()->get_ticks_msec()));
|
||||
return;
|
||||
}
|
||||
|
||||
ZN_ASSERT_RETURN(ob.virtual_textures != nullptr);
|
||||
ZN_ASSERT_RETURN(ob.virtual_textures->valid);
|
||||
|
||||
apply_virtual_texture_update_to_block(*block, *ob.virtual_textures, ob.lod_index);
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::apply_virtual_texture_update_to_block(
|
||||
VoxelMeshBlockVLT &block, VoxelMesher::VirtualTextureOutput &ob, unsigned int lod_index) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
ZN_ASSERT(ob.valid);
|
||||
|
||||
NormalMapTextures normalmap_textures;
|
||||
normalmap_textures.atlas = ob.normalmap_atlas;
|
||||
normalmap_textures.lookup = ob.cell_lookup;
|
||||
|
||||
if (normalmap_textures.lookup.is_null()) {
|
||||
// TODO When this code path is required, use a time-spread task to reduce stalls
|
||||
NormalMapImages normalmap_images;
|
||||
normalmap_images.atlas_images = ob.normalmap_atlas_images;
|
||||
normalmap_images.lookup_image = ob.cell_lookup_image;
|
||||
normalmap_textures = store_normalmap_data_to_textures(normalmap_images);
|
||||
}
|
||||
|
||||
Ref<ShaderMaterial> material = block.get_shader_material();
|
||||
if (material.is_valid()) {
|
||||
const bool had_texture =
|
||||
material->get_shader_uniform(VoxelStringNames::get_singleton().u_voxel_cell_lookup) != Variant();
|
||||
material->set_shader_uniform(
|
||||
VoxelStringNames::get_singleton().u_voxel_normalmap_atlas, normalmap_textures.atlas);
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_cell_lookup, normalmap_textures.lookup);
|
||||
const int cell_size = 1 << lod_index;
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_cell_size, cell_size);
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_block_size, get_mesh_block_size());
|
||||
|
||||
if (!had_texture) {
|
||||
if (_lod_fade_duration > 0.f) {
|
||||
// Fade-in to reduce "popping" details
|
||||
_fading_virtual_textures.push_back(FadingVirtualTexture{ block.position, lod_index, 0.f });
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_virtual_texture_fade, 0.f);
|
||||
} else {
|
||||
material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_virtual_texture_fade, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the material is not valid... well it means the user hasn't set up one, so all the hardwork of making these
|
||||
// textures goes in the bin. That should be a warning in the editor.
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::process_deferred_collision_updates(uint32_t timeout_msec) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
@ -1919,6 +1969,38 @@ void VoxelLodTerrain::process_fading_blocks(float delta) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ZN_PROFILE_SCOPE();
|
||||
const unsigned int lod_count = get_lod_count();
|
||||
|
||||
for (unsigned int i = 0; i < _fading_virtual_textures.size();) {
|
||||
FadingVirtualTexture &item = _fading_virtual_textures[i];
|
||||
bool remove = true;
|
||||
|
||||
if (item.lod_index < lod_count) {
|
||||
VoxelMeshBlockVLT *block = _mesh_maps_per_lod[item.lod_index].get_block(item.block_position);
|
||||
|
||||
if (block != nullptr) {
|
||||
Ref<ShaderMaterial> sm = block->get_shader_material();
|
||||
|
||||
if (sm.is_valid()) {
|
||||
item.progress = math::min(item.progress + speed, 1.f);
|
||||
remove = item.progress >= 1.f;
|
||||
sm->set_shader_uniform(
|
||||
VoxelStringNames::get_singleton().u_voxel_virtual_texture_fade, item.progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
_fading_virtual_textures[i] = _fading_virtual_textures.back();
|
||||
_fading_virtual_textures.pop_back();
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VoxelLodTerrain::LocalCameraInfo VoxelLodTerrain::get_local_camera_info() const {
|
||||
|
@ -265,6 +265,9 @@ private:
|
||||
|
||||
void apply_mesh_update(VoxelEngine::BlockMeshOutput &ob);
|
||||
void apply_data_block_response(VoxelEngine::BlockDataOutput &ob);
|
||||
void apply_virtual_texture_update(VoxelEngine::BlockVirtualTextureOutput &ob);
|
||||
void apply_virtual_texture_update_to_block(
|
||||
VoxelMeshBlockVLT &block, VoxelMesher::VirtualTextureOutput &ob, unsigned int lod_index);
|
||||
|
||||
void start_updater();
|
||||
void stop_updater();
|
||||
@ -359,6 +362,14 @@ private:
|
||||
// TODO Optimization: use FlatMap? Need to check how many blocks get in there, probably not many
|
||||
FixedArray<std::map<Vector3i, VoxelMeshBlockVLT *>, constants::MAX_LOD> _fading_blocks_per_lod;
|
||||
|
||||
struct FadingVirtualTexture {
|
||||
Vector3i block_position;
|
||||
uint32_t lod_index;
|
||||
float progress;
|
||||
};
|
||||
|
||||
std::vector<FadingVirtualTexture> _fading_virtual_textures;
|
||||
|
||||
VoxelInstancer *_instancer = nullptr;
|
||||
|
||||
Ref<VoxelMesher> _mesher;
|
||||
|
@ -19,6 +19,19 @@ bool is_surface_triangulated(Array surface) {
|
||||
return positions.size() >= 3 && indices.size() >= 3;
|
||||
}
|
||||
|
||||
bool is_mesh_empty(Span<const Array> surfaces) {
|
||||
if (surfaces.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < surfaces.size(); ++i) {
|
||||
Array surface = surfaces[i];
|
||||
if (is_surface_triangulated(surface)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_mesh_empty(const Mesh &mesh) {
|
||||
if (mesh.get_surface_count() == 0) {
|
||||
return true;
|
||||
|
@ -20,6 +20,7 @@ namespace zylann {
|
||||
|
||||
bool is_surface_triangulated(Array surface);
|
||||
bool is_mesh_empty(const Mesh &mesh);
|
||||
bool is_mesh_empty(Span<const Array> surfaces);
|
||||
|
||||
// Combines all mesh surface arrays into one collider.
|
||||
Ref<ConcavePolygonShape3D> create_concave_polygon_shape(Span<const Array> surfaces);
|
||||
|
Loading…
x
Reference in New Issue
Block a user