3D绘图小帮手WebGL入门与进阶(中)——着色器的基本编程
程序创建完之后,我们需要需要对着色器进行动态控制才能达到我们所需要的功能。(如不知道怎么创建WebGL,可参考上篇文章)。
首先让我来介绍2个变量,我们需要借助这2个变量搭建的桥梁才能使JavaScript与GLSL ES之间进行沟通。
- attribute: 用于顶点点着色器(Vertex Shader)传值时使用。
- uniform:可用于顶点着色器(Vertex Shader)与片元着色器(Fragment Shader)使用。
将顶点动态化
先在顶点着色器代码中,将对应的vec4的固定值变成变量。
var VSHADER_SOURCE =
'attribute vec4 a_Position;n' +
'void main() {n' +
' gl_Position = a_Position;n' +
' gl_PointSize = 10.0;n' +
'}n';
位置参数使用了attribute变量来承载。这样WebGL对象就可以获取到对应的存储位置,就可以去动态改变GLSL变量了。
使用WebGL来获取对应参数的存储地址。
//返回对应的地址信息
var aPosition = gl.getAttribLocation(gl.program, 'a_Position');
//判断地址是否获取成功
if(aPosition < 0) {
console.log('没有获取到对应position');
}
然后给变量赋值。
gl.vertexAttrib3f(aPosition, 1.0, 1.0, 0.0);
//或者使用Float32Array来传参
var p = new Float32Array([1.0, 1.0, 1.0]);
gl.vertexAttrib3fv(aPosition, p);
注意:vertexAttrib3fv这个函数是典型的GLSL语法命名规范,vertexAttrib函数功能:
3:对应需要传3个参数,或者是几维向量。
f:表示参数是float类型。
v:表示传如的为一个vector变量。
也就是说对应设置顶点着色器的函数有一下几种功能:
- void gl.vertexAttrib1f(index, v0);
- void gl.vertexAttrib2f(index, v0, v1);
- void gl.vertexAttrib3f(index, v0, v1, v2);
- void gl.vertexAttrib4f(index, v0, v1, v2, v3);
- void gl.vertexAttrib1fv(index, value);
- void gl.vertexAttrib2fv(index, value);
- void gl.vertexAttrib3fv(index, value);
- void gl.vertexAttrib4fv(index, value);
同样操作可以如下修改PointSize:
//着色器中添加变量
var VSHADER_SOURCE =
'attribute vec4 a_Position;n' +
'attribute float a_PointSize;n' +
'void main() {n' +
' gl_Position = a_Position;n' +
' gl_PointSize = a_PointSize;n' +
'}n';
var aPointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
gl.vertexAttrib1f(aPointSize, 10.0);
片元着色器编程
对片元着色器变成需要使用uniform变量来承载。
var FSHADER_SOURCE =
'precision mediump float;n'+
'uniform vec4 vColor;n'+
'void main() {n' +
' gl_FragColor = vColor;n' + // Set the point color
'}n';
获取片元着色器变量地址。
var vColor = gl.getUniformLocation(gl.program, 'vColor');
给变量赋值。
gl.uniform4f(vColor, 1.0, 0.0, 0.0, 1.0);
//或使用Float32Array来传参
var color = new Float32Array([1.0, 0.0, 0.0, 1.0]);
gl.uniform4fv(vColor,color)
注意:uniform3fv这个函数是典型的GLSL语法命名规范,uniform3fv函数功能:
3:对应需要传3个参数,或者是几维向量。
f:表示参数是float类型。
u:表示参数是Uint32Array类型。
i:表示参数是integer类型。
ui:表示参数是unsigned integer类型。
v:表示传如的为一个vector变量。
uniform对应函数同attribute的函数构成相似,这里就不详细列举,具体请参考 [1]。
着色器中的代码precision mediump float;表示的意思是着色器中配置的float对象会占用中等尺寸内存。 具体包含的尺寸:
- highp for vertex positions,
- mediump for texture coordinates,
- lowp for colors.
如果不设置此参数会报错:
我们可以绘制自定义的点了,接下来我们就可以尝试绘制大批量点来达到波浪的基础效果,但是之前的操作都是针对一个点的,如何可以同时绘制多个订点呢,如果你的回答是循环数据,BINGGO,没错这样你的确是可以达到这个目的,但是不是我们接下来要讲的,因为在3D绘制的时候是会经常出现大批量点、线、面的绘制的,所以WebGL提供了一种承载机制来达到传递多点的能力,说了这么多,也让我们来看看它到底是什么吧。
缓存区对象
之前的方式可以通过循环来绘制多个点,一次需要绘制多个点,需要同时传递进去多个点的数据。刚好,在WebGL中提供了一种机制:缓存区对象(buffer data),缓存区对象可以同时向着色器传递多个顶点坐标。缓存区是WebGL中的一块内存区域,我们可以向里面存放大量顶点坐标数据,可随时供着色器使用。
使用缓存区步骤
- 创建缓存区对象(gl.createBuffer())
- 绑定缓存区对象(gl.bindBuffer())
- 将数据写入缓存区对象(gl.bufferData())
- 将缓存区对象分配给一个attribute变量(gl.vertexAttribPointer())
- 开启attribute变量(gl.enableVertexAttribArray())
我们需要进行缓冲区的操作: 首先,需要创建一个缓冲区来承载大量顶点的坐标。(代码继续上文)
// 创建缓存区
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer) {
log('创建缓存区失败。');
return -1;
}
// 将创建的缓存区对象绑定到target表示的目标上
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 开辟存储空间,向绑定在target上的缓存区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// 获取着色器中的变量值
var a_position = gl.getAttribLocation(gl.program, 'a_p');
// 将缓存区对象绑定到着色器变量中
gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false, 0, 0);
// 启用缓存区
gl.enableVertexAttribArray(a_position);
// 绘制缓存区中画的多个顶点
gl.drawArrays(gl.POINTS, 0 , array);
看完了绘制过程,让我们来拆解一下具体内容:
首先,我们要在茫茫内存中申请一个区域来放置缓存区对象的内容,但是我们无法直接放置缓存对象进入内存中,否则会无法识别对应的数据类型,从而无法达到存取自如的境界,那我们就需要将数据的类型告知内存,bingBuffer就是为解决此问题诞生的,函数会在内存中申请一部分区域,并且通过target来制定数据类型,也就是说,缓存区是需要放置在target表示的类型部分去存储。
gl.bindBuffer(target, buffer):
target: 指定存储缓存区的目标类型,
- gl.ARRAY_BUFFER : 指缓存区中包含了顶点的数据。
- gl.ELEMENTARRAYBUFFER : 指缓存区中包含了顶点数据的索引值。
buffer: 自己创建的缓存区对象,
接下来,我们需要做的是填充刚刚申请的缓存区,我们需要使用一个符合GLSL语法的数据格式,Javascript中可用Float32Array类型来创建支持GLSL的数据。使用bufferData函数将数据放入缓存区内。
gl.bufferData(target, size, usage):
target: 同上,
size: 为多个顶点坐标的集合数组,
usage: 表示程序将如何使用缓存区中的数据,
- gl.STATIC_DRAW : 只会向缓存区对象中写入一次数据,但需要绘制很多次。
- gl.STREAM_DRAW : 只会向缓存区对象中写入一次数据,然后绘制若干次。
- gl.DYNAMIC_DRAW : 会想缓存区对象中多次写入数据,并绘制很多次。
缓存区中已经存储了多个顶点坐标,接下来我们需要将此数据运用到对应的着色器上,才能真正的绘制出来可视化图像,如何传递呢?首先我们需要在着色器中建立一个attribute类型的变量以方便我们操作,着色器中的对象,着色器中存在对象之后,我们可以使用Javascript中getAttribLocation函数获取着色器中的attribute类型变量,并且通过vertexAttribPointer将其赋值改变,从而达到改变图像呈现。
gl.getAttribLocation(program,name):
param: webgl之前创建的进程,
name: 变量名称,
gl.vertexAttribPointer(name, size, type, normalized, stride, offset):
name: 指定要赋值的attribute变量位置,
size: 指定每个顶点数据的分量个数(1或4),
type: 指定传入的数据格式,
- gl.BYTE: 字节型, 取值范围[-128, 127]。
- gl.SHORT: 短整型,取值范围[-32768, 32767]。
- gl.UNSIGNED_BYTE: 无符号字节型,取值范围[0, 255]。
- gl.UNSIGNED_SHORT: 无符号短整型, 取值范围[0, 65535]。
- gl.FLOAT: 浮点型。
normalized: 表明是否将非浮点数的数据归入到[0, 1]或[-1, 1]区间,
stride: 指定相邻2个顶点间的字节数,默认为0,
offset: 指定缓存区对象中的偏移量,设置为0即可,
如为2,则
new Float32Array([
1.0, 1.0,
1.0,1.0
])
代表2个顶点
如为4,则
new Float32Array([
1.0, 1.0, 1.0,1.0
])
代表1个顶点
现在缓存区已经存在多个顶点数据,接下来我们来启用携带缓存区数据的attribute变量,使用enableVertexAttribArray来启用对应变量。
gl.enableVertexAttribArray(name):
name: 待启动的变量指针,也就是名称,
所有的缓存区操作步骤我们都已经完成,那么接下来我们可以绘制出缓存区中的多个顶点。
gl.drawArrays(mode, first, count)
mode: 需要绘制的图像形状,
- gl.POINTS: 绘制一个点。
- gl.LINE_STRIP: 绘制一条直线到下一个顶点。
- gl.LINE_LOOP: 绘制一条首尾相连的线。
- gl.LINES: 绘制一条线。
- gl.TRIANGLES: 绘制一个三角形。
first: 绘制的开始点,
count: 需要绘制的图形个数,
让我们先来创建多个点,上一课已经讲过,WebGL的坐标与真实坐标会有一些出入,所以我们需要转换一下,并且数据我们需要使用Float32Array对象来创建,我们创建一个三维的点数据,总数为200个。
function createPoints() {
//波动最大幅度 10px;
var arr = [];
var n = 20;
var m = 10;
for(var i = 0; i < n; i++) {
for(var j = 0; j < m; j++) {
var x = webglX(-(width/2) + i*20);
var y = webglY((height/2) - j*20);
var z = -1;
var item = [x, y, z];
arr = arr.concat(item);
}
}
return new Float32Array(arr)
}
接下来我们使用数据缓存区来讲此200个点一次渲染出来。
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, createPoints(), gl.STATIC_DRAW);
我们先获取到对应的顶点着色器中的变量
var a_position = gl.getAttribLocation(gl.program, 'a_Position');
//我们需要设置数据中的点的维度。否则会解析出错。
gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_position);
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
//我们需要确定绘制的具体点的数量
gl.drawArrays(gl.POINTS, 0 , 200);
看看屏幕吧,是不是出来了好多点?没错你已经成功的掌握了着色器基本编程以及数据缓存区的知识。
我们掌握了这些知识之后,下一篇让我们先来使用这些内容创建一个点的波浪吧。
扩展阅读
[1] https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/uniform
下面的内容同样精彩
点击图片即可阅读
移动测试避坑指南(第一篇)
京东博士后工作站正在联合众多大咖搞事情
京东技术 ∣关注技术的公众号
- 让WordPress RSS/Feed订阅数据延迟发布,附RSS技巧集锦
- Linux系统防CC攻击自动拉黑IP增强版Shell脚本
- 利用artDialog给网站添加一个能显示搜索来路和关键词的欢迎框
- 解决启用wp super cache缓存后,页面追加多个斜杠仍然可以访问的隐患
- WordPress集成底部滚动推荐条,让好文章不再被埋没
- go语言base64加密解密的方法
- WordPress酷炫CSS3读者墙,排名按年度、本月、本周划分的小方法
- WordPress给文章添加百度是否已收录查询和显示功能(自定义栏目优化版)
- PHP制作百度站内搜索绿色通道的网页列表数据文件
- 分享几个可用的二维码API,以及给博客添加文章二维码图片的方法
- AI即开即用,这是悄然推出的“腾讯最新AI技术”小程序
- Android Linker 与 SO 加壳技术
- Go语言操作mysql数据库简单例子
- go语言的sql包原理与用法分析
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一文搞懂 Flink Kafka Consumer 类两阶段提交
- 在 Nowin 下运行 ASP.NET 5 Beta 2
- Bytom侧链Vapor源码浅析-节点出块过程
- Kubernetes Pod OOM 排查日记
- Netty之旅:你想要的NIO知识点,这里都有!
- (数据科学学习手札92)利用query()与eval()优化pandas代码
- Spring Boot 集成 Elasticsearch 实战
- Python之错误和异常、模块(基础系列第四篇)
- Spark存储Parquet数据到Hive,对map、array、struct字段类型的处理
- 为什么这条异常没有上报? HTTP 429
- 三问Spring事务:解决什么问题?如何解决?存在什么问题?
- 从 OAuth2 服务器获取授权授权
- NHibernate 代码映射实体类
- 使用 Castle Windsor 实现 Web API 依赖注入
- SparkSQL与Hive metastore Parquet转换