合并的模型需要给每个区域的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