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.POINTSgl.LINESgl.LINE_STRIPgl.LINE_LOOPgl.TRIANGLESgl.TRIANGLE_STRIPgl.TRIANGLE_FAN
first指定从哪个点开始绘制(整型数)
count指定绘制需要多少个顶点(整型数)
调用 gl.drawArrays(mode, first, count) 时,顶点着色器将被执行 count 次,,每次处理一个顶点。
顶点着色器执行完成后,片元着色器将会执行。
WebGL 的坐标系统
当你面向计算机平屏幕时,X轴时水平的(正方向为右),Y轴时垂直的(正方向为下),而Z轴垂直于屏幕(正方向为外),这套坐标系又被称为右手坐标系(right-handed coordinate system)。
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 变量。
其中 precision mediump float 使用精度限定词来指定变量的范围(最大值与最小值)和精度。
gl.getUniformLocation(program, name)
获取由
name参数指定的attribute变量的存储地址
program指定包含顶点着色器和片元着色器的着色器程序对象
name指定想要获取其存储地址的uniform变量的名称
未找到变量,getUniformLocation 返回 null,getAttribLocation 返回 -1。
gl.uniform4f(location, v0, v1, v2, v3)
将数据
(v0,v1,v2,v3)传给由location参数指定的uniform变量
location指定将要修改的uniform变量的存储位置
v0,v1,v2填充attribute变量的三个分量
还有类似 gl.uniform1f,gl.uniform2f 和 gl.uniform3f,第 2、3、4 个分量默认值分别为 0.0,0.0,1.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;
}
使用缓冲区对象的步骤
- 创建缓冲区对象
gl.createBuffer() - 绑定缓冲区对象
gl.bindBuffer() - 将数据写入缓冲区对象
gl.bufferData() - 将缓冲区对象分配给一个
attribute变量gl.getAttribLocation() - 开启
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。
targetgl.ARRAY_BUFFER或gl.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无符号字节,Uint8Arraygl.SHORT短整形,Int16Arraygl.UNSIGNED_SHORT无符号短整形,Uint16Arraygl.INT整形,Int32Arraygl.UNSIGNED_INT无符号整形,Uint32Arraygl.FLOAT浮点型,Float32Array
normalize传入true或false,表明是否将非浮点型的数据归一化到[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_STRIP 或 gl.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变量。
locationuniform变量的存储位置
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(以及相关的 vec2, vec3, vec4, mat2, mat3 和 mat4)类型的。
在片元着色器和顶点着色器声明同名 varying 变量,就可以在片元着色器接收变量。
彩色三角形
将上面的 gl.drawArrays(gl.POINTS, 0, n); 改为 gl.drawArrays(gl.TRIANGLES, 0, n);,可以得到一个彩色的三角形!
在顶点着色器和片元着色器之间,由下面两个步骤:
- 图形装配过程:将孤立的顶点坐标装配成几何图形,几何图形的类别由
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 中,纹理映射需要遵循以下四步:
- 准备好映射到几何图形上的纹理图像。
- 为几何图形配置纹理映射方式。
- 加载纹理图像,对其进行一些配置,以在 WebGL 中使用它。
- 在片元着色器中将响应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。
纹理坐标
纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色。WebGL 系统中的纹理坐标系统是二维的,在 WebGL 中使用 s 和 t 命名纹理坐标(st 坐标系统)。
将纹理图像粘贴到几何图形上
示例代码
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)
使用
pname和param指定的方式处理和加载得到的图像
pname以下两种之一:
gl.UNPACK_FLIP_Y_WEBGL可以对图像进行Y轴反转,默认值为falsegl.UNPACK_PREMULTIPLE_ALPHA_WEBGL将图像 RGB 颜色每一个分量乘以 A,默认值为 false
param指定非0(true)或0(false)。必须为整数。
激活纹理单元
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参数上
targetgl.TEXTURE_2D或gl.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_FILTER 和 gl.TEXTURE_MIN_FILTER 的可选值:(还有些书上没涉及的金字塔纹理没有讲)
gl.NEAREST使用原纹理上距离映射后像素(新像素)中心最近的那个像素的颜色值,做为新像素的使用值(使用哈曼顿距离。)gl.LINEAR使用距离新像素中心最近的四个像素的颜色值的平均加权,做为新像素的值。(与gl.NEAREST相比,质量好,开销大)
gl.TEXTURE_WRAP_S 和 gl.TEXTURE_WRAP_T 的可选值:
gl.REPEAT平铺式的重复纹理gl.MIRRORED_REPEAT镜像对称式的重复纹理gl.CLAMP_TO_EDGE使用纹理图像边缘值
将纹理图像分配给纹理对象
gl.texImage2D(target, level, internalformat, format, type, image)
将 image 指定的图像分配给绑定到目标上的纹理对象
targetgl.TEXTURE_2D或gl.TEXTURE_CUBE_MAP
level传0(该参数为金字塔纹理准备,本书不涉及)
internalformat图像内部格式
gl.RGBgl.RGBAgl.ALPHAgl.LUMINANCE流明(luminance)表示我们感知到的物体表面的亮度gl.LUMINANCE_ALPHA
format纹理数据的格式,必须使用与internalformat相同的值
type纹理数据的类似gl.UNSIGNED_BYTE无符号整形,每个颜色分量占据 1 字节gl.UNSIGNED_SHORT_5_6_5RGB: 每个分量分别占据 5、6、5 比特gl.UNSIGNED_SHORT_4_4_4_4RGBA: 每个分量分别占据 4、4、4、4 比特gl.UNSIGNED_SHORT_5_5_5_1RGBA: 每个分量分别占据 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()函数 - 注释 单行注释
//和多行注释/*...*/ - 数据值类型
- 数值类型,整型数(没有小数点)和浮点数(有小数点)
- 布尔值类型,
true和false
- 变量,不能以
gl_、webgl_或_webgl_开头 - 强类型语言(type sensitive language)
- 基本类型
float、int、bool - 赋值和类型转换
- 赋值
=必须类型相同 - 类型转换
float f = float(8)
- 赋值
- 运算符 同 JavaScript 类型
矢量和矩阵
GLSL ES 支持矢量和矩阵类型
矢量和矩阵类型
| 类别 | GLSL ES 数据类型 | 描述 |
|---|---|---|
| 矢量 | vec2、vec3、vec4 |
具有 2、3、4 个浮点数元素的矢量 |
| 矢量 | ivec2、ivec3、ivec4 |
具有 2、3、4 个整形数元素的矢量 |
| 矢量 | bvec2、bvec3、bvec4 |
具有 2、3、4 个布尔值元素的矢量 |
| 矩阵 | mat2、mat3、mat4 |
2*2、3*3、4*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 | 用来获取纹理坐标分量 |
事实上,任何矢量的 x,r,s 都会返回第一个分量,y,g,t 都会返回第二个分量,以此类推。如果试图访问超过矢量长度的分量,就会出错。
将(同一个集合的)多个分量名公同置于点运算符后,就可以从矢量中同同时抽取出多个分量。这个过程称作混合(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() 。
实例:
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) 访问纹理。
有两种基本的取样器:sampler2D 和 samplerCube。
唯一能赋值给取样器变量的就是纹理单元编号,而且必须使用 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 中,可以为函数参数指定限定字,以控制参数的行为。我们可以将函数参数定义成:
- 传递给函数的
- 将要在函数中被赋值的
- 既是传递给函数的,也是将要在函数中被赋值的
其中 2、3 有点类似C语言的指针
| 类别 | 规则 | 描述 |
|---|---|---|
in |
向函数中传入值 | 可以修改,不影响外部值 |
const in |
向函数中传入值 | 不可以修改 |
out |
在函数中被赋值,并被传出 | 为变量的引用,修改影响外部值 |
inout |
传入函数,在函数中被赋值,并被传出 | 同上,但是会用到变量初始值 |
| <无:默认> | 将一个值传给函数 | 和 in 一样 |
内置函数
存储限定字
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相等,near与far不相等。left,right 指定近裁截面的左边界和右边界
bottom,top 指定近裁截面的上边界和下边界
浙公网安备 33010602011771号