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)。
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
。
target
gl.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
变量。
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
(以及相关的 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
轴反转,默认值为false
gl.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
参数上
target
gl.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 指定的图像分配给绑定到目标上的纹理对象
target
gl.TEXTURE_2D
或gl.TEXTURE_CUBE_MAP
level
传0
(该参数为金字塔纹理准备,本书不涉及)
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()
函数 - 注释 单行注释
//
和多行注释/*...*/
- 数据值类型
- 数值类型,整型数(没有小数点)和浮点数(有小数点)
- 布尔值类型,
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 指定近裁截面的上边界和下边界