创建精确纹理的球形网格

时间:2018-05-12 15:17:39

标签: javascript webgl mesh

我正在尝试使用WebGL在javascript中对地球进行建模,并按照this tutorial开始这样做。

网格上显示的纹理应具有最小的失真。为了避免扭曲的区域,距离和形状,我使用间断的正弦曲线图投影而不是等距矩形投影。我使用this site上的地球纹理进行测试。

我面临的问题是顶点渲染到预期的纹理边界之外。我抬起顶点的数量越多,看到的“外部”纹理越少(从左到右,纬度和经度带数量每步增加一倍):

preview

投影公式应该是正确的,我能够生成模板文件  确保 - 在c#tho中,我不习惯js: map stencil

所以,我的问题是,如何改进球体近似并避免在预期纹理区域之外显示像素?

相关代码:

// the projection used in the tutorial
function project_equirectangular(xangle, yangle) {
    return [xangle, yangle];
}

// the projection I intend to use to minimize distortion
function project_sinusoidial(xangle, yangle, segments) {
    var segment = Math.round(segments*yangle-1/2)+1;
    var segment_middle = (segment-1/2)/segments;
    return [
        (yangle-segment_middle)*Math.cos(Math.PI*(1/2-xangle))+segment_middle,
        xangle
    ];
}

// creating the spherical mesh for the globe
function initBuffers() {
    var M = 6;
    var N = 2*M;
    var radius = 1;
    var vertexPositionData = [];
    var normalData = [];
    var textureCoordData = [];
    for (var m=0; m <= M; m++) {
        var theta = Math.PI * m / M;
        var sinTheta = Math.sin(theta);
        var cosTheta = Math.cos(theta);
        for (var n=0; n <= N; n++) {
            var phi = 2 * Math.PI * n / N;
            var sinPhi = Math.sin(phi);
            var cosPhi = Math.cos(phi);
            
            var x = cosPhi * sinTheta;
            var y = cosTheta;
            var z = sinPhi * sinTheta;
            
            var proj = project_sinusoidial(m/M, n/N, 12);
            var u = 1 - proj[0];
            var v = 1 - proj[1];
            
            normalData.push(x);
            normalData.push(y);
            normalData.push(z);
            textureCoordData.push(u);
            textureCoordData.push(v);
            vertexPositionData.push(radius * x);
            vertexPositionData.push(radius * y);
            vertexPositionData.push(radius * z);
        }
    }
    var indexData = [];
    for (var m=0; m < M; m++) {
        for (var n=0; n < N; n++) {
            var first = (m * (N + 1)) + n;
            var second = first + N + 1;
            indexData.push(first);
            indexData.push(second);
            indexData.push(first + 1);
            indexData.push(second);
            indexData.push(second + 1);
            indexData.push(first + 1);
        }
    }
    planetVertexNormalBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, planetVertexNormalBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normalData), gl.STATIC_DRAW);
    planetVertexNormalBuffer.itemSize = 3;
    planetVertexNormalBuffer.numItems = normalData.length / 3;
    planetVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, planetVertexTextureCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordData), gl.STATIC_DRAW);
    planetVertexTextureCoordBuffer.itemSize = 2;
    planetVertexTextureCoordBuffer.numItems = textureCoordData.length / 2;
    planetVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, planetVertexPositionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexPositionData), gl.STATIC_DRAW);
    planetVertexPositionBuffer.itemSize = 3;
    planetVertexPositionBuffer.numItems = vertexPositionData.length / 3;
    planetVertexIndexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, planetVertexIndexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), gl.STATIC_DRAW);
    planetVertexIndexBuffer.itemSize = 1;
    planetVertexIndexBuffer.numItems = indexData.length;
}

1 个答案:

答案 0 :(得分:2)

您的纹理坐标在您的球体上不连续。它们之间存在很大的不连续性。因此,您无法使用常规机制生成球体;你必须根据你想要的纹理坐标来生成它。也就是说,您的网格几何必须与纹理的不连续性相匹配。

您的纹理中包含这些数据条。因此,这些条带必须是顶点生成的基础,包括位置。所以你的球体不是“球体”,而是一系列形成球形的抛物线条。每个单独的条带必须与其他条带分开;他们不能重复使用顶点。

同时,您必须确保每个条带的边缘生成与其相邻条带相同的位置值。否则,你可以在条带之间找到间隙。

您需要做的另一件事是确保纹理在条带的边缘具有适当的数据。边缘上三角形之间的纹理坐标的插值以及纹理过滤有时会访问纹理的“白色”区域中的值。因此,您需要在条带的边缘处添加一个像素的附加信息,以便过滤不会吸引不需要的数据。这必须在每个mipmap级别完成。

通常的想法是简单地重复相邻的纹素。

但是,从广义上讲,如果要对地球进行纹理处理(并且您可以根据需要生成地球的纹理),请使用立方体贴图。该方法将产生最小的失真并且需要最少的努力(在获得立方体贴图纹理本身之外)。另外,你甚至不需要纹理坐标;只需使用纹理坐标的插值法线。