WebGL 踩坑系列-3
绘制球体
在 WebGL 中绘制物体时需要的顶点是以直角坐标表示的,
当然了,gl_Position 是一个四维的向量,一般将顶点赋值给 gl_Position 时,最后一维会设为 1,
gl_Position = uMVPMatrix * vec4(aVertexPosition, 1.0);
这个时候的 aVertexPosition 三维向量就代表了顶点的直角坐标。
如果我们计算出球面上的顶点,并以直角坐标的形式传入着色器中,用合适的方式绘制,就能画出球面了。
但是,绘制球体需要用到顶点,如果直接用直角坐标,并不好计算,
这时候需要用到球坐标系将球面上的各个顶点表示出来,然后再将球坐标表示成直角坐标。
/**
* 假设球心即为原点,将球面坐标系转换成平面直角坐标系
* @param theta 球心到顶点的连线与 Z 轴正方向的夹角为 theta,范围是 [0, 180]
* @param beta 球心到顶点的连线在 xoy 平面上的投影与 X 轴正方向的夹角为 beta,范围是 [0, 360]
* @param r 球半径
* @return 顶点的坐标,用三维数组表示
*/
function calcVertex(theta, beta, r) {
var st = Math.sin(Math.PI * theta / 180);
var ct = Math.cos(Math.PI * theta / 180);
var sb = Math.sin(Math.PI * beta / 180);
var cb = Math.cos(Math.PI * beta / 180);
var x = r * st * cb;
var y = r * st * sb;
var z = r * ct;
return [x, y, z];
}
这个 calcVertex 函数就够把特定角度和半径的球坐标转换成相应的直角坐标了。
现在只需要从 0 - 180 遍历 theta,0 - 360 遍历 beta 角,就可以得到球面上的所有顶点了。
var n = 48;
var vetices = [];
var r = 1;
for( var j = 0; j < n; j++ ) {
for( var i = 0; i < n: i++ ) {
vertices.push.apply( vertices, calcVertex( i * 180 / n, j * 360 / n, r ) );
// 或者用数组的 concat 方法,效果是一样的,不过据说 concat 方法更高效
// vertices = vertices.concat( calcVertex( i * 180 / n, j * 360 / n, r ) );
}
}
接下来把得到的顶点传入 gl 的缓冲区中,
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
如果只是利用这些顶点,还不能画出球面,借助索引缓冲区可以实现:
var index = [];
for ( j = 0; j < n; j++ ) {
for ( i = 0; i < n+1; i++ ) {
index.push(
i + j * (n+1), // 0
i+1 + j * (n+1), // 1
i+1 + (j+1) * (n+1) // n+1
);
index.push(
i + j * (n+1), // 0
i+1 + (j+1) * (n+1) // n+1
i + (j+1) * (n+1) // n
);
}
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
最后就能在 canvas 画布上画出球面了,n 越大,球面越精细,画出的球越圆滑。
ThreeJS 绘制球形
由于有导入模型的需求,开始接触 ThreeJS,用这个框架只需要调用 SphereGeometry 的 API 生成一个球,最后把球添加到场景中并进行渲染即可。
sphere = new THREE.Mesh(
new THREE.SphereGeometry(4, 36, 36),
new THREE.MeshPhongMaterial( {
opacity: 0.65,
transparent: true,
color: 0xeeeeee
} )
);
scene.add(sphere);
总结
尽管 ThreeJS 对底层的 WebGL 封装的很好,但是我只需要导入 OBJ 格式的模型就行了,ThreeJS 很强大,用起来也很方便。
而要从 WebGL 写的话,还需要去了解 GLSL 着色器语言,自己编写着色器代码,繁琐很多。