第8课 鼠标输入

本节课要介绍的内容是:

程序截图如下:

程序截图

打开新窗口观看Lesson8.html

上一节课我们已经介绍了如何在WebGL应用程序处理键盘输入,这节课介绍鼠标输入处理,它是WebGL应用程序获取用户的另一个重要方法。虽然存在几个不同的鼠标事件,但是有3个事件在WebGL应用程序中特别有用:

当把鼠标移动到某个对象之上时就会引发mousemove事件,在某个元素之上按下鼠标按键时就会引发mousedown事件,在某个对象之上释放鼠标按键时就会引发mouseup事件。这3个事件都包含几个非常有用的属性。这三个事件都有clientX和clientY属性,这两个属性包含了鼠标指针相对与浏览器视口左上角的位置信息。

mousedown和mouseup也包含一个button属性,它决定鼠标上的哪个按键被按下或释放。如果button值为0,则表示此事件与鼠标的左按键有关;如果button值为1,则此事件与鼠标的中间按键有关;如果button值为2,则事件与鼠标的右按键有关。

要使用鼠标事件,首先需要在startup()方法中为这些事件注册—个事件处理程序,如下面几行代码所示:

canvas.addEventListener('mousemove', handleMouseMove, false); 
canvas.addEventListener('mousedown', handleMouseDown, false); canvas.addEventListener('mouseup', handleMouseUp, false);

下面这段代码包含3个函数,它们是这3个事件的处理程序。

var cameraZ = -2.5;
var mouseDown = false;
var lastMouseX = null;
var lastMouseY = null;
var cubeRotationMatrix = mat4.create();
mat4.identity(cubeRotationMatrix);

function handleMouseMove(event) {
    if (!mouseDown) {
        return;
    }
    var newX = event.clientX;
    var newY = event.clientY;

    var deltaX = newX - lastMouseX;
    var newRotationMatrix = mat4.create();
    mat4.identity(newRotationMatrix);
    mat4.rotate(newRotationMatrix, deltaX / 400, [0, 1, 0]);

    var deltaY = newY - lastMouseY;
    mat4.rotate(newRotationMatrix, deltaY / 400, [1, 0, 0]);

    mat4.multiply(newRotationMatrix, cubeRotationMatrix, cubeRotationMatrix);

    lastMouseX = newX
    lastMouseY = newY;
}

function handleMouseDown(event) {
    mouseDown = true;
    lastMouseX = event.clientX;
    lastMouseY = event.clientY;
}

function handleMouseUp(event) {
    mouseDown = false;
}

上面的代码中,我们从event属性中获取鼠标的当前位置,并进一步计算了出当前帧鼠标在X、Y方向上的移动距离,然后根据这个距离确定屏幕上立方体的旋转角度。最后的效果就是可以按住鼠标键不放左右拖动就可以旋转立方体。

处理纹理过滤

在这个示例中,我还使用了jquery.ui创建了一个ui界面,通过按钮可以改变纹理的过滤模式。

// jquery代码,改变纹理过滤模式
$(function () {
    $("#slider-z").slider(
        { value: 2.5, min: 1, max: 12, step: 0.1, slide: function () { updateCameraPosition() }, change: function () { updateCameraPosition() } }
        );
    $('#opt-mag-filter').buttonset();
    $('#opt-min-filter').buttonset();

    $('#mag-nearest').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#mag-linear').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#mag-linear').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#min-nearest').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#min-linear').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#min-nearest-mipmap-nearest').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#min-linear-mipmap-nearest').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#min-nearest-mipmap-linear').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

    $('#min-linear-mipmap-linear').click(function () {
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
    });

});

function updateCameraPosition() {
    var z = $('#slider-z').slider("value");
    cameraZ = -z;
    $('#slider-z-value').html(z);
}

那什么是纹理过滤模式?假设有一张大小为256×256的纹理,再假设将此纹理应用于一个正方形。这个正方形投影到屏幕上,它的大小与纹理的大小一样。则在这个情形下,应用纹理后的正方形看起来与原图非常相似。

像素与纹素一一对应

在一般的3D场景中,对象离相机距离有时很近,有时很远。根据透视投影,这意味着当对象离照相机较近时,其在屏幕上的投影比较大;当对象离照相机较远时,其在屏幕上的投影较小。

如果投影对象在屏幕上占据的区域大于纹理原始图像的大小,则需要拉伸纹理,这就是所谓的纹理伸展(magnification)。与此相反,如果投影对象在屏幕上占据的区域小于纹理原始图像的大小,则需要减小纹理的大小,这就是所谓的纹理收缩(minification)。

