《WebGL 编程指南》读书笔记(2、3章)

完整 demo 和 lib 文件可以在 https://github.com/tengge1/webgl-guide-code 中找到。

第 2 章

第一个 WebGL 程序

function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');
    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }
    // 设置清空webgl的颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
}

一旦指定了背景色之后,背景色就会驻存在 WebGL 系统中,在下一次调用 gl.clearColor 方法前不会改变。

gl.clear(buffer) 用之前指定的背景色清空

buffer 指定待清空的缓冲区,可通过 | 指定多个

gl.COLOR_BUFFER_BIT 指定颜色缓存
gl.DEPTH_BUFFER_BIT 指定深度缓冲区
gl.STENCIL_BUFFER_BIT 指定模板缓冲区

清空缓冲区的默认颜色及其相关函数

缓冲区名称 默认值 相关函数
颜色缓冲区 (0.0, 0.0, 0.0, 0.0) gl.clearColor(red, green, blue, alpha)
深度缓冲区 1.0 gl.clearDepth(depth)
模板缓冲区 0 gl.clearStencil(s)

绘制一个点(版本一)

// 定点着色器程序
var VSHADER_SOURCE = 
    'void main() {\n' +
    ' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + //设置坐标
    ' gl_PointSize = 50.0;\n' + // 设置尺寸
    '}\n';
// 片元着色器程序
var FSHADER_SOURCE = 
    'void main() {\n' +
    ' gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n' + // 设置颜色
    '}\n';

function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');
    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to initialize shaders.');
        return;
    }
    // 设置背景色
    gl.clearColor(0.0, 0.0, 1.0, 1.0);
    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1);
}

目前可以简单的认为原点 (0.0, 0.0, 0.0) 处的点就出现在 <canvas> 的中心位置。

着色器

WebGL 依赖一种称为 着色器(shader) 的绘图机制。

定点着色器(Vertex shader) :定点着色器用来描述定点特性(如位置、颜色等)的程序。定点(vertex) 是指二维或三维空间的一个点,比如二维或三维图形的端点或交点。

片元着色器(Fragment shader):进行逐片元处理过程如光照的程序。片元(fragment)是一个 WebGL 术语,可以理解为像素。

顶点着色器的内置变量

类型 变量名 变量描述 类型描述
float gl_PointSize 表示点的尺寸(像素数) 浮点数
vec4 gl_Position 表示顶点的位置 表示由四个浮点数组成的矢量

函数 vec4(v0, v1, v2, v3) 返回一个 vec4 类型的变量。

由 4 个分量组成的矢量被称为齐次坐标。齐次坐标 (x,y,x,w) 等价于三维坐标 (x/w,y/w,z/w)

片元着色器的内置变量

类型 变量名 变量描述
vec4 gl_FragColor 指定片元颜色(RGBA格式)

gl.drawArrays(mode, first, count) 执行顶点着色器,按照 mode 参数指定的方式绘制图形。

mode 指定绘制的方式,可以接收以下常量符号

  • gl.POINTS
  • gl.LINES
  • gl.LINE_STRIP
  • gl.LINE_LOOP
  • gl.TRIANGLES
  • gl.TRIANGLE_STRIP
  • gl.TRIANGLE_FAN

first 指定从哪个点开始绘制(整型数)
count 指定绘制需要多少个顶点(整型数)

调用 gl.drawArrays(mode, first, count) 时,顶点着色器将被执行 count 次,,每次处理一个顶点。

顶点着色器执行完成后,片元着色器将会执行。

WebGL 的坐标系统

当你面向计算机平屏幕时,X轴时水平的(正方向为右),Y轴时垂直的(正方向为下),而Z轴垂直于屏幕(正方向为外),这套坐标系又被称为右手坐标系(right-handed coordinate system)。

image.png

