《WebGL编程指南》学习笔记

《WebGL编程指南》学习笔记

第二章

缓冲区:

  • 颜色缓冲区(gl.COLOR_BUFF_BIT): 存储绘画到屏幕上的像素点
  • 深度缓冲区(gl.DEPTH_BUFFER_BIT): 存储深度检测的当前深度值
  • 模板缓冲区(gl.STENCIL_BUFFER_BIT): 存储模板检测的当前对比值
缓冲区 默认值 函数
颜色缓冲区 (0.0, 0.0, 0.0, 0.0) gl.clearColor(r, g, b, a)
深度缓冲区 1.0 gl.clearDepth(depth)
模板缓冲区 0 gl.clearStencil(s)

注意:gl.clear(缓冲区1 | 缓冲区2) 可以清除指定缓冲区,可以用'|'来指定多个缓冲区

//用蓝色来清空颜色缓冲区
gl.clearColor(0.0, 0.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

齐次坐标:

图片

百度百科

博客


第三章

缓冲区对象:WebGL里的一块存储区域,可以在缓存区里保存所有要绘制的顶点数据,然后一次性向顶点着色器里传入多个点的attribute变量数据

注意,向缓冲区写入数据,一定是在初始化顶点数据时执行,不要放在update里,因为这个操作很耗时,而且也没必要每帧都写入数据,只需要每次渲染

的时候重新绑定缓冲区就行(通过创建多个缓冲区以牺牲内存的方式拯救CPU)

矩阵变换原理:从三角函数推断各种变换矩阵


第四章

矩阵数学库

单位矩阵的作用

“变换”是一个动作概念,例如“将物体与位移变换矩阵相乘”,意思是将物体位移

矩阵变换的顺序(先旋转后平移)

先旋转/缩放后平移,跟先平移后旋转/缩放效果完全不同。
例如一个物体在坐标原点(0,0),想要旋转90°跟平移5个距离
先旋转后平移:
    1.将旋转矩阵乘以物体坐标,实现一个将物体绕原点旋转90°的矩阵
    2.将平移矩阵乘以上面的矩阵,将绕原点旋转90°的矩阵,平移5个单位
先平移后旋转:
    1.将平移矩阵乘以物体坐标,将物体平移5个单位
    2.将旋转矩阵乘以以上矩阵,将物体平移5个单位后的位置再绕原点旋转90°

必须严格按照先Scale,再Rotate,再Translate的顺序,否则得到的结果肯定是不满意的
例如有一个1X1正方形在原点,我们想要得到一个1X2,并且斜向45°,而且离坐标原点1个单位处
如果先旋转,再缩放的话,旋转方向是对了,但是我们是将旋转后45°的正方形的Y轴拉伸2倍,得到的是一个被拉长的菱形
如果先平移,再旋转的话,因为我们旋转都是绕着坐标原点的,结果自然是正方形不是自身旋转45°,而是绕着原点旋转

/*
 * 注:实际上大部分游戏引擎都会处理这一部分逻辑,例如cocos引擎,不管我们先设置旋转
 * 还是先设置平移,最终的结果都是一样的,因为不管你写哪个,引擎都不会立刻去乘矩阵,
 * 而是设置一个脏标记,然后在下一次循环的时候,按照先计算 scale -> rotate -> translate
 * 的顺序执行,之所以要按照这个顺序,是因为如果把平移放在前面,会出现与开发者预期不符的效果,例如开发者明明想通过代码设置一个在坐标(5,0)处,带有90°旋转的物体,由于书写顺序,导致变成一个在坐标(5,0)处绕原点旋转90°的物体
 */
node.position = vec2(5, 0)
node.rotate = 90 

但是如果是自己计算矩阵,还是要注意顺序,不然最后的结果可能也会不一样

平移,旋转,缩放矩阵的推断过程

绕任意点旋转/缩放(只有2D模式下才有意义,3D模式不存在锚点的概念)

可以通过转换为复合变换将旋转变为绕原点旋转:
    1.获取一个将旋转点平移到原点的矩阵,从该矩阵变换,实现目标点平移
    2.目标点根据旋转矩阵,绕原点旋转
    3.将旋转后的点,再平移回原来旋转点所在的矩阵

https://www.cnblogs.com/zhoug2020/p/7842808.html

绕任意轴旋转(3D模式下)

1.将绕X轴,绕Y轴,绕Z轴的3个矩阵乘起来,合并成一个新的矩阵,就是绕任意轴
即依次按照XYZ轴的旋转进行变换
因为矩阵相乘先后顺序很重要,所以不同次序的旋转会得到不同的结果,通过这种方式获取旋转矩阵,除了要给出欧拉角,还需要给出旋转顺序
https://threejs.org/docs/#api/en/math/Euler.order
http://planning.cs.uiuc.edu/node102.html

2.如果不想严格按照某个轴的顺序先后,那么应该将绕任意轴分解为3个轴的投影,然后分别旋转各自的轴
https://blog.csdn.net/Master_Ding/article/details/82459342

旋转矩阵,维基百科
1

一个网站,可以输入数据,生成旋转矩阵,四元数啥的

从矩阵提取位移,旋转,缩放矩阵

【第三弹】从矩阵中提取平移、旋转、缩放矩阵(有推算过程)
Given this transformation matrix, how do I decompose it into translation, rotation and scale matrices?

从旋转矩阵获取欧拉角

从旋转矩阵获取欧拉角 DECOMPOSING AND COMPOSING A 3×3 ROTATION MATRIX
https://stackoverflow.com/questions/15022630/how-to-calculate-the-angle-from-rotation-matrix
http://www.gregslabaugh.net/publications/euler.pdf

仿射变换

转置矩阵

逆矩阵,因为M*-M = I,而任何矩阵乘I又等于自身,也就是说,一个顶点经过矩阵M的变换后,Mv,可再

乘M的矩阵来还原,即M-Mv = I*v = v

法线通过逆转置矩阵变换后可以得出正确的垂直效果的推算过程

四元数

https://krasjet.github.io/quaternion/quaternion.pdf

四元数的插值

四元数右乘三维向量(得到将三维坐标进行该四元数旋转后的新坐标),是否是通过旋转矩阵变换得到的?


第五章

纹理的配置,纹理的缩放模式,纹理大于UV时如何显示,等等

纹理的尺寸一定要是2的N次方,否则无法显示

立方体纹理贴图

利用mipmap金字塔纹理,解决由于图片缩小导致的闪烁,抖动锯齿

利用gl.generateMipmap可以自动生成金字塔纹理,避免麻烦的手动创建以及加载,但会造成一定开销,如果为了性能考虑,还是要手动创建

gl.FILTER_MINMAP_SELECTOR,SELECTOR表示如何选择mip层,是最近的,还是2个层直接插值,FILTER表示选择完mip层后,该层的纹理的过滤模式,结论:gl.LINEAR_MIPMAP_LINEAR效果最好,但比较吃性能,因为他是完全插值的,gl.NEAREST_MIPMAP_NEAREST则反之,它只选择最近的金字塔贴图

参考:https://www.cnblogs.com/kekec/p/5065155.html

各项异性过滤


第七章

摄影机空间变换

观察空间矩阵的推导

//方式1: 直接变换到摄影机的世界坐标,即变换到摄影机矩阵的逆矩阵里
//方式2:构建一个先朝摄影机反方向移动,再反方向旋转的矩阵,其实得到的也就是上面摄影机的世界坐标矩阵
//从哪里看向哪里,也可以理解为摄影机视角,即观察空间
//若要变换到摄影机空间,可以假设整个观察空间以摄影机位于世界坐标原点,然后将所有物体朝摄影机原先在世界空间中的位置反向移动即可
//在纸上画下图就清晰了

投影矩阵的推导

没有指定投影矩阵时,默认的矩阵是如何裁剪的(书里说是默认的矩阵深度很浅,所以三角形旋转会导致缺角看不见)

!!!!!!!!!震惊,今天在做天空盒贴图时,为了让天空盒纹理永远显示在最远的地方,会将gl_Position的z轴设置为1,
原来是因为webgl里的显示是一个2X2X2的矩形盒,屏幕左边为x=-1,下边为y=-1,最远的地方为z=1,最近的为z=-1
任何最终gl_Position(p.x / w, p.y / w, p.z / w)值超出这个范围的,都不会被渲染,即被裁剪掉
这时候齐次坐标的作用就来了,附加一个w值来换算深度,例如一个坐标在z=100处远的物体,假设w=1,即没有经行裁剪矩阵的情况,
它的最终坐标z=z/w=100,肯定会被裁剪,假设此时的摄影机far距离刚好为100,进行裁剪矩阵变换后,算出w=100,
gl系统再次获取这个坐标,就是z=z/w=1,刚好在最远的地方,不会被裁剪。相当于裁剪矩阵的作用,就是将摄影机视锥体的内容映射到
这个2X2X2的立方体里
这也是为什么gl_Posintion.w能用来表示物体离摄影机距离的关系,
(暂时还搞不懂w表示距离是怎么算出来的)

https://blog.csdn.net/stl112514/article/details/83927643
https://zhuanlan.zhihu.com/p/50547909
https://zhuanlan.zhihu.com/p/73034007

投影矩阵裁剪的原理,为何乘以一个矩阵,就会有部分区域看不见了

开启深度检测

通过指定三角形索引来绘画代替直接用顶点数组绘画

第八章

平行光

点光源

第十章

加载解析obj格式模型文件

参考:https://en.wikipedia.org/wiki/Wavefront_.obj_file

雾气

顶点着色器的顶点经过MVP变换后,gl_Position.w能表示该顶点距离摄影机的距离,原理?

渲染到纹理

创建帧缓冲区
//如果要渲染到纹理,那么渲染的屏幕大小就是纹理的大小,渲染纹理相当于屏幕
//!!!!!注意,渲染纹理一定是要2的N次幂,否则是黑色的显示不了
过程:
1.创建一个帧缓冲对象
2.创建一个和屏幕大小相同的纹理(2的N次方)
3.将纹理附加到帧缓冲上
4.渲染时,指定将其渲染到特定的帧缓冲上。不需要渲染到帧缓冲时,我们需要关闭这一效果。
5.将纹理通过普通方式渲染出来

alpah 混合 和深度写入

//注意,由于某些操作会锁定深度写入,因此在gl.clear里即使有指明要清除深度信息,由于被锁定,也无法成功清除
//因此如果gl.clear指明要清除深度信息,应该先确保下是否有解锁深度缓冲区的写入功能

创建缓冲区过程

创建着色器过程

加载纹理流程

切记一句话:矩阵变换,是指将一个点(或矩阵)按照该矩阵的描述进行运动,而不是之前老是错误的认为是将点转到该矩阵坐标系下

例如对某点进行一个平移矩阵的变换(即左乘),意思是将这个点按照这个矩阵描述的进行平移

所以要将某个点转到某个矩阵的局部坐标系下,是要乘该矩阵的逆矩阵,即反方向平移

var mat = new Matrix4x4(
new Vector4(xAxis),
new Vector4(yAxis),
new Vector4(zAxis),
new Vector4(0, 0, 0, 1));

竟然能直接通过这种神奇的方式构建旋转矩阵,
也就是意味着,第一行代表的就是X轴的指向,
改天算下,这种方式跟旋转矩阵的推导有啥关系

重要:gl里的bind操作的意思

gl里的每个操作,例如gl.texImage2D(gl.TEXTURE_2D),并没有在函数里指明你要操作的对象,即这个函数它不知要给哪个纹理设置,
它只指定接下来要操作的类型是纹理,因此gl系统使用一个叫绑定的机制,即类似gl.bindTexture(gl.TEXTURE_2D, target),
意思是将某个纹理对象绑定到当前的操作纹理的目标,接下来的所有操作就会都针对它进行
因此在每次执行某些gl操作的时候,都要先注意操作的目标有没有先被绑定
还有操作完了有些目标注意要解除操作,防止下次操作某个功能的时候,忘了绑定新对象,而被操作成旧的对象

造成的误操作典型的问题就是,渲染2个对象,第一个纹理,绑定并绘画,第二个没纹理,跳过设置纹理步骤,但如果上一步没取消绑定,会让第二步

误认为它有而渲染出来

出于性能考虑你希望在远的物体之前先绘制近的物体,因为GPU使用深度缓冲检测,会不绘制无法通过测试的像素。
也就是说,绘画普通物体,应该由近到远绘画,避免远处的物体像素花半天绘画后,还被抛弃掉。
而对于半透明物体,则要反过来,需要由远到近绘画,因为每个像素的颜色都会保留下来用于计算叠加

“一個 Draw Call,等於呼叫一次 DrawIndexedPrimitive (DX) or glDrawElements (OGL),等於一個 Batch”
https://blog.csdn.net/hany3000/article/details/44033243

渲染顺序:
顶点着色器->片段着色器->深度测试->alpha测试->颜色缓冲区
1.不透明物体(包括天空盒),从近到远绘制,因为近的物体会遮挡远的物体的部分,如果先渲染远的,再渲染近的,会造成浪费,被遮挡部分也渲染了,但后面深度检测的时候又抛弃掉(但实际渲染顺序,深度测试在片段着色器之后,也就是说,不管遮挡不遮挡,都会渲染像素,记得好像有个优化方法是可以将深度检测提前的?不知叫啥)
2.再渲染半透明物体,顺序反过来,从远到近绘制,因为近的物体,会与远处物体(即背景色)进行叠加混合,所以需要先绘制远的,否则一旦绘制了最近的半透明物体,其后面的物体由于都无法通过深度检测,不管透明不透明,全部显示不出来
3.渲染半透明物体的时候,应该分2步,先开启正面裁剪,然后绘制背面,在开启背面裁剪,绘制正面,叠加上去,否则造成的后果就是,正面三角形可能先绘制,然后绘制背面的时候,由于背面像素通不过深度检测,被抛弃,导致透明物体的里层部分丢失,实际情况应该是,我们可通过正面看进去,看到物体内部
图片

posted @ 2021-08-31 17:09  JeasonBoy  阅读(326)  评论(0编辑  收藏  举报