WebGL学习笔记

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

第 2 章 WebGL 入门

第一个 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
]);

theme: orange

第 4 章 高级变换与动画基础

矩阵变换库

使用矩阵函数库来实现图形变换。

// 为旋转矩阵创建 Matrix4 对象
var xformMatrix = new Matrix4();
// 将 xformMatrix 设置为旋转矩阵
xformMatrix.setRotate(ANGLE, 0, 0, 1); // 绕着z轴旋转

var u_xformMarix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
// 将旋转矩阵传输给顶点着色器
gl.uniformMatrix4fv(u_xformMarix, false, xformMatrix.elements);

Matrix4 所支持的方法和属性

方法和属性名称 描述
Matrix4.setIdentity() Matrix4 实例初始化为单位阵
Matrix4.setTranslate(x,y,z) Matrix4 实例设置为平移变换矩阵,x,y,z轴分别平移 x,y,z
Matrix4.setRotate(angle,x,y,z) Matrix4 实例设置为旋转变换矩阵,旋转角度为 angle,旋转轴为 (x,y,z)
Matrix4.setScale(x,y,z) Matrix4 实例设置为缩放变换矩阵,三个轴的缩放因子分别为z,y,z
Matrix4.translate(x,y,z) Matrix4 实例乘以一个平移变换矩阵,所得结果还是存储在 Matrix4
Matrix4.rotate(angle,x,y,z) Matrix4 实例乘以一个旋转变换矩阵
Matrix4.scale(x,y,z) Matrix4 实例乘以一个缩放变换矩阵
Matrix4.set(m) Matrix4 实例设置为m,m也是一个 Matrix4 实例
Matrix4.elements 类型化数组(Float32Array),包含 Matrix4 实例的矩阵元素

复合矩阵

<平移 后的坐标> = <平移矩阵> * <原始矩阵>
<平移后旋转 后的坐标> = <旋转矩阵> * <平移 后的坐标>
<平移后旋转 后的坐标> = <旋转矩阵> * (<平移矩阵> * <原始矩阵>)
<平移后旋转 后的坐标> = (<旋转矩阵> * <平移矩阵>) * <原始矩阵>

公式中矩阵变换的顺序和矩阵乘法顺序是相反的!!!

我们把这些变换全部复合成一个等效的变换,就得到了模型变换(model transformation),或者建模变换(modeling transformation),相应地,模型变换的矩阵称为模型矩阵(model matrix)。

// 顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'uniform mat4 u_ModelMatrix;\n' +
    'void main() {\n' +
    ' gl_Position = u_ModelMatrix * a_Position;\n' +
    '}\n';
// ...
var modelMatrix = new Matrix4();
var ANGLE = 60.0; // 旋转角度
var Tx = 0.5;
modelMatrix.setRotate(ANGLE, 0, 0, 1); // 设置为旋转矩阵
modelMatrix.translate(Tx, 0, 0); // 乘以平移矩阵
var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

矩阵旋转圆心是原点,上面的程序先旋转再平移和先平移再旋转的位置是不一样的。

// 平移后旋转
modelMatrix.setRotate(ANGLE, 0, 0, 1); // 设置为旋转矩阵
modelMatrix.translate(Tx, 0, 0); // 乘以平移矩阵
// 旋转后平移
modelMatrix.setTranslate(Tx, 0, 0); // 乘以平移矩阵
modelMatrix.rotate(ANGLE, 0, 0, 1); // 设置为旋转矩阵

动画

// 顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'uniform mat4 u_ModelMatrix;\n' +
    'void main() {\n' +
    ' gl_Position = u_ModelMatrix * a_Position;\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(0.0, 0.0, 0.0, 1.0);

    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');

    var currentAngle = 0.0;
    var modelMatrix = new Matrix4();

    var tick = function() {
        currentAngle = animate(currentAngle); // 更新旋转角
        draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix);
        requestAnimationFrame(tick); // 请求浏览器调用tick
    }
    tick();
    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
}

function draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix) {
    modelMatrix.setRotate(currentAngle, 0, 0, 1);
    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, n);
}

var ANGLE_STEP = 45.0;
// 记录上一次调用函数的时刻
var g_last = Date.now();
function animate(angle) {
    var now = Date.now();
    var elapsed = now - g_last;
    g_last = now;
    var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
    return newAngle %= 360;

}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0.0, 0.3, -0.3, -0.3, 0.3, -0.3
    ]);
    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;
}