WebGL 坐标和 <canvas> 坐标对应如下

  • <canvas> 的中心点 (0.0, 0.0, 0.0)
  • <canvas> 的上边缘和下边缘 (-1.0, 0.0, 0.0)(1.0, 0.0, 0.0)
  • <canvas> 的左边缘和右边缘 (0.0, -1.0, 0.0)(0.0, 1.0, 0.0)

绘制一个点(版本2)

上面的版本缺乏扩展性,现在要做的是将变量从 JavaScript 程序中传到顶点着色器。

attribute 变量 传输的是那些与顶点相关的数据。

uniform 变量 传输的是对于所有顶点都相同(或与顶点无关的)数据。

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'void main() {\n' +
    ' gl_Position = a_Position;\n' + //设置坐标
    ' gl_PointSize = 50.0;\n' + // 设置尺寸
    '}\n';
// 片元着色器程序
var FSHADER_SOURCE =
    'void main() {\n' +
    ' gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n' + // 设置颜色
    '}\n';

function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');
    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to initialize shaders.');
        return;
    }
    // 获取attribute变量的存储位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('Failed to get the storage location of a_Position');
        return;
    }
    // 将顶点位置传输给attribute变量
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
    // 设置背景色
    gl.clearColor(0.0, 0.0, 1.0, 1.0);
    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1);
}

其中关键词 attribute 被称为存储限定符(storage qualifier),表示接下来的变量是一个 attribute 变量。attribute 变量必须声明成全局变量,数据将从着色器外部传给该变量。声明变量格式:<存储限定符> <类型> <变量名>

gl.getAttribLocation(program, name)

获取由 name 参数指定的 attribute 变量的存储地址

program 指定包含顶点着色器和片元着色器的着色器程序对象
name 指定想要获取其存储地址的 attribute 变量的名称

gl.vertexAttrib3f(location, v0, v1, v2)

将数据 (v0,v1,v2) 传给由 location 参数指定的 attribute 变量

location 指定将要修改的 attribute 变量的存储位置
v0, v1, v2 填充 attribute 变量的三个分量

gl.vertexAttrib3f(location, v0, v1, v2) 的同族函数

// 从 JavaScript 向顶点着色器中的 attribute 变量传值
gl.vertexAttrib1f(location, v0) // 第
gl.vertexAttrib2f(location, v0, v1)
gl.vertexAttrib3f(location, v0, v1, v2)
gl.vertexAttrib4f(location, v0, v1, v2, v3)

设置 vec4 缺省的第 2、3 个分量会被默认设置为 0.0,第 4 个分量会被设置为 1.0

通过鼠标点击绘制

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'void main() {\n' +
    ' gl_Position = a_Position;\n' + //设置坐标
    ' gl_PointSize = 10.0;\n' + // 设置尺寸
    '}\n';
// 片元着色器程序
var FSHADER_SOURCE =
    'void main() {\n' +
    ' gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n' + // 设置颜色
    '}\n';

function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');
    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOUR
    CE, FSHADER_SOURCE)) {
        console.log('Failed to initialize shaders.');
        return;
    }
    // 获取attribute变量的存储位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('Failed to get the storage location of a_Position');
        return;
    }
    // 注册鼠标点击事件响应函数
    canvas.onmousedown = function (ev) {
        click(ev, gl, canvas, a_Position);
    }

    // 将顶点位置传输给attribute变量
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);


    // 设置背景色
    gl.clearColor(0.0, 0.0, 1.0, 1.0);
    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
}

var g_points = [];
function click(ev, gl, canvas, a_Position) {
    var x = ev.clientX;
    var y = ev.clientY;
    var rect = ev.target.getBoundingClientRect();
    x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
    y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);
    g_points.push(x);
    g_points.push(y);

    gl.clear(gl.COLOR_BUFFER_BIT);
    var len = g_points.length;
    for (var i = 0; i < len; i += 2) {
        gl.vertexAttrib3f(a_Position, g_points[i], g_points[i + 1], 0.0);
        gl.drawArrays(gl.POINTS, 0, 1);
    }
}

