You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求菱形十二面体的精确有符号距离函数(SDF)

嘿,这问题挺有意思的——菱形十二面体的SDF确实因为它的对称性和凸多面体特性,比普通立方体体素要费点功夫。先给你梳理下:你的近似SDF其实是正确的凸多面体SDF,但它的梯度在边和顶点处不连续,所以渲染出来的边缘会显得很锐利。要做精确SDF(带圆角),核心是分区域计算点到面、边、顶点的最近距离,再用平滑组合实现圆角。

先给你补全你未完成的顶点区域计算,再优化整个代码,最后加上圆角效果:

完整精确SDF(分区域计算)

float Sdf(Voxel voxel, vec3 position) {
    position = position - voxel.Position;
    const float sqrt2 = sqrt(2.0);
    const float sqrt3 = sqrt(3.0);
    const float voxelSize = voxel.voxelSize;
    
    // 利用八面体对称性,将点映射到 x ≥ y ≥ z ≥ 0 的子空间,减少计算分支
    position = abs(position);
    if (position.x < position.y) position.xy = position.yx;
    if (position.x < position.z) position.xz = position.zx;
    if (position.y < position.z) position.yz = position.zy;
    
    // 定义6组归一化的面法向量(对应菱形十二面体的6组相对面)
    vec3 normals[6] = {
        vec3(1.0, 0.0, 0.0),
        vec3(0.5, 0.0, sqrt3/2.0),
        vec3(0.5, 0.0, -sqrt3/2.0),
        vec3(0.0, -sqrt2/sqrt3, 1.0/sqrt3),
        vec3(0.5, sqrt2/sqrt3, sqrt3/6.0),
        vec3(0.5, -sqrt2/sqrt3, -sqrt3/6.0)
    };
    
    float dists[6];
    float signs[6];
    
    // 计算到每组面的带符号距离:dot(n,p) - voxelSize,符号表示点在面的哪一侧
    for (int i = 0; i < 6; i++) {
        float dotProd = dot(normals[i], position);
        signs[i] = sign(dotProd);
        dists[i] = dotProd - voxelSize;
    }
    
    // 手动找出前三个最大的距离和对应法向量,比循环排序更高效
    float d0 = dists[0], d1 = dists[1], d2 = dists[2];
    vec3 n0 = normals[0], n1 = normals[1], n2 = normals[2];
    float s0 = signs[0], s1 = signs[1], s2 = signs[2];
    
    for (int i = 3; i < 6; i++) {
        if (dists[i] > d0) {
            d2 = d1; n2 = n1; s2 = s1;
            d1 = d0; n1 = n0; s1 = s0;
            d0 = dists[i]; n0 = normals[i]; s0 = signs[i];
        } else if (dists[i] > d1) {
            d2 = d1; n2 = n1; s2 = s1;
            d1 = dists[i]; n1 = normals[i]; s1 = signs[i];
        } else if (dists[i] > d2) {
            d2 = dists[i]; n2 = normals[i]; s2 = signs[i];
        }
    }
    
    // 情况1:最近点在面上(仅一个面的距离为正,其余为负/零)
    if (d1 <= 0.0) {
        return d0;
    }
    // 情况2:最近点在边上(两个面的距离为正,第三个为负/零)
    else if (d2 <= 0.0) {
        vec3 norm0 = n0 * s0;
        vec3 norm1 = n1 * s1;
        
        // 求解两个面的交线(边)上的最近点
        float dotNN = dot(norm0, norm1);
        float det = 1.0 - dotNN * dotNN;
        vec3 edgeDir = normalize(cross(norm0, norm1));
        vec3 proj = position - dot(position, edgeDir) * edgeDir;
        
        float t0 = (voxelSize - dot(norm1, proj) * dotNN) / det;
        float t1 = (voxelSize - dot(norm0, proj) * dotNN) / det;
        vec3 closestPoint = t0 * norm0 + t1 * norm1;
        
        return length(position - closestPoint);
    }
    // 情况3:最近点在顶点上(三个面的距离为正)
    else {
        vec3 norm0 = n0 * s0;
        vec3 norm1 = n1 * s1;
        vec3 norm2 = n2 * s2;
        
        // 求解三个面的交点(顶点)
        mat3 faceMatrix = mat3(norm0, norm1, norm2);
        vec3 closestPoint = inverse(faceMatrix) * vec3(voxelSize);
        
        return length(position - closestPoint);
    }
}

加入圆角效果(平滑组合)

要实现圆角,我们可以用smoothmin函数把面、边、顶点的距离平滑融合,避免生硬的边缘。先定义平滑最小函数:

// k 是平滑半径,值越大圆角越明显
float smoothmin(float a, float b, float k) {
    float h = clamp(0.5 + 0.5*(b - a)/k, 0.0, 1.0);
    return mix(b, a, h) - k*h*(1.0 - h);
}

float smoothmin3(float a, float b, float c, float k) {
    return smoothmin(smoothmin(a, b, k), c, k);
}

然后修改SDF的返回逻辑,把三种情况的距离用smoothmin组合:

// 预计算所有可能的距离
float faceDist = d0;

// 计算边距离
vec3 norm0 = n0 * s0;
vec3 norm1 = n1 * s1;
float dotNN = dot(norm0, norm1);
float det = 1.0 - dotNN * dotNN;
vec3 edgeDir = normalize(cross(norm0, norm1));
vec3 proj = position - dot(position, edgeDir) * edgeDir;
float t0 = (voxelSize - dot(norm1, proj) * dotNN) / det;
float t1 = (voxelSize - dot(norm0, proj) * dotNN) / det;
vec3 edgeClosest = t0 * norm0 + t1 * norm1;
float edgeDist = length(position - edgeClosest);

// 计算顶点距离
vec3 norm2 = n2 * s2;
mat3 faceMatrix = mat3(norm0, norm1, norm2);
vec3 vertexClosest = inverse(faceMatrix) * vec3(voxelSize);
float vertexDist = length(position - vertexClosest);

// 平滑组合三种距离,k 是圆角半径(可根据需求调整)
const float roundRadius = 0.1 * voxelSize;
return smoothmin3(faceDist, edgeDist, vertexDist, roundRadius);

一些优化建议

  1. 预计算逆矩阵:因为法向量是固定的,你可以预先计算好三个法向量组合的逆矩阵,避免在SDF计算中实时求逆,提升光线步进的效率。
  2. 对称性优化:我们已经用了x≥y≥z的对称映射,这能大幅减少分支判断的数量,建议保留。
  3. 精度调整:如果光线步进时出现精度问题,可以适当调整距离计算中的浮点精度,比如用double类型替代float(如果平台支持)。

这样修改后,你的SDF就能输出平滑的边缘,加上圆角后效果会更自然。如果后续还有问题,欢迎更新内容继续探讨!

内容的提问来源于stack exchange,提问作者selimbat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 12:12:46