第 5 章 颜色与纹理

非坐标数据传入顶点着色器

修改之前绘制多个点的程序,为每个点指定不同的大小。

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

// 片元着色器程序
// ...

function main() {
    // ... 不改动
}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0.0, 0.5, -0.5, -0.5, 0.5, -0.5
    ]);
    var n = 3; // 点的个数
    var sizes = new Float32Array([
        10.0, 20.0, 30.0 // 点的尺寸
    ]);
    // 创建缓冲区对象
    var vertexBuffer = gl.createBuffer();
    var sizeBuffer = 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');
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(a_Position);
    // 将顶点尺寸写入缓冲区对象并开启
    gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW);
    var a_PositionSize = gl.getAttribLocation(gl.program, 'a_PositionSize');
    gl.vertexAttribPointer(a_PositionSize, 1, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(a_PositionSize);

    return n;
}

使用一个缓冲区

把顶点的坐标和大小数据放到同一个缓冲区,交错组织(interleaving)

function initVertexBuffers(gl) {
    // 顶点坐标和点的尺寸
    var verticesSizes = new Float32Array([
        0.0, 0.5, 10.0, // first point
        -0.5, -0.5, 20.0, // second
        0.5, -0.5, 30.0, // third
    ]);
    var n = 3; // 点的个数

    // 创建缓冲区对象
    var vertexBuffer = gl.createBuffer();
    var sizeBuffer = 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, verticesSizes, gl.STATIC_DRAW);

    var FSIZE = verticesSizes.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
    gl.enableVertexAttribArray(a_Position);

    // 将顶点尺寸写入缓冲区对象并开启
    gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verticesSizes, gl.STATIC_DRAW);
    var a_PositionSize = gl.getAttribLocation(gl.program, 'a_PositionSize');
    gl.vertexAttribPointer(a_PositionSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);
    gl.enableVertexAttribArray(a_PositionSize);

    return n;
}

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

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

location 指定待分配 attribue 变量的存储位置
size 指定缓冲区每个顶点的分量个数(1到4),
type 用指定数据类型
normalize 传入 true 或 false,表明是否将非浮点型的数据归一化到 [0,1] 或 [-1,1] 区间。
stride 指定相邻两个顶点间的字节数,默认为 0。
offset 指定缓冲区对象中偏移量(以字节为单位)

修改颜色(varying变量)

varying 变量的作用是从顶点向片元着色器传输数据。

// 定点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\n' +
    'attribute vec4 a_Color;\n' +
    'varying vec4 v_Color;\n' + // varying 变量
    'void main() {\n' +
    ' gl_Position = a_Position;\n' + // 设置坐标
    ' gl_PointSize = 10.0;\n' + // 设置尺寸
    ' v_Color = a_Color;\n' + // 将数据传给片元着色器
    '}\n';

// 片元着色器程序
var FSHADER_SOURCE =
    'precision mediump float;\n' +
    'varying vec4 v_Color;' +
    'void main() {\n' +
    ' gl_FragColor = v_Color;\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.POINTS, 0, n);
}

