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