Skip to content

Commit

Permalink
Merge pull request #585 from zeux/gltf-vtf
Browse files Browse the repository at this point in the history
gltfpack: Implement support for floating-point texture coordinate quantization
  • Loading branch information
zeux authored Jul 25, 2023
2 parents 03606ee + 9e05936 commit 4934c4b
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 45 deletions.
19 changes: 15 additions & 4 deletions gltf/gltfpack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ static void process(cgltf_data* data, const char* input_path, const char* output

comma(json_materials);
append(json_materials, "{");
writeMaterial(json_materials, data, material, settings.quantize && !settings.pos_float ? &qp : NULL, settings.quantize ? &qt_materials[i] : NULL);
writeMaterial(json_materials, data, material, settings.quantize && !settings.pos_float ? &qp : NULL, settings.quantize && !settings.tex_float ? &qt_materials[i] : NULL);
if (settings.keep_extras)
writeExtras(json_materials, material.extras);
append(json_materials, "}");
Expand Down Expand Up @@ -804,7 +804,7 @@ static void process(cgltf_data* data, const char* input_path, const char* output
const ExtensionInfo extensions[] = {
{"KHR_mesh_quantization", settings.quantize, true},
{"EXT_meshopt_compression", settings.compress, !settings.fallback},
{"KHR_texture_transform", (settings.quantize && !json_textures.empty()) || ext_texture_transform, false},
{"KHR_texture_transform", (settings.quantize && !settings.tex_float && !json_textures.empty()) || ext_texture_transform, false},
{"KHR_materials_pbrSpecularGlossiness", ext_pbr_specular_glossiness, false},
{"KHR_materials_clearcoat", ext_clearcoat, false},
{"KHR_materials_transmission", ext_transmission, false},
Expand Down Expand Up @@ -1213,6 +1213,14 @@ int main(int argc, char** argv)
{
settings.pos_float = true;
}
else if (strcmp(arg, "-vtn") == 0)
{
settings.tex_float = false;
}
else if (strcmp(arg, "-vtf") == 0)
{
settings.tex_float = true;
}
else if (strcmp(arg, "-at") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))
{
settings.trn_bits = clamp(atoi(argv[++i]), 1, 24);
Expand Down Expand Up @@ -1455,6 +1463,9 @@ int main(int argc, char** argv)
fprintf(stderr, "\t-vpi: use integer attributes for positions (default)\n");
fprintf(stderr, "\t-vpn: use normalized attributes for positions\n");
fprintf(stderr, "\t-vpf: use floating point attributes for positions\n");
fprintf(stderr, "\nTexture coordinates:\n");
fprintf(stderr, "\t-vtn: use normalized attributes for texture coordinates (default)\n");
fprintf(stderr, "\t-vtf: use floating point attributes for texture coordinates\n");
fprintf(stderr, "\nAnimations:\n");
fprintf(stderr, "\t-at N: use N-bit quantization for translations (default: 16; N should be between 1 and 24)\n");
fprintf(stderr, "\t-ar N: use N-bit quantization for rotations (default: 12; N should be between 4 and 16)\n");
Expand Down Expand Up @@ -1524,9 +1535,9 @@ int main(int argc, char** argv)
return 1;
}

if (settings.fallback && settings.pos_float)
if (settings.fallback && (settings.pos_float || settings.tex_float))
{
fprintf(stderr, "Option -cf can not be used together with -vpf\n");
fprintf(stderr, "Option -cf can not be used together with -vpf or -tpf\n");
return 1;
}

Expand Down
1 change: 1 addition & 0 deletions gltf/gltfpack.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ struct Settings

bool pos_normalized;
bool pos_float;
bool tex_float;

int trn_bits;
int rot_bits;
Expand Down
125 changes: 84 additions & 41 deletions gltf/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,15 @@ static void encodeExpShared(uint32_t v[3], const Attr& a, int bits)
v[2] = (mz & mmask) | (unsigned(exp) << 24);
}

static uint32_t encodeExpOne(float v, int bits)
static uint32_t encodeExpOne(float v, int bits, int min_exp = -100)
{
// extract exponent
int e;
frexp(v, &e);

// clamp exponent to ensure it's valid after bias
e = std::max(e, min_exp);

// scale the mantissa to make it a K-bit signed integer (K-1 bits for magnitude)
int exp = e - (bits - 1);

Expand All @@ -321,49 +324,47 @@ static uint32_t encodeExpOne(float v, int bits)
return (m & mmask) | (unsigned(exp) << 24);
}

static void encodeExpParallel(std::string& bin, const Attr* data, size_t count, int bits)
static void encodeExpParallel(std::string& bin, const Attr* data, size_t count, int channels, int bits, int min_exp = -100)
{
int expx = -128, expy = -128, expz = -128;
int exp[4] = {};

for (int k = 0; k < channels; ++k)
exp[k] = min_exp;

for (size_t i = 0; i < count; ++i)
{
const Attr& a = data[i];

// get exponents from all components
int ex, ey, ez;
frexp(a.f[0], &ex);
frexp(a.f[1], &ey);
frexp(a.f[2], &ez);

// use maximum exponent to encode values; this guarantees that mantissa is [-1, 1]
expx = std::max(expx, ex);
expy = std::max(expy, ey);
expz = std::max(expz, ez);
for (int k = 0; k < channels; ++k)
{
int e;
frexp(a.f[k], &e);
exp[k] = std::max(exp[k], e);
}
}

// scale the mantissa to make it a K-bit signed integer (K-1 bits for magnitude)
expx -= (bits - 1);
expy -= (bits - 1);
expz -= (bits - 1);
for (int k = 0; k < channels; ++k)
exp[k] -= (bits - 1);

for (size_t i = 0; i < count; ++i)
{
const Attr& a = data[i];

// compute renormalized rounded mantissas
int mx = int(ldexp(a.f[0], -expx) + (a.f[0] >= 0 ? 0.5f : -0.5f));
int my = int(ldexp(a.f[1], -expy) + (a.f[1] >= 0 ? 0.5f : -0.5f));
int mz = int(ldexp(a.f[2], -expz) + (a.f[2] >= 0 ? 0.5f : -0.5f));
uint32_t v[4];

int mmask = (1 << 24) - 1;
for (int k = 0; k < channels; ++k)
{
// compute renormalized rounded mantissas
int m = int(ldexp(a.f[k], -exp[k]) + (a.f[k] >= 0 ? 0.5f : -0.5f));

// encode exponent & mantissa
uint32_t v[3];
v[0] = (mx & mmask) | (unsigned(expx) << 24);
v[1] = (my & mmask) | (unsigned(expy) << 24);
v[2] = (mz & mmask) | (unsigned(expz) << 24);
// encode exponent & mantissa
int mmask = (1 << 24) - 1;
v[k] = (m & mmask) | (unsigned(exp[k]) << 24);
}

bin.append(reinterpret_cast<const char*>(v), sizeof(v));
bin.append(reinterpret_cast<const char*>(v), sizeof(uint32_t) * channels);
}
}

Expand Down Expand Up @@ -405,7 +406,7 @@ StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const Qua

if (settings.compressmore)
{
encodeExpParallel(bin, &stream.data[0], stream.data.size(), qp.bits + 1);
encodeExpParallel(bin, &stream.data[0], stream.data.size(), 3, qp.bits + 1);
}
else
{
Expand Down Expand Up @@ -511,24 +512,66 @@ StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const Qua
if (!settings.quantize)
return writeVertexStreamRaw(bin, stream, cgltf_type_vec2, 2);

float uv_rscale[2] = {
qt.scale[0] == 0.f ? 0.f : 1.f / qt.scale[0],
qt.scale[1] == 0.f ? 0.f : 1.f / qt.scale[1],
};

for (size_t i = 0; i < stream.data.size(); ++i)
if (settings.tex_float)
{
const Attr& a = stream.data[i];
StreamFormat::Filter filter = settings.compress ? StreamFormat::Filter_Exp : StreamFormat::Filter_None;

uint16_t v[2] = {
uint16_t(meshopt_quantizeUnorm((a.f[0] - qt.offset[0]) * uv_rscale[0], qt.bits)),
uint16_t(meshopt_quantizeUnorm((a.f[1] - qt.offset[1]) * uv_rscale[1], qt.bits)),
};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
// expand the encoded range to ensure it covers [0..1) interval
// this can slightly reduce precision but we should not need more precision inside 0..1, and this significantly
// improves compressed size when using encodeExpOne
const int min_exp = 0;

if (settings.compressmore)
{
encodeExpParallel(bin, &stream.data[0], stream.data.size(), 2, qt.bits + 1, min_exp);
}
else
{
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];

if (filter == StreamFormat::Filter_Exp)
{
uint32_t v[2];
v[0] = encodeExpOne(a.f[0], qt.bits + 1, min_exp);
v[1] = encodeExpOne(a.f[1], qt.bits + 1, min_exp);
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
else
{
float v[2] = {
meshopt_quantizeFloat(a.f[0], qt.bits),
meshopt_quantizeFloat(a.f[1], qt.bits)};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}
}
}

StreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_32f, false, 8, filter};
return format;
}
else
{
float uv_rscale[2] = {
qt.scale[0] == 0.f ? 0.f : 1.f / qt.scale[0],
qt.scale[1] == 0.f ? 0.f : 1.f / qt.scale[1],
};

StreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_16u, qt.normalized, 4};
return format;
for (size_t i = 0; i < stream.data.size(); ++i)
{
const Attr& a = stream.data[i];

uint16_t v[2] = {
uint16_t(meshopt_quantizeUnorm((a.f[0] - qt.offset[0]) * uv_rscale[0], qt.bits)),
uint16_t(meshopt_quantizeUnorm((a.f[1] - qt.offset[1]) * uv_rscale[1], qt.bits)),
};
bin.append(reinterpret_cast<const char*>(v), sizeof(v));
}

StreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_16u, qt.normalized, 4};
return format;
}
}
else if (stream.type == cgltf_attribute_type_normal)
{
Expand Down

0 comments on commit 4934c4b

Please sign in to comment.