function initVertexBuffers(gl) {
    var verticesColors = new Float32Array([
        // 顶点坐标和颜色
        0.0, 0.5, 1.0, 0.0, 0.0,
        -0.5, -0.5, 0.0, 1.0, 0.0,
        0.5, -0.5, 0.0, 0.0, 1.0
    ]);
    var n = 3; // 顶点数量

    // 创建缓冲区对象
    var vertexBuffer = gl.createBuffer();
    var vertexColorBuffer = gl.createBuffer();

    // 将顶点坐标和颜色写入缓冲区对象并开启
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

    var FSIZE = verticesColors.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    gl.vertexAttribPointer(a_Color, 2, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
    gl.enableVertexAttribArray(a_Color);

    return n;
}

声明 atttribute 变量接收颜色数据,声明 varying 变量负责将颜色传给片元着色器。 varying 只能是 float(以及相关的 vec2vec3vec4mat2mat3mat4)类型的。

在片元着色器和顶点着色器声明同名 varying 变量,就可以在片元着色器接收变量。

彩色三角形

将上面的 gl.drawArrays(gl.POINTS, 0, n); 改为 gl.drawArrays(gl.TRIANGLES, 0, n);,可以得到一个彩色的三角形!

image.png

在顶点着色器和片元着色器之间,由下面两个步骤:

  • 图形装配过程:将孤立的顶点坐标装配成几何图形,几何图形的类别由 gl.drawArrays() 函数的第一个参数决定。
  • 光栅化过程:将装配好的几何图形转化为片元。

根据片元位置来确定片元颜色

光栅化过程生成的片元都是带有坐标信息的,调用片元着色器时访问这些坐标信息也随着片元传了进去,我们可以通过片元着色器的内置变量来访问片元的坐标。

// 定点着色器程序
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 float u_Width;\n' +
    'uniform float u_Height;\n' +
    'void main() {\n' +
    ' gl_FragColor = vec4(gl_FragCoord.x / u_Width, 0.0, gl_FragCoord.y / u_Height, 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);

    var u_Width = gl.getUniformLocation(gl.program, 'u_Width');
    if (!u_Width) {
      console.log('Failed to get the storage location of u_Width');
      return;
    }
  
    var u_Height = gl.getUniformLocation(gl.program, 'u_Height');
    if (!u_Height) {
      console.log('Failed to get the storage location of u_Height');
      return;
    }
  
    // Pass the width and hight of the <canvas>
    gl.uniform1f(u_Width, gl.drawingBufferWidth);
    gl.uniform1f(u_Height, gl.drawingBufferHeight);

    // 连接 a_Position 变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(a_Position);

    return n;
}

varying 变量的作用和内插过程

我们为三角形三个顶点指定不同的颜色,而三角形表面这些片元的颜色值都是 WebGL 系统用这3个顶点的颜色内插出来的。计算的过程为内插过程,interpolation process。

纹理映射

纹理映射(texture mapping):将一张图片映射到一个几何图形表面。这张图片可以称为纹理图形(texture image)纹理(texture)

纹理映射的作用,就是根据纹理图像,为之前光栅化的每个片元涂上合适的颜色,组成纹理图像的像素又被称为纹素(texels, texture elements),每一个纹素的颜色都使用 RGB 或 RGBA 格式编码。

在 WebGL 中,纹理映射需要遵循以下四步:

  1. 准备好映射到几何图形上的纹理图像。
  2. 为几何图形配置纹理映射方式。
  3. 加载纹理图像,对其进行一些配置,以在 WebGL 中使用它。
  4. 在片元着色器中将响应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。

纹理坐标

纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色。WebGL 系统中的纹理坐标系统是二维的,在 WebGL 中使用 s 和 t 命名纹理坐标(st 坐标系统)。

image.png

将纹理图像粘贴到几何图形上

image.png

示例代码

var VSHADER_SOURCE = `
    attribute vec4 a_Position;
    attribute vec2 a_TexCoord;
    varying vec2 v_TexCoord;
    void main() {
        gl_Position = a_Position;
        v_TexCoord = a_TexCoord;
    }
`;

var FSHADER_SOURCE = `
    precision mediump float;
    uniform sampler2D u_Sampler;
    varying vec2 v_TexCoord;
    void main() {
        gl_FragColor = texture2D(u_Sampler, v_TexCoord);
    }
`;

function  main() {
    var canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);

    var n = initVertexBuffers(gl);

    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    initTextures(gl, n);
}

function initVertexBuffers(gl) {
    var verticesTexCoords = new Float32Array([
        // 顶点坐标,纹理坐标
        -0.5,  0.5,   0.0, 1.0,
        -0.5, -0.5,   0.0, 0.0,
        0.5,  0.5,   1.0, 1.0,
        0.5, -0.5,   1.0, 0.0,
    ]);
    var n = 4;
    var vertexTexCoordBuffer = gl.createBuffer();
    // 顶点坐标和纹理坐标写入缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

    var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
    gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
    gl.enableVertexAttribArray(a_TexCoord);

    return n;
}
// 配置和加载纹理
function initTextures(gl, n) {
    var texture = gl.createTexture(); // 创建纹理对象
    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
    var image = new Image();
    image.onload = function () {
        loadTexture(gl, n, texture, u_Sampler, image);
    }

    image.src = './sky.jpg';

    return true;
}
// 为 WebGL 配置配置纹理
function loadTexture(gl, n, texture, u_Sampler, image) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 对纹理图像进行y轴翻转
    // 开启0号纹理单元
    gl.activeTexture(gl.TEXTURE0);
    // 向target绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);
    // 配置纹理参数
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    // 配置纹理图像
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
    // 将0号纹理传递给着色器
    gl.uniform1i(u_Sampler, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}

