前言
本篇文章继续跟着李伟老师学习 webgl,本篇主要内容是 webgl 代码的封装
明确封装层级
首先我们要明确我们该如何封装代码,大概思路如下
- 场景 Scene:包含所有的三维对象,并负责绘图
- 三维对象 Obj3D:包含几何体 Geo 和材质 Mat,对两者进行统一管理
- 几何体 Geo:对应 attribute 顶点数据
- 材质 Mat:包含程序对象,对应 uniform 变量
几何体 Geo
默认属性
1 | const defAttr = () => ({ |
- data 顶点数据
- count 顶点总数
- index 顶点索引数据
- 默认为 null,用 drawArrays 的方式绘图
- 若不为 null,用 drawElements 的方式绘图
- drawType 绘图方式
- drawArrays 使用顶点集合绘图,默认
- drawElements,使用顶点索引绘图
data 的数据结构如下:
1 | { |
- array 存储所有的 attribute 数据
- size 构成一个顶点的所有分量的数目
- buffer 用 createBuffer() 方法建立的缓冲对象
- location 用 getAttribLocation() 方法获取的 attribute 变量
- needUpdate 在连续渲染时,是否更新缓冲对象
index 数据结构
1 | { |
初始化方法
1 | init(gl,program) { |
init(gl,program) 方法会在场景 Scene 初始化时被调用
- gl:webgl 上下文对象,会通过场景 Scene 的初始化方法传入
- program:程序对象,会通过 Obj3D 的初始化方法传入
- initData() 初始化 attribute 变量
- initIndex() 初始化顶点索引
初始化顶点数量 count 和绘图方式 drawType
若顶点索引不为 null,就建立缓冲区对象,向其中写入顶点索引数据
initData() 初始化 attribute 变量
1 | initData(gl,program) { |
initIndex() 初始化顶点索引
1 | initIndex(gl) { |
drawElements 和 drawArrays 区别
这里拓展回顾一下 drawElements 和 drawArrays 的区别
drawElements
1 | gl.drawElements(mode, count, type, offset); |
参数 1-mode
枚举类型 指定要渲染的图元类型。可以是以下类型:
POINTS
上面六个点的绘制顺序是:v0, v1, v2, v3, v4, v5
LINES
上面三条有向线段的绘制顺序是:
v0>v1
v2>v3
v4>v5
LINES_STRIP
上面线条的绘制顺序是:v0>v1>v2>v3>v4>v5
LINE_LOOP
上面线条的绘制顺序是:v0>v1>v2>v3>v4>v5>v0
对于面的绘制,我们首先要知道一个原理:
面有正反两面。
面向我们的面,如果是正面,那它必然是逆时针绘制的;
面向我们的面,如果是反面,那它必然是顺时针绘制的;
TRIANGLES
上面两个面的绘制顺序是:
v0>v1>v2
v3>v4>v5
TRIANGLE_STRIP
上面四个面的绘制顺序是:
v0>v1>v2
以上一个三角形的第二条边+下一个点为基础,以和第二条边相反的方向绘制三角形
v2>v1>v3
以上一个三角形的第三条边+下一个点为基础,以和第三条边相反的方向绘制三角形
v2>v3>v4
以上一个三角形的第二条边+下一个点为基础,以和第二条边相反的方向绘制三角形
v4>v3>v5
规律:
第一个三角形:v0>v1>v2
第偶数个三角形:以上一个三角形的第二条边+下一个点为基础,以和第二条边相反的方向绘制三角形
第奇数个三角形:以上一个三角形的第三条边+下一个点为基础,以和第三条边相反的方向绘制三角形
TRIANGLE_FAN
上面四个面的绘制顺序是:
v0>v1>v2
以上一个三角形的第三条边+下一个点为基础,按照和第三条边相反的顺序,绘制三角形
v0>v2>v3
以上一个三角形的第三条边+下一个点为基础,按照和第三条边相反的顺序,绘制三角形
v0>v3>v4
以上一个三角形的第三条边+下一个点为基础,按照和第三条边相反的顺序,绘制三角形
v0>v4>v5
规律:
第一个三角形:v0>v1>v2
以上一个三角形的第三条边+下一个点为基础,按照和第三条边相反的顺序,绘制三角形
参数 2-count
整数型 指定要渲染的元素数量。
参数 3-type
枚举类型 指定元素数组缓冲区中的值的类型。可能的值是:
- gl.UNSIGNED_BYTE
- gl.UNSIGNED_SHORT
当使用OES_element_index_uint
扩展时: - gl.UNSIGNED_INT
参数 4-offset
字节单位 指定元素数组缓冲区中的偏移量。必须是给定类型大小的有效倍数
drawArrays
1 | gl.drawArrays(mode, first, count); |
参数 1-mode
同上
参数 2-first
指定从哪个点开始绘制
参数 3-count
指定绘制需要使用到多少个点。
更新方法,用于连续渲染
1 | update(gl) { |
updateData(gl) 更新 attribute 变量
1 | updateData(gl) { |
updateIndex(gl) 更新顶点索引
1 | updateIndex(gl) { |
设置 attribute 数据和顶点索引数据的方法
setData(key,val) 设置 attribute 数据
1 | setData(key,val){ |
setIndex(val)设置顶点索引数据
1 | setIndex(val) { |
材质 Mat
知道了上面的点,那么这个材质也就差不多懂了
默认值
1 | const defAttr = () => ({ |
- program 程序对象
- data uniform 数据
- mode 图形的绘制方式,默认独立三角形。
注:mode 也可以是数组,表示多种绘图方式,如[‘TRIANGLE_STRIP’, ‘POINTS’] - maps 集合
data 结构
1 | { |
- value uniform 数据值
- type uniform 数据的写入方式
- location 用 getUniformLocation() 方法获取的 uniform 变量
- needUpdate 在连续渲染时,是否更新 uniform 变量
maps 数据结构:
1 | u_Sampler:{ |
- image 图形源
- format 数据类型,默认 gl.RGB
- wrapS 对应纹理对象的 TEXTURE_WRAP_S 属性
- wrapT 对应纹理对象的 TEXTURE_WRAP_T 属性
- magFilter 对应纹理对象的 TEXTURE_MAG_FILTER 属性
- minFilter 对应纹理对象的 TEXTURE_MIN_FILTER 属性
初始化方法
获取 uniform 变量,绑定到其所在的对象上。
1 | init(gl) { |
更新方法,用于连续渲染
1 | update(gl) { |
updateData(gl) 更新 uniform 变量
1 | updateData(gl) { |
updateMaps(gl) 更新纹理
1 | updateMaps(gl) { |
设置 uniform 数据和纹理的方法
setData(key,val) 设置 uniform 数据
1 | setData(key,val){ |
setMap(val)设置纹理
1 | setMap(key,val) { |
三维对象Obj3D
obj3D对象比较简单,主要负责对Geo对象和Mat对象的统一初始化和更新。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const defAttr = () => ({
geo: null,
mat: null,
})
export default class Obj3D {
constructor(attr) {
Object.assign(this, defAttr(), attr)
}
init(gl) {
const {mat,geo}=this
mat.init(gl)
geo.init(gl,mat.program)
}
update(gl) {
const { mat, geo } = this
mat.update(gl)
geo.update(gl)
}
}
场景
Scene对象的主要功能就是收集所有的三维对象,然后画出来。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59/*默认属性*/
const defAttr = () => ({
gl:null,
children: [],
});
export default class Scene{
constructor(attr={}){
Object.assign(this,defAttr(),attr);
}
init() {
const { children, gl} = this
children.forEach(obj => {
obj.init(gl)
})
}
add(...objs){
const {children,gl}=this
objs.forEach(obj=>{
children.push(obj)
obj.parent = this
obj.init(gl)
})
}
remove(obj){
const {children}=this
const i = children.indexOf(obj)
if (i!==-1) {
children.splice(i, 1)
}
}
setUniform(key, val) {
this.children.forEach(({ mat }) => {
mat.setData(key,val)
})
}
draw() {
const { gl,children } = this
gl.clear(gl.COLOR_BUFFER_BIT)
children.forEach(obj => {
const { geo: {drawType,count }, mat:{mode,program}}=obj
gl.useProgram(program)
obj.update(gl)
if (typeof mode==='string') {
this[drawType](gl,count, mode)
} else {
mode.forEach(m => {
this[drawType](gl,count, m)
})
}
})
}
drawArrays(gl, count, mode) {
gl.drawArrays(gl[mode], 0, count)
}
drawElements(gl,count, mode) {
gl.drawElements(gl[mode], count, gl.UNSIGNED_BYTE, 0)
}
}
- Scene 对象的属性只有两个:
- gl:webgl上下文对象
- children:三维对象集合
- init() 初始化方法
- add() 添加三维对象
- remove() 删除三维对象
- setUniform() 统一设置所有对象共有的属性,比如视图投影矩阵
- draw() 绘图方法
结语
本篇文章就到这里了,更多内容敬请期待,债见~