webGL入门:绘制一个三角形
webGL入门尝试,绘制一个简单的三角形
webGL 是浏览器提供的 3D API,可以在浏览器端实现一些比较炫酷的 3D 效果,其实 webGL 只是一个光栅化引擎,能够根据给定的数据绘制出点、线、三角形,并将其填充,输出栅格图像,万丈高楼平地起,3D 图像其实也是由简单的三角形组合而成,webGL 能绘制出什么完全看你自己的使用。webGL 在电脑的 GPU 中运行,需要编写能在 GPU 端运行的代码:顶点着色器和片元着色器。顶点着色器定义图像的顶点并确定位置,片元着色器确定图像的颜色,webGL 所做的就是给顶点着色器传递数据确定位置,然后调用片元着色器填充颜色,这就是 webGL 绘图的简单流程。那么,在代码中如何编写呢,下面一步步的来看:
# 1.获取 webGL 上下文:WebGLRenderingContext
编写 webGL 程序需要一个 canvas 元素作为绘图区域,并创建 3D 绘图上下,3D 上下文有 4 种,我们只需要根据浏览器的支持来确定参数,可以编写如下的代码获取 webGL 绘图上下文:
const names = ['webgl', 'experimental-webgl', 'webkit-3d', 'moz-webgl']
let context = null
for (let ii = 0; ii < names.length; ++ii) {
try {
context = canvas.getContext(names[ii])
} catch (e) {
// no-empty
}
if (context) {
break
}
}
2
3
4
5
6
7
8
9
10
11
12
# 2. 编译着色器
着色器分为顶点着色器和片元着色器,顶点着色器主要是顶点装配,确定位置,一个基本的顶点着色器如下所示:
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
}
2
3
4
5
片元着色器主要是确定渲染颜色,一个基本的片元着色器如下所示:
void main() {
gl_FragColor = vec4(1.0);
}
2
3
由于片元着色器是以文本的形式存在于浏览器端的,在绘制之前需要先进行编译:
对于顶点着色去我们创建gl.VERTEX_SHADER
类型的着色器,对于片元着色器我们创建gl.FRAGMENT_SHADER
类型的着色器
const vertShdr = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vertShdr, vertex)
gl.compileShader(vertShdr)
const fragShdr = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragShdr, fragment)
gl.compileShader(fragShdr)
2
3
4
5
6
7
# 3.连接到上下文 program
着色器编译之后我们需要链接到绘图上下文的 program 上面,这样程序才知道我们绘制的时候使用哪个着色器。 我们先创建一个 program,然后添加已编译的着色器,最后链接到上下文 program 上。
const program = gl.createProgram()
gl.attachShader(program, vertShdr)
gl.attachShader(program, fragShdr)
gl.linkProgram(program)
2
3
4
# 4.创建 buffer
编译好之后,我们就可以给着色器传递一些数据来绘制图形了,这里我们绘制一个基本的三角形。
在 webGL 中,坐标轴位于画布中心,xy 的范围为[-1,1],并且由于着色器是强类型的,因此我们在传递数据的是不能使用传统的 JS array,需要使用强类型的Float32Array
。buffer 可以认为是 webGL 中的一个全局变量,设置之后绑定数据然后就着色器就可以在 buffer 中获取所需的数据,具体代码如下:
// 顶点数据
const points = new Float32Array([-1.0, 0.0, 1.0, 0.0, 0.0, 1.0])
// 创建buffer
const verticesBuffer = gl.createBuffer()
// 绑定我们创建的buffer
gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer)
// 给绑定的buffer填充数据
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
2
3
4
5
6
7
8
9
# 5.启用位置点,并设置数据读取方式
在着色器中我们设置了一个变量:attribute vec4 aPosition;
,现在我们要启用它,并从 buffer 中读取数据,由于着色器编译之后我们存储在内存中,
我们需要先获取它的位置:
const aPosition = gl.getAttribLocation(program, 'aPosition')
然后启用它:
gl.enableVertexAttribArray(aPosition)
然后告诉它怎么从缓冲中读取数据,一个隐藏的信息是gl.vertexAttribPointer
将当前属性绑定到了ARRAY_BUFFER
上,也就是我们之前创建并绑定的verticesBuffer
上面。
// 告诉属性怎么从positionBuffer中读取数据 (ARRAY_BUFFER)
const size = 2 // 每次迭代运行提取两个单位数据
const type = this.gl.FLOAT // 每个单位的数据类型是32位浮点型
const normalize = false // 不需要归一化数据
const stride = 0 // 0 = 移动单位数量 * 每个单位占用内存(points.BYTES_PER_ELEMENT)
// 每次迭代运行运动多少内存到下一个数据开始点
const offset = 0 // 从缓冲起始位置开始读取
this.gl.vertexAttribPointer(aPosition, size, type, normalize, stride, offset)
2
3
4
5
6
7
8
aPosition 是一个 vec4 变量,相当于一个 length 为 4 的数据,在 webGL 中,gl_Position
我们只设置了前 2 个分量,而后两个分量将使用默认值 0 和 1.
# 6.绘制
绘制之前我们先清除画布
// 清除
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
// 绘制图元
var primitiveType = gl.TRIANGLES
// 偏移
var offset = 0
// 绘制次数
var count = 3
gl.drawArrays(primitiveType, offset, count)
2
3
4
5
6
7
8
9
10
11
因为我们有 3 个点,因此绘制次数为 3 次,我们选择绘制的图元为三角形。最终:绘制效果如下:
以上,webGL 本身做的事情很简单,就是根据我们给定的数据和着色器来绘制一个三角形,但是由于其 API 比较底层,每一步都需要我们来指定,从获取上下文到编译着色器以至于设置缓冲等都需要我们一步步的在程序中编写,webGL 难在于我们编写程序本身,如何从简单的三角形搭建出绚丽的三维效果,这就是困难所在。