gl.createTexture()

创建纹理对象以存储纹理图像

gl.deleteTexture(texture)

使用 texture 删除纹理对象

图像Y轴反转

WebGL 纹理坐标系统中 t 轴的方向和 PNG,BMP,JPG 等格式图片的坐标系统的 Y 轴方向是相反的。因为只有现将图像 Y 轴反转,才能正确地将图像映射到图形上。

gl.pixelStorei(pname, param)

使用 pnameparam 指定的方式处理和加载得到的图像

pname 以下两种之一:

  • gl.UNPACK_FLIP_Y_WEBGL 可以对图像进行 Y 轴反转,默认值为 false
  • gl.UNPACK_PREMULTIPLE_ALPHA_WEBGL 将图像 RGB 颜色每一个分量乘以 A,默认值为 false
    param 指定非 0true)或 0false)。必须为整数。

激活纹理单元

WebGL 通过一种称作 纹理单元(texture unit) 的机制来同时使用多个纹理。每一个纹理单元有一个单元编号来管理一张纹理图像。即使你的程序只需要使用一张纹理图像,也必须为其指定一个纹理单元。

系统支持的纹理单元个数取决于硬件和浏览器的 WebGL 实现,但是在默认情况下,WebGL 至少支持 8 个纹理单元。

在使用纹理单元之前,需要调用 gl.activeTexture() 来激活它。

gl.activeTexture(texUnit)

激活 texUnit 指定的纹理单元
texUnit 指定准备激活的纹理单元,gl.TEXTURE0,gl.TEXTURE1,...

绑定纹理对象

在对纹理对象操作之前,我们需要绑定纹理对象。

gl.bindTexture(target, texture)

开启 texture 指定的纹理对象,并将其绑定到 target 上,此外,如果已经通过 gl.activeTexture() 激活了某个纹理单元,则纹理对象也会绑定到这个纹理单元上。

target 以下两种之一:

  • gl.TEXTURE_2D 二维纹理
  • gl.TEXTURE_CUBE_MAP 立方体纹理
    texture 绑定的纹理单元

配置纹理对象的参数

gl.texParameteri(target, pname, param)

param 的值赋给绑定到目标的纹理对象的 pname 参数上

target gl.TEXTURE_2Dgl.TEXTURE_CUBE_MAP
pname 纹理参数
param 纹理参数德的值

pname 可以指定的值:

  • 放大方法(gl.TEXTURE_MAG_FILTER):这个参数表示当纹理的绘制范围比纹理本身更大时,如何获取纹素颜色。默认值:gl.LINEAR
  • 缩小方法(gl.TEXTURE_MIN_FILTER):默认值:gl.NEAREST_MIPMAP_LINEAR
  • 水平填充方法:(gl.TEXTURE_WRAP_S):表示如果对纹理的左侧和右侧的区域进行填充,默认值:gl.REPEAT
  • 垂直填充方法:(gl.TEXTURE_WRAP_T):默认值:gl.REPEAT

gl.TEXTURE_MAG_FILTERgl.TEXTURE_MIN_FILTER 的可选值:(还有些书上没涉及的金字塔纹理没有讲)

  • gl.NEAREST 使用原纹理上距离映射后像素(新像素)中心最近的那个像素的颜色值,做为新像素的使用值(使用哈曼顿距离。)
  • gl.LINEAR 使用距离新像素中心最近的四个像素的颜色值的平均加权,做为新像素的值。(与 gl.NEAREST 相比,质量好,开销大)

gl.TEXTURE_WRAP_Sgl.TEXTURE_WRAP_T 的可选值:

  • gl.REPEAT 平铺式的重复纹理
  • gl.MIRRORED_REPEAT 镜像对称式的重复纹理
  • gl.CLAMP_TO_EDGE 使用纹理图像边缘值

将纹理图像分配给纹理对象

gl.texImage2D(target, level, internalformat, format, type, image)

将 image 指定的图像分配给绑定到目标上的纹理对象

