球、旋转矩阵、鼠标事件

这一节我们做一个球体,贴上月球的纹理,加入鼠标的交互。

效果如图12。

图12

图12

光照方面使用第七节的代码,不再多说。

function webGLStart()
{
    //...
    canvas.mousedown(handleMouseDown);
    $(document).mouseup(handleMouseUp);
    $(document).mousemove(handleMouseMove);
}
`

类似键盘事件,让鼠标事件触发我们的函数。这里鼠标按下事件绑定的canvas,表示在canvas外面按下鼠标是没有效果的,而鼠标移动事件依然是$(document),在canvas内按下鼠标之后移出canvas依然可以保持交互效果。可以把canvas和$(document)相互修改一下试试效果。

var moonVertexPositionBuffer;
var moonVertexNormalBuffer;
var moonVertexTextureCoordBuffer;
var moonVertexIndexBuffer;
function initBuffers()
{
    var latitudeBands = 30;
    var longitudeBands = 30;
    var radius = 2;
    var vertexPositionData = [];
    var normalData = [];
    var textureCoordData = [];
    for(var latNumber = 0; latNumber <= latitudeBands; latNumber ++)
    {
        var theta = latNumber * Math.PI / latitudeBands;
        var sinTheta = Math.sin(theta);
        var cosTheta = Math.cos(theta);
        for(var longNumber = 0; longNumber <= longitudeBands; longNumber ++)
        {
            var phi = longNumber * 2 * Math.PI / longitudeBands;
            var sinPhi = Math.sin(phi);
            var cosPhi = Math.cos(phi);
            var x = cosPhi * sinTheta;
            var y = cosTheta;
            var z = sinPhi * sinTheta;
            var u = 1 - (longNumber / longitudeBands);
            var v = 1 - (latNumber / latitudeBands);

            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);
        }
    }

画一个球体,表面是由多个四边形来近似的,有一些基础几何知识就比较容易理解了。想一想或查一查地球仪,看看经纬度的划分,画出的等间距经线与纬线把地球表面划分成一个个曲面四边形(南北极周围是三角形,为了方便,极点也直接算成多个重合的点,这样在代码处理中就都当四边形了),我们用平面四边形来近似它们。

图13

图13,球面坐标

如图13所示,球坐标系统中,球表面任意一点的坐标,由一个与Z轴正方向的夹角θ(0~180°),和原点与该点在XY平面投影点连线与X轴正方向夹角φ(0~360°)决定。

在图中,|O1A|就是球的半径r,在XY平面的投影|O1B|=|O1A|*sin(θ)。于是:

X坐标值x为:|O1C|=|O1B|*sin(φ)  --> x=r*sin(θ)*cos(φ);
Y坐标值y为:|O1D|=|O1B|*sin(φ)  --> y=r*sin(θ)*sin(φ);
Z坐标值z为:|O1O2|=|O1A|*cos(θ) --> z=r*cos(θ);

均匀枚举θ和φ,然后算出每个点的坐标。我们的视角一直相当于从Z轴俯视,“上”方向相当于是Y轴正方向,所以代码中把Y和Z的计算互换了一下。

球体表面每个点的法向量都是球心与点连线方向,所以为计算光照而需要的法向量刚好在计算坐标的时候就得到了。

    var indexData = [];
    for(var latNumber = 0; latNumber < latitudeBands; latNumber ++)
    {
        for(var longNumber = 0; longNumber < longitudeBands; longNumber ++)
        {
            var first = (latNumber * (longitudeBands + 1)) + longNumber;
            var second = first + longitudeBands + 1;
            indexData.push(first);
            indexData.push(second);
            indexData.push(first + 1);
            indexData.push(second);
            indexData.push(second + 1);
            indexData.push(first + 1);
        }
    }
    //...createBuffer(),bindBuffer(),bufferData(),itemSize,numItems等
}

得到每个点坐标之后,还得合理组织这些四边形,在计算坐标时候我们是按顺序“一圈一圈”像削平果一样算下来的,于是再一圈一圈把四边形像过去的方法一样用两个三角形表示,把顶点序号组织成一个个三角形,如图14:

图14

图14,球面分割的小四边形的序号组织

var mouseDown = false;
var lastMouseX = null;
var lastMouseY = null;
var moonRotationMatrix = mat4.create();
mat4.identity(moonRotationMatrix);
function handleMouseDown(event)
{
    mouseDown = true;
    lastMouseX = event.clientX;
    lastMouseY = event.clientY;
}
function handleMouseUp(event)
{
    mouseDown = false;
}
function handleMouseMove(event)
{
    if(!mouseDown)
    {
        return;
    }
    var newX = event.clientX;
    var newY = event.clientY;
    var newRotationMatrix = mat4.create();
    mat4.identity(newRotationMatrix);

    var deltaX = newX - lastMouseX;
    mat4.rotate(newRotationMatrix, newRotationMatrix,
        degToRad(deltaX / 10), [0, 1, 0]);
    var deltaY = newY - lastMouseY;
    mat4.rotate(newRotationMatrix, newRotationMatrix,
        degToRad(deltaY / 10), [1, 0, 0]);

    mat4.multiply(moonRotationMatrix, newRotationMatrix, moonRotationMatrix);

    lastMouseX = newX;
    lastMouseY = newY;
}

用一个moonRotationMatrix记录累积旋转的状态,每次新的旋转乘在moonRotationMatrix上,在让mvMatrix去乘以它。这里旋转矩阵用了乘,则要注意左乘右乘了,可以试试调换相乘两个矩阵的顺序看会发生什么。再说明一下gl-matrix的2.x版本,第一个参数都是输出。

这里我有过一个疑惑,在鼠标斜着移动时候,先绕X和先绕Y真的都没关系么?于是用两个矩阵分别计算两个旋转顺序的结果并比较,是不同的。但是为什么实际体验中似乎并没有什么问题(想象一下,如果鼠标从中心出发往左下斜着走很远,那先绕X走很远和先绕Y走,应该最后一个落在Y轴上,一个落在X轴上,而不是预期的落在左下方,可是实际操作却并不如此)。导致这个效果是因为这个计算并不是“一下子”完成的,handleMouseMove()在tick周期中执行,大约每秒60次,鼠标移动的这个计算过程被细分了,产生了“积分”的效果,于是我们往左下移动鼠标的过程,近似了绕着[左上/右下]方向轴旋转的操作。

results matching ""

    No results matching ""