纹理伸展和纹理收缩

我们用纹理过滤表示纹理伸展或收缩时计算片段颜色的一般过程。调用gl.texParameteri()方法可以控制纹理的过滤方式,原型如下:

gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR) ; 

为了定义纹理的伸展过滤器,需要把这个方法的第二个参数设置为gl.TEXTURE_MAG_FILTER;为了定义纹理的收缩过滤器,需要把它的第二个参数设置为gl.TEXTURE_MIN_FILTER。

纹理伸展

纹理伸展时一个纹素对于屏幕上的几个像素。下图说明了使用伸展过滤模式时像素与纹素之间的关系,其中桔色、白色方块表示纹素,蓝色圆点表示像素。纹理稍微进行了一点伸展,你会发现左下角、右上角和右下角的三个像素落在了不同的颜色区域,那么,这几个像素的颜色究竟应该是桔色还是白色呢?

纹理伸展

纹理的过滤过程实质上就是确定像素(实际上就是片段)颜色的过程。伸展纹理时,可以选择两种过滤模式。最基本的过滤模式是最近相邻模式,用gl.NEAREST名称指定。用以下代码把伸展过滤设置为最近相邻模式:

 gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST) ;

最近相邻过滤模式表示把最接近纹素的颜色值赋给每个纹理坐标。这种过滤模式速度快,因为每个纹理坐标只需要使用一个纹素作为输入。

最近点过滤

如果纹理伸展很多,这种过滤模式会产生块状效果,这有时也称为像素化效果。这是因为许多像素落在同一个纹素中,因此许多像素显示同一种颜色。

伸展过滤的另一个过滤模式称为线性过滤或双线性过滤,用gl.LINEAR名称定义。根据线性过滤模式,每个纹理坐标的颜色不是来自单个纹素,而是它周围的4个纹素的加权平均值。

线性过滤

为了把伸展过滤设置为线性过滤模式,需要调用gl.texParameteri()方法,如下所示:

 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 

线性过滤不会像最近相邻过滤模式那样产生块状效果,但线性过滤的特点是,当纹理拉伸时,会产生模糊效果。

纹理收缩

纹理收缩时几个纹素对应于屏幕上的一个像素。这意味着,几个纹素影响屏幕上一个像素的颜色,但是实际上,很难确定影响每个像素的全部纹素。

对于纹理收缩,与纹理伸展一样,也可以选择同样的两个过滤模式:最近相邻过滤和线性过滤。

用下面的调用语句设置纹理收缩的最近相邻过滤模式:

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 

最近相邻过滤模式的颜色计算过程与纹理伸展的最近相邻过滤模式相同。纹理坐标的颜色由最接近纹素的颜色决定。当每个像素对应的区域覆盖很多纹素时,这种过滤模式会产生锯齿现象。

纹理收缩可以使用的另一个基本过滤模式是线性过滤。下面的代码为收缩纹理设置线性过滤模式:

gl.texParameteri(gl.TEXTURE_2D,gl. TEXTURE_MIN_FILTER,gl. LINEAR) ; 

纹理收缩线性过滤与纹理伸展线性过滤的处理过程完全相同。将离纹理坐标最近的4个纹素的加权平均值作为此纹理坐标的颜色。线性过滤得到的效果略好于最近相邻过滤,但是当一个像素对应不止4个纹素时,这种过滤模式也会产生锯齿问题。

Mip映射纹理

当一个像素对应很多纹素时,不管是最近相邻过滤还是线性过滤都会产生锯齿问题。这个问题的一个解决方法是使用一个较小尺寸的纹理,这样每个像素不会对应很多纹素。但是对于场景中靠近观察者的对象,为了避免出现纹理伸展现象,你可能希望纹理具有较高的分辨率,纹理伸展会使纹理对象看起来有块状效果或模糊效果。

Mip映射是这个问题的解决方法。除了基础纹理(即零级纹理)外,Mip映射使用一系列的较小纹理。每个新纹理都是系列中前一个纹理的一半大小。这一系列纹理形成一个纹理链,即为Mip映射纹理链。与只应用基础纹理的情形相比,一个完整的Mip映射链大约要多占用1/3的内存空间。

纹理不必是正方形,但是纹理链上的纹理必须一直继续到最后一个纹理的大小为1×1为止。作为一个示例,Mip映射纹理链可以包含大小为256×256、128×128、64×64、32×32、16×16,8×8、4×4、2×2、1×1的纹理。下图显示了Mip映射链的图像。

Mip映射链

