我看的OpenGL红宝书为:
- 《OpenGL编程指南》-- 原书第9版
- OpenGL Programming Guide – The Official Guide to Learning OpenGL, Version 4.5 with SPIR-V Ninth Edition
一直觉得OpenGL红宝书就不是给初学者看看的。
它比较适合有基础,而且有认识OpenGL底层开发人员的公司去阅读。因为如果你没有认识开发OpenGL底层的人员,在部分遇到没有说明的部分,或是说法有问题的部分,你就没办法详细的理解原理了。(写邮件给OpenGL开发团队吗?我估计会给无视吧,可能对它们来说,是比较新手的问题。所以我说嘛,不适合新手看)
虽然有说明部分API的功能,也有衍生 说明相关的部分原理,但部分原理,也是没有说明,如下:6 Chapter 中的 : textureLod中不支持mipmap,那lod参数与mipmap有什么关系,或是说lod有什么用,会对采样过程有什么影响,都没有说明。
当然,你要是感兴趣,也可以去买本看看,就像我一样,虽然看的过程中,好多不懂的,书本没有再进一步说明,所以我都是一边看一边查阅不懂的部分,但是还有是部分知识点,就算去查阅,也很难查阅到的。
本文只是个人学习过程中记录一个觉得比较重点,且说的清晰一些的内容。
看了此书,最大感悟是:为何很多接口要那样设计,难以理解与使用,估计是因为底层硬件限制,才这样设计的吧?(不了解硬件原理)
Chapter 1 – OpenGL 概述- p1 OpenGL 是一种应用程序编程结构(Application Programming Interface, API),它是一种可以对图形硬件设备特性进行访问的软件库。
- 其实我自己的理解就是 用户Application层与底层显示处理设备驱动层的一个封装使用层。这样用户层就可以通过OpenGL来调用显示处理设备的驱动程序了。
- p8 OpenGL 渲染管线
- 粗略概述:顶点数据⇒ 顶点着色器⇒ 细分控制着色器⇒ 细分计算着色器⇒ 几何着色器⇒ 图元设置⇒ 裁减和剪切⇒ 光栅化⇒ 片元着色器
- p9 有对每个上面所有的阶段的概述
- p25 OpenGL的可编程管线
- 顶点着色阶段
- 细分着色阶段
- 几何着色阶段
- OpenGL 着色器最后阶段:片元着色阶段
- 计算着色阶段
- p27 OpenGL着色语言概述
- 使用GLSL构建着色器(基本结构)
- p28~30 着色语言的基础数据类型, float, double, int, uint, bool, vec2/3/4, ivec2/3/4, uvec2/3/4, bvec2/3/4, mat2/3/4x2/3/4
- p33~34 存储限制符: const, in, out, bniform, buffer, shared
- (2 chapter 占位)
- p36~37 算术操作符
- p38 流控制
- p38~39 循环语句
- p39 函数
- p40 参数限制符 in, const in, out, inout
- (2 chapter 占位)
- p40~42 计算的不变性 invariant, precise 限制符
- p42 着色器预处理器
- (2 chapter 占位)
- p43 宏定义 -
- 无参数 #define NUM_ELEMENTS 10
- 有参数 #define LPos(n) gl_LightSource[(n)].position
- (2 chapter 占位)
- p44 编译器的控制 #pragma,如:
- #pragma optimize(on)
- #pragma optimize(off)
- #pragma debug(on)
- #pragma debug(off)
- p44全局着色器编译选项 #extension extension_name :
- 或者是#extension all :
- directive可以是以下的值
- p44 编译器的控制 #pragma,如:
- (2 chapter 占位)
- p45 数据块接口
uniform b { // 限定符可以为uniform、in、out或者buffer
vec4 v1; // 块中的变量列表
bool v2; // ...
}; // 访问匿名块成员是使用v1、v2
// 或者
uniform b { // 限定符可以为uniform、in、out或者buffer
vec4 v1; // 块中的变量列表
bool v2; // ..
} name; // 访问有名块成员时使用name.v1、name.v2
- (2 chapter 占位)
- p54 着色器的编译
- 字符串⇒ glShader Source⇒ 着色器源代码⇒ glCompileShader⇒ 着色器对象⇒ glCreateShader⇒ glAttachShader⇒ glCreateProgram⇒ 着色器程序⇒ glLinkProgram⇒ 可执行的着色器程序⇒ glUseProgram
- 对于每个着色器程序,我们都需要在应用程序中通过下面的步骤进行设置。
- 创建一个着色器对象。
- 将着色器源代码编译为对象。
- 验证着色器的编译是否成功。
- 然后需要将多个着色器对象链接为一个着色器程序,包括
- 创建一个着色器程序。
- 将着色器对象关联到着色器程序。
- 链接着色器程序。
- 判断着色器的连接过程是否成功完成。
- 使用着色器来处理顶点和片元。
- p64 SPIR-V 形式的着色器
- 与GLSL 形式的着色器相比,有以下几点优势:
- 更好的可移植性。因为语法上更严格。
- 多种源语言支持。可以使用其他语言来编写,再生产SPIR-V。
- 减少发布尺寸。对函数、等其他共用的数据抽象出来。
- 保护源代码。因为SPIR-V可读性底,而想将SPIR-V反编译成GLSL也是可以的,但会有法律许可的问题,所有就有保护性。(但这点有点鸡肋)
- 与GLSL 形式的着色器相比,有以下几点优势:
- p54 着色器的编译
- p70 OpenGL图元
- 点,线,条带线,循环线,独立三角形,三角形条带,三角形扇面 void glPolygonMode(GLenum face, GLenum mode); – 控制多边形的正面与背面绘制模式。参数face必须是GL_FRONT_AND_BACK,而mode可以是GL_POINT、GL_LINE或者GL_FILL,它们分别设置多边形的绘制模式是点集、轮廓线还是填充模式。默认情况下,正面和背面的绘制都使用填充模式来完成。
- (3 chapter 占位)
- p75~86 OpenGL 缓存数据
- p86~92 顶点规范
- p92~111 OpenGL 的绘制命令
- p102 多实例渲染 需要了解一下(GPU Instancing)
- p113 基本颜色理论
物理世界中,光是由光子(photon)所组成的,简单来说,也就是细小的例子沿着一条直线路径进行运动 ( 1 ) ^{(1)} (1),每个粒子都有自己的“色彩”,使用物理学来定量描述,也就是例子的波长(或者频率) ( 2 ) ^{(2)} (2)。
我们可以看到的光子在可见光光谱中都有对应的波长,范围从大约390纳米(紫色)到720纳米(红色)。这一范围内的所有颜色组成了七色彩虹:紫色、靛蓝、蓝色、绿色、黄色、橙色、红色。
人的眼睛里包含了名为视杆细胞的光敏感结构。视杆细胞对于光的强度(intensity)是敏感的,而视椎细胞对于光强不是很敏感,反而能够区分出光的不同波长(wavelength)。现金的研究认为视椎细胞一共有三种,每一种都对光波长的某个波段敏感。通过计算这三种视椎细胞的响应结果,大脑可以感知多种不同的颜色,而不只是组成彩虹的七种颜色。举例来说,理想的白色光是由全部可见波长的等量光子所组成的。与之相比,激光就是一种单频光,也就是说所有光子的频率都相等。
那么,这与计算机图形学以及OpenGL又有说明关系呢?现代显示设备对于可以显示的颜色有着更严格的范围规定–可见光谱当中只有一部分是可用的(虽然设备也在不断改进这一点)。实际上,设备可以显示的颜色范围通常是由它的色域来表示的。OpenGL所支持的绝大多数显示设备都会使用一种组合三原色的方法来构成颜色值,三原色也就是红色、绿色和蓝色,它们构成了显示设备的整个颜色域。我们将其称作RGB颜色空间,并且使用这三个值的组合来表达每一种颜色。我们只使用三种颜色来表达可见光谱中的如此庞大的一个范围的理由是,这三种颜色非常接近与人眼光锥细胞的响应曲线的中心区域。
OpenGL 当中,通常会在这三个颜色分量之外再增加第四个alpha分量(我们会在4.4.6节中介绍),因此可以成为RGBA颜色空间。作为RGB的一种补充,OpenGL还支持sRGB颜色空间。我们会在讨论帧缓存对象与纹理贴图的时候再次讲解与sRGB相关的内容。
注意:颜色空间的种类还有很多,例如HSV(色调-饱和度-值,Hue-Saturation-Value),或者CMYK(青-品红-黄-黑,Cyan-Magenta-Yellow-Black)。如果数据保存在一个不同于RGB的颜色空间中,那么你需要将它转换到RGB(或者sRGB)空间,然后再使用OpenGL进行处理。
- ( 1 ) ^{(1)} (1) 当然,重力的影响忽略不计。
- ( 2 ) ^{(2)} (2) 光子的频率和波长可以通过方程式 c = v l c=vl c=vl来表达,其中c表示光传播的速度 ( 3 × 1 0 8 m / s ) (3 \times 10^8 m/s) (3×108m/s),v表示光子的频率,l表示波长。当然,也有很多人对于光的波粒二象性有自己的看法,这些纹理不妨以后坐下来慢慢讨论。
- (4 chapter 占位)
- p114~121 缓存用途
- 颜色缓存(color buffer)
- 深度缓存(depth buffer)
- 模板缓存(stencil buffer)
- p121~138 片元的测试与操作
- 剪切测试(scissor test)
- 多重采样的片元操作
- 模板测试(stencil test)
- 深度测试(depth test)
- 融混(blending)
- 逻辑操作
- p139 多重采样
- 红宝书没有说清楚这点,我自己理解为:多重采样开启的话,例如,SSAA,MSAA中,都是将屏幕上每个像素,最终都以n个指定多重采样配置指定的多重采样缓存来插值算出来的(会根据多边形覆盖的采样点的计算,具体可以看看下面我给出的连接)。(颜色,深度,模板,都会多重缓存),例如,原来是400400的屏幕,要是你multisampler为4,那就是(4400)(4400) ⇒ 16001600的(颜色、深度、模板光栅化缓存大小),需要的显示设备的内存会多出很多倍,比你设置的n要多n+3+被以上,其中这个3就至少包含:颜色,深度,模板,所以开启多重采样一定要留意的你硬件是否支持。
- 关于多重采样,你可以看看微软的光栅化规则中的MSAA的规则:Rasterization Rules – Multisample Anti-Aliasing Rasterization Rules*
- 对多重采样(MSAA)原理的一些疑问?
- [译]Vulkan教程(33)多重采样
- Multisampling in pipeline
- OpenGL-抗锯齿
- 红宝书没有说清楚这点,我自己理解为:多重采样开启的话,例如,SSAA,MSAA中,都是将屏幕上每个像素,最终都以n个指定多重采样配置指定的多重采样缓存来插值算出来的(会根据多边形覆盖的采样点的计算,具体可以看看下面我给出的连接)。(颜色,深度,模板,都会多重缓存),例如,原来是400400的屏幕,要是你multisampler为4,那就是(4400)(4400) ⇒ 16001600的(颜色、深度、模板光栅化缓存大小),需要的显示设备的内存会多出很多倍,比你设置的n要多n+3+被以上,其中这个3就至少包含:颜色,深度,模板,所以开启多重采样一定要留意的你硬件是否支持。
- p114~121 缓存用途
也可以看看我之前写的变换顺序的总结:OpenGL Transformation 几何变换的顺序概要(MVP,NDC,Window坐标变换过程)
- p148 观察视图 基本上来说,显示器本身是一个平面的、固定的,二维矩形区域,但是模型却是一个三维空间的几何体。本章我们将学习如何将模型的三维坐标投影到固定的二维屏幕坐标上。 将三维空间的模型投影到二维的关键方法,就是齐次坐标(homogeneous coordinate)的应用、矩阵乘法的线性变换方法,以及视口映射。我们将在下文中详细讨论这些方法。
- p148 相机模型 使用相机(或者计算机)的主要步骤列举如下:
- 将相机移动到准备拍摄的位置,将它对准某个方向(视图变换,view transform)。
- 将准备拍摄的对象移动到场景中必要的位置上(模型变换,model transform)。
- 设置相机的焦距,或者调整缩放比例(投影变换,projection transform)。
- 拍摄照片(应用变换结果)。
- 对结果图像进行拉伸或者挤压,将它变换到需要的图片大小(视口变换,viewport transform)。对3D图形来说,这里同样需要对深度信息进行拉伸或者挤压(深度范围的缩放)。这一步与第3步不一样,后者只是选择捕捉场景的范围大小,并不是对结果的拉伸。
- p150 视椎体 – 这个就不想写了,可以去查阅本书概述。或是百度。
- p151 视椎体的剪切 – 同上
- p151 正交投影,你可以理解为没有使用透视除法。(在OpenGL或是DX中的管线有处理这部,在vs后,在primitive assembly前),可查考,我之前的软光栅器,就是这么处理的:用C# Bitmap作为画布写个3D软渲染器
- p152 用户变换 – 就是值顶点、细分、几何着色器对顶点的几何变换,如果没写细分、几何着色器的话,那么就全都在顶点着色器中处理。就是对gl_Position赋什么样的值,如:gl_Position=Transform*Vertex;就看Transform的uniform变量是个什么矩阵,可以是单位矩阵,那么gl_Postion就是等价于Vertex的值,就是model/object space上的坐标;如果Transform是model矩阵,那gl_Position就是world space上的坐标;如果Transform是model view矩阵,那gl_Position就是world view space上的坐标;如果Transform是model view projection矩阵,那gl_Position就是world view projection space – 就是clip coordinate坐标。通常Transform是一个mvp(world view projection)的矩阵。
- p153 矩阵乘法的回顾(不写,看本书或是百度)。
- p155 齐次坐标 三维数据可以通过三维向量与3x3矩阵的乘法操作,来完成缩放和旋转的线性变换。 但是,对三维笛卡尔坐标的平移(移动/滑动)操作是无法通过与3x3矩阵的乘法操作来完成的。我们还需要一个额外的向量,将点(0,0,0)移动到另一个位置。这一步叫做仿射变换(affine transformation),它不属于线性变换(你应该还记得线性变换的一个重要规律,它总是将(0,0,0)映射到(0,0,0))。加入这个额外的运算过程意味着我们将无法再运用线性变换的各种优势,例如将多个变换过程合成为一个变换。因此,我们需要找到一种方法,通过使用线性变换来表达平移过程。幸运的是,只要将数据置入四维坐标空间当中,仿射变换就回归成为一种简单的线性变换了(也就是说,我们可以直接使用4x4矩阵的乘法来完成模型的移动操作了)。 举例来说,将数据沿着 y y y轴移动0.3,假设第四个向量坐标为1.0,则有: [ 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 ] ( x y z 1.0 ) → ( x y + 0.3 z 1.0 ) \begin{bmatrix} 1.0 & 0.0 & 0.0 & 0.0\\ 0.0 & 1.0 & 0.0 & 0.0\\ 0.0 & 0.0 & 1.0 & 0.0\\ 0.0 & 0.0 & 0.0 & 1.0 \end{bmatrix} \begin{pmatrix} x\\y\\z\\1.0 \end{pmatrix} \to \begin{pmatrix} x\\y+0.3\\z\\1.0 \end{pmatrix} ⎣⎢⎢⎡1.00.00.00.00.01.00.00.00.00.01.00.00.00.00.01.0⎦⎥⎥⎤⎝⎜⎜⎛xyz1.0⎠⎟⎟⎞→⎝⎜⎜⎛xy+0.3z1.0⎠⎟⎟⎞ 在这里可以了解到,这个额外的第四分量其实是用来实现透视投影变换的。 齐次坐标总是有一个额外的分量,并且如果所有的分量都除以一个相同的值,那么将不会改变它所表达的坐标位置。 举例来说,一下所有的坐标都表达了同一个点: ( 2.0 , 3.0 , 5.0 , 1.0 ) ( 4.0 , 6.0 , 10.0 , 2.0 ) ( 0.2 , 0.3 , 0.5 , 0.1 ) (2.0,3.0,5.0,1.0)\\ (4.0,6.0,10.0,2.0)\\ (0.2,0.3,0.5,0.1) (2.0,3.0,5.0,1.0)(4.0,6.0,10.0,2.0)(0.2,0.3,0.5,0.1) 这样的话,齐次坐标所表达的其实是方向而不是位置;对一个方向值的缩放不会改变方向本身。 在一个笛卡尔坐标中: ( x , y , z ) (x,y,z) (x,y,z),我们可以直接添加第四个w分量: ( x , y , z , w ) (x,y,z,w) (x,y,z,w),并设置值为1.0来实现齐次坐标的简历: ( 2.0 , 3.0 , 5.0 ) → ( 2.0 , 3.0 , 5.0 , 1.0 ) (2.0,3.0,5.0)\to(2.0,3.0,5.0,1.0) (2.0,3.0,5.0)→(2.0,3.0,5.0,1.0) 然后可以让前三个分量来除以第四个分量,并且将其舍弃,以重新得到笛卡尔坐标。 ( 4.0 , 6.0 , 10.0 , 2.0 ) 除 以 w ⟶ ( 2.0 , 3.0 , 5.0 , 1.0 ) 舍 弃 w ⟶ ( 2.0 , 3.0 , 5.0 ) (4.0,6.0,10.0,2.0) \begin{matrix} 除以w\\ \longrightarrow \end{matrix} (2.0,3.0,5.0,1.0) \begin{matrix} 舍弃w\\ \longrightarrow \end{matrix} (2.0,3.0,5.0) (4.0,6.0,10.0,2.0)除以w⟶(2.0,3.0,5.0,1.0)舍弃w⟶(2.0,3.0,5.0) 透视变换会将w分量修改为1.0以外的值。如果w更大,那么坐标将位于更远的位置。当OpenGL准备显示几何体的时候,它会使用最后一个分量除以前三个分量,从而将齐次坐标重新变换到三维的笛卡尔坐标。因此距离更远的物体(w值更大)的笛卡尔坐标也会更小,从而绘制的比例也就更小。w为0.0表示(x,y)坐标位于无限近的位置(物体与观察点非常近,以至于它的透视效果是无限大的)。这样可能会产生无法预知的结果。而从理论上来说,使用负数的w值并没有错误,例如下面的坐标值就表达同一个点。 ( 2.0 , 3.0 , 5.0 , 1.0 ) ( − 2.0 , − 3.0 , − 5.0 , − 1.0 ) (2.0,3.0,5.0,1.0)\\ (-2.0,-3.0,-5.0,-1.0) (2.0,3.0,5.0,1.0)(−2.0,−3.0,−5.0,−1.0) 但是负数whi可能会图形管线的某些环节带来麻烦,尤其是可能会与其他的整数w值进行插值计算,而得到的结果有可能非常接近或者正好为0.0.要避免这个问题,最简单的方法就是保证w值总是整数。
- p157~167 线性变换与矩阵
- p157 平移
- p161 旋转
- p163 透视
- p166 正交投影
- p167 法线变换
- p168 矩阵
- p169 OpenGL中矩阵的行与列
- 170 OpenGL变换
- p171 视口
- p171 多视口
- p171~172 高级技巧:z的精度 (废话多,建议另搜索)
- p172 高级技巧:用户裁减和剪切(废话多,建议另搜索)
- p173 OpenGL变换的控制
- p174 transform feeback 是OpenGL管线中,顶点处理阶段结束之后,图元装配和光栅化之前的一个步骤。transform feedback可以重新捕获即将装配为图元(点、线段、三角形)的顶点,然后将它们的部分或者全部属性传递到缓存对象中。(就是可以将vertex shader postprocessing 到 rasterization 之间的shader 变量可以写入到application指定的缓存对象中,以便于app阶段获取使用)
- p175 transform feedback 对象
-
void glCreateTransformFeedbacks(GLsizei n, GLuint* ids);创建n个新的transform feedback对象并且将生成的名称记录到数组ids中。
-
void glBindTransformFeedback(GLenum target, GLuint id);将一个名称为id的transform feedback对象绑定到目标target上,目标的值必须是GL_TRANSFORM_FEEDBACK。
-
GLboolean glIsTransformFeedback(GLenum id);如果id是一个已有的transform feedback对象的名称,那么返回GL_TRUE,否则返回GL_FALSE。
-
void glDeleteTransformFeedbacks(GLsizei n, const GLuint* ids);删除n个transform feedback对象,其名称保存在数组ids中。如果ids的某个元素不是transform feedback对象的名称,或者设置为0,那么都会被直接忽略,不会给出提示。
-
- p176 transform feedback缓存
-
void glTransformFeedbackBufferBase(GLuint fbx, GLuint index, GLuint buffer);将名为buffer的缓存对象绑定到名为xfb的transform feedback对象上,其索引通过index设置。如果index为0,那么buffer将被绑定到默认的transform feedback对象的绑定点。
-
void glTransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);将缓存对象buffer的一部分绑定到名为xfb的transform feedback对象的绑定点索引index上。offset和size的单位均为字节,它们设置了要绑定的缓存对象的范围。如果xfb为0,那么buffer将绑定到默认的transform feedback对象的绑定点。
-
void glBindBuffersRange(GLenum target, GLuint firest, GLsizei count, const GLuint *buffer, const GLintptr *offset, const GLsizeiptr *sizes);绑定来自一个或者多个缓存的多个范围值,对应于target所指定的目标绑定点。first表示绑定缓存范围的第一个索引值,count表示要绑定的数量。这里的buffers、offsets、sizes参数分别对应于数组中的count个缓存名称,count个起始地址偏移量,count个绑定范围的大小。offsets和sizes中保存的数值是采样字节方式设置的。这里的每一个范围数据都是通过offsets和sizes中对应元素指定的,然后绑定到target所制定的索引位置,从first开始计数。如果buffers为NULL,那么offsets和sizes将被忽略,同时target的索引绑定点上所有绑定关系会被删除。从功能上来说,glBindBuffersRange()等于:
for(i=0;i
关注打赏
-
- p175 transform feedback 对象
- p148 相机模型 使用相机(或者计算机)的主要步骤列举如下:
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?