target gl.TEXTURE_2Dgl.TEXTURE_CUBE_MAP
level0(该参数为金字塔纹理准备,本书不涉及)
internalformat 图像内部格式

  • gl.RGB
  • gl.RGBA
  • gl.ALPHA
  • gl.LUMINANCE 流明(luminance)表示我们感知到的物体表面的亮度
  • gl.LUMINANCE_ALPHA
    format 纹理数据的格式,必须使用与 internalformat 相同的值
    type 纹理数据的类似
  • gl.UNSIGNED_BYTE 无符号整形,每个颜色分量占据 1 字节
  • gl.UNSIGNED_SHORT_5_6_5 RGB: 每个分量分别占据 5、6、5 比特
  • gl.UNSIGNED_SHORT_4_4_4_4 RGBA: 每个分量分别占据 4、4、4、4 比特
  • gl.UNSIGNED_SHORT_5_5_5_1 RGBA: 每个分量分别占据 5、5、5、1 比特
    image 包含纹理

将纹理单元传递给片元着色器

使用 uniform 变量来表示纹理。

uniform sampler2D u_Sampler;

专用于纹理的数据类型

类型 描述
sampler2D 绑定到 gl.TEXTURE_2D 上的纹理数据类型
samplerCube 绑定到 gl.TEXTURE_CUBE_MAP 上的纹理数据类型

gl.uniform1i()

获取 uniform 变量的存储地址,需要指定纹理单元编号。

在片元着色器中获取纹理像素颜色

vec4 texture2D(sampler2D sampler, vec2 coord)

sampler 指定的纹理上获取 coord 指定的纹理坐标处的像素颜色
返回值格式由 gl.texImage2D()internalformat 参数决定。

使用多幅纹理

var VSHADER_SOURCE = `
    attribute vec4 a_Position;
    attribute vec2 a_TexCoord;
    varying vec2 v_TexCoord;
    void main() {
        gl_Position = a_Position;
        v_TexCoord = a_TexCoord;
    }
`;

var FSHADER_SOURCE = `
    precision mediump float;
    uniform sampler2D u_Sampler0;
    uniform sampler2D u_Sampler1;
    varying vec2 v_TexCoord;
    void main() {
        vec4 color0 = texture2D(u_Sampler0, v_TexCoord);
        vec4 color1 = texture2D(u_Sampler1, v_TexCoord);
        gl_FragColor = color0 * color1;
    }
`;

function  main() {
    var canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);

    var n = initVertexBuffers(gl);

    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    initTextures(gl, n);
}

function initVertexBuffers(gl) {
    var verticesTexCoords = new Float32Array([
        // 顶点坐标,纹理坐标
        -0.5,  0.5,   0.0, 1.0,
        -0.5, -0.5,   0.0, 0.0,
        0.5,  0.5,   1.0, 1.0,
        0.5, -0.5,   1.0, 0.0,
    ]);
    var n = 4;
    var vertexTexCoordBuffer = gl.createBuffer();
    // 顶点坐标和纹理坐标写入缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

    var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
    gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
    gl.enableVertexAttribArray(a_TexCoord);

    return n;
}

function initTextures(gl, n) {
    var texture0 = gl.createTexture(); // 创建纹理对象
    var texture1 = gl.createTexture(); // 创建纹理对象
    var u_Sampler0 = gl.getUniformLocation(gl.program, 'u_Sampler0');
    var u_Sampler1 = gl.getUniformLocation(gl.program, 'u_Sampler1');
    var image0 = new Image();
    var image1 = new Image();
    image0.onload = function () {
        loadTexture(gl, n, texture0, u_Sampler0, image0, 0);
    }
    image1.onload = function () {
        loadTexture(gl, n, texture1, u_Sampler1, image1, 1);
    }

    image0.src = '../../resources/redflower.jpg';
    image1.src = '../../resources/circle.gif';

    return true;
}

var g_texUnit0 = false, g_texUnit1 = false;
function loadTexture(gl, n, texture, u_Sampler, image, texUnit) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 对纹理图像进行y轴翻转
    // 激活纹理
    if (texUnit === 0) {
        gl.activeTexture(gl.TEXTURE0);
        g_texUnit0 = true;
    } else {
        gl.activeTexture(gl.TEXTURE1);
        g_texUnit1 = true;
    }

    // 向target绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);
    // 配置纹理参数
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);

    // 配置纹理图像
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
    // 将0号纹理传递给着色器
    gl.uniform1i(u_Sampler, texUnit);
    gl.clear(gl.COLOR_BUFFER_BIT);
    if (g_texUnit0 && g_texUnit1) {
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
    }
}