这个程序书上的代码有问题,求 x 和 y 的坐标部分,已改。

把每次鼠标点击的位置都记录下来,每次点击都清空然后绘制所有的点。

如果不请清空,颜色缓冲区被重置,第一次点击后背景就变成白色了。

改变点的颜色

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'void main() {\n' +
    ' gl_Position = a_Position;\n' + //设置坐标
    ' gl_PointSize = 10.0;\n' + // 设置尺寸
    '}\n';


// 片元着色器程序
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'uniform vec4 u_FragColor;\n' + // uniform变量
    'void main() {\n' +
    ' gl_FragColor = u_FragColor;\n' + // 设置颜色
    '}\n';

function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');
    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to initialize shaders.');
        return;
    }
    // 获取attribute变量的存储位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('Failed to get the storage location of a_Position');
        return;
    }
    // 获取 u_FragColor 变量的存储位置
    var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
    // 注册鼠标点击事件响应函数
    canvas.onmousedown = function (ev) {
        click(ev, gl, canvas, a_Position, u_FragColor);
    }

    // 将顶点位置传输给attribute变量
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);


    // 设置背景色
    gl.clearColor(0.0, 0.0, 1.0, 1.0);
    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
}

var g_points = [];
var g_colors = [];
function click(ev, gl, canvas, a_Position, u_FragColor) {
    var x = ev.clientX;
    var y = ev.clientY;
    var rect = ev.target.getBoundingClientRect();
    x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
    y = (canvas.height / 2 - (y - rect.top)) / (canvas.height / 2);
    g_points.push([x, y]);
    if (x >= 0.0 && y >= 0.0) { // 第一象限
        g_colors.push([1.0, 0.0, 0.0, 1.0]); // red
    } else if (x < 0.0 && y < 0.0) { // 第三象限
        g_colors.push([0.0, 1.0, 0.0, 1.0]); // green
    } else {
        g_colors.push([1.0, 1.0, 1.0, 1.0]); // white
    }

    gl.clear(gl.COLOR_BUFFER_BIT);
    var len = g_points.length;
    for (var i = 0; i < len; i++) {
        let xy = g_points[i];
        let rgba = g_colors[i];
        gl.vertexAttrib3f(a_Position, xy[0], xy[1], 0.0);
        gl.uniform4f(u_FragColor, rgba[0], rgba[1], rgba[2], rgba[3]);
        gl.drawArrays(gl.POINTS, 0, 1);
    }
}

只有顶点着色器才可以用 attribute 变量,使用片元着色器需要使用 uniform 变量,或者使用 varying 变量。

image.png

其中 precision mediump float 使用精度限定词来指定变量的范围(最大值与最小值)和精度。

gl.getUniformLocation(program, name)

获取由 name 参数指定的 attribute 变量的存储地址

program 指定包含顶点着色器和片元着色器的着色器程序对象
name 指定想要获取其存储地址的 uniform 变量的名称

未找到变量,getUniformLocation 返回 nullgetAttribLocation 返回 -1

gl.uniform4f(location, v0, v1, v2, v3)

将数据 (v0,v1,v2,v3) 传给由 location 参数指定的 uniform 变量

location 指定将要修改的 uniform 变量的存储位置
v0, v1, v2 填充 attribute 变量的三个分量

还有类似 gl.uniform1fgl.uniform2fgl.uniform3f,第 2、3、4 个分量默认值分别为 0.00.01.0

第 3 章

绘制多个点

构成三维模型的基本单位是三角形。

缓冲区对象(buffer object) 可以一次性地向着色器传入多个顶点的数据。缓冲区对象是 WebGL 的一块存储区域,可以在缓冲区中保存所有顶点的数据。

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'void main() {\n' +
    ' gl_Position = a_Position;\n' + //设置坐标
    ' gl_PointSize = 10.0;\n' + // 设置尺寸
    '}\n';


