DJ's WebGL Tutorial 005--3D渲染与所需矩阵变换

来源:互联网 发布:redis 数据库设计实例 编辑:程序博客网 时间:2024/05/17 07:46

在前面几节,我们省略了顶点的矩阵变换,直接使用的屏幕空间坐标进行渲染。
本节中,我们将开始进入3D世界,渲染一个旋转的3D立方体。


一个3D模型要显示到屏幕上,顶点要经过以下坐标变换:

  • 本地坐标->世界坐标
  • 世界坐标->视图坐标
  • 视图坐标->屏幕坐标

这就涉及到3个变换矩阵:Model,View,Projection。
由于WebGL不提供矩阵相关API,所以,我写了一个Matrix4x4类,同时还提供了所需的Vector3、Quaternion类。

在讲矩阵之前,我们先约定:

  • 使用左手坐标系,x轴向右,y轴向上,z轴由屏幕向里。
  • 矩阵使用列主序,是常用的行主序的转置。

Matrix Model

模型矩阵可以分解为三个矩阵:

  • 位移:Translation
  • 旋转:Rotation
  • 缩放:Scaling

所以又称之为TRS矩阵。
矩阵接口:

Matrix4x4.TRS = function(vect, vecr, vecs) {    var t = Matrix4x4.Translation(vect);    var r = Matrix4x4.Rotation(vecr);    var s = Matrix4x4.Scaling(vecs);    return t.multiply(r).multiply(s);};

Matrix View

视图矩阵描述了摄像机的位置和朝向:

Matrix4x4.LookTo = function(eye_position, to_direction, up_direction) {    var m = Matrix4x4.Identity();    var array = m.array;    var zaxis = new Vector3([-to_direction[0], -to_direction[1], -to_direction[2]]);    zaxis.normalize();    var xaxis = zaxis.multiply(new Vector3(up_direction));    xaxis.normalize();    var yaxis = xaxis.multiply(zaxis);    var xa = xaxis.array;    var ya = yaxis.array;    var za = zaxis.array;    var eye = new Vector3(eye_position);    array[0] = xa[0]; array[1] = xa[1]; array[2] = xa[2]; array[3] = -xaxis.dot(eye);    array[4] = ya[0]; array[5] = ya[1]; array[6] = ya[2]; array[7] = -yaxis.dot(eye);    array[8] = za[0]; array[9] = za[1]; array[10] = za[2]; array[11] = -zaxis.dot(eye);    array[12] = 0; array[13] = 0; array[14] = 0; array[15] = 1.0;    return m;};

Matrix Projection

投影矩阵描述了摄像机的投影参数,
使用常用的透视投影:

Matrix4x4.Perspective = function(fov, aspect, z_near, z_far) {    var m = Matrix4x4.Identity();    var array = m.array;    var y_scale = 1 / Math.tan(Math.PI / 180 * fov / 2);    var x_scale = y_scale / aspect;    array[0] = x_scale;    array[5] = y_scale;    array[10] = (z_near + z_far) / (z_near - z_far);    array[11] = 2 * z_near * z_far / (z_near - z_far);    array[14] = -1.0;    array[15] = 0;    return m;};

有了变换矩阵,可以开始渲染了。

1.扩展顶点buffer来创建立方体,并建立顶点索引buffer(前几节没有使用顶点索引)。
一个立方体需要12个顶点和36个顶点索引。

function create_buffer() {    var vertices = [        -0.5, 0.5, -0.5,        -0.5, -0.5, -0.5,        0.5, -0.5, -0.5,        0.5, 0.5, -0.5,        -0.5, 0.5, 0.5,        -0.5, -0.5, 0.5,        0.5, -0.5, 0.5,        0.5, 0.5, 0.5,        -0.5, 0.5, 0.5,        0.5, 0.5, 0.5,        -0.5, -0.5, 0.5,        0.5, -0.5, 0.5    ];    var uvs = [        0.0, 0.0,        0.0, 1.0,        1.0, 1.0,        1.0, 0.0,        1.0, 0.0,        1.0, 1.0,        0.0, 1.0,        0.0, 0.0,        0.0, 1.0,        1.0, 1.0,        0.0, 0.0,        1.0, 0.0    ];    var indices = [        0, 1, 2,        0, 2, 3,        4, 6, 5,        4, 7, 6,        3, 2, 6,        3, 6, 7,        4, 5, 1,        4, 1, 0,        8, 0, 3,        8, 3, 9,        10, 2, 1,        10, 11, 2    ];    vertex_buffer = gl.createBuffer();    gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);    uv_buffer = gl.createBuffer();    gl.bindBuffer(gl.ARRAY_BUFFER, uv_buffer);    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvs), gl.STATIC_DRAW);    index_buffer = gl.createBuffer();    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);}