第 6 章 OpenGL ES 着色器语言(GLSL ES)

GLSL ES 编程语言是在 OpenGL 着色器语言(GLSL)的基础上,删除和简化一部分功能后形成的。

语言基础

  • 程序大小写敏感,每个语句必须以分号结束。
  • 有且只有一个 main() 函数
  • 注释 单行注释 // 和多行注释 /*...*/
  • 数据值类型
    • 数值类型,整型数(没有小数点)和浮点数(有小数点)
    • 布尔值类型,truefalse
  • 变量,不能以 gl_webgl__webgl_ 开头
  • 强类型语言(type sensitive language)
  • 基本类型 floatintbool
  • 赋值和类型转换
    • 赋值 = 必须类型相同
    • 类型转换 float f = float(8)
  • 运算符 同 JavaScript 类型

矢量和矩阵

GLSL ES 支持矢量和矩阵类型

矢量和矩阵类型

类别 GLSL ES 数据类型 描述
矢量 vec2vec3vec4 具有 2、3、4 个浮点数元素的矢量
矢量 ivec2ivec3ivec4 具有 2、3、4 个整形数元素的矢量
矢量 bvec2bvec3bvec4 具有 2、3、4 个布尔值元素的矢量
矩阵 mat2mat3mat4 2*23*34*4 的浮点数元素的矢量

赋值和构造

我们是用等号(=)来对矢量和矩阵进行赋值操作。赋值运算符左右两边变量/值类型必须一致,左右两边的元素个数也必须一致。

构造函数的名称和其创建的变量和类型名称总是一致的。

矢量构造函数

vec3 v3 = vec3(1.0, 0.0, 0.5);
vec3 v2 = vec2(v3);  // 只取前两个元素
vec4 v4 = vec4(1.0); // 设置为 1.0,1.0,1.0,1.0

如果构造函数接收了不只一个参数,且参数的个数又比矢量的元素个数少,就会出错。

可以将多个矢量组合成一个矢量,

vec4 v4b = vec4(v2, v4); // 1.0,0.0,1.0,1.0

矩阵构造函数

存储在矩阵中的元素是按照列主序排列的。

mat4 m4 = mat4(
    1.0, 2.0, 3.0, 4.0,
    5.0, 6.0, 7.0, 8.0,
    9.0, 10.0, 11.0, 12.0,
    13.0, 14.0, 15.0, 16.0
);
/* 构造出的矩阵
[
    1.0, 5.0, 9.0, 13.0,
    2.0, 6.0, 10.0, 14.0,
    3.0, 7.0, 11.0, 15.0,
    4.0, 8.0, 12.0, 16.0,
]
*/

向矩阵构造函数中传入一个或多个矢量,按照列主序使用矢量里的元素值来构造矩阵。

向矩阵构造函数中传入矢量和数值,按照列主序使用矢量里的元素值和直接传入的数值来构造矩阵。

向矩阵构造函数传入单个数值,这样生成一个对角线上元素都是该数值,其他元素为 0.0 的矩阵。

与矢量构造函数类型,如果传入的数值数量大于 1,有没有达到矩阵元素的数量,就会出错。

访问元素

为了访问矢量或矩阵中的元素,可以使用 .[] 运算符。

运算符

在矢量变量名后接点运算符(.),然后接上分量名,就可以访问矢量的元素了。

分量名表

类别 描述
x,y,z,w 用来获取顶点坐标分量
r,g,b,a 用来获取颜色分量
s,t,p,q 用来获取纹理坐标分量

事实上,任何矢量的 xrs 都会返回第一个分量,ygt 都会返回第二个分量,以此类推。如果试图访问超过矢量长度的分量,就会出错。

将(同一个集合的)多个分量名公同置于点运算符后,就可以从矢量中同同时抽取出多个分量。这个过程称作混合(swizzling)

vec3 v3 = vec3(1.0, 2.0, 3.0);
vec2 v2;
v2 = v3.xy; // (1.0, 2.0)
v2 = v3.yz; // (2.0, 3.0)
v2 = v3.xx; // (1.0, 1.0)

聚合分量名也可以用来做赋值表达式(=)的左值。

vec4 position = vec4(1.0, 2.0, 3.0, 4.0);
position.xw = vec2(5.0, 6.0); // (5.0, 2.0, 3.0, 6.0)

