合并的模型需要给每个区域的mesh一个PivotPoint用于顶点着色器的计算。而FBX又不支持3通道的UV,数据储存起来比较麻烦,因此想了个算法。
NDM压缩算法(Normalized Direction & Magnitude),将一个相对位置转换成归一化方向和长度,因为是归一化方向所以只需要储存两个方向,剩下一个方向直接反算得出。 后面的hh0f代表着 half half 0(丢弃) full,意思为x轴与y轴都是half,z轴被丢弃,长度完整储存。
为了防止各个软件对数据进行处理,而没有使用二进制位储存,采用十进制储存。float可以有着7位有效的十进制数,归一化向量两个分量保留小数点后3位,y分量的符号储存在u通道的小数位,v通道的符号位用来保存z 的符号(因为反算有sqrt,结果无法为负数)。
|
U |
Signed x |
x (3) y(3) |
. |
Signed y(1) |
|
V |
Signed z |
Length integer |
. |
Length dec |
其中y的符号位被设计为了负数时为xxxyyy.1,正数时为xxxyyy.3。在计算的时候要减0.2在判断符号,这是为了防止发生数据通过不同软件可能的计算时,导致的蓝点抖动而对整数位产生影响。
Houdini的Vex压缩算法
vector pos = 999999;
for(int i = 0; i < @numpt; i++)
{
int success;
vector P = pointattrib(0, "P", i, success);
if (P.y < pos.y)
{
pos = P;
}
}
for(int i = 0; i < @numpt; i++)
{
int success;
vector P = pointattrib(0, "P", i, success);
vector dir = pos - P;
if (dir == 0)
{
dir += 0.000001;
}
vector norm_dir = normalize(dir);
float len = length(dir);
float sign_y = norm_dir.y >= 0 ? 1 : 0;
int u_x = abs(rint(norm_dir.x * 1000)) * 1000;
int u_y = abs(rint(norm_dir.y * 1000)) * 1;
int u_s = norm_dir.x >= 0 ? 1 : -1;
float u_sy = norm_dir.y >= 0 ? 0.3 : 0.1;
float u = (u_x + u_y + u_sy) * u_s;
float z_sign = norm_dir.z >= 0 ? 1 : -1;
float v = len * z_sign;
float values[] = array(u, v);
setpointattrib(0, "pivot_info", i, values);
}
Houdini中Vex解压
float uv[] = f[]@pivot_info;
float u = uv[0];
float v = uv[1];
float x = rint(u / 1000.0) / 1000.0;
float y = frac(abs(u) / 1000.0) * sign(frac(abs(u)) - 0.2);
float z = sign(v) * sqrt(1-x*x-y*y);
float len = abs(v);
vector dir;
dir.x = x;
dir.y = y;
dir.z = z;
@P += (dir * len) * chf("v");
Hlsl版本的实现
#ifndef COMPRESSIONLIBRARY_HLSL
#define COMPRESSIONLIBRARY_HLSL
namespace compression
{
float3 UncompressNDMhh0f(float2 pak)
{
float x = round(pak.x / 1000.0) / 1000.0;
float y = frac(abs(pak.x) / 1000.0) * sign(frac(abs(pak.x)) - 0.2);
float z = sign(pak.y) * sqrt(1-x*x-y*y);
float m = abs(pak.y);
return float3(x, y, z) * m;
}
float2 CompressNDMhh0f(float3 pak)
{
if (pak == 0) pak += 0.000001;
float3 norm_dir = normalize(pak);
float len = length(pak);
int u_x = abs(round(norm_dir.x * 1000)) * 1000;
int u_y = abs(round(norm_dir.y * 1000)) * 1;
int u_s = norm_dir.x >= 0 ? 1 : -1;
float u_sy = norm_dir.y >= 0 ? 0.3 : 0.1;
float u = (u_x + u_y + u_sy) * u_s;
float z_sign = norm_dir.z >= 0 ? 1 : -1;
float v = len * z_sign;
return float2(u, v);
}
}
#endif