WebGL简易教程(四):颜色
1. 概述
在上一篇教程《WebGL简易教程(三):绘制一个三角形(缓冲区对象)》中,通过使用缓冲区对象(buffer object)来向顶点着色器传送数据。那么,如果这些数据(与顶点相关的数据,如法向量、颜色等)需要继续传送到片元着色器该怎么办呢?
例如这里给三角形的每个顶点赋予不同的颜色,绘制一个彩色的三角形。这个时候就需要用到之前(《WebGL简易教程(二):向着色器传输数据》)介绍过的varying变量了。
2. 示例:绘制三角形
改进上一篇中绘制三角形(HelloTriangle.js)的代码:
// 顶点着色器程序
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + // attribute variable
'attribute vec4 a_Color;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' + // Set the vertex coordinates of the point
' v_Color = a_Color;\n' +
'}\n';
// 片元着色器程序
var FSHADER_SOURCE =
'precision mediump float;\n' +
'varying vec4 v_Color;\n' +
'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 intialize shaders.');
return;
}
// 设置顶点位置
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// 指定清空<canvas>的颜色
gl.clearColor(0.0, 0.0, 0.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, 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 FSIZE = verticesColors.BYTES_PER_ELEMENT; //数组中每个元素的字节数
// 创建缓冲区对象
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, verticesColors, gl.STATIC_DRAW);
//获取着色器中attribute变量a_Position的地址
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;
}
// 将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 5*FSIZE, 0);
// 连接a_Position变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);
//获取着色器中attribute变量a_Color的地址
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
if(a_Color < 0) {
console.log('Failed to get the storage location of a_Color');
return -1;
}
// 将缓冲区对象分配给a_Color变量
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
// 连接a_Color变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Color);
// 解除绑定
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return n;
}
1) 数据的组织
与之前的例子相似,数据仍然通过缓冲区传递到顶点着色器。在顶点着色器中,定义了两个attribute变量,分别代表位置和颜色信息:
// 顶点着色器程序
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + // attribute variable
'attribute vec4 a_Color;\n' +
…
'}\n';
这意味着需要向顶点着色器传递两次数据。这里采取的做法仍然是一次性向缓冲区写入位置和颜色等所有的数据,然后分批次传入顶点着色器:
// 创建缓冲区对象
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, verticesColors, gl.STATIC_DRAW);
//获取着色器中attribute变量a_Position的地址
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;
}
// 将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 5*FSIZE, 0);
// 连接a_Position变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);
//获取着色器中attribute变量a_Color的地址
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
if(a_Color < 0) {
console.log('Failed to get the storage location of a_Color');
return -1;
}
// 将缓冲区对象分配给a_Color变量
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
// 连接a_Color变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Color);
可以看到创建缓冲区对象、将缓冲区对象绑定到目标、向缓冲区对象写入数据这三个步骤都是一致的。但分配attribute变量和连接attribute变量这两个步骤分别进行了两次。其中的关键点就在于gl.vertexAttribPointer()这个函数。之前使用这个函数都是使用的默认值,这里通过设置步进和偏移值,分别访问了缓冲区中不同的数据。

通过gl.vertexAttribPointer()函数定义可以知道,传送到缓冲区的数据是2(size)的位置数据和3(size)的颜色数据,所以步进参数stride都是5(size)。第一次传送位置数据的时候是从初始位置开始的,所以offset是0;而第二次传送颜色数据的时候需要偏移第一个位置数据,所以offfset是2(size)。
2) varying变量
在之前的教程(《WebGL简易教程(二):向着色器传输数据》)中提到,可以传送数据给片元着色器,来给绘制场景赋予颜色。但是这里却通过缓冲区把数据传递给了顶点着色器。因此,在这里给顶点着色器和片元着色器,分别定义了一个相同的varying变量:
// 顶点着色器程序
var VSHADER_SOURCE =
…
'varying vec4 v_Color;\n' +
'void main() {\n' +
…
' v_Color = a_Color;\n' +
'}\n';
// 片元着色器程序
var FSHADER_SOURCE =
…
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
varying变量表达的正是一种可变的变量,它的作用就是从顶点着色器向片元着色器传输数据。在顶点着色器的main函数中,将从缓冲区对象中获取的attribute变量a_Color赋值给预先定义的varying变量v_Color;同时在片元着色器中又定义了一个同类型同名的varying变量v_Color,那么顶点着色器中该变量的值就会自动传入到片元着色器中。最后在片元着色器的main函数中将该值传入到gl_FragColor中,就得到最终的结果了。其示意图如下:

3. 结果
最后的运行结果如下,最后会发现得到了一个颜色平滑过渡的,三个角各是红、绿、蓝颜色的三角形:

4. 理解
1) 图形装配和光栅化
更进一步思考下,这里虽然给每个顶点赋予的颜色值,但是为什么三角形的表面都赋予了颜色,并且是平滑过渡的效果呢?其实这里省略了顶点着色器与片元着色器之间数据传输细节——图形装配和光栅化。
点组成线,线组成面,将孤立的点组成基本图形(图元)的过程就是图形装配。图形装配的输入数据就是顶点着色器中gl_Position得到的值,由gl.drawArrays()中第一个参数值来确定装配成什么样的图元。在这个例子中,顶点着色器告诉WebGL系统,准备了三个点,WebGL通过图像装配,将其装配成三角形。
知道装配的图形还是不够的,理论上的三角形是连续不断的图形,而一般的图形显示设备都是离散的片元(像素)。图像转换成片元,就是光栅化的过程。
图形装配和光栅化的示意图如下:

2) 内插过程
在第二节详解示例中的代码时,提到了顶点着色器和片元着色都定义了相同的varying变量v_Color,数据就会从顶点着色器传入到片元着色器。但其实两者虽然同名,但并不是一回事。在顶点着色器中,这个varying变量是与顶点相关的值,而经过图形装配和光栅化后,片元着色器的varying变量就是与片元相关的值了。并且,这个值是经过内插过程得到的。
在这个例子中,给三个顶点赋予了三个不同的颜色值。WebGL就根据三个顶点的颜色值内插了三角形中每个片元(像素)的颜色值,并传递给片元着色器。所谓内插过程,可以想象成一条渐变色带,知道确定了起止颜色,就能获取中间任意位置的颜色。
5. 参考
本来部分代码和插图来自《WebGL编程指南》。
WebGL简易教程(四):颜色的更多相关文章
- WebGL简易教程——目录
目录 1. 绪论 2. 目录 3. 资源 1. 绪论 最近研究WebGL,看了<WebGL编程指南>这本书,结合自己的专业知识写的一系列教程.之前在看OpenGL/WebGL的时候总是感觉 ...
- WebGL简易教程(十四):阴影
目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 3. 结果 4. 参考 ...
- WebGL简易教程(九):综合实例:地形的绘制
目录 1. 概述 2. 实例 2.1. TerrainViewer.html 2.2. TerrainViewer.js 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(八 ...
- WebGL简易教程(十):光照
目录 1. 概述 2. 原理 2.1. 光源类型 2.2. 反射类型 2.2.1. 环境反射(enviroment/ambient reflection) 2.2.2. 漫反射(diffuse ref ...
- WebGL简易教程(二):向着色器传输数据
目录 1. 概述 2. 示例:绘制一个点(改进版) 1) attribute变量 2) uniform变量 3) varying变量 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL ...
- WebGL简易教程(三):绘制一个三角形(缓冲区对象)
目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...
- WebGL简易教程(六):第一个三维示例(使用模型视图投影变换)
目录 1. 概述 2. 示例:绘制多个三角形 2.1. Triangle_MVPMatrix.html 2.2. Triangle_MVPMatrix.js 2.2.1. 数据加入Z值 2.2.2. ...
- WebGL简易教程(七):绘制一个矩形体
目录 1. 概述 2. 示例 2.1. 顶点索引绘制 2.2. MVP矩阵设置 2.2.1. 模型矩阵 2.2.2. 投影矩阵 2.2.3. 视图矩阵 2.2.4. MVP矩阵 3. 结果 4. 参考 ...
- WebGL简易教程(八):三维场景交互
目录 1. 概述 2. 实例 2.1. 重绘刷新 2.2. 鼠标事件调整参数 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(七):绘制一个矩形体>中,通过一个绘制矩 ...
随机推荐
- 统一流控服务开源:基于.Net Core的流控服务
先前有一篇博文,梳理了流控服务的场景.业界做法和常用算法 统一流控服务开源-1:场景&业界做法&算法篇 最近完成了流控服务的开发,并在生产系统进行了大半年的验证,稳定可靠.今天整理一下 ...
- 0x03 前缀和与差分
前缀和 [例题]BZOJ1218 激光炸弹 计算二位前缀和,再利用容斥原理计算出答案即可. #include <iostream> #include <cstdio> #inc ...
- WebService—— IDEA创建WebServices
一.File–>New–>Project 弹出这个对话框后,照下图的勾选然后点击Next,然后填写项目名和项目路径后,点击finish. 二.生成目录如下 需要注意的有HelloWorld ...
- (六)c#Winform自定义控件-单选框
前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...
- 「雕爷学编程」Arduino动手做(10)——敲击传感器模块
37款传感器和模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器与模块,依照实践出真知(动手试试)的理念,以学习和交流为目的,这里准备 ...
- Kafka 原理和实战
本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/bV8AhqAjQp4a_iXRfobkCQ作者简介:郑志彬,毕业于华南理工大学计算机科学与技术(双语 ...
- Alfred上可提高工作效率的Workflow推荐
温馨提示:本文中Alfred是Mac平台的工具,不适用于其他平台. Alfred是Mac平台上被很多人吹爆的一款效率提升软件,我刚毕业工作的时候就看到公司内网有人推荐,但没有尝试. 后来我跳槽后自己买 ...
- python使用zabbix的API接口
一.实验环境 python3.6.6 zabbix 3.0.9 二.实验目的 了解Zabbix的API接口格式 通过python实现登陆zabbix服务,获得登陆token 通过python检索zab ...
- Eclipse导入spring-boot-plus(三)
Eclipse导入spring-boot-plus 安装lombok插件 !!!请先确保Eclipse已安装lombok插件!!!
- SQL Server避免漏加where条件导致的批量误操作
很多开发人员,包括数据库管理员都有马失前蹄的时候,update/delete时忘记了添加where条件,导致不必要的麻烦.一旦失误,必须要尝试各种恢复手段来恢复数据,尤其是正在使用的生产数据库,造成的 ...