Added option for octahedral encoding of normals to save memory
This commit is contained in:
parent
3606ab9b93
commit
dcc5e3c174
@ -139,22 +139,38 @@ unsigned int prepare_triangles(unsigned int first_index, const transvoxel::CellI
|
||||
return triangle_count;
|
||||
}
|
||||
|
||||
inline uint8_t snorm_to_u8(float x) {
|
||||
return math::clamp(127.f + 128.f * x, 0.f, 255.f);
|
||||
inline uint8_t unorm_to_u8(float x) {
|
||||
return math::clamp(255.f * x, 0.f, 255.f);
|
||||
}
|
||||
|
||||
inline NormalMapData::EncodedNormal encode_normal_xyz(const Vector3f n) {
|
||||
return { snorm_to_u8(n.x), snorm_to_u8(n.y), snorm_to_u8(n.z) };
|
||||
// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/
|
||||
Vector2f octahedron_wrap(Vector2f v) {
|
||||
return (Vector2f(1.f) - math::abs(Vector2f(v.y, v.x))) * math::sign_nonzero(v);
|
||||
}
|
||||
|
||||
// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/
|
||||
inline Vector2f encode_normal_octahedron(Vector3f n) {
|
||||
const float sum = Math::abs(n.x) + Math::abs(n.y) + Math::abs(n.z);
|
||||
n.x /= sum;
|
||||
n.y /= sum;
|
||||
Vector2f r = n.z >= 0.f ? Vector2f(n.x, n.y) : octahedron_wrap(Vector2f(n.x, n.y));
|
||||
r = r * 0.5f + Vector2f(0.5f);
|
||||
return r;
|
||||
}
|
||||
|
||||
inline Vector3f encode_normal_xyz(const Vector3f n) {
|
||||
return Vector3f(0.5f) + 0.5f * n;
|
||||
}
|
||||
|
||||
// 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,
|
||||
Vector3i origin_in_voxels, unsigned int lod_index) {
|
||||
Vector3i origin_in_voxels, unsigned int lod_index, bool octahedral_encoding) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
normal_map_data.normals.resize(math::squared(tile_resolution) * cell_infos.size());
|
||||
const unsigned int encoded_normal_size = octahedral_encoding ? 2 : 3;
|
||||
normal_map_data.normals.resize(math::squared(tile_resolution) * cell_infos.size() * encoded_normal_size);
|
||||
|
||||
const unsigned int cell_size = 1 << lod_index;
|
||||
const float step = float(cell_size) / tile_resolution;
|
||||
@ -317,11 +333,24 @@ void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, const transv
|
||||
}
|
||||
|
||||
// Encode normals
|
||||
// TODO Optimize: use octahedral encoding to use one less byte
|
||||
unsigned int tile_begin = cell_index * math::squared(tile_resolution);
|
||||
for (unsigned int i = 0; i < tls_tile_normals.size(); ++i) {
|
||||
ZN_ASSERT(tile_begin + i < normal_map_data.normals.size());
|
||||
normal_map_data.normals[tile_begin + i] = encode_normal_xyz(tls_tile_normals[i]);
|
||||
const unsigned int tile_begin = cell_index * math::squared(tile_resolution) * encoded_normal_size;
|
||||
if (octahedral_encoding) {
|
||||
for (unsigned int i = 0; i < tls_tile_normals.size(); ++i) {
|
||||
const unsigned int offset = tile_begin + i * encoded_normal_size;
|
||||
ZN_ASSERT(offset + encoded_normal_size <= normal_map_data.normals.size());
|
||||
const Vector2f n = encode_normal_octahedron(tls_tile_normals[i]);
|
||||
normal_map_data.normals[offset + 0] = unorm_to_u8(n.x);
|
||||
normal_map_data.normals[offset + 1] = unorm_to_u8(n.y);
|
||||
}
|
||||
} else {
|
||||
for (unsigned int i = 0; i < tls_tile_normals.size(); ++i) {
|
||||
const unsigned int offset = tile_begin + i * encoded_normal_size; //
|
||||
ZN_ASSERT(offset + encoded_normal_size <= normal_map_data.normals.size());
|
||||
const Vector3f n = encode_normal_xyz(tls_tile_normals[i]);
|
||||
normal_map_data.normals[offset + 0] = unorm_to_u8(n.x);
|
||||
normal_map_data.normals[offset + 1] = unorm_to_u8(n.y);
|
||||
normal_map_data.normals[offset + 2] = unorm_to_u8(n.z);
|
||||
}
|
||||
}
|
||||
|
||||
first_index += 3 * cell_info.triangle_count;
|
||||
@ -329,7 +358,7 @@ void compute_normalmap(Span<const transvoxel::CellInfo> cell_infos, const transv
|
||||
}
|
||||
|
||||
NormalMapImages store_normalmap_data_to_images(
|
||||
const NormalMapData &data, unsigned int tile_resolution, Vector3i block_size) {
|
||||
const NormalMapData &data, unsigned int tile_resolution, Vector3i block_size, bool octahedral_encoding) {
|
||||
ZN_PROFILE_SCOPE();
|
||||
|
||||
NormalMapImages images;
|
||||
@ -340,18 +369,21 @@ NormalMapImages store_normalmap_data_to_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 * sizeof(NormalMapData::EncodedNormal);
|
||||
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_pixels, 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, Image::FORMAT_RGB8, bytes);
|
||||
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)));
|
||||
|
@ -21,13 +21,8 @@ class VoxelGenerator;
|
||||
// triangles, and be stored in an atlas. A shader can then read the atlas using a lookup texture to find the tile.
|
||||
|
||||
struct NormalMapData {
|
||||
struct EncodedNormal {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t z;
|
||||
};
|
||||
// Encoded normals
|
||||
std::vector<EncodedNormal> normals;
|
||||
std::vector<uint8_t> normals;
|
||||
struct Tile {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
@ -46,7 +41,7 @@ struct NormalMapData {
|
||||
// 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,
|
||||
Vector3i origin_in_voxels, unsigned int lod_index);
|
||||
Vector3i origin_in_voxels, unsigned int lod_index, bool octahedral_encoding);
|
||||
|
||||
struct NormalMapImages {
|
||||
Vector<Ref<Image>> atlas_images;
|
||||
@ -59,7 +54,7 @@ struct NormalMapTextures {
|
||||
};
|
||||
|
||||
NormalMapImages store_normalmap_data_to_images(
|
||||
const NormalMapData &data, unsigned int tile_resolution, Vector3i block_size);
|
||||
const NormalMapData &data, unsigned int tile_resolution, Vector3i block_size, bool octahedral_encoding);
|
||||
|
||||
// Converts normalmap data into textures. They can be used in a shader to apply normals and obtain extra visual details.
|
||||
// This may not be allowed to run in a different thread than the main thread if the renderer is not using Vulkan.
|
||||
|
@ -244,13 +244,13 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
|
||||
tls_normalmap_data.clear();
|
||||
ZN_ASSERT(input.generator != nullptr);
|
||||
compute_normalmap(to_span(*cell_infos), tls_mesh_arrays, tls_normalmap_data, _normalmap_tile_resolution,
|
||||
*input.generator, input.origin_in_voxels, input.lod);
|
||||
*input.generator, input.origin_in_voxels, input.lod, _octahedral_normal_encoding_enabled);
|
||||
const Vector3i block_size =
|
||||
input.voxels.get_size() - Vector3iUtil::create(get_minimum_padding() + get_maximum_padding());
|
||||
// TODO This may be deferred to main thread when using GLES3
|
||||
// TODO Link tile resolution to LOD level, with a cap
|
||||
NormalMapImages images =
|
||||
store_normalmap_data_to_images(tls_normalmap_data, _normalmap_tile_resolution, block_size);
|
||||
NormalMapImages images = store_normalmap_data_to_images(
|
||||
tls_normalmap_data, _normalmap_tile_resolution, block_size, _octahedral_normal_encoding_enabled);
|
||||
NormalMapTextures textures = store_normalmap_data_to_textures(images);
|
||||
output.normalmap_atlas = textures.atlas;
|
||||
output.normalmap_lookup = textures.lookup;
|
||||
@ -403,6 +403,14 @@ unsigned int VoxelMesherTransvoxel::get_normalmap_tile_resolution() const {
|
||||
return _normalmap_tile_resolution;
|
||||
}
|
||||
|
||||
void VoxelMesherTransvoxel::set_octahedral_normal_encoding(bool enable) {
|
||||
_octahedral_normal_encoding_enabled = enable;
|
||||
}
|
||||
|
||||
bool VoxelMesherTransvoxel::get_octahedral_normal_encoding() const {
|
||||
return _octahedral_normal_encoding_enabled;
|
||||
}
|
||||
|
||||
void VoxelMesherTransvoxel::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("build_transition_mesh", "voxel_buffer", "direction"),
|
||||
&VoxelMesherTransvoxel::build_transition_mesh);
|
||||
@ -441,6 +449,11 @@ void VoxelMesherTransvoxel::_bind_methods() {
|
||||
ClassDB::bind_method(
|
||||
D_METHOD("get_normalmap_tile_resolution"), &VoxelMesherTransvoxel::get_normalmap_tile_resolution);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_octahedral_normal_encoding", "enabled"),
|
||||
&VoxelMesherTransvoxel::set_octahedral_normal_encoding);
|
||||
ClassDB::bind_method(
|
||||
D_METHOD("get_octahedral_normal_encoding"), &VoxelMesherTransvoxel::get_octahedral_normal_encoding);
|
||||
|
||||
ADD_PROPERTY(
|
||||
PropertyInfo(Variant::INT, "texturing_mode", PROPERTY_HINT_ENUM, "None,4-blend over 16 textures (4 bits)"),
|
||||
"set_texturing_mode", "get_texturing_mode");
|
||||
@ -463,6 +476,8 @@ void VoxelMesherTransvoxel::_bind_methods() {
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalmap_enabled"), "set_normalmap_enabled", "is_normalmap_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "normalmap_tile_resolution"), "set_normalmap_tile_resolution",
|
||||
"get_normalmap_tile_resolution");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalmap_octahedral_encoding_enabled"), "set_octahedral_normal_encoding",
|
||||
"get_octahedral_normal_encoding");
|
||||
|
||||
BIND_ENUM_CONSTANT(TEXTURES_NONE);
|
||||
// TODO Rename MIXEL
|
||||
|
@ -53,6 +53,9 @@ public:
|
||||
void set_normalmap_enabled(bool enable);
|
||||
bool is_normalmap_enabled() const;
|
||||
|
||||
void set_octahedral_normal_encoding(bool enable);
|
||||
bool get_octahedral_normal_encoding() const;
|
||||
|
||||
void set_normalmap_tile_resolution(unsigned int resolution);
|
||||
unsigned int get_normalmap_tile_resolution() const;
|
||||
|
||||
@ -96,6 +99,7 @@ private:
|
||||
// visual details using a shader.
|
||||
bool _normalmap_enabled = false;
|
||||
uint8_t _normalmap_tile_resolution = 8;
|
||||
bool _octahedral_normal_encoding_enabled = true;
|
||||
};
|
||||
|
||||
} // namespace zylann::voxel
|
||||
|
@ -239,8 +239,9 @@ inline void sort(T &a, T &b, T &c, T &d) {
|
||||
|
||||
// Returns -1 if `x` is negative, and 1 otherwise.
|
||||
// Contrary to a usual version like GLSL, this one returns 1 when `x` is 0, instead of 0.
|
||||
inline float sign_nonzero(float x) {
|
||||
return x < 0.f ? -1.f : 1.f;
|
||||
template <typename T>
|
||||
inline T sign_nonzero(T x) {
|
||||
return x < 0 ? -1 : 1;
|
||||
}
|
||||
|
||||
// Trilinear interpolation between corner values of a unit-sized cube.
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef ZN_VECTOR2T_H
|
||||
#define ZN_VECTOR2T_H
|
||||
|
||||
#include "funcs.h"
|
||||
|
||||
namespace zylann {
|
||||
|
||||
template <typename T>
|
||||
@ -18,6 +20,11 @@ struct Vector2T {
|
||||
};
|
||||
|
||||
Vector2T() : x(0), y(0) {}
|
||||
|
||||
// It is recommended to use `explicit` because otherwise it would open the door to plenty of implicit conversions
|
||||
// which would make many cases ambiguous.
|
||||
explicit Vector2T(T p_v) : x(p_v), y(p_v) {}
|
||||
|
||||
Vector2T(T p_x, T p_y) : x(p_x), y(p_y) {}
|
||||
|
||||
inline const T &operator[](const unsigned int p_axis) const {
|
||||
@ -75,6 +82,16 @@ T cross(const Vector2T<T> &a, const Vector2T<T> &b) {
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Vector2T<T> abs(const Vector2T<T> v) {
|
||||
return Vector2T<T>(Math::abs(v.x), Math::abs(v.y));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Vector2T<T> sign_nonzero(Vector2T<T> v) {
|
||||
return Vector2T<T>(sign_nonzero(v.x), sign_nonzero(v.y));
|
||||
}
|
||||
|
||||
} // namespace math
|
||||
} // namespace zylann
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user