2.修改vertex shader,使用mvp矩阵变换顶点:

var shader_src_vs = "\    uniform mat4 u_mat_mvp;\    attribute vec4 a_position;\    attribute vec2 a_uv;\    varying vec2 v_uv;\    void main()\    {\        vec4 pos = a_position * u_mat_mvp;\        v_uv = a_uv;\        gl_Position = pos;\    }\";

3.开启深度测试、背面裁剪:

function init_gl() {    gl.clearColor(0.0, 0.0, 1.0, 1.0);    gl.enable(gl.DEPTH_TEST);    gl.enable(gl.CULL_FACE);    create_shader();    create_buffer();    create_texture();}

4.渲染:

function render() {    update_fps();    gl.clear(gl.COLOR_BUFFER_BIT);    if (texture != null) {        gl.useProgram(program);        var index_mvp = gl.getUniformLocation(program, "u_mat_mvp");        var index_pos = gl.getAttribLocation(program, "a_position");        var index_uv = gl.getAttribLocation(program, "a_uv");        var index_tex = gl.getUniformLocation(program, "u_tex");        rot += 1;        var mat_m = Matrix4x4.TRS([0, 0, 0], [45, rot, 0], [1, 1, 1]);        var mat_v = Matrix4x4.LookTo([0, 0, -3], [0, 0, 1], [0, 1, 0]);        var ratio = gl.drawingBufferWidth / gl.drawingBufferHeight;        var mat_p = Matrix4x4.Perspective(45, ratio, 0.3, 100);        var mat_mvp = mat_p.multiply(mat_v).multiply(mat_m);        gl.uniformMatrix4fv(index_mvp, false, mat_mvp.array);        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);        gl.enableVertexAttribArray(index_pos);        gl.vertexAttribPointer(index_pos, 3, gl.FLOAT, false, 0, 0);        gl.bindBuffer(gl.ARRAY_BUFFER, uv_buffer);        gl.enableVertexAttribArray(index_uv);        gl.vertexAttribPointer(index_uv, 2, gl.FLOAT, false, 0, 0);        gl.activeTexture(gl.TEXTURE0);        gl.bindTexture(gl.TEXTURE_2D, texture);        gl.uniform1i(index_tex, 0);        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);        gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);    }    window.requestAnimationFrame(render);}

矩阵使用解析:

  • 模型矩阵
    var mat_m = Matrix4x4.TRS([0, 0, 0], [45, rot, 0], [1, 1, 1]);
    立方体位于(0, 0, 0),
    绕x轴旋转45度,绕y轴旋转rot度,
    缩放系数为1。

  • 视图矩阵
    var mat_v = Matrix4x4.LookTo([0, 0, -3], [0, 0, 1], [0, 1, 0]);
    摄像机位于(0, 0, -3),
    前方向向量(0, 0, 1),即朝向z轴正方向,
    上方向向量(0, 1, 0),朝向y轴正方向。

  • 投影矩阵
    var ratio = gl.drawingBufferWidth / gl.drawingBufferHeight;
    var mat_p = Matrix4x4.Perspective(45, ratio, 0.3, 100);
    摄像机视口张角45度,
    宽高比等于屏幕宽高比,
    近裁剪面z=0.3,
    远裁剪面z=100。

  • MVP
    var mat_mvp = mat_p.multiply(mat_v).multiply(mat_m);
    连乘得到mvp,之后传给vertex shader。


运行结果(由于上传图片大小限制,动画帧率较低):


代码下载

0 0
原创粉丝点击