// 片元着色器程序
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'uniform vec4 u_FragColor;\n' + // uniform变量
    'void main() {\n' +
    ' gl_FragColor = u_FragColor;\n' + // 设置颜色
    '}\n';

function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');
    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to initialize shaders.');
        return;
    }
    // 设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }
    // 设置背景色
    gl.clearColor(0.0, 0.0, 1.0, 1.0);
    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 绘制三个点
    gl.drawArrays(gl.POINTS, 0, n);
}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0.0, 0.5, -0.5, -0.5, 0.5, -0.5
    ]);
    var n = 3; // 点的个数
    // 创建缓冲区对象
    var vertexBuffer = gl.createBuffer();
    if (!vertexBuffer) {
        console.log('Failed to create the buffer object.');
        return -1;
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    // 向缓冲区对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    // 将缓冲区对象分配给 a_Position 变量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
    // 连接 a_Position 变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(a_Position);

    return n;
}

使用缓冲区对象的步骤

  1. 创建缓冲区对象 gl.createBuffer()
  2. 绑定缓冲区对象 gl.bindBuffer()
  3. 将数据写入缓冲区对象 gl.bufferData()
  4. 将缓冲区对象分配给一个 attribute 变量 gl.getAttribLocation()
  5. 开启 attribute 变量 gl.enableVertexAttribArray()

gl.createBuffer()

创建缓冲区对象

gl.deleteBuffer(buffer)

删除参数 buffer 表示的缓冲区对象

gl.bindBuffer(target, buffer)

允许使用 buffer 表示缓冲区对象并将其绑定到 target 表示的目标上。

target 参数可以是以下其中一个

  • gl.ARRAY_BUFFER 表示缓冲区对象中包含了顶点的数据
  • gl.ELEMENT_ARRAY_BUFFER 表示缓冲区对象中包含了顶点的索引值
    buffer 指定之前由 gl.createBuffer() 返回的待绑定的缓冲区对象

gl.bufferData(target, data, usage)

开辟存储空间,向绑定在 target 上的缓冲区对象写入数据 data

target gl.ARRAY_BUFFERgl.ELEMENT_ARRAY_BUFFER
data 写入缓冲区对象的数据
uasge 表示程序将如何使用存储在缓冲区对象的数据。

  • gl.STATIC_DRAW 只会向缓冲区对象中写入一次数据,但需要绘制很多次
  • gl.STREAM_DRAW 只会向缓冲区对象中写入一次数据,然后绘制若干次
  • gl.DYNAMIC_DRAW 会向缓冲区对象中多次写入数据,并绘制很多次

类型化数组

WebGL 通常需要同时处理大量相同类型的数据,所以为每种数据类型引入了一种特殊的数组(类型化数组)。

WebGL 使用的各种类型化数组

数组类型 每个元素所占字节数 描述(C语言中的数据类型)
Int8Array 1 8位整型数(signed char)
UInt8Array 1 8位无符号整型数(unsigned char)
Int16Array 2 16位整型数(signed short)
UInt32Array 2 16位无符号整型数(unsigned char)
Int32Array 4 32位整型数(signed int)
UInt32Array 4 32位无符号整型数(unsigned int)
Float32Array 4 单精度32位浮点数(float)
Float64Array 8 双精度64位浮点数(double)

类型化数组的方法和属性

方法、属性和常量 描述
get(index) 获取第 index 个元素值
set(index, value) 设置第 index 个元素值为 value
set(array, offset) 从第 offset 个元素开始将数组 array 中的值填充进去
length 数组的长度
BYTES_PER_ELEMENT 数组中每个元素所占的字节数

类型化数组需要通过 new 创建,不能使用 [] 运算符。

gl.vertexAttribPointer(location, size, normalized, stride, offset)

将绑定到 gl.ARRAY_BUFFER 的缓冲区对象分配给由 location 指定的 attribute 变量。

