# webGL 基础
我们将现实世界中绘制到屏幕上,需要进行两次坐标的变换:
整个 webgl 渲染流程:
# webGL 程序的结构
在 HTML 中,动态网页包含 HTML 和 JavaScript 两种语言,引入 webgl 后,还需要加入着色器语言 GLSL ES。
我们是通过对 GPU 进行编程,进行渲染图形:对于 GPU 编程主要从下面两个方面进行编程:
- webgl:API glTranslate/glRotatef
- GLSL:顶点着色器 - gl_Position 和 片元着色器 - gl_FragColor
# webgl 渲染管线
JavaScript 通过显存的 Buffer 来实现顶点着色器与片元着色器的数据传递。
frame Buffer 是帧缓冲区,保存顶点着色器与片元着色器渲染后的结果,可以进行进一步的操作。
# 渲染过程
首先运行 JavaScript 程序,调用 webgl 的相关 方法,然后顶点着色器和片元着色器就会指向,在颜色缓冲区内进行绘制,这时就清空了绘图区,最后,颜色缓冲区的内容会自动在浏览器的 canvas 上显示出来。
- 传入坐标
- 计算显示坐标
- 图元装备(根据坐标,组成图形)
- 将装备好的图元变成像素的形式;(顶点着色器)
- 片元着色器对每一个像素点进行渲染
顶点着色器的作用:将逻辑上的传入的顶点坐标,根据空间上的变换,进行计算屏幕上展示点的坐标。
# GLSL
- OpenGL 着色器语言
- 让开发者可以对渲染过程拥有更多的控制
GLSL 参考:https://colin1994.github.io/2017/11/11/OpenGLES-Lesson04/
# 存储限定符
对于着色器语言,具有三种数据类型:
- attribute:只能在 vertex shader 中使用的变量,一般用于传递顶点数据;
- uniform:常量,不能被 shader 修改,uniform 变量在 vertex 和 fragment 两者之间声明方式完全一致,则他可以在 vertex 和 fragment 共享使用,相当于一个被 vertex 和 fragment shader 共享的全局变量。
- varying:varying 变量是vertex 和 fragment shader 之间做数据传递用的。
如下代码,声明一个背景色为红色的三角形:
var VSHADER_SOURCE, FSHADER_SOURCE
VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'uniform mat4 u_ViewMatrix;\n' +
'uniform mat4 u_ProjectionMatrix;\n' +
'void main () {\n' +
'gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;\n' +
'}\n'
FSHADER_SOURCE =
'void main () {\n' +
'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n'
也可以直接写在页面 script 标签中:
<script type="notjs" id="vertex-shader">
# 传入坐标 vec4 是代表一个 4x4 的浮点型矩阵
attribute vec4 a_Position;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
void main() {
# 顶点位置-webgl 内置 API
gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
}
</script>
<script type="notjs" id="fragment-shader">
void main() {
# 设置背景色
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</script>
然后通过 dom 操作获取内容:
const vertexShaderSource = document.getElementById('vertex-shader').text
const fragShaderSource = document.getElementById('fragment-shader').text
# 其他
- 精度限定 使用精度限定词来指定变量的范围(最大值和最小值)和精度,下面代码表示中等精度:
precision mediump float;
# webgl 坐标系统
遵循右手法则:
# webgl 机制 —— 缓冲区对象(buffer object)
我们绘制一个点的时候是不需要用到缓冲区对象:
// 顶点着色器程序
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
' gl_PointSize = 10.0;\n' + // Set the point size
'}\n';
// 片元着色器程序
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
'}\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;
}
// 创建 shader 对象
function loadShader(gl, type, source) {
// Create shader object
var shader = gl.createShader(type);
if (shader == null) {
console.log('unable to create shader');
return null;
}
// Set the shader program
gl.shaderSource(shader, source);
// Compile the shader
gl.compileShader(shader);
// Check the result of compilation
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
var error = gl.getShaderInfoLog(shader);
console.log('Failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
// 创建程序
function createProgram(gl, vshader, fshader) {
// Create shader object
var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
if (!vertexShader || !fragmentShader) {
return null;
}
// Create a program object
var program = gl.createProgram();
if (!program) {
return null;
}
// Attach the shader objects
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// Link the program object
gl.linkProgram(program);
// Check the result of linking
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
var error = gl.getProgramInfoLog(program);
console.log('Failed to link program: ' + error);
gl.deleteProgram(program);
gl.deleteShader(fragmentShader);
gl.deleteShader(vertexShader);
return null;
}
return program;
}
// 初始化着色器方法
function initShaders(gl, vshader, fshader) {
var program = createProgram(gl, vshader, fshader);
if (!program) {
console.log('Failed to create program');
return false;
}
gl.useProgram(program);
gl.program = program;
return true;
}
// 初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// 设置 canvas 的背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清空 canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);
}
我们只需要一个 gl.drawArrays(gl.POINTS, 0, 1) 进行每次绘制一个点,但是如果我们需要绘制多个点,就需要用到缓冲区对象,他可以一次性向着色器传入多个顶点的数据,缓冲区对象是 webgl 系统中的一块内存区域,我们可以一次性地向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。
下面代码是绘制一个三角形并增加旋转动画:
var canvas = document.getElementById('myCanvas')
// 获取 webgl 绘图上下文
var gl = canvas.getContext('webgl')
// 一个程序,可以绑定顶点着色器跟片源着色器
var program = gl.createProgram()
var VSHADER_SOURCE, FSHADER_SOURCE
// 顶点着色器程序
VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'uniform mat4 u_ViewMatrix;\n' +
'uniform mat4 u_ProjectionMatrix;\n' +
'void main () {\n' +
'gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;\n' +
'}\n'
// 片元着色器程序
FSHADER_SOURCE =
'void main () {\n' +
'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n'
var vertexShader, fragmentShader
function createShader (gl, sourceCode, type) {
// create shader
var shader = gl.createShader(type)
gl.shaderSource(shader, sourceCode)
// 编译 shader
gl.compileShader(shader)
return shader
}
// 定义顶点着色器
vertexShader = createShader(gl, VSHADER_SOURCE, gl.VERTEX_SHADER)
// 定义片段着色器
fragmentShader = createShader(gl, FSHADER_SOURCE, gl.FRAGMENT_SHADER)
// 和 shader 绑定
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 连接 program
gl.linkProgram(program)
gl.useProgram(program)
gl.program = program
var currentAngle = 0
var g_last = Date.now()
var tick = function () {
// update the new rotation angle
animate()
// draw
draw()
requestAnimationFrame(tick)
}
function initVertexBuffers (gl) {
// 定义顶点
var vertices = new Float32Array([
0, 0.5, -0.5, -0.5, 0.5, -0.5
])
// 点的个数
var n = 3
// 创建缓冲区对象
var vertexBuffer = gl.createBuffer()
// 将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
// 向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
// 在顶点着色器中获取 a_Position 的地址;gl.program 为包含顶点着色器和片元着色器的着色器程序对象
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
}
// 将顶点的位置写入顶点着色器
var n = initVertexBuffers(gl)
// 指定清空 canvas 的颜色
gl.clearColor(0, 0, 0, 1)
var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix')
var modelMatrix = new Matrix4()
var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix')
var viewMatrix = new Matrix4()
viewMatrix.lookAt(100, 100, 100, 0, 0, 0, 0, 1, 0)
var u_ProjectionMatrix = gl.getUniformLocation(gl.program, 'u_ProjectionMatrix')
var projectionMatrix = new Matrix4()
// projectionMatrix.perspective(120, 1, 0.1, 1000)
projectionMatrix.ortho(-1, 1, -1, 1, 0.1, 1000)
function animate () {
var now = Date.now()
var duration = now - g_last
g_last = now
currentAngle = currentAngle + duration / 1000 * 180
}
function draw () {
// clear canvas and add background color
modelMatrix.setRotate(currentAngle, 0, 1, 0)
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements)
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements)
gl.uniformMatrix4fv(u_ProjectionMatrix, false, projectionMatrix.elements)
// 清空 canvas,清空区域,实际上是在清空颜色缓冲区(color buffer)
gl.clear(gl.COLOR_BUFFER_BIT)
// 第一个参数为绘制的类型,第二个参数是指定从哪个顶点开始绘制(整数型),第三个参数是指定绘制需要用到多少个顶点(整数型)
// 当程序调用 l.drawArrays 方法时,顶点着色器将被执行 n 次,每次处理一个顶点。执行顶点着色器代码中的 main 函数。
// 一旦顶点着色器执行完之后,片元着色器开始执行。
gl.drawArrays(gl.TRIANGLES, 0, n)
}
tick()
渲染流程如下:
使用缓冲区对象向顶点着色器传入多个顶点的数据,需要遵循下面五个步骤:
- 创建缓冲区对象(gl.createBuffer())
- 绑定缓冲区对象(gl.bindBuffer())
- 将数据写入缓冲区对象(gl.bufferData())
- 将缓冲区对象分配给一个 attribute 变量(gl.vertexAttributPointer())
- 开启 attribute 变量(gl.enableVertexAttribArray())
对应代码:
function initVertexBuffers (gl) {
// 定义顶点
var vertices = new Float32Array([
0, 0.5, -0.5, -0.5, 0.5, -0.5
])
// 点的个数
var n = 3
// 创建缓冲区对象
var vertexBuffer = gl.createBuffer()
// 将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
// 向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
// 在顶点着色器中获取 a_Position 的地址;gl.program 为包含顶点着色器和片元着色器的着色器程序对象
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.deleteBuffer() 函数用来删除 gl.createBuffer() 创建出来的缓冲区对象。
- 绑定缓冲区:将缓冲区对象绑定到 webgl 系统中已经存在的目标上。这个目标表示缓冲区对象的用途(在这里,就是向顶点着色器提供传给 attribute 变量的数据),这样 webgl 才能够正确处理其中的内容;
缓冲区中写入数据:gl.bufferData(),该方法时将第二个参数中的数据写入到第一个参数 gl.ARRAY.BUFFER 上的缓冲区对象。我们不能直接向缓冲区写入数据,而只能向目标写入数据,所以要向缓冲区写数据,必须要先绑定;
使用类型化数组 Float32Array:为了绘制三维图形,webgl 通常需要同时处理大量相同类型的数据,例如顶点的坐标和颜色数据。为了优化性能,webgl 为每种基本数据类型引入了一种特殊的数组(类型化数组)。浏览器事先知道数组中的数据类型,所以处理起来也更加有效率。Float32Array 与普通数组不同,类数组不支持 push() 和 pop() 方法。注意创建类型化数组的唯一方法就是使用 new 操作符。不能使用 [] 运算符,那样创建的就是普通数组。 下面是类型化数组的方法、属性以及常量:
将缓冲区对象分配给 attribute 变量:gl.vertexAttributPointer(),之前可以使用 gl.vertexAttrib[1, 2, 3, 4]f 系列函数为 attribute 变量分配至,但是这些方法一次只能向 attribute 变量分配一个值,gl.vertexAttributPointer() 可以将整个数组一次性分配给 attribute 变量。
开启 attribute 变量:gl.enableVertexAttribArray(),为了使顶点着色器能够访问缓冲区内的数据,需要使用 gl.enableVertexAttribArray() 开启 attribute 变量。同样可以使用 gl.disableVertexAttribArray() 来关闭分配。开启了 attribute 变量后,就不能使用 gl.vertexAttrib[1, 2, 3, 4]f 向他传递数据了,除非显式地关闭该 attribute 变量。
gl.drawArrays():
webgl 方法通过 gl.drawArrays() 的第一个参数指定不同的值,绘制不同的图形。下表的 v0,v1 等表示缓冲区 中的顶点,顶点的顺序将影响绘制的结果:
下面就是绘制这些基本的图形:
- gl_PointSize 顶点着色器中设置点的大小,只有在绘制单个点的时候才起作用。
# 移动、旋转、缩放
# 移动
只需要传入坐标偏移量即可: u_Translation 就是传入的偏移量,每次 a_Position 加上偏移量之后,就会移动图形
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform vec4 u_Translation;\n' +
'void main() {\n' +
' gl_Position = a_Position + u_Translation;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('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 intialize shaders.');
return;
}
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// 偏移量传值
var u_Translation = gl.getUniformLocation(gl.program, 'u_Translation');
if (!u_Translation) {
console.log('Failed to get the storage location of u_Translation');
return;
}
gl.uniform4f(u_Translation, Tx, Ty, Tz, 0.0);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
}
# 旋转
旋转必须要指明:
- 旋转轴:图形将围绕旋转轴旋转
- 旋转方向:方向是顺时针还是逆时针
- 旋转角度:图形旋转经过的角度
描述旋转:绕 Z 轴旋转了 β 角度,如果 β 是正值,那么看到的物体就是逆时针旋转的。这种情况又称为正旋转。正旋转又称为右手法则旋转。
设点 p(x, y, z) 旋转 β 角度后变成 p'(x', y', z'):首先旋转是绕 Z 轴信息的,所以 z 坐标不会变化,可以直接忽略;然后 x 坐标和 y 坐标如下:
上图,r 是从圆点到点 p 的距离,而 α 是 X 轴旋转到 p 的角度,用这两个变量计算出点 p 的坐标,如下:
- x = r cos α
- y = r sin α
类似地,可以使用 α 、r、β 来表示 p' 的坐标:
- x' = r cos(α + β)
- y' = r sin(α + β)
利用三角函数两角和公示,可得:
- x' = r (cos α cos β - sin α sin β))
- y' = r (sin α cos β + cos α sin β))
消除 r 和 α 后:
- x' = x cosβ - y sinβ
- y' = x sinβ - y cosβ
- z' = z
我们可以将 sin β 和 cos β 的值传给顶点着色器,然后在着色器中根据上面的式子计算旋转后的点坐标,就可以实现旋转了。使用 JavaScript 内置的 Math 对象的 sin() 和 cos() 方法来进行三角函数运算。
代码如下:
var VSHADER_SOURCE =
// x' = x cosβ - y sinβ
// y' = x sinβ + y cosβ Equation 3.3
// 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 FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
// 旋转角度
var ANGLE = 90.0;
function main() {
var canvas = document.getElementById('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 intialize shaders.');
return;
}
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// 将旋转图形所需的数据传递给顶点着色器
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');
if (!u_CosB || !u_SinB) {
console.log('Failed to get the storage location of u_CosB or u_SinB');
return;
}
gl.uniform1f(u_CosB, cosB);
gl.uniform1f(u_SinB, sinB);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
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');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
return n;
}
# 变换矩阵旋转
我们可以将上面的操作看成是矩阵计算得到的结果:
上面的这个矩阵通常称为旋转矩阵。
# 变换矩阵平移
平移时需要加上一个常量,来表示偏移量,可以使用 4 × 4 的矩阵以及具有第四个分量的矢量来表示,假设 p 的坐标为(x, y, z, 1),平移之后的 p' 的坐标为(x', y', z', 1);如下:
该矩阵的乘法结果如下:
- x' = ax + by + cz + d
- y' = ex + fy + gz + h
- z' = ix + jy + kz + l
- 1 = mx + ny + oz + p
根据最后一个式子就可以计算出系数 m = 0, n = 0, o = 0, p = 1, 这些方式都有常数项 d、h、l 和 p,我们跟之前做平移的等式进行比较:
- x' = x + Tx
- y' = y + Ty
- z' = z + Tz
比较两个等式,姐可以知道 a = 1,b = 0,c = 0, d = Tx,e = 0,f = 1,g = 0,h = Ty,i = 0,j = 0,k = 1,l = Tz。这样就可以表示平移矩阵,如下:
# 使用变换矩阵实现旋转平移
如果我们使用最开始计算点坐标在进行旋转平移变化,每次都需要进行一次新的变换。每次重新取一个新的等式,然后实现一个新的着色器,很不科学,我们可以通过变换矩阵来完成。 上面我们获取到旋转的矩阵为 3 × 3 的矩形,但是平移矩阵是 4 × 4 的矩阵,阶数不同不能进行计算,可以处理一下旋转矩阵为 4 × 4 的。
实现代码如下:
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
// mat4 表示 4 × 4 的矩阵
'uniform mat4 u_xformMatrix;\n' +
'void main() {\n' +
// 矩阵相乘
' gl_Position = u_xformMatrix * a_Position;\n' +
'}\n';
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
var ANGLE = 90.0;
function main() {
var canvas = document.getElementById('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 intialize shaders.');
return;
}
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// 创建旋转矩阵
var radian = Math.PI * ANGLE / 180.0; // 角度转换为弧度制
var cosB = Math.cos(radian), sinB = Math.sin(radian);
// 注意 webgl 中矩阵是列主序的
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_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
if (!u_xformMatrix) {
console.log('Failed to get the storage location of u_xformMatrix');
return;
}
// 将矩阵传递给着色器 v 表示可以向着色器传输多个数据值
// 第一个参数为 uniform 变量的存储位置,第二个参数在 webgl 中必须指定 false,第三个参数是待传输的类型化数组,4 × 4 矩阵按列主序存储在其中
gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
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 false;
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
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 -1;
}
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
return n;
}
注意这里的矩阵表示法,数组为一维的,其元素排成一行,我们可以按照两种方式在数组中存储矩阵元素:按行主序和按列主序,如图:
webgl 跟 opengl 一样,矩阵元素是按列主组存储在数组中的。
# 变换矩阵 - 缩放
假设 p 经过缩放操作变成了 p':
假设在三个方向 X 轴,Y 轴,Z 轴的缩放因子 Sx,Sy,Sz 不相关,那么有:
- x' = Sx × x
- y' = Sy × y
- z' = Sz × z
写成变换矩阵如下:
我们只需要修改前面代码的矩阵就可以实现缩放效果。
评 论:
← 数学基础 webgl高级变换与动画基础 →