在WebGL中应用Mip映射纹理的最简单方法是让WebGL自动生成Mip映射纹理链。我们只需要载入基础纹理作为零级纹理,然后调用gl.generateMipmap()方法,WebGL会自动生成整个Mip映射纹理链。以下是生成一个2D纹理的典型代码:

gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBAr,gl.RGBA,gl. UNSIGNED_BYTE,image) ; 
gl.generateMipmap(gl.TEXTURE_2D); 

另一个方法是用一个图像编辑工具或专门为生成Mip映射纹理而设计的纹理工具,离线生成Mip映射链上的全部纹理图像。然后,在生成Mip映射链上的全部图像后,用gl.texImage2D()方法载入每幅图像,这个方法的第二个参数定义了图像的Mip映射级。以下代码载入Mip映射0级至Mip映射4级:

gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,imageLevel_0) ;
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,imageLevel_1) ;
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,imageLevel_2) ;
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,imageLevel_3) ;
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,imageLevel_4) ;

可以看出,第二个方法需要较多的操作。但是,如果对Mip映射链上的低分辨率图像有特别的要求,则会知道这种方法是很有用的。

如何选择纹理过滤模式

当我们为自己的纹理创建一个完整的Mip映射纹理链后,除了gl.NEAREST和gl.LINEAR这两个普通的过滤模式外,我们还可以为纹理收缩选择4种新过滤模式。下面列出所有可以使用的过滤模式并对它们逐一作详细的介绍:

可以看出,4个Mip映射纹理过滤模式的名称都采用gl.A_MIPMAP_B的形式,其中A和B为NEAREST或LINEAR。这里的A定义在一种Mip映射级中使用纹素的方式。如果A为NEAREST,则表示在Mip映射级中使用最近相邻过滤。如果A为LINEAR,则表示在Mip映射级中使用线性插值计算。这里的B规定是使用单个Mip映射级还是使用两个最近的Mip映射级。如果B是NEAREST,则使用最近的Mip映射级。如果B是LINEAR,则表示使用两个最近的Mip映射级,在这两个Mip映射级中,由A规定过滤模式。然后把两个Mip映射级的结果进行线性插值得到最终的结果。

虽然使用Mip映射纹理的主要理由是它的视觉效果,但是使用这种纹理还可以得到更好的性能。原因是,当使用Mip映射纹理时,一般情形下缓存的利用率会比较高,因为使用纹理收缩并使用比较“粗糙”的纹理时,纹素的读取往往发生在内存中比较靠近的单元中。

如果不用Mip映射纹理,则在收缩纹理时,需要从内存中相隔很远的单元读取纹素。这会降低需要读取的纹素已经出现在靠近GPU的缓存中的可能性。

如果用gl.LINEAR_MIPMAP_LINEAR参数定义三线性过滤模式,则比起gl.LINEAR_MIPMAP_NEAREST等过滤模式,GPU需要执行更多的运算,需要从内存中读取更多的纹素。最好在不同的设备上测试不同的过滤模式,选择一种性能和质量都比较好的过滤模式。

完整js代码

</script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    attribute vec2 aTextureCoords;

    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec2 vTextureCoords;

    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vTextureCoords =  aTextureCoords;
    }
</script>
</script id="shader-fs" type="x-shader/x-fragment">
    precision mediump float;

    varying vec2 vTextureCoords;
    uniform sampler2D uSampler;

    void main(void) {
        gl_FragColor = texture2D(uSampler, vTextureCoords);
    }
</script>