此时多个分量必须属于同一个集合。

[] 运算符

可以使用 [] 运算符来访问矢量或矩阵元素。此时矩阵元素仍然是按列主序读取的。

运算符

矩阵矢量的运算符与基本类型的运算符很类似。但是比较运算符只可以使用 ==!=,不可以使用 ><>=<=,如果想比较矩阵的大小,应该是用内置函数,比如 lessThan()

image.png

实例:

vec3 v3a,v3b,v3c;
mat3 m3a, m3b, m3c;
float f;
// 矢量和浮点数的运算
v3b = v3a + f; // v3b.x = v3a.x + f;
                // v3b.y = v3a.y + f;
                // v3b.z = v3a.z + f;
// 矢量运算
v3c = v3a + v3b; 
// 矩阵和浮点数的运算
m3b = m3a * f; // m3a 的每个值都 *f 赋值给 m3b
// 矩阵右乘矢量 结果是矩阵
m3b = m3a * v3a;
// 矩阵左乘矢量 结果是矢量
v3b = m3a * v3a;
// 矩阵和矩阵相乘
m3c = m3a * m3b;

结构体

GLSL ES 支持用户自定义的类型,即结构体。

struct light {
    vec4 color;
    vec3 position;
}
light l1, l2;

结构体名称会紫铜称为类型名。

构造和赋值

结构体构造函数和结构体名一致,参数的顺序必须与定义的顺序相同。

l1 = light(vec4(0.0,1.0,0.0,1.0), vec3(8.0,3.0,0.0));

访问成员

vec4 color = l1.color;

运算符