location 指定待分配 attribue 变量的存储位置
size 指定缓冲区每个顶点的分量个数(1到4),
type 用以下类型之一来指定数据格式:

  • gl.UNSIGNED_BYTE 无符号字节,Uint8Array
  • gl.SHORT 短整形,Int16Array
  • gl.UNSIGNED_SHORT 无符号短整形,Uint16Array
  • gl.INT 整形,Int32Array
  • gl.UNSIGNED_INT 无符号整形,Uint32Array
  • gl.FLOAT 浮点型,Float32Array
    normalize 传入 truefalse,表明是否将非浮点型的数据归一化到 [0,1][-1,1] 区间。
    stride 指定相邻两个顶点间的字节数,默认为 0。
    offset 指定缓冲区对象中偏移量(以字节为单位)。

gl.enableVertexAttribArray(location)

开启 location 指定的 attribute 变量。为了使顶点着色器能够访问缓冲区内的数据。

location 指定 attribue 变量的存储位置

gl.disableVertexAttribArray(location)

开启 location 指定的 attribute 变量。为了使顶点着色器能够访问缓冲区内的数据。

location 指定 attribue 变量的存储位置

Hello Triangle (绘制三角形)

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'void main() {\n' +
    ' gl_Position = a_Position;\n' + //设置坐标
    // ' gl_PointSize = 10.0;\n' + // 设置尺寸
    '}\n';


// 片元着色器程序
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'void main() {\n' +
    ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 设置颜色
    '}\n';

function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');
    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }
    // 初始化着色器
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to initialize shaders.');
        return;
    }
    // 设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }
    // 设置背景色
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 绘制三个点
    gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0.0, 0.5, -0.5, -0.5, 0.5, -0.5
    ]);
    var n = 3; // 点的个数
    // 创建缓冲区对象
    var vertexBuffer = gl.createBuffer();
    if (!vertexBuffer) {
        console.log('Failed to create the buffer object.');
        return -1;
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    // 向缓冲区对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    // 将缓冲区对象分配给 a_Position 变量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
    // 连接 a_Position 变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(a_Position);

    return n;
}

gl_PointSize = 10.0; 该语句只有在绘制单点的时候才能使用,

gl.drawArrays(mode, first, count) 执行顶点着色器,按照 mode 参数指定的方式绘制图形。

mode 指定绘制的方式,可以接收以下常量符号

  • gl.POINTS 一系列点
  • gl.LINES 一系列单独的线段 绘制在 (v0,v1)、(v2,v3)、(v4,v5)……处,奇数最后一个点会被忽略
  • gl.LINE_STRIP 一系列连接的点,最后一个点是最后一条线段的终点
  • gl.LINE_LOOP 一系列连接的点,最后一个点会和第一个点连接起来
  • gl.TRIANGLES 一系列单独的三角形 (v0,v1,v2) (v3,v4,v5) 这样绘制一系列三角形,不是3的倍数最后剩下一个或两个点会被忽略
  • gl.TRIANGLE_STRIP 一系列带状三角形 (v0,v1,v2) (v2,v1,v3) (v2,v3,v4)
  • gl.TRIANGLE_FAN 三角扇 以v0为中心,分别和每两个点组成三角形 (v0,v1,v2) (v0,v2,v3) (v0,v3,v4)

first 指定从哪个点开始绘制(整型数)
count 指定绘制需要多少个顶点(整型数)

绘制正方形

就是绘制两个相邻的三角形。可以通过 gl.TRIANGLE_STRIPgl.TRIANGLE_FAN 实现。(gl.TRIANGLES 也可以,但是需要指定更多的点。)

var vertices = new Float32Array([
    -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5
]);
var n = 4; // 点的个数

gl.drawArrays(gl.TRIANGLE_FAN, 0, n);

var vertices = new Float32Array([
    -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5
]);
var n = 4; // 点的个数

gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);

