diff --git a/constants/voxel_string_names.cpp b/constants/voxel_string_names.cpp index ffb2f28f..57a253b4 100644 --- a/constants/voxel_string_names.cpp +++ b/constants/voxel_string_names.cpp @@ -46,6 +46,7 @@ VoxelStringNames::VoxelStringNames() { 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"); + u_voxel_virtual_texture_tile_size = StaticCString::create("u_voxel_virtual_texture_tile_size"); #ifdef DEBUG_ENABLED _voxel_debug_vt_position = StaticCString::create("_voxel_debug_vt_position"); diff --git a/constants/voxel_string_names.h b/constants/voxel_string_names.h index 4fa93050..a9df5007 100644 --- a/constants/voxel_string_names.h +++ b/constants/voxel_string_names.h @@ -41,6 +41,7 @@ public: StringName u_voxel_cell_size; StringName u_voxel_block_size; StringName u_voxel_virtual_texture_fade; + StringName u_voxel_virtual_texture_tile_size; #ifdef DEBUG_ENABLED StringName _voxel_debug_vt_position; diff --git a/doc/source/smooth_terrain.md b/doc/source/smooth_terrain.md index a2c04f59..78417260 100644 --- a/doc/source/smooth_terrain.md +++ b/doc/source/smooth_terrain.md @@ -343,16 +343,17 @@ 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 `RG8` texture where each pixel contains a cell index packed in bytes of `R` and part of `G` (`r + ((g & 0x3f) << 8)`), and an axis index in 2 bits of `G` (`g >> 6`). 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). +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 `RG8` texture where each pixel contains a cell index packed in bytes of `R` and part of `G` (`r + ((g & 0x3f) << 8)`), and an axis index in 2 bits of `G` (`g >> 6`). 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` | `sampler2D` | Texture atlas where each tile contains a model-space normalmap (it is not relative to surface, unlike common normalmaps). Coordinates may be computed from `u_voxel_cell_lookup` and `u_voxel_virtual_texture_tile_size`. UV orientation is similar to triplanar mapping, but the axes are known from the information in `u_voxel_cell_lookup`. Will only be assigned in meshes using virtual texturing of [normalmaps](#distance-normals). +`u_voxel_virtual_texture_tile_size` | `int` | Resolution in pixels of each tile in `u_voxel_normalmap_atlas`. +`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) @@ -513,7 +514,8 @@ Rendering these normals requires special shader code in your terrain material. //uniform usampler2D u_voxel_cell_lookup; uniform sampler2D u_voxel_cell_lookup : filter_nearest; -uniform sampler2DArray u_voxel_normalmap_atlas; +uniform sampler2D u_voxel_normalmap_atlas; +uniform int u_voxel_virtual_texture_tile_size; uniform float u_voxel_cell_size; uniform int u_voxel_block_size; @@ -539,7 +541,7 @@ vec3 octahedron_decode(vec2 f) { vec3 get_voxel_normal_model() { float cell_size = u_voxel_cell_size; int block_size = u_voxel_block_size; - float normalmap_tile_size = float(textureSize(u_voxel_normalmap_atlas, 0).x); + int normalmap_tile_size = u_voxel_virtual_texture_tile_size; ivec3 cell_pos = ivec3(floor(v_vertex_pos_model / cell_size)); vec3 cell_fract = fract(v_vertex_pos_model / cell_size); @@ -550,9 +552,9 @@ vec3 get_voxel_normal_model() { //uvec3 lookup_value = texelFetch(u_voxel_cell_lookup, lookup_pos, 0).rgb; //vec3 lookup_valuef = texelFetch(u_voxel_cell_lookup, lookup_pos, 0).rgb; vec2 lookup_valuef = texture(u_voxel_cell_lookup, (vec2(lookup_pos) + vec2(0.5)) / float(lookup_sqri)).rg; - uvec2 lookup_value = uvec2(round(lookup_valuef * 255.0)); - uint tile_index = lookup_value.r | ((lookup_value.g & uint(0x3f)) << 8u); - uint tile_direction = lookup_value.g >> 6u; + ivec2 lookup_value = ivec2(round(lookup_valuef * 255.0)); + int tile_index = lookup_value.r | ((lookup_value.g & 0x3f) << 8); + int tile_direction = lookup_value.g >> 6; vec3 tile_texcoord = vec3(0.0, 0.0, float(tile_index)); // TODO Could do it non-branching with weighted addition @@ -570,13 +572,17 @@ vec3 get_voxel_normal_model() { float padding = 0.5 / normalmap_tile_size; tile_texcoord.xy = pad_uv(tile_texcoord.xy, padding); + ivec2 atlas_size = textureSize(u_voxel_normalmap_atlas, 0); + int tiles_per_row = atlas_size.x / normalmap_tile_size; + ivec2 tile_pos_pixels = ivec2(tile_index % tiles_per_row, tile_index / tiles_per_row) * normalmap_tile_size; + vec2 atlas_texcoord = (vec2(tile_pos_pixels) + float(normalmap_tile_size) * tile_texcoord) / vec2(atlas_size); + vec3 encoded_normal = texture(u_voxel_normalmap_atlas, atlas_texcoord).rgb; + // You may switch between these two snippets depending on if you use octahedral compression or not // 1) XYZ - vec3 encoded_normal = texture(u_voxel_normalmap_atlas, tile_texcoord).rgb; vec3 tile_normal_model = 2.0 * encoded_normal - vec3(1.0); // 2) Octahedral - // vec2 encoded_normal = texture(u_voxel_normalmap_atlas, tile_texcoord).rg; - // vec3 tile_normal_model = octahedron_decode(encoded_normal); + // vec3 tile_normal_model = octahedron_decode(encoded_normal.rg); return tile_normal_model; } diff --git a/engine/distance_normalmaps.cpp b/engine/distance_normalmaps.cpp index 3c6909d6..190d7721 100644 --- a/engine/distance_normalmaps.cpp +++ b/engine/distance_normalmaps.cpp @@ -521,6 +521,29 @@ void compute_normalmap(ICellIterator &cell_iterator, Span mesh_v } } +inline void copy_2d_region(Span dst, Vector2i dst_size, Span src, Vector2i src_size, + Vector2i dst_pos, unsigned int item_size_in_bytes) { +#ifdef DEBUG_ENABLED + ZN_ASSERT(src_size.x >= 0 && src_size.y >= 0); + ZN_ASSERT(dst_size.x >= 0 && dst_size.y >= 0); + ZN_ASSERT(dst_pos.x >= 0 && dst_pos.y >= 0 && dst_pos.x + src_size.x <= dst_size.x && + dst_pos.y + src_size.y <= dst_size.y); + ZN_ASSERT(src.size() == src_size.x * src_size.y * item_size_in_bytes); + ZN_ASSERT(dst.size() == dst_size.x * dst_size.y * item_size_in_bytes); + ZN_ASSERT(!src.overlaps(dst)); +#endif + const unsigned int dst_begin = (dst_pos.x + dst_pos.y * dst_size.x) * item_size_in_bytes; + const unsigned int src_row_size = src_size.x * item_size_in_bytes; + const unsigned int dst_row_size = dst_size.x * item_size_in_bytes; + uint8_t *dst_p = dst.data() + dst_begin; + const uint8_t *src_p = src.data(); + for (unsigned int src_y = 0; src_y < (unsigned int)src_size.y; ++src_y) { + memcpy(dst_p, src_p, src_row_size); + dst_p += dst_row_size; + src_p += src_row_size; + } +} + NormalMapImages store_normalmap_data_to_images( const NormalMapData &data, unsigned int tile_resolution, Vector3i block_size, bool octahedral_encoding) { ZN_PROFILE_SCOPE(); @@ -529,32 +552,57 @@ NormalMapImages store_normalmap_data_to_images( { ZN_PROFILE_SCOPE_NAMED("Atlas images"); + + const unsigned int pixel_size = octahedral_encoding ? 2 : 3; + const Image::Format format = octahedral_encoding ? Image::FORMAT_RG8 : Image::FORMAT_RGB8; + const unsigned int tile_size_in_pixels = math::squared(tile_resolution); + const unsigned int tile_size_in_bytes = tile_size_in_pixels * pixel_size; + +#ifdef VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY + Vector> tile_images; tile_images.resize(data.tiles.size()); - { - const unsigned int pixel_size = octahedral_encoding ? 2 : 3; - const Image::Format format = octahedral_encoding ? Image::FORMAT_RG8 : Image::FORMAT_RGB8; - - for (unsigned int tile_index = 0; tile_index < data.tiles.size(); ++tile_index) { - PackedByteArray bytes; - { - const unsigned int tile_size_in_pixels = math::squared(tile_resolution); - const unsigned int tile_size_in_bytes = tile_size_in_pixels * pixel_size; - bytes.resize(tile_size_in_bytes); - memcpy(bytes.ptrw(), data.normals.data() + tile_index * tile_size_in_bytes, tile_size_in_bytes); - } - - Ref image; - image.instantiate(); - image->create_from_data(tile_resolution, tile_resolution, false, format, bytes); - - tile_images.write[tile_index] = image; - //image->save_png(String("debug_atlas_{0}.png").format(varray(tile_index))); + for (unsigned int tile_index = 0; tile_index < data.tiles.size(); ++tile_index) { + PackedByteArray bytes; + { + bytes.resize(tile_size_in_bytes); + memcpy(bytes.ptrw(), data.normals.data() + tile_index * tile_size_in_bytes, tile_size_in_bytes); } + + Ref image; + image.instantiate(); + image->create_from_data(tile_resolution, tile_resolution, false, format, bytes); + + tile_images.write[tile_index] = image; + //image->save_png(String("debug_atlas_{0}.png").format(varray(tile_index))); } images.atlas = tile_images; + +#else + const unsigned int tiles_across = int(Math::ceil(Math::sqrt(float(data.tiles.size())))); + const unsigned int pixels_across = tiles_across * tile_resolution; + + PackedByteArray bytes; + bytes.resize(math::squared(tiles_across) * tile_size_in_bytes); + Span bytes_span(bytes.ptrw(), bytes.size()); + + for (unsigned int tile_index = 0; tile_index < data.tiles.size(); ++tile_index) { + const Vector2i tile_pos_pixels = + int(tile_resolution) * Vector2i(tile_index % tiles_across, tile_index / tiles_across); + Span tile = + to_span_from_position_and_size(data.normals, tile_index * tile_size_in_bytes, tile_size_in_bytes); + copy_2d_region(bytes_span, Vector2i(pixels_across, pixels_across), tile, + Vector2i(tile_resolution, tile_resolution), tile_pos_pixels, pixel_size); + } + + Ref atlas; + atlas.instantiate(); + atlas->create_from_data(pixels_across, pixels_across, false, format, bytes); + images.atlas = atlas; + +#endif // VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY } { @@ -610,11 +658,15 @@ NormalMapTextures store_normalmap_data_to_textures(const NormalMapImages &data) { ZN_PROFILE_SCOPE_NAMED("Atlas texture"); +#ifdef VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY Ref atlas; atlas.instantiate(); const Error err = atlas->create_from_images(data.atlas); ZN_ASSERT_RETURN_V(err == OK, textures); textures.atlas = atlas; +#else + textures.atlas = ImageTexture::create_from_image(data.atlas); +#endif } { diff --git a/engine/distance_normalmaps.h b/engine/distance_normalmaps.h index 679cba91..059d1fb5 100644 --- a/engine/distance_normalmaps.h +++ b/engine/distance_normalmaps.h @@ -8,6 +8,10 @@ #include #include +//#define VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY +// Texture arrays are handy but the maximum amount of layers is often too low (2048 on an nVidia 1060), which happens +// too frequently with block size 32. So instead we have to keep using 2D atlases with padding. + class Texture2DArray; class Texture2D; class Image; @@ -85,12 +89,20 @@ void compute_normalmap(ICellIterator &cell_iterator, Span mesh_v Vector3i origin_in_voxels, unsigned int lod_index, bool octahedral_encoding); struct NormalMapImages { +#ifdef VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY Vector> atlas; +#else + Ref atlas; +#endif Ref lookup; }; struct NormalMapTextures { +#ifdef VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY Ref atlas; +#else + Ref atlas; +#endif Ref lookup; }; diff --git a/engine/generate_distance_normalmap_task.cpp b/engine/generate_distance_normalmap_task.cpp index 0e630324..80553a70 100644 --- a/engine/generate_distance_normalmap_task.cpp +++ b/engine/generate_distance_normalmap_task.cpp @@ -58,6 +58,11 @@ void GenerateDistanceNormalmapTask::run(ThreadedTaskContext ctx) { // String("debug_data/debug_normalmap_atlas_{0}_{1}_{2}_lod{3}_n{4}.png") // .format(varray(mesh_block_position.x, mesh_block_position.y, mesh_block_position.z, lod_index, // images.atlas.size()))); + // if (images.atlas.is_valid()) { + // images.atlas->save_png(String("debug_data/debug_normalmap_atlas_{0}_{1}_{2}_lod{3}_n{4}.png") + // .format(varray(mesh_block_position.x, mesh_block_position.y, + // mesh_block_position.z, lod_index, tls_normalmap_data.tiles.size()))); + // } // if (images.lookup.is_valid()) { // images.lookup->save_png(String("debug_data/debug_lookup_{0}_{1}_{2}_lod{3}.png") // .format(varray(mesh_block_position.x, mesh_block_position.y, diff --git a/terrain/variable_lod/voxel_lod_terrain.cpp b/terrain/variable_lod/voxel_lod_terrain.cpp index c0af15c2..1bf1a95e 100644 --- a/terrain/variable_lod/voxel_lod_terrain.cpp +++ b/terrain/variable_lod/voxel_lod_terrain.cpp @@ -1828,23 +1828,26 @@ void VoxelLodTerrain::apply_virtual_texture_update_to_block( Ref 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 VoxelStringNames &sn = VoxelStringNames::get_singleton(); + + const bool had_texture = material->get_shader_uniform(sn.u_voxel_cell_lookup) != Variant(); + material->set_shader_uniform(sn.u_voxel_normalmap_atlas, normalmap_textures.atlas); + material->set_shader_uniform(sn.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()); + material->set_shader_uniform(sn.u_voxel_cell_size, cell_size); + material->set_shader_uniform(sn.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); + material->set_shader_uniform(sn.u_voxel_virtual_texture_fade, 0.f); } else { - material->set_shader_uniform(VoxelStringNames::get_singleton().u_voxel_virtual_texture_fade, 1.f); + material->set_shader_uniform(sn.u_voxel_virtual_texture_fade, 1.f); } + const unsigned int tile_size = get_virtual_texture_tile_resolution_for_lod( + _update_data->settings.virtual_texture_settings, lod_index); + material->set_shader_uniform(sn.u_voxel_virtual_texture_tile_size, tile_size); } } // If the material is not valid... well it means the user hasn't set up one, so all the hardwork of making these diff --git a/util/span.h b/util/span.h index 4d6b4c90..3b49504f 100644 --- a/util/span.h +++ b/util/span.h @@ -112,6 +112,10 @@ public: } } + inline bool overlaps(const Span other) const { + return _ptr + _size > other._ptr && _ptr < other._ptr + other._size; + } + private: T *_ptr; size_t _size;