本节内容来自于小册 WebGL 入门与实践。javascript
上节带领你们学习了基本三角形图元的绘制过程,以及如何使用缓冲区
向着色器传递多个数据,但上节只演示了往着色器传递坐标
这一种数据,本节经过绘制渐变三角形,讲解一下如何经过缓冲区向着色器传递多种数据。html
本节经过一个鼠标每点击三次便会绘制一个渐变三角形的示例,带你们深刻理解缓冲区的用法,最终效果以下图所示: 前端
经过本节学习,你将会掌握以下内容:java
buffer
中的排布方式。buffer
时,bindBuffer
的重要性。buffer
读取多种顶点数据。buffer
读取多种顶点数据。上节咱们实现的是单色三角形,经过在片元着色器中定义一个 uniform
变量,接收 JavaScript 传递过去的颜色值来实现。那渐变三角形的处理与单色三角形有何不一样呢?git
渐变三角形颜色不单一,在顶点与顶点之间进行颜色的渐变过渡,这就要求咱们的顶点信息除了包含坐标
,还要包含颜色
。这样在顶点着色器以后,GPU 根据每一个顶点的颜色对顶点与顶点之间的颜色进行插值,自动填补顶点之间像素的颜色,因而造成了渐变三角形。github
那既然咱们须要为每一个顶点传递坐标信息和颜色信息,所以须要在顶点着色器中额外增长一个 attribute
变量a_Color
,用来接收顶点的颜色,同时还须要在顶点着色器和片元着色器中定义一个 varying 类型的变量v_Color
,用来传递顶点颜色信息。web
//设置浮点数精度为中等精度。
precision mediump float;
//接收顶点坐标 (x, y)
attribute vec2 a_Position;
//接收浏览器窗口尺寸(width, height)
attribute vec2 a_Screen_Size;
//接收 JavaScript 传递的顶点颜色
attribute vec4 a_Color;
//传往片元着色器的颜色。
varying vec4 v_Color;
void main(){
vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0;
position = position * vec2(1.0,-1.0);
gl_Position = vec4(position, 0, 1);
v_Color = a_Color;
}
复制代码
片元着色器新增一个 varying 变量 v_Color
,用来接收插值后的颜色。编程
//设置浮点数精度为中等。
precision mediump float;
//接收 JavaScript 传过来的颜色值(rgba)。
varying vec4 v_Color;
void main(){
vec4 color = v_Color / vec4(255, 255, 255, 1);
gl_FragColor = color;
}
复制代码
咱们的着色器部分仍是和以前同样简单,只是在顶点着色器中增长了顶点颜色这一变量。canvas
接下来咱们用 JavaScript 向着色器传递数据。数组
用缓冲区向着色器传递数据有两种方式:
上节绘制三角形的时候咱们给顶点着色器传递的只是坐标信息,而且只用了一个 buffer
,本节示例,咱们除了传递顶点的坐标数据,还要传递顶点颜色。 按照正常思路,咱们能够建立两个 buffer
,其中一个 buffer
传递坐标,另一个 buffer
传递颜色。
建立两个 buffer
,将 a_Position
和 positionBuffer
绑定,a_Color
和 colorBuffer
绑定,而后设置各自读取 buffer
的方式。
请谨记:程序中若是有多个
buffer
的时候,在切换buffer
进行操做时,必定要经过调用gl.bindBuffer
将要操做的buffer
绑定到gl.ARRAY_BUFFER
上,这样才能正确地操做buffer
。您能够将bindBuffer
理解为一个状态机,bindBuffer
以后的对buffer
的一些操做,都是基于最近一次绑定的buffer
来进行的。
如下 buffer
的操做须要在绑定 buffer
以后进行:
- gl.bufferData:传递数据。
- gl.vertexAttribPointer:设置属性读取 buffer 的方式。
咱们使用一个 buffer 传递坐标信息,另外一个 buffer 传递颜色信息。
// 建立 坐标信息 buffer
var positionBuffer = gl.createBuffer();
// 将当前 buffer 设置为 postionBuffer,接下来对 buffer 的操做都是针对 positionBuffer 了。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 设置 a_Position 变量读取 positionBuffer 缓冲区的方式。
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(
a_Position, size, type, normalize, stride, offset);
// 建立 颜色信息 buffer
var colorBuffer = gl.createBuffer();
// 将当前 buffer 设置为 postionBuffer,接下来对 buffer 的操做都是针对 positionBuffer 了。
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
// 设置 a_Position 变量读取 positionBuffer 缓冲区的方式。
var size = 4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(
a_Color, size, type, normalize, stride, offset);
复制代码
gl.vertexAttribPointer( a_Color, size, type, normalize, stride, offset)。这个方法比较重要,上节已经向你们详细介绍了,若是还不太明白的,能够再次回顾下上节内容。
咱们发现,上面代码对 buffer
的操做有些冗余,咱们仍是提取出一个方法 createBuffer
放到 webgl-helper.js
,减小重复编码,以后咱们对 buffer
的一系列调用只须要以下两句就能够了:
var positionBuffer = createBuffer(gl, a_Position, { size: 2});
var colorBuffer = createBuffer(gl, a_Color, { size: 4});
复制代码
假如咱们顶点坐标数组中有四个顶点 8 个元素【30, 30, 30, 40, 40, 30, 20, 0】,顶点着色器中的 a_Position
属性在读取顶点坐标信息时,以 2 个元素为一组进行读取:
又假如咱们顶点颜色数组中有两个顶点 8 个元素 【244, 230, 100, 1, 125, 30, 206, 1】,那么顶点着色器中的 a_Color
属性在读取顶点颜色信息时,以 4 个元素(r, g, b, a)为一组进行读取,以下图所示。
以多少元素做为一个顶点信息进行读取的设置,是在调用
gl.vertexAttribPointer
时设置的size
参数值。
言归正传,接下来咱们为 canvas
添加点击事件:
canvas.addEventListener('click', e => {
var x = e.pageX;
var y = e.pageY;
positions.push(x, y);
//随机一种颜色
var color = randomColor();
//将随机颜色的 rgba 值添加到顶点的颜色数组中。
colors.push(color.r, color.g, color.b, color.a);
//顶点的数量是 3 的整数倍时,执行绘制操做。
if (positions.length % 6 == 0) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.DYNAMIC_DRAW);
render(gl);
}
})
复制代码
万事俱备,只欠绘制:
function render(gl) {
//用设置的清空画布颜色清空画布。
gl.clear(gl.COLOR_BUFFER_BIT);
if (positions.length <= 0) {
return;
}
//绘制图元设置为三角形。
var primitiveType = gl.TRIANGLES;
//由于咱们要绘制三个点,因此执行三次顶点绘制操做。
gl.drawArrays(primitiveType, 0, positions.length / 2);
}
复制代码
至此,三角形的渐变效果就实现啦。
常规思路使用多个 buffer
传递多种数据(坐标和颜色),咱们再演示另一种思路:使用 1 个 buffer
同时传递多种数据。
着色器部分的代码和上面的同样,无需改动,改动的主要部分是 JavaScript 程序。
首先,咱们依然是建立 buffer
,只不过此次是建立一个 buffer
。
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
复制代码
建立完 buffer
,接下来设置读取 buffer
的方式,咱们有两个属性 a_Position
、a_Color
,因为咱们只有一个 buffer
,该 buffer
中既存储坐标信息,又存储颜色信息,因此两个属性须要读取同一个 buffer
:
咱们能够看到,一个顶点信息占用 6 个元素,前两个元素表明坐标信息,后四个元素表明颜色信息,因此在下面设置属性读取 buffer
方式时,a_Color
和 a_Position
的设置会有不一样:
a_Position:坐标信息占用 2 个元素,故 size 设置为 2。 坐标信息是从第一个元素开始读取,偏移值为 0 ,因此 offset 设置为 0.
a_Color:因为 color 信息占用 4 个元素,因此 size 设置为 4 。 color 信息是在坐标信息以后,偏移两个元素所占的字节(2 * 4 = 8)。因此,offset 设置为 8。
stride:表明一个顶点信息所占用的字节数,咱们的示例,一个顶点占用 6 个元素,每一个元素占用 4 字节,因此,stride = 4 * 6 = 24 个字节。
gl.vertexAttribPointer(
a_Color, 4, gl.FLOAT, false, 24, 8);
gl.vertexAttribPointer(
a_Position, 2, gl.FLOAT, false, 24, 0);
复制代码
canvas
的点击事件也有所不一样,一个顶点占用 6 个元素,三个顶点组成一个三角形,因此咱们的 positions
的元素数量必须是 18 的整数倍,才能组成一个三角形:
canvas.addEventListener('click', e => {
var x = e.pageX;
var y = e.pageY;
positions.push(x);
positions.push(y);
//随机出一种颜色
var color = randomColor();
//将随机颜色的 rgba 值添加到顶点的颜色数组中。
positions.push(color.r, color.g, color.b, color.a);
//顶点的数量是 18 的整数倍时,执行绘制操做。
if (positions.length % 18 == 0) {
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
render(gl);
}
})
复制代码
实现效果和上面操做多缓冲区的方式同样,可是单缓冲区不只减小了缓冲区的数量,并且减小了传递数据的次数以及复杂度。
至此,咱们对缓冲区的讲解就结束了,本节所讲知识点和上节基本相似,不一样点在于用单个缓冲区传递多类数据时,gl.vertexAttribPointer
各个参数如何设置,理解这点对咱们之后编程十分有用,但愿你们课下多多练习,深入理解它的用法。
到目前为止,咱们掌握了三角形的绘制方法,接下来学习怎样用三角形构建其余图形。
下一节咱们将从简单平面开始:先用三角形构建一个矩形。
这一系列的内容来自于小册 WebGL 3D 入门与实践,若是你们对进阶知识感兴趣,能够到小册中去学习:小册:WebGL 3D 入门与实践。
小册内容除了包含 WebGL 相关的基础练习,还包括 3D 图形概念与相关数学的原理与推导,旨在帮助你们创建图形学的技术轮廓。这部分图形学知识独立于 WebGL,除了能够适用于 WebGL,还适用于 OpenGL 等。
固然,本小册主要目的仍是帮助 Web 前端同窗学习 3D 技术,除了介绍适用 WebGL 实现 3D 效果之外,还对 CSS3 中的 3D 技术相关属性进行了深刻剖析,并演示了与数学库的结合使用,但愿可以让前端同窗不只仅局限于通常的二维平面开发,也可以在 3D 开发上更近一步~