结构体只支持赋值(=)和比较(==!=

数组

GLSL ES 只支持一维数组。

float floatArray[4];
vec4 vec4Array[2];

数组长度可以是整形字面量或者 const 指定常量,或者两者组成的表达式。

数组不能在声明时被一次性统一初始化,必须显式的对每个元素进行初始化。

取样器(纹理)

我们必须通过 取样器(sampler) 访问纹理。

有两种基本的取样器:sampler2DsamplerCube

唯一能赋值给取样器变量的就是纹理单元编号,而且必须使用 WebGL 方法 gl.uniform1f() 来赋值。

取样器只能进行 ===!= 运算。

取样器类型变量收到着色器支持的纹理单元的最大数量限制。

着色器 表示最大数量的内置常量 最小数量
顶点着色器 const mediump int gl_MaxVertexTextureImageUnits 0
片元着色器 const mediump int gl_MaxTextureImageUnits 0

规范声明

函数需要先声明再调用。如果需要先调用,要通过规范声明告诉 WebGL 函数的参数。


float luma(vec4); // 规范声明

void main() {
    float brightness = luma(color); // 调用
}

float luma(vec4 color) { // 定义
    // ...
}

参数限定词

在 GLSL ES 中,可以为函数参数指定限定字,以控制参数的行为。我们可以将函数参数定义成:

  1. 传递给函数的
  2. 将要在函数中被赋值的
  3. 既是传递给函数的,也是将要在函数中被赋值的

其中 2、3 有点类似C语言的指针

类别 规则 描述
in 向函数中传入值 可以修改,不影响外部值
const in 向函数中传入值 不可以修改
out 在函数中被赋值,并被传出 为变量的引用,修改影响外部值
inout 传入函数,在函数中被赋值,并被传出 同上,但是会用到变量初始值
<无:默认> 将一个值传给函数 in 一样

内置函数

image.png

存储限定字

  • const 常量,不可变
  • attribute 变量,只能出现在顶点着色器中,只能被声明为全局变量,被用来表示逐顶点的信息。只能是 vec2、vec3、vec4、mat2、mat3、mat4 类型。
  • uniform变量,可以用在顶点着色器中和片元着色器中,只能被声明为全局变量,可以是除了数组和结构体外的任意类型。uniform 变量包含了“一致”(非逐顶点/逐片元的,各顶点或各片元共用)的数据。
  • varying变量,必须是全局变量,它的任务是从顶点着色器向片元着色器传输数据。只能是 vec2、vec3、vec4、mat2、mat3、mat4 类型。

精度限定字

精度限定字用来表示每种数据具有的精度(比特数)。精度限定字是可选的,如果不确定可以使用下面这个适中的默认值:

precision mediump float;

WebGL 支持三种精度

精度限定字 描述
highp 高精度,顶点着色器的最低精度
mediump 中精度,片元着色器的最低精度
lowp 低精度,可以表示所有颜色

使用 precision 来声明着色器的默认精度

// precision 精度限定字 类型名称;
precision mediump float;

预处理指令

#if 条件表达式
IF 表达式为真 执行这里
#endif

#ifdef 宏
IF 定义了该宏 执行这里
#endif

#ifndef 宏
IF 没有定义了该宏 执行这里
#endif

#define 宏名 宏内容 // 定义宏

#undef 宏名 // 取消定义

#if 条件表达式
IF 表达式为真 执行这里
#else
否则执行这里
#endif

#ifdef 宏
IF 定义了该宏 执行这里
#else
否则执行这里
#endif

#version number // 指定着色器使用的 GLSL ES 版本 该指令必须在着色器顶部

第 7 章 进入三维世界

三维场景需要考量,观察方向,可视距离。

观察者所处的位置称为视点(eye point),从视点出发沿着观察者方向的射线称作视线(viewing direction)。

在 WebGL 中,默认情况下,视点处于原点(0,0,0),视线为Z轴负半轴(指向屏幕内部)。

  • 视点:观察者所在的三维空间的位置,视线的起点。

  • 观察目标点:被观察目标所在的点。

  • 上方向:最终绘制在屏幕的影像中,向上的方向。

示例程序,绘制三个三角形

// 定点着色器程序
var VSHADER_SOURCE = `
    attribute vec4 a_Position;
    attribute vec4 a_Color;
    uniform mat4 u_ViewMatrix;
    varying vec4 v_Color;
    void main() {
        gl_Position = u_ViewMatrix * a_Position;
        v_Color = a_Color; 
    }
`;
// 片元着色器程序
var FSHADER_SOURCE = `
    precision mediump float;
    varying vec4 v_Color;
    void main() {
        gl_FragColor = v_Color;
    }
`;
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;
    }
    var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
    // 设置视点 视线 和上方向
    var viewMatrix = new Matrix4();
    viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

    gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

    // 设置背景色
    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 verticesColors = new Float32Array([
        // 顶点坐标和颜色
        0.0,  0.5,  -0.4,  0.4,  1.0,  0.4, // The back green one
        -0.5, -0.5,  -0.4,  0.4,  1.0,  0.4,
        0.5, -0.5,  -0.4,  1.0,  0.4,  0.4,

        0.5,  0.4,  -0.2,  1.0,  0.4,  0.4, // The middle yellow one
        -0.5,  0.4,  -0.2,  1.0,  1.0,  0.4,
        0.0, -0.6,  -0.2,  1.0,  1.0,  0.4,

        0.0,  0.5,   0.0,  0.4,  0.4,  1.0,  // The front blue one
        -0.5, -0.5,   0.0,  0.4,  0.4,  1.0,
        0.5, -0.5,   0.0,  1.0,  0.4,  0.4,
    ]);
    var n = 9; // 点的个数

    // 创建缓冲区对象
    var vertexColorBuffer = gl.createBuffer();
    // 将顶点坐标写入缓冲区对象并开启
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

    var FSIZE = verticesColors.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
    gl.enableVertexAttribArray(a_Color);

    // unbind the buffer object
    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return n;
}

可视范围(正射类型)

虽然你可以将三维物体放在三维空间中的任何地方,但只有当它在可视范围内时,WebGL 才会绘制它。人类只能看到眼前的东西,WebGL也是以类似的方式只绘制可视范围内的三维对象。

有两种常用的可视空间:

  • 长方体可视空间,也称盒状空间,由正射投影产生。由前后两个矩形表面确定,分别称为近裁剪面和远裁剪面。
  • 四棱锥/金字塔可视空间,由透视投影产生

<canvas> 上显示的就是可视空间中物体在近裁剪面上的投影。

定义盒状可视空间

Matrix4.setOrtho(left, right, bottom, top, near, far)

通过各参数计算正射投影矩阵,将其存储在 Matrix4 中,注意,left 不一定与 right 相等,buttom 不一定与 top 相等,nearfar 不相等。

left,right 指定近裁截面的左边界和右边界
bottom,top 指定近裁截面的上边界和下边界

posted @ 2024-08-15 15:12  我不吃饼干呀  阅读(18)  评论(0编辑  收藏  举报