移动

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'uniform vec4 u_Translation;\n' +
    'void main() {\n' +
    ' gl_Position = a_Position + u_Translation;\n' + //设置坐标
    '}\n';
// ...
// 在 x,y,z 方向上平移的距离
var Tx = 0.5, Ty = 0.5, Tz = 0.0;
// ...
// 设置顶点位置
var n = initVertexBuffers(gl);
// 将传输距离传输给顶点着色器
var u_Translation = gl.getUniformLocation(gl.program, 'u_Translation');
gl.uniform4f(u_Translation, Tx, Ty, Tz, 0.0);

旋转

为了描述一个旋转,必须指明:旋转轴、旋转方向、旋转角度。

旋转角度,逆时针为正。(右手法则旋转)

// 定点着色器程序
var VSHADER_SOURCE =
    // x' = x cosb - y sinb
    // y' = x sinb + y cosb
    // z' = z
    'attribute vec4 a_Position;\n' +
    'uniform float u_CosB, u_SinB;\n' +
    'void main() {\n' +
    ' gl_Position.x = a_Position.x * u_CosB - a_Position.y * u_SinB;\n' +
    ' gl_Position.y = a_Position.x * u_SinB + a_Position.y * u_CosB;\n' +
    ' gl_Position.z = a_Position.z;\n' +
    ' gl_Position.w = 1.0;\n' +
    '}\n';

// 旋转角度
var ANGLE = 90.0;

var n = initVertexBuffers(gl);

// 将旋转图形所需数据传输给顶点着色器
var radian = Math.PI * ANGLE / 180.0; // 转为弧度制
var cosB = Math.cos(radian);
var sinB = Math.sin(radian);

var u_CosB = gl.getUniformLocation(gl.program, 'u_CosB');
var u_SinB = gl.getUniformLocation(gl.program, 'u_SinB');

gl.uniform1f(u_CosB, cosB);
gl.uniform1f(u_SinB, sinB);

变换矩阵(Transformation matrix)

矩阵旋转

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'uniform mat4 u_xformMatrix;\n' +
    'void main() {\n' +
    ' gl_Position = u_xformMatrix * a_Position;\n' +
    '}\n';

// 将旋转图形所需数据传输给顶点着色器
var radian = Math.PI * ANGLE / 180.0; // 转为弧度制
var cosB = Math.cos(radian);
var sinB = Math.sin(radian);

var xformMatrix = new Float32Array([
    cosB, sinB, 0.0, 0.0,
    -sinB, cosB, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    0.0, 0.0, 0.0, 1.0
]);

var u_xformMarix = gl.getUniformLocation(gl.program, 'u_xformMatrix');

gl.uniformMatrix4fv(u_xformMarix, false, xformMatrix);

在 WebGL 中,是 按列主序(column major order) 在数组中存储矩阵元素。

gl.uniformMatrix4fv(location, transpose, array)

array 表示的 4*4 的矩阵分配给由 location 指定的 uniform 变量。

location uniform 变量的存储位置
transpose 表示是否转置矩阵,在 WebGL 中必须设置为 false
array 待传输的类型化数组,4*4矩阵按列主序存储在其中

矩阵平移

将上面程序的矩阵改为:

var Tx = 0.5, Ty = 0.5, Tz = 0.0;
var xformMatrix = new Float32Array([
    1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    Tx, Ty, Tz, 1.0
]);

矩阵缩放

将上面程序的矩阵改为:

var Sx = 1.0, Sy = 1.5, Sz = 1.0; // x,y,z轴的缩放因子
var xformMatrix = new Float32Array([
    Sx, 0.0, 0.0, 0.0,
    0.0, Sy, 0.0, 0.0,
    0.0, 0.0, Sz, 0.0,
    0.0, 0.0, 0.0, 1.0
]);
posted @ 2022-03-29 15:43  我不吃饼干呀  阅读(94)  评论(0编辑  收藏  举报