</script type="text/javascript">
    var gl;
    var canvas;
    var fpsCounter;

    function createGLContext(canvas) {
        var names = ["webgl", "experimental-webgl"];
        var context = null;
        for (var i = 0; i < names.length; i++) {
            try {
                context = canvas.getContext(names[i]);
            } catch (e) { }
            if (context) {
                break;
            }
        }
        if (context) {
            context.viewportWidth = canvas.width;
            context.viewportHeight = canvas.height;
        } else {
            alert("无法创建WebGL上下文!");
        }
        return context;
    }

    var cubeVertexBuffer;
    var cubeVertexIndexBuffer;

    function setupBuffers() {
        cubeVertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer);

        vertices = [
            // 前表面
            -0.5, -0.5, 0.5, 0.0, 0.0,
             0.5, -0.5, 0.5, 1.0, 0,
             0.5, 0.5, 0.5, 1.0, 1.0,
            -0.5, 0.5, 0.5, 0.0, 1.0,

            // 后表面
            -0.5, -0.5, -0.5, 1.0, 0.0,
            -0.5, 0.5, -0.5, 1.0, 1.0,
             0.5, 0.5, -0.5, 0.0, 1.0,
             0.5, -0.5, -0.5, 0.0, 0.0,

            // 上表面
            -0.5, 0.5, -0.5, 0.0, 1.0,
            -0.5, 0.5, 0.5, 0.0, 0.0,
             0.5, 0.5, 0.5, 1.0, 0.0,
             0.5, 0.5, -0.5, 1.0, 1.0,

            // 下表面
            -0.5, -0.5, -0.5, 1.0, 1.0,
             0.5, -0.5, -0.5, 0.0, 1.0,
             0.5, -0.5, 0.5, 0.0, 0.0,
            -0.5, -0.5, 0.5, 1.0, 0.0,

            // 右表面
             0.5, -0.5, -0.5, 1.0, 0.0,
             0.5, 0.5, -0.5, 1.0, 1.0,
             0.5, 0.5, 0.5, 0.0, 1.0,
             0.5, -0.5, 0.5, 0.0, 0.0,

            // 左表面
            -0.5, -0.5, -0.5, 0.0, 0.0,
            -0.5, -0.5, 0.5, 1.0, 0.0,
            -0.5, 0.5, 0.5, 1.0, 1.0,
            -0.5, 0.5, -0.5, 0.0, 1.0,
        ];

        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

        // 顶点数量为24
        var numberOfVertices = 24;
        // 计算一个顶点元素的字节数量,(x,y,z)位置为12个字节,(u,v)纹理坐标为8个字节,此处为20
        var vertexSizeInBytes = 20;
        // 根据上面算出的大小创建一个数组缓冲,此处这个大小为480个字节
        var buffer = new ArrayBuffer(numberOfVertices * vertexSizeInBytes);

        // 新建一个Float32Array视图,用于访问位置数据
        var positionView = new Float32Array(buffer);
        // 新建一个Float32Array视图, 用于访问纹理坐标
        var texcoordView = new Float32Array(buffer);

        var index = 0; // JavaScript数组中的索引
        for (var index = 0; index < numberOfVertices; index++) {
            positionView[index] = vertices[index];     // x
            positionView[1 + index] = vertices[index + 1]; // y
            positionView[2 + index] = vertices[index + 2]; // z
            texcoordView[3 + index] = vertices[index + 3]; // s
            texcoordView[4 + index] = vertices[index + 4]; // t
            index += 5;
        }
        cubeVertexBuffer.positionSize = 3;
        cubeVertexBuffer.texcoordSize = 2;

        cubeVertexIndexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
        var cubeVertexIndices = [
            0, 1, 2, 0, 2, 3,    // 前表面
            4, 5, 6, 4, 6, 7,    // 后表面
            8, 9, 10, 8, 10, 11,  // 上表面
            12, 13, 14, 12, 14, 15, // 下表面
            16, 17, 18, 16, 18, 19, // 右表面
            20, 21, 22, 20, 22, 23  // 左表面
        ];
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
        cubeVertexIndexBuffer.itemSize = 1;
        cubeVertexIndexBuffer.numItems = 36;
    }

    var myTexture;

    function setupTexture() {
        myTexture = gl.createTexture();
        myTexture.image = new Image();
        myTexture.image.onload = function () {
            textureFinishedLoading(myTexture)
        }

        myTexture.image.src = "textures/webgl.gif";
    }

    function textureFinishedLoading(texture) {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.generateMipmap(gl.TEXTURE_2D);
        gl.bindTexture(gl.TEXTURE_2D, null);
    }

    function setupShaders() {
        vertexShader = loadShaderFromDOM("shader-vs");
        fragmentShader = loadShaderFromDOM("shader-fs");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("创建shader失败");
        }

        gl.useProgram(shaderProgram);

        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoords");
        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
        shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");

        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
        gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);

        modelViewMatrix = mat4.create();
        projectionMatrix = mat4.create();
        modelViewMatrixStack = [];
    }

    function loadShaderFromDOM(id) {
        var shaderScript = document.getElementById(id);

        // 若未找到指定id的元素则退出
        if (!shaderScript) {
            return null;
        }

        // 遍历DOM元素的子节点,将shader代码重新构建为一个字符串
        var shaderSource = "";
        var currentChild = shaderScript.firstChild;
        while (currentChild) {
            if (currentChild.nodeType == 3) { // 3对应TEXT_NODE
                shaderSource += currentChild.textContent;
            }
            currentChild = currentChild.nextSibling;
        }

        var shader;
        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }

        gl.shaderSource(shader, shaderSource);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }
        return shader;
    }

    function uploadModelViewMatrixToShader() {
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, modelViewMatrix);
    }

    function uploadProjectionMatrixToShader() {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, projectionMatrix);
    }

    var previousSecondTimeStamp;  // 上一秒的开始时刻
    var previousTime;             // 上一帧的开始时刻
    var elapsedTime = 0;          // 每帧流逝的时间(毫秒)
    var totalTime = 0;            // 动画开始起的总时间
    var FPS;                    // 帧数

    function draw() {
        requestAnimFrame(draw);

        var currentTime = Date.now();
        // 经过1秒就重新更新一次FPS
        if (currentTime - previousSecondTimeStamp >= 1000) {
            fpsCounter.innerHTML = FPS;
            FPS = 0;
            previousSecondTimeStamp = currentTime;
        }
        // 每帧流逝的时间(毫秒)
        elapsedTime = currentTime - previousTime;
        previousTime = currentTime;

        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, projectionMatrix);

        mat4.identity(modelViewMatrix);
        mat4.translate(modelViewMatrix, [0.0, 0.0, cameraZ]);
        mat4.multiply(modelViewMatrix, cubeRotationMatrix);

        uploadModelViewMatrixToShader();
        uploadProjectionMatrixToShader();

        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer);
        // 描述顶点位置在数组中的组织形式
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexBuffer.positionSize, gl.FLOAT, false, 20, 0);
        // 描述顶点纹理坐标在数组中的组织形式
        gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexBuffer.texcoordSize, gl.FLOAT, false, 20, 12);

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, myTexture);
        gl.uniform1i(shaderProgram.samplerUniform, 0);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
        gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);

        // 更新FPS
        FPS++;
    }

    var cameraZ = -2.5;
    var mouseDown = false;
    var lastMouseX = null;
    var lastMouseY = null;
    var cubeRotationMatrix = mat4.create();
    mat4.identity(cubeRotationMatrix);

    function handleMouseMove(event) {
        if (!mouseDown) {
            return;
        }
        var newX = event.clientX;
        var newY = event.clientY;

        var deltaX = newX - lastMouseX;
        var newRotationMatrix = mat4.create();
        mat4.identity(newRotationMatrix);
        mat4.rotate(newRotationMatrix, deltaX / 400, [0, 1, 0]);

        var deltaY = newY - lastMouseY;
        mat4.rotate(newRotationMatrix, deltaY / 400, [1, 0, 0]);

        mat4.multiply(newRotationMatrix, cubeRotationMatrix, cubeRotationMatrix);

        lastMouseX = newX
        lastMouseY = newY;
    }

    function handleMouseDown(event) {
        mouseDown = true;
        lastMouseX = event.clientX;
        lastMouseY = event.clientY;
    }

    function handleMouseUp(event) {
        mouseDown = false;
    }

    function startup() {
        canvas = document.getElementById("lesson8-canvas");
        fpsCounter = document.getElementById("fps");
        gl = createGLContext(canvas);

        canvas.addEventListener('mousemove', handleMouseMove, false);
        canvas.addEventListener('mousedown', handleMouseDown, false);
        canvas.addEventListener('mouseup', handleMouseUp, false);

        previousTime = previousSecondTimeStamp = Date.now();
        FPS = 0;

        setupShaders();
        setupBuffers();
        setupTexture();

        gl.clearColor(0.3, 0.3, 0.3, 1.0);
        gl.enable(gl.DEPTH_TEST);

        draw();
    }

    // jquery代码,改变纹理过滤模式
    $(function () {
        $("#slider-z").slider(
            { value: 2.5, min: 1, max: 12, step: 0.1, slide: function () { updateCameraPosition() }, change: function () { updateCameraPosition() } }
            );
        $('#opt-mag-filter').buttonset();
        $('#opt-min-filter').buttonset();

        $('#mag-nearest').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#mag-linear').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#mag-linear').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#min-nearest').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#min-linear').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#min-nearest-mipmap-nearest').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#min-linear-mipmap-nearest').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#min-nearest-mipmap-linear').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

        $('#min-linear-mipmap-linear').click(function () {
            gl.bindTexture(gl.TEXTURE_2D, myTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
            gl.bindTexture(gl.TEXTURE_2D, null);
        });

    });

    function updateCameraPosition() {
        var z = $('#slider-z').slider("value");
        cameraZ = -z;
        $('#slider-z-value').html(z);
    }
</script>
文件下载(已下载 2534 次)

发布时间:2013/10/17 7:49:20  阅读次数:6127

2006 - 2024,推荐分辨率1024*768以上,推荐浏览器Chrome、Edge等现代浏览器,截止2021年12月5日的访问次数:1872万9823 站长邮箱

沪ICP备18037240号-1

沪公网安备 31011002002865号