Switch to 2D textures instead of texture arrays due to layer count limitation

master
Marc Gilleron 2022-08-18 21:29:09 +01:00
parent a28a172763
commit 9cdaf1fd45
8 changed files with 130 additions and 46 deletions

View File

@ -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");

View File

@ -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;

View File

@ -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;
}

View File

@ -521,6 +521,29 @@ void compute_normalmap(ICellIterator &cell_iterator, Span<const Vector3f> mesh_v
}
}
inline void copy_2d_region(Span<uint8_t> dst, Vector2i dst_size, Span<const uint8_t> 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<Ref<Image>> 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;
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;
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<uint8_t> 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<const uint8_t> 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<Image> 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<Texture2DArray> 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
}
{

View File

@ -8,6 +8,10 @@
#include <core/object/ref_counted.h>
#include <vector>
//#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<const Vector3f> mesh_v
Vector3i origin_in_voxels, unsigned int lod_index, bool octahedral_encoding);
struct NormalMapImages {
#ifdef VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY
Vector<Ref<Image>> atlas;
#else
Ref<Image> atlas;
#endif
Ref<Image> lookup;
};
struct NormalMapTextures {
#ifdef VOXEL_VIRTUAL_TEXTURE_USE_TEXTURE_ARRAY
Ref<Texture2DArray> atlas;
#else
Ref<Texture2D> atlas;
#endif
Ref<Texture2D> lookup;
};

View File

@ -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,

View File

@ -1828,23 +1828,26 @@ void VoxelLodTerrain::apply_virtual_texture_update_to_block(
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 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

View File

@ -112,6 +112,10 @@ public:
}
}
inline bool overlaps(const Span<T> other) const {
return _ptr + _size > other._ptr && _ptr < other._ptr + other._size;
}
private:
T *_ptr;
size_t _size;