webgl 系列 —— 渐变三角形
其他章节请看:
渐变三角形
本文通过一个渐变三角形
的示例逐步分析:varying变量、合并缓冲区、图形装配
、光栅化
、varying 内插
绘制三个点v1
需求
:绘制三个相同颜色的点,效果如下:
通过三角形的学习,这个需求非常容易实现。代码如下:
const VSHADER_SOURCE = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
}
`
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
function main() {
const canvas = document.getElementById('webgl');
const gl = canvas.getContext("webgl");
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;
}
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const vertices = {
data: new Float32Array([
0.0, 0.5,
-0.5, -0.5,
0.5, -0.5
]),
vertexNumber: 3,
count: 2,
}
initVertexBuffers(gl, vertices)
gl.drawArrays(gl.POINTS, 0, vertices.vertexNumber);
}
function initVertexBuffers(gl, {data, count}) {
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('创建缓冲区对象失败');
return -1;
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
const 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, count, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
}
绘制三个点v2
需求
需求
:绘制三个不同颜色的点(基于版本1
),效果如下:
Tip: 绘制三个点不同颜色的点其实也就完成了渐变三角形的绘制。这里调用了两次 drawArrays()
,也就是绘制了两个图元,一系列点、三角形。
核心代码
相对版本1,变化的代码如下:
const VSHADER_SOURCE = `
attribute vec4 a_Position;
+attribute vec4 a_Color;
+varying vec4 v_Color;
void main() {
gl_Position = a_Position;
- gl_PointSize = 10.0;
+ gl_PointSize = 10.0;
+ v_Color = a_Color;
}
`
const FSHADER_SOURCE = `
+precision mediump float;
+varying vec4 v_Color;
void main() {
- gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_FragColor = v_Color;
}
`
function main() {
const vertices = {
data: new Float32Array([
- 0.0, 0.5,
- -0.5, -0.5,
- 0.5, -0.5
+ 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,
]),
vertexNumber: 3,
count: 2,
initVertexBuffers(gl, vertices)
gl.drawArrays(gl.POINTS, 0, vertices.vertexNumber);
+ gl.drawArrays(gl.TRIANGLE_FAN, 0, vertices.vertexNumber);
}
function initVertexBuffers(gl, {data, count}) {
const vertexBuffer = gl.createBuffer();
- gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, 0, 0);
+ const FSIZE = data.BYTES_PER_ELEMENT;
+ gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, FSIZE * 5, 0);
gl.enableVertexAttribArray(a_Position);
+ const 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;
+ }
+ gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
+ gl.enableVertexAttribArray(a_Color);
}
完整代码
const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
v_Color = a_Color;
}
`
const FSHADER_SOURCE = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
const canvas = document.getElementById('webgl');
const gl = canvas.getContext("webgl");
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;
}
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const vertices = {
data: 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,
]),
vertexNumber: 3,
count: 2,
}
initVertexBuffers(gl, vertices)
gl.drawArrays(gl.POINTS, 0, vertices.vertexNumber);
gl.drawArrays(gl.TRIANGLE_FAN, 0, vertices.vertexNumber);
}
function initVertexBuffers(gl, { data, count }) {
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('创建缓冲区对象失败');
return -1;
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
const 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;
}
const FSIZE = data.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, FSIZE * 5, 0);
gl.enableVertexAttribArray(a_Position);
const 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;
}
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
gl.enableVertexAttribArray(a_Color);
}
改变颜色(varying)
前面我们说过着色器语言(GLSL ES)有三种类型的“变量”,我们已经使用了两种:
attribute
- 传输的是那些与顶点相关的数据。只有顶点着色器才能使用。例如顶点的位置、大小、颜色uniform
- 传输的是那些对于所有顶点都相同的数据。例如变化矩阵
现在我们可以将颜色从 js 传入 attribute。但真正影响颜色绘制的是片元着色器的 gl_FragColor,目前我们是静态设置。就像这样:
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
如何将顶点着色器中的数据传入片元着色器?
我们曾经通过 uniform
给片元着色器传递颜色。就像这样:
const FSHADER_SOURCE = `
uniform vec4 u_FragColor;
void main() {
gl_FragColor = u_FragColor;
}
`
但是 uniform 是相同的
的变量,没法为每个顶点准备一个值。为了让每个点的颜色不同,需要使用varying
(不同的)变量。
使用 varying 给片元着色器传递值(颜色)。就像这样:
const VSHADER_SOURCE = `
// 定义一个 attribute 变量,用于接收 js 传入的颜色
attribute vec4 a_Color;
// 定义 varying 变量。用于传递给片元着色器
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
gl_PointSize = 10.0;
// 给 varying 变量赋值
v_Color = a_Color;
}
`
const FSHADER_SOURCE = `
precision mediump float;
// 声明一个与顶点着色器中相同的 varying 变量名,用于接收颜色
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
代码解析:
- 通过在顶点着色器中声明一个 attribute 变量用于接收 js 传入的颜色
- 在顶点着色器中声明一个 varying 变量,用于接收 attribute 中的颜色,并将颜色传给片元着色器
- 片元着色器声明一个与顶点着色器中相同的 varying 变量名,接收颜色
Tip:顶点着色器中的 varying 变量 v_Color 与 片元着色器中的 varying 变量 v_Color 不同。中间涉及 varying 内插
,下文会介绍。
合并缓冲区
渐变三角形将顶点和每个顶点的颜色写在一起,数据结构如下:
data: new Float32Array([
- 0.0, 0.5,
- -0.5, -0.5,
- 0.5, -0.5
+ 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,
]),
在渐变三角形示例中我们只用了一个缓冲区对象(const vertexBuffer = gl.createBuffer();
),当然也可以使用两个缓冲区对象来实现相同的效果。核心代码如下:
// 声明第二个缓冲区对象:颜色缓冲区
const vertexColorBuffer = gl.createBuffer();
if (!vertexColorBuffer) {
console.log('创建缓冲区对象失败');
return -1;
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
// 颜色数据抽离出来
const colors = new Float32Array([
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
]);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const 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;
}
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color);
将多个缓冲区合并,代码更简洁
。思路
:
- 首先将顶点位置和颜色写在一个数组中
- 然后通过 vertexAttribPointer() 来读取不同的信息(顶点位置、颜色)。
请看代码:
const vertices = {
// 顶点位置和颜色写在一起
data: 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,
]),
vertexNumber: 3,
count: 2,
}
// 每个元素所占用的字节数
const FSIZE = data.BYTES_PER_ELEMENT;
// FSIZE * 5 - 指定每个点的字节数
// 0 - 偏移量
gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, FSIZE * 5, 0);
/*
提取颜色:
3 - 分量数
FSIZE * 2 - 偏移量,从第三个开始
*/
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
例如提取颜色:每个点总共字节数是 FSIZE * 5
,颜色占3个分量,从第三(FSIZE * 2
)个数开始读取 3 个分量。
为什么是渐变
我们定义了三个不同颜色的点,绘制出来的三角形为什么却是渐变色彩?
要回答这个问题,需要说一下整个绘制过程。
请看下图:
- 首先确定顶点坐标,我们传了三个顶点
- 接着将孤立的顶点坐标
装配
成几何图形。几何图形的类别由 drawArrays() 第一个参数决定 - 将装配好的几何图形转为
片元
(简单认为是像素,这里为了示意,只显示了10个片元),这个过程称为光栅化
。
图形装配
和光栅化
过程如下图所示:
一旦光栅化结束,程序就开始逐片元调用片元着色器。这里调用了10次,每调用一次就处理一个片元。对于每个片元,片元着色器计算出该片元的颜色,并写入颜色缓冲区,当最后一个片元被处理完成,浏览器就会显示最终结果。就像这样:
渐变其实是由 varying 变量的内插
导致的。比如绘制一条线,一端是红色,一端是蓝色,我们在顶点着色器向 varying 变量 v_Color 赋上两个颜色,webgl 会计算出线段上所有点(片元)的颜色,并赋值给片元着色器中的 varying 变量 v_Color。就像这样:
顶点着色器中的 v_Color 和片元着色器中的 v_Color 不是一回事。示意如下:
其他章节请看:
webgl 系列 —— 渐变三角形的更多相关文章
- WebGL学习(1) - 三角形
原文地址:WebGL学习(1) - 三角形 还记得第一次看到canvas的粒子特效的时候,真的把我给惊艳到了,原来在浏览器也能做出这么棒的效果.结合<HTML5 Canvas核心技术>和网 ...
- 3.QOpenGLWidget-通过着色器来渲染渐变三角形
在上章2.通过QOpenGLWidget绘制三角形,我们学习绘制三角形还是单色的,本章将为三角形每个顶点着色. 1.着色器描述 着色器的开头总是要声明版本,接着是输入和输出变量.uniform和m ...
- 【LESS系列】三角形Mixins
又是一篇自 W3CPLUS 中转化而来的文章. 和 W3CPLUS 上的做法,在设计上最大的不同就在于,这里我用的是多个 Mixins 函数来实现. 先总结这种做法的特点: 需要额外的标签来实现,因此 ...
- [js] webgl 初探 - 绘制三角形
摘要: 1. webgl 概念挺多的, 顶点着色器.片段着色器, 坐标 2. 绘制前期准备工作好多 目前看的比较好的教材: https://developer.mozilla.org/zh-CN/do ...
- WebGL学习(2) - 3D场景
原文地址:WebGL学习(2) - 3D场景 经过前面WebGL学习(1) - 三角形的学习,我们已经掌握了webGL的基础知识,也已经能够画出最基本的图形,比如点,线,三角形,矩形等.有了2D绘图的 ...
- WEBGL学习【八】模型视图投影矩阵
<!--探讨WEBGL中不同图形的绘制方法:[待测试2017.11.6]--> <!DOCTYPE HTML> <html lang="en"> ...
- WEBGL学习【七】画布绘图
主要是对WEBGL的绘图部分进行了进一步加强的认识和理解 <!DOCTYPE HTML> <html lang="en"> <head> < ...
- WebGL学习笔记(四):绘图
图元 WebGL可以绘制非常复杂的3D模型,这些模型都是由下面3种基本几何图元构成的,下面我们来详细的看看. 三角形 WebGL中任何复杂的模型,都是由三角形组合而成的,可以说三角形是任意形状的最小构 ...
- 4.QOpenGLWidget-对三角形进行纹理贴图、纹理叠加
在上章3.QOpenGLWidget-通过着色器来渲染渐变三角形,我们为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像.但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多 ...
- NeHe OpenGL教程 第四课:旋转
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
随机推荐
- Filament初探,全场景性能测试
一直很想研究下Filament在移动端全场景(大约20万Triangle,约120个渲染实体)的实时帧率.终于在今天有时间腾出来研究下Filament在Android上的全场景PBR渲染性能. 这里以 ...
- 第九章 MySQL 高可用(MHA)
MySQL 高可用(MHA) 一 MHA高可用部署 需要使用的前提: 当普通主从复制不能满足我们的需求, 主节点宕机 影响业务的不间断运行.这里就需要用到MHA 高可用 1. MHA高可用的介绍 ...
- Java-JSP页面实现简单登录退出(菜鸟一枚、仅供参考)
1.JSP页面代码 <%@ page language="java" contentType="text/html; charset=UTF-8" pag ...
- cam对象类型
//此函数的功能是打印当前坐标系试图的所有坐标系名称 static void geom_list_name(tag_t group_tag) { //ask_member_list int count ...
- Mysql学习:2、mysql基本命令
1.下载安装好的mysql分为两种数据库: a.系统自带: b.用户自己创建: 2.基本命令: create database 数据库名称; // 创建数据库:记住后面加 分号 drop databa ...
- Hive. 函数 instr 的用法
INSTR(C1,C2,I,J) 在一个字符串中搜索指定的字符,返回发现指定的字符的位置; C1 被搜索的字符串 C2 希望搜索的字符串 I 搜索的开始位置,默认为1 J 出现的位置,默认为1 sel ...
- if __name__ == '__main__':中的语句无法执行
在pycarm中我们用了pytest或unittest框架写测试用例,我们如果我们在最后加上if name == 'main':,如以下代码所示.最后我们右键点击运行的时候是不会执行**if name ...
- source Insight 的常用设置
1. 去除^M 的显示 "Options->Preferences->Files", 设置"Default File Format" to &quo ...
- Vscode,php运行
1.下载好vscode,点击左侧扩展,然后搜索php,安装插件 2.打开小皮面板创建网站 点击确认 创建成功 3.浏览器输入http://myblog验证 4.在vscode打开新建的myblog文件 ...
- gpio 理解
NVIC :NVIC_Init(&NVIC_Initsture); 1.NVIC只是设置某一种中断的优先级,而不是打开某种中断. 2.ppp_ITConfig():才是开/关具体某种中断使能位 ...