三角形的绘制比点的绘制多对多一个步骤。那就是三角形需要创建缓冲区。有以下几种绘制三角形的方法。
以下例子主要用到以下GLSL
知识。
- gl.createBuffer:创建缓冲区。
- gl.bindBuffer:绑定某个缓冲区对象为当前缓冲区。
- gl.bufferData:往缓冲区中复制数据。
- gl.enableVertexAttribArray:启用顶点属性。
- gl.vertexAttribPointer:设置顶点属性从缓冲区中读取数据的方式。
三种绘制方式的代码几乎是一样的。只是在进行渲染时drawArrays(type,start,count)
中的type
不一样。
- 顶点着色器代码
<script id="vertexShader" type="x-shader/x-vertex">
//attribute声明vec4类型变量apos
attribute vec4 apos;
void main() {
//顶点坐标apos赋值给内置变量gl_Position
//逐顶点处理数据
gl_Position = apos;
}
</script>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 片元着色器代码
<script id="fragmentShader" type="x-shader/x-fragment">
void main() {
// 逐片元处理数据,所有片元(像素)设置为红色
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
1
2
3
4
5
6
2
3
4
5
6
- javascript代码,主要由以下4部分代码构成
- 获取
WebGL
上下文
//通过getElementById()方法获取canvas画布 const canvas = document.getElementById('WebGL'); //通过方法getContext()获取WebGL上下文 const gl = canvas.getContext('WebGL'); //初始化着色器 const program = initShader(gl); //初始化缓存区 const count = initBuffer(gl); render(gl, program, count);
1
2
3
4
5
6
7
8
9- 声明初始化着色器函数
//声明初始化着色器函数 function initShader(gl) { //顶点着色器源码 const vertexShaderSource = document.getElementById('vertexShader').innerText; //片元着色器源码 const fragmentShaderSource = document.getElementById('fragmentShader').innerText; const vertexShader = gl.createShader(gl.VERTEX_SHADER); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(vertexShader); gl.compileShader(fragmentShader); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); return program; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19- 初始化缓冲数据
//初始化缓冲数据 function initBuffer(gl) { //类型数组构造函数Float32Array创建顶点数组 const data = new Float32Array([ -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, ]); //创建缓冲区对象 var buffer = gl.createBuffer(); //绑定缓冲区对象,激活buffer gl.bindBuffer(gl.ARRAY_BUFFER, buffer); //顶点数组data数据传入缓冲区 gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); return data.length/2; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17- 渲染
function render(gl, program, count) { //获取顶点着色器的位置变量apos,即aposLocation指向apos变量。 const aposLocation = gl.getAttribLocation(program, 'apos'); //缓冲区中的数据按照一定的规律传递给位置变量apos gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0); //允许数据传递 gl.enableVertexAttribArray(aposLocation); //设置清屏颜色为黑色。 gl.clearColor(0, 0, 0, 1.0); //清屏 gl.clear(gl.COLOR_BUFFER_BIT); //开始绘制图形 gl.drawArrays(gl.TRIANGLES, 0, count); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 - 获取
# 1、常规方式
gl.drawArrays(gl.TRIANGLES, 0, 3);
1
# 2、三角形带
绘制三角形的数量 = 顶点数 - 2
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
1
# 3、三角形扇
绘制三角形的数量 = 顶点数 - 2
gl.drawArrays(gl.TRIANGLE_FAN, 0, 3);
1
# 4、动态绘制三角形
着色器中的代码跟动态绘制点的代码几乎是一样的。只是多了一个颜色插值的处理。
- 顶点着色器代码
<script type="shader-source" id="vertexShader">
//浮点数设置为中等精度
precision mediump float;
//接收 JavaScript 传递过来的点的坐标(X, Y)
attribute vec2 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main(){
// 最终的顶点坐标。
gl_Position = vec4(a_Position, 0.0, 1.0);
//进行颜色插值计算
v_Color = a_Color;
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 片元着色器代码
<script type="shader-source" id="fragmentShader">
//浮点数设置为中等精度
precision mediump float;
// 用来接收顶点着色器插值后的颜色。
varying vec4 v_Color;
void main(){
// 点的最终颜色。
gl_FragColor = v_Color;
}
</script>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- javascript代码
WebGL
上下文的获取
//通过getElementById()方法获取canvas画布 const canvas = document.getElementById('WebGL'); //设置canvas尺寸为满屏 //注意设置canvas的尺寸必须在获取WebGL上下文之前调用 resizeCanvas(canvas); //通过方法getContext()获取WebGL上下文 const gl = canvas.getContext('WebGL');
1
2
3
4
5
6
7canvas
的尺寸动态设置
//设置canvas的大小 function resizeCanvas(canvas, width, height) { if (canvas.width !== width) { canvas.width = width ? width : window.innerWidth; } if (canvas.height !== height) { canvas.height = height ? height : window.innerHeight; } }
1
2
3
4
5
6
7
8
9- 着色器中的变量赋值处理
function assignValue(gl,program){ let a_Position = gl.getAttribLocation(program, 'a_Position'); let a_Color = gl.getAttribLocation(program, 'a_Color'); gl.enableVertexAttribArray(a_Position); gl.enableVertexAttribArray(a_Color); let buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); //将缓冲区中的数据按照一定规律传递给a_Position,表示从缓存区中获取两个浮点型数据(浮点型数据,4个字节),缓存区一行为4*6个字节,偏移量为0 gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 4*6, 0); gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false,4*6, 8); }
1
2
3
4
5
6
7
8
9
10
11
12- 点击事件监听
const positions = []; function initClick() { canvas.addEventListener("click", e => { const color = randomColor(); const position = coordTransform(e.pageX, e.pageY) positions.push(...position, ...Object.values(color)); // 顶点信息为 18 的整数倍即3个顶点时执行绘制操作,因为三角形由三个顶点组成,每个顶点由六个元素组成。 if (positions.length % 18 == 0) { // gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); render(gl); } }) } //颜色值的随机生成 const random = Math.random; function randomColor() { return { r: random(), g: random(), b: random(), a: random() }; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24- 声明初始化着色器函数
//声明初始化着色器函数 function initShader(gl) { //顶点着色器源码 const vertexShaderSource = document.getElementById('vertexShader').innerText; //片元着色器源码 const fragmentShaderSource = document.getElementById('fragmentShader').innerText; const vertexShader = gl.createShader(gl.VERTEX_SHADER); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(vertexShader); gl.compileShader(fragmentShader); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); return program; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19- 屏幕坐标换算成裁剪坐标系(顶点着色器)的坐标
//屏幕坐标换算成裁剪坐标系的坐标(在着色器中的坐标) const { width, height } = canvas; function coordTransform(x, y) { // 将 canvas 的坐标值 转换为 [-1.0, 1.0]的范围。 const posisionX = 2 * x / width - 1; // canvas的 Y 轴坐标方向和 裁剪坐标系的相反。 const positionY = -(2 * y / height - 1); return [posisionX, positionY]; }
1
2
3
4
5
6
7
8
9WebGL
渲染
function render(gl) { //设置清屏颜色为黑色。 gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); if (positions.length > 0) { gl.drawArrays(gl.TRIANGLES, 0, positions.length / 6); } } //创建着色器程序 const program = initShader(gl); constassignValue(gl,program) initClick(); render(gl);
1
2
3
4
5
6
7
8
9
10
11
12
13
参考
WebGL零基础入门教程(郭隆邦) (opens new window)
WebGL 入门与实践 (opens new window)
WebGL官方文档 (opens new window)