键盘交互与纹理过滤

这一节做一些简单的交互,并认识一下不同的纹理过滤类型。

效果如图6。

图6

图6

用四个方向键控制绕x轴和绕y轴的旋转,Page Up和Page Down键控制z轴方向上的放大与缩小,用F键在三种纹理过滤类型间切换。

var crateTextures = Array();
function initTexture()
{
    var crateImage = new Image();
    for(var i = 0; i < 3; i ++)
    {
        var texture = gl.createTexture();
        texture.image = crateImage;
        crateTextures.push(texture);
    }
    crateImage.onload = function()
    {
        handleLoadedTexture(crateTextures);
    }
    crateImage.src = "/Public/image/crate.gif";
}

与上一节不同的是,我们创建了三个纹理对象,存到了一个数组里,纹理换成了一个板条箱的图片。这次传给handleLoadedTexture()的是一个数组。

function handleLoadedTexture(textures)
{
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);

    gl.bindTexture(gl.TEXTURE_2D, textures[0]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
        gl.UNSIGNED_BYTE, textures[0].image);
    gl.texParameteri(gl.TEXTURE_2D,
        gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D,
        gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    gl.bindTexture(gl.TEXTURE_2D, textures[1]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
        gl.UNSIGNED_BYTE, textures[1].image);
    gl.texParameteri(gl.TEXTURE_2D,
        gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D,
        gl.TEXTURE_MIN_FILTER, gl.LINEAR);

    gl.bindTexture(gl.TEXTURE_2D, textures[2]);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
        gl.UNSIGNED_BYTE, textures[2].image);
    gl.texParameteri(gl.TEXTURE_2D,
        gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D,
        gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);

    gl.generateMipmap(gl.TEXTURE_2D);

    gl.bindTexture(gl.TEXTURE_2D, null);
}

对传进来的纹理对象数组中三个对象使用了三种参数组合。 textures[0]和上一节一样,用的gl.NEAREST。

NEAREST

使用纹理坐标中最接近的一个像素,作为需要绘制的像素的颜色。边界分明,纹理图片太小的话会有马赛克的感觉。

LINEAR

使用纹理坐标中最接近的若干个颜色,通过加权平均得到需要绘制的像素的颜色。

MIPMAP

LINEAR方式在纹理放大的时候比NEAREST效果好一些,但是在缩小的时候并没有明显差别,都会产生锯齿。

可以在我们这一节做出的交互页面上试试看,木板与木板之间有细缝,当缩小到大约1/10的时候,NEAREST和LINEAR模式都会在某些角度看上去一些细缝消失了。这个很好理解,当纹理需要缩小到一个尺寸的时候,需要绘制的模型上的两个点,换算到纹理上的位置,可能会刚好跨过细缝。

在纹理需要缩小比较多的情况下,如果想让LINEAR解决这个问题,就需要取更多的像素算均值,运算量会变大很多。MIPMAP方法是对原纹理生成了一个MIPMAP图,这是个“图像金字塔”,即图像的1/2、1/4、1/8...等多个图,算一下等比数列求和会知道,至多只多占了一倍的空间,这在算法上是常数级的,可以接受。当然,在生成MIPMAP的时候图片已经做抗锯齿处理了,这是一次性的,总比在即时演算的时候反复做处理好得多。于是在纹理需要缩小时,选择最接近的纹理尺寸再做LINEAR处理。需要生成MIPMAP图,于是有了这句gl.generateMipmap(gl.TEXTURE_2D)。

var xRot = 0;
var xSpeed = 0;
var yRot = 0;
var ySpeed = 0;
var z = -5.0;
var filter = 0;

定义与旋转、缩放、纹理过滤类型标记有关的全局变量。

var currentlyPressedKeys = {};
function handleKeyDown(event)
{
    currentlyPressedKeys[event.keyCode] = true;
    if(String.fromCharCode(event.keyCode) == "F")
    {
        filter += 1;
        if(filter == 3)
        {
            filter = 0;
        }
    }
}
function handleKeyUp(event)
{
    currentlyPressedKeys[event.keyCode] = false;
}
function handleKeys()
{
    if(currentlyPressedKeys[190])
    {
        //"."/">"句号键
        z -= 0.05;
    }
    if(currentlyPressedKeys[188])
    {
        //","/"<"逗号键
        z += 0.05;
    }
    if(currentlyPressedKeys[65])
    {
        //A
        ySpeed -= 1;
    }
    if(currentlyPressedKeys[68])
    {
        //D
        ySpeed += 1;
    }
    if(currentlyPressedKeys[87])
    {
        //W
        xSpeed -= 1;
    }
    if(currentlyPressedKeys[83])
    {
        //S
        xSpeed += 1;
    }    
}
function tick()
{
    //...
    drawScene();
}

上面是对键盘事件的处理逻辑。键盘上每个按键有自己的编码,网上可以查到keycode表。用一个数组currentlyPressedKeys标记每个按键的按下状态,它就像个字典一样。按下事件标记,松开事件取消标记。handleKeys()放在tick()中就会在每个周期运行一次去查currentlyPressedKeys这个“字典”完成需要的操作。

“F”键控制纹理过滤类型的选择,我们想让它按一下变一次,就在keydown里执行。改变速度和缩放我们想按着不松手就持续变,就放到tick周期里执行。并且处理方向键的时候都用了if而没有else或return,这样就允许了它们并行执行。想想在打赛车游戏的时候,转弯就必须松油门还不能同时刹车甩尾的话,感觉一定很糟糕。

function webGLStart()
{
    //...
    $(document).keydown(handleKeyDown);
    $(document).keyup(handleKeyUp);
}

handleKeys()是tick()控制的,但handleKeyDown()和handleKeyUp()却是我们“一厢情愿”想让事件触发的,这就需要把他们绑定到JS的事件去,jQuery可以像上面这样写,keydown和keyup是jQuery的真实事件,我们让发生这些事件的时候执行想要的函数。

function drawScene()
{
    //...
    mat4.translate(mvMatrix, mvMatrix, [0.0, 0.0, z]);
    mat4.rotate(mvMatrix, mvMatrix, degToRad(xRot), [1, 0, 0]);
    mat4.rotate(mvMatrix, mvMatrix, degToRad(yRot), [0, 1, 0]);
    //...
    gl.bindTexture(gl.TEXTURE_2D, crateTextures[filter]);
    //...
}

drawScene这一步我们已经轻车熟路了,按照预想的方式放大缩小、运动,三个纹理对象设置为选中的那一个。

results matching ""

    No results matching ""