我看的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 有对每个上面所有的阶段的概述

Chapter 2 – 着色器基础

  • 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
类型修饰符 描述
const 将一个变量定义为只读形式。如果它初始时用的是一个编译时常量,那么它本身也会成为编译时常量
in 设置这个变量为着色器阶段的输入变量
out 设置这个变量为着色器阶段的输出变量
uniform 设置这个变量为用户应用程序传递给着色器的数据,它对于给定的图元而言是一个常量
buffer 设置应用程序共享的一块可读写的内存。这块内存也作为着色器中存储缓存(storage buffer)使用
shared 设置变量是本地工作组(local work group)中共享的。它只能用于计算着色器中
  • (2 chapter 占位)

    • p36~37 算术操作符
    • p38 流控制
    • p38~39 循环语句
    • p39 函数
    • p40 参数限制符 in, const in, out, inout
访问修饰符 描述
in 将数据拷贝到函数中(如果没有指定修饰符,默认这种形式)
const in 将只读数据拷贝到函数中
out 从函数中获取数值(因此输入函数的值是未定义的)
inout 将数据拷贝到函数中,并且返回函数中修改的数据
  • (2 chapter 占位)

    • p40~42 计算的不变性 invariant, precise 限制符
    • p42 着色器预处理器
预处理器命令 描述
#define 同下
#undef 控制常量与宏的定义,与C语言的预处理器命令类似
#if 同下
#ifdef 同下
#ifndef 同下
#else 同下
#elif 同下
#endif 代码的条件编译,与C语言的预处理器命令和defined操作符均类似。条件表达式中只可以使用证书表达式或者#defined定义的值
#error tex 强制编译器将text文字内容(知道第一个换行符为止)插入到着色器的信息日志中
#pragma options 控制编译器的特定选项
#extension options 设置编译器支持特定GLSL扩展功能
#version number 设置当前使用的GLSL版本名称
#line options 设置诊断行号
  • (2 chapter 占位)

    • p43 宏定义 -
    • 无参数 #define NUM_ELEMENTS 10
    • 有参数 #define LPos(n) gl_LightSource[(n)].position
宏名称 描述
__LINE__ 行号,默认为已经处理的所有换行符的个数加一,也可以通过#line命令修改
__FILE__ 当前处理的源字符串编号
__VERSION__ OpenGL着色语言版本的整数表示形式
  • (2 chapter 占位)

    • p44 编译器的控制 #pragma,如:

      • #pragma optimize(on)
      • #pragma optimize(off)
      • #pragma debug(on)
      • #pragma debug(off)
    • p44全局着色器编译选项 #extension extension_name : <directive>
      • 或者是#extension all : <directive>
      • directive可以是以下的值
命令 描述
requrie 如果无法支持给定的扩展功能,或者被设置为all,则提示错误
enable 如果无法支持给定的扩展功能,则给出警告;如果设置为all,则提示错误
warn 如果无法支持给定的扩展功能,或者在编译过程中使用了任何扩展,则给出警告
disable 禁止支持给定的扩展(即强制编译器不提供对扩展功能的支持),或者如果设置为all则禁止所有的扩展支持,之后当代码中涉及蛇者个扩展使用时,提示警告或者错误
  • (2 chapter 占位)

    • p45 数据块接口
uniform b {      // 限定符可以为uniform、in、out或者buffervec4 v1; // 块中的变量列表bool v2;  // ...
};              // 访问匿名块成员是使用v1、v2
// 或者
uniform b {     // 限定符可以为uniform、in、out或者buffervec4 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也是可以的,但会有法律许可的问题,所有就有保护性。(但这点有点鸡肋)

Chatper 3 – OpenGL 绘制方式

  • p70 OpenGL图元

    • 点,线,条带线,循环线,独立三角形,三角形条带,三角形扇面
      void glPolygonMode(GLenum face, GLenum mode); – 控制多边形的正面与背面绘制模式。参数face必须是GL_FRONT_AND_BACK,而mode可以是GL_POINT、GL_LINE或者GL_FILL,它们分别设置多边形的绘制模式是点集、轮廓线还是填充模式。默认情况下,正面和背面的绘制都使用填充模式来完成。
图元类型 glPolygonMode中mode参数(OpenGL枚举量)
GL_POINTS
线 GL_LINES
条带线 GL_LINE_STRIP
循环线 GL_LINE_LOOP
独立三角形 GL_TRIANGLES
三角形条带 GL_TRIAGNLES_STRIP
三角形扇面 GL_TRIANGLES_FAN
  • (3 chapter 占位)

    • p75~86 OpenGL 缓存数据
    • p86~92 顶点规范
    • p92~111 OpenGL 的绘制命令
      • p102 多实例渲染 需要了解一下(GPU Instancing)

Chapter 4 – 颜色、像素和片元

  • 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=vlc=vlc=vl来表达,其中c表示光传播的速度(3×108m/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-抗锯齿

Chapter 5 – 视口变换、裁减、剪切与反馈

也可以看看我之前写的变换顺序的总结: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矩阵的乘法来完成模型的移动操作了)。
      举例来说,将数据沿着yyy轴移动0.3,假设第四个向量坐标为1.0,则有:
      [1.00.00.00.00.01.00.00.00.00.01.00.00.00.00.01.0](xyz1.0)→(xy+0.3z1.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.0​0.01.00.00.0​0.00.01.00.0​0.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<count;i++) glBindBufferRange(target, first+i, buffers[i], offsets[i], sizes[i]);

      • p179 配置transform feedback的变量
        • p179 通过OpenGL API配置transform feedback的变量

          • void glTransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar ** varyings, GLenum bufferMode);设置使用varyings来记录transform feedback的信息,所用的程序通过program来指定。count设置varyings数组中所包含的字符串的数量,它们存储的也是所有要捕获的变量的名称。bufferMode设置的是捕获变量的模式–可以是分离模式(GL_SEPARATE_ATTRIBS)或者交叉模式(GL_INTERLEAED_ATTRIBS)。

        • p183 通过着色器配置transform feedback的变量
      • p185 transform feedback的启动和停止
        • void glBeginTransformFeedback(GLenum primitiveModel);设置transform feedback准备记录的图元类型。primitiveMode必须是GL_POINTS、GL_LINES或者GL_TRIANGLES。在这之后的绘制命令中的图元类型必须与这里的primitiveMode相符,或者几何着色器(如果存在的话)的输出类型必须与primitiveMode相符。

        • voi glPauseTransformFeedback(void);暂停transform feedback对变量的记录。我们可以通过glResumeTransformFeedback()重新启动transform feedback。

        • void glResumeTransformFeedback(void);重新启动一个之前通过glPauseTransformFeedback()暂停的transform feedback过程。

        • void glEndTransformFeedback(void);完成transform feedback模式下的变量记录过程。

      • p 187 transform feedback的示例:粒子系统

Chapter 6 – 纹理与帧缓存

第六章的纹理内容太多,我还是只记录部分就好。

  • p193 纹理综述
  • p194 基本纹理类型
    纹理目标和对应的采样器类型
目标(GL_TEXTURE_*) 采样器类型 维度
1D sampler1D 一维
1D_ARRAY sampler1DArray 一维数组
2D sampler2D 二维
2D_ARRAY sampler2DArray 二维数组
2D_MULTISAMPLE sampler2DMs 二维多重采样
3D sampler3D 三维
CUBE samplerCube 立方体映射纹理
ARRAY samplerCubeArray 立方体映射纹理数组
RECTANGLE samplerRect 二维长方形
BUFFER samplerBuffer 一维缓存
  • p195 创建并初始化纹理–在OpenGL中使用纹理的第一步是创建纹理对象,然后将对象绑定到环境的纹理单元。与OpenGL中的其他对象一样,纹理也需要设置名称。而纹理的维度和类型都需要在创建纹理的时候指定。

    • void glCreateTextures(GLenum target, GLsizei n, GLuint *textures);返回n个当前没有使用的纹理对象名称,并保存到textures数组中。textures中返回的名称不一定是一组连续的整数值。textures中返回的名称表示n个新创建的纹理,采样默认的状态信息以及target中设置的维度类型(例如一维、二维或者三维)。0是一个保留的纹理名称,永远不会由glCreateTextures()返回。

    • void glDeleteTextures(GLsizei n, const GLuint *textures);删除n个纹理对象,它们的名字被保存为数组textures的元素。被释放后的纹理名称可以再次使用(例如由glCreateTextures()再次分配)。如果一个当前绑定到环境的纹理被删除了,那么这个绑定点也会被删除,相当于调用glBindTextureUnit()并设置texture参数为0。如果视图删除不存在的纹理名称,或者纹理名称为0,那么命令将被忽略且不会产生错误提示。

    • GLboolean glIsTexture(GLuint texture);如果texture是一个已经被创建的纹理的名称,并且没有被删除,那么返回GL_TRUE;如果texture为0或者是一个非0值,但是并非是已有纹理的名称,那么返回GL_FALSE。

    • void glTextureStrorage1D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width);

    • void glTextureStorage2D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);

    • void glTextureStorage3D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);函数glTextureStorage1D/2D/3D分别负责分配一维、二维以及三位的纹理数据。而对于某个维度的纹理数组数据的分配而言,通常我们需要把存储空间的维度加1.也极具实说,一维数组纹理的分配需要使用glTextureStorage2D(),而二维数组纹理的分配需要使用glTextureStorage3D()。立方体映射纹理可以被认为是与二维数组纹理等价的。对于上述所有函数来说,texture指的是准备分配存储空间的纹理对象的名称。levels是分配给纹理的mipmap的层数。第0层也就是纹理的基础层,后续金字塔的每一层都会比之前的层数据要更少。width、height和depth表示纹理基础层的宽度、高度和深度值。对于一维数组纹理来说,height就是纹理切片的数量,而对于二维数组纹理来说,depth是切片的数量。对于立方体应声数组,可以使用glTextureStorage3D()并且设置depth为立方体映射表面的数量。在这里,depth应当设置为6的整数倍。internalformat设置纹理存储时使用的内部数据格式。一旦我们为纹理分配了存储空间,那么它就无法被重新分配或者释放了。只有纹理自己被删除的时候才会删除对应的存储空间。

    • voi glBindTextureUnit(GLuint unit, GLuint texture);glBindTextureUnit()会完成两项工作。首先,如果绑定了一个已经创建的纹理对象,那么这个纹理对象在给定纹理单元unit上回被激活。齐次,如果设置绑定名称texture为0,那么OpenGL会删除当前激活的纹理单元上所有已经绑定的对象,也就是不绑定纹理的状态。如果一个纹理对象已经初始化,那么它的维度信息应该是glCreateTextures()的target参数所设置的,也就是GL_TEXTURE_1D/2D/3D/1D_ARRAY/2D_ARRAY/RECTANGLE/BUFFER/CUBE_MAP/CUBE_MAP_ARRAY/2D_MULTISAMPLE/2D_MULTISAMPLE_ARRAY中的其中一个。如果texture不是0也不是通过glCreateTextures()创建的名称,那么将产生GL_INVALID_OPERATION错误。如果texture是一个已经存在的纹理对象,但是它的维度信息与target所设置的维度不匹配的话,将产生GL_INVALID_OPERATION错误。

  • p198 代理纹理,以下是纹理目标u对应的代理目标的关系表
    纹理目标(GL_TEXTURE_*) 代理纹理目标(GL_PROXY_TEXTURE_*)
    1D 1D
    1D_ARRAY 1D_ARRAY
    2D 2D
    2D_ARRAY 2D_ARRAY
    2D_MULTISAMPLE 2D_MULTISAMPLE
    2D_MULTISAMPLE_ARRAY 2D_MULTISAMPLE_ARRAY
    3D 3D
    CUBE CUBE
    CUBE_ARRAY CUBE_ARRAY
    RECTANGLE RECTANGLE
    BUFFER /

    代理纹理目标可以用来测试OpenGL具体实现的能力,检查是否存在一些特定的限制。举例来说,我们可能需要某个OpenGL硬件实现能否支持最大尺寸为16384的纹理(这是OpenGL 4的最小需求)。如果某个换将可以创建16384x16384的大小,内部格式为GL_RGBA8(每个纹素存储4个字节)的纹理,那么这样一个纹理所需的总存储空间至少是1GB–如果还有mipmap活着其他内部存储的需求,那么这个数值还会更大。因此,对于可用纹理存储空间已经不足1GB的环境来说,这样的请求将会失败。如果通过代理纹理目标来请求分配这个纹理的话,硬件系统会告诉用户这个请求对于标准目标而言是否可以成功,或者必然会失败。如果代理纹理目标对应的纹理分配失败的话,那么虚拟代理目标产生的纹理的宽度和高度都是0。如果查询代理目标的尺寸,就可以知道刚才的调用是否成功,以及在实际目标上进行请求是否可以成功。

    • void glTextureStorage2DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations);

    • void glTextureStorage3DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations);设置texture所指定的多重采样纹理对象的永久纹理存储空间。对于glTextureStorage2DMultisample()来说,texture必须是GL_TEXTURE_2D_MULTISAMPLE类型,然后设置二维多重采样纹理的存储空间,width和height用于设置纹理的尺寸。glTextureStorage3DMultisample()用来设置二维多重采样纹理数组的存储空间,texture必须是GL_TEXTURE_2D_MULTISAPLE_ARRAY类型。对用二维多重采样纹理数组来说width和height用于设置单张纹理切片的尺寸,depth用来设置数组中切片的数量。在这两个函数中,samples设置了纹理中采样点的数值。如果fixedsamplelocations为GL_TRUE,OpenGL将会对每个纹素中的同一个采样点使用同一个子纹素位置。如果fixedsamplelocations为GL_FALSE,OpenGL会选择空间上变化的位置来匹配每个纹素中同一个采样点。

  • p200 指定纹理数据
    • void glTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels);

    • void glTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);

    • void glTextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); 替换texture所指定的纹理的某个区域中的数据,使用pixels所指定的新数据内容。level中设置了需要更新的mipmap层,而format和type参数指定了新的纹理数据的外部存储格式。pixels中包含了这个子图像的纹理数据。width、height和depth(如果存在)是这个子区域的大小,它会替代当前纹理图像的全部或者一部分。xoffset、yoffset和zoffset(如果存在)分别表示x、y、z三个维度上纹素偏移量。target设置要修改的纹理对象所对应的纹理目标。如果target表示一维的数组纹理,那么yoffset和height分别制定更新后数组的第一层切片和总的切片数;否则,它们表示的就是纹理表座。如果target是二维数组纹理、立方体映射数组,那么zoffset和depth表示更新后数组的第一层切片和总的切片数;否则,它们表示的也是纹理坐标。函数中指定的更新区域不能包含任何超出原始纹理数组范围的纹素数据。

  • p202 从缓存中加载纹理
    • p203 从文件加载图像
    // OpenGL 4.x 以及更高版本中所需的mipmap的最大层级数量,对于16K X 16K的纹理也是足够的
    #define MAX_TEXTURE_MIPS 14// 每个纹理图像数据的结构体中都会包含一个MAX_TEXTURE_MIPS大小的数组来记录mipmap的信息。
    // 这个结构体定义了某一级mipmap数据的所有信息
    struct vglImageMipData
    {GLsizei width;                 // 该级mipmap的宽度GLsizei height;                   // 该级mipmap的高度GLsizei depth;                    // 该级mipmap的深度GLsizeiptr mipStride;         // 相邻级别的mipmap在内存中的距离GLvoid* data;                  // 数据指针
    };// 主要的图像数据结构体,其中包含了所有必要的OpenGL参数,可以将纹理数据传递到纹理对象中
    struct vglImageData
    {GLenum target;                 // 纹理目标(二维,立方体映射等)GLenum internalFormat;           // 推荐的内部格式GLenum format;                    // 内存中的格式GLenum type;                   // 内存中数据类型(GL_RGB等)GLenum swizzle[4];             // RGBA分量的乱序设置(swizzle)GLsizei mipLevels;             // mipmap的层次数量GLsizei slices;                   // (对于纹理数组)切片的数量GLsizeiptr sliceStride;           // 纹理数组中相邻切片之间的距离GLsizeiptr totalDataSize;      // 纹理总共分配的数据大小vglImageMipData mip[MAX_TEXTURE_MIPS];    // 实际的mipmap数据
    };
    
    • void vglLoadImage(const char* filename, vglImageData* image);

    • void vglUnloadImage(vglImageData* image);vglLoadImage()负责从磁盘文件中加载图像。filename指定要加载的文件名称。image传入的是一个vglImageData结构体的地址,如果文件加载成功,图像数据和参数将会被填充进来。如果失败,image将会被清楚。vglUnloadImage()负责释放上一次成功调用vglLoadImage()消耗的所有资源。

    • GLuint vglLoadTexture(const char* filename, GLuint texture, vglImageData* image); 从磁盘加载纹理并将它传递给一个OpenGL纹理对象。filename设置了要加载的文件名称。texture设置了纹理对象的名称,我们讲吧数据加载到其中。如果texture是0,vglLoadTexture()会创建一个新的纹理对象来存储图像数据。image是vglImageData结构体的地址,可以用来存储函数返回的图像参数数据。如果image不是NULL,那么它将负责记录图像的参数信息,并且不会主动释放本地的图像数据。此时用户程序需要使用vglUnloadImage()来释放图像相关的所有资源。如果image是NULL,那么将使用内部的数据结构体来加载图像,并且自动释放本地的图像数据。如果vglLoadTexture()成功,那么它将返回一个纹理对象名称,纹理图像已经被加载到其中。如果texture非0,那么返回值应当与texture相同;否则会新建一个纹理对象并返回。如果函数运行失败,vglLoadTexture()将返回0。

    • p206 获取纹理数据
      • void glGetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels);从纹理texture中获取图像数据。level表示细节层次的层数。format和type表示所需数据的像素格式和数据类型。pixels可以被理解为用户内存中的一个地址,用来存储图像数据,或者如果当前有缓存对象绑定到GL_PIXEL_PACK_BUFFER,这里设置的就是图像数据传递到缓存对象时的数据偏移地址。

      • p207 纹理数据的排列布局
        • void glPixelStorei(GLenum pname, GLint param);

        • void glPixelStoref(GLenum panem, GLfloat param); 设置像素存储的参数pname以及对应的数值param。pname必须是下面的像素解包(unpack)参数名称之一:GL_UNPACK_ROW_LENGTH/SWAP_BTYES/SKP_PIXELS/SKIP_ROWS/SKIP_IMAGES/ALIGNMENT/IMAGE_HEIGHT/LSB_FIRST;或者打包(pack)参数名称之一:GL_PACK_ROW_LENGTH/SWAP_BTYES/SKIP_PIXELS/SKIP_ROWS/SKIP_IMAGES/ALIGNMENT/IMAGE_HEIGHT/LSB_FIRST。

  • p211 纹理格式(太多,这里就不列出来)
    • p214 外部格式 。。。
  • p216 压缩纹理
    压缩是一种降低存储或者信息传输所需的数据总量的方法。由于纹理数据会消耗大量的内存(包括内存带宽),OpenGL支持压缩形式的纹理存储方法来降低纹理的尺寸。压缩算法的实现方式有量大类:无损和有损。无损压缩算法不会丢失任何信息,解压缩之后可以得到原始数据的完全准确的拷贝结果。不过有损压缩的过程中就会牺牲掉一些原始的信息,以确保剩下的数据内容能够充分使用与压缩算法并缩减它的尺寸。这样一定程度上回降低数据的质量,不过在降低内存耗费方面会有更大的提升。对于某些形式的数据(例如可执行的程序、文本文档),是不允许任何信息丢失的。此时用户会经常使用一些无损压缩的方法来处理这种文档,例如zip格式。

    • void glCompressedTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data);

    • void glCompressedTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data);

    • void glCompressedTextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data);更新压缩过的纹理数据,纹理名称为texture,层级为level。xoffset和width负责设置x方向的偏移值和纹理数据的宽度(以纹素为单位)。对于二维和三维纹理,还需要yoffset和height来设置y方向的偏移值和纹理数据的高度。对于一维数组纹理来说,yoffset和height相当于数组中的其实切片和要更新的切片数量。对于三维纹理,zoffset和depth设置了z方向的偏移值和纹理数据的深度。而对于二维数组纹理来说,这两个参数设置的是数组中其实切片和要更新的切片数量。format设置了压缩图像数据的格式,它必须和纹理的内部格式匹配。imageSize和data设置了准备更新的纹理数据的尺寸以及数据的地址。

  • p218 采样器对象
    我们可以通过着色器中带有纹理单元信息的采样器变量来读取纹理,使用GLSL内置的函数从纹理图像中读取纹素。而纹素读取的方式依赖于另一个对象中的参数,名为采样器对象(sampler object)。采样器对象会绑定到采样器单元,这类似于纹理对象绑定到纹理单元。为了简便起见,我们在每一个纹理对象中包含了一个默认内置的采样器对象,如果没有把采样器对象绑定到专门的采样器单元,这个对象可以用来从纹理对象中读取数据。

    • void glCreateSamplers(GLsizei n, GLuint *samplers);返回n个保存在数组samplers中的采样器对象的名称。samplers中返回的对象名称表示新初始化的一系列采样器对象,使用默认的状态。0是一个保留字,glCreateSampelrs()不会返回这个数值。

    • void glBindSampler(GLuint unit, GLuint samplers);

    • void glBindSampelrs(GLuint first, GLsizei count, const GLuint *samplers);glBindSamper()将一个采样器对象sampler绑定到unit所设置的采样器单元。glBindSamplers()则是将多个采样器对象绑定到连续的采样器单元。这里的第一个单元通过参数first给出,采样器单元的数量通过count给出,而samplers指向采样器对象名称的数组。如果sampler活着samplers中某个元素为0,那么对应的目标单元中已经绑定的采样器对象将被接触绑定,当前单元位置恢复为未绑定的状态。

在一个名称被真正绑定到采样器单元之前,我们不认为它是一个合法的采样器对象。如果要判断某个数值是否是一个已知的采样器对象的名称,可以调用glIsSampler()函数,它的原型为:

  • p219 采样器对象

    • GLboolean glIsSampler(GLenum id);如果id是一个已知的采样器对象,返回GL_TRUE,否则返回GL_FALSE。

    • p219 采样器参数
      每个采样器对象都对应了一系列用于控制纹理对象中纹素读取过程的参数。采样器对象的参数是通过glSamplerParamteri()和glSamplerParamterf()函数来设置的(分别用于整数和浮点数参数),此外还有glSamplerParameteriv()和glSamplerParamterfv()函数(用于整数和浮点数向量参数)。

      • void glSamplerParameter{fi}(GLuint sampler, GLenum pname, Type param);

      • void glSamplerParameter{fi}v(GLuint sampler, GLenum pname, const Type* param);

      • void glSamplerParameterl{i ui}v(GLuint sampler, GLenum pname, const Type* param);对于名为sampler的采样器对象,设置参数pname的数值为param给定的值。glSamplerParameteri()中的param是一个整数值,而glSamplerParameterf()中的param是一个浮点数值。glSamplerParameteriv()中的param是一个整数值数组的地址,而glSamplerParameterfv()中的param是一个浮点数数组的地址。

    • p219 采样器参数
      glSamplerParamteri()和类似函数都可以直接设置采样器对象的参数。其中的sampler参数是准备修改的采样器对象的名字。但是也要注意,每个纹理对象都会包含一个默认的采样器对象,可以在不需要绑定到响应采样器单元的前提下直接从纹理中读取数据。如果要修改这个对象的参数,可以调用名为glTextureParameter的函数。
    • void glTextureParameter{fi}(GLuint texture, GLenum pname, Type param);

    • void glTextureParameter{fi}v(GLuint texture, GLenum pname, const Type *param);

    • void glTextureParameterl{i ui}v(GLuint texture, GLenum pname, const Type *param);设置纹理对象texture的参数pname的数值为param给定的值。glTextureParameteri()中的param是一个整数值,而glTextureParameterf()中的param是一个浮点数值。glTextureParameterfv()中的param是一个浮点数数组的地址。最后,glTextureParameterIuiv()中的param是一个无符号整数值数组的地址。如果pname设置的是采样器对象的参数,那么函数会访问这个纹理的内置默认采样器对象。

    • p220 采样器参数
      对于glSamplerParameter和glTextureParameter类的函数来说,它们都有一系列可以使用的pname参数值。每个值控制的都是采样的不同方面,不过glTextureParameter函数的一些pname参数值和采样器并没有关系。我们并不打算在这里马上介绍pname的每一个合法值的意义,而是在后文合适的章节里进行讲解。当完成了对采样器对象的试用之后,和处理其他类型的OpenGL对象类似,我们最好也删除这些不再使用的对象。要删除一个采样器对象,可以使用glDeleteSamplers()函数。

      • void glDeleteSamplers(GLsizei count, const GLuint *samplers);删除count个采样器,它们的名称保存在数组samplers中。删除之后,samplers中的名称都会恢复到未使用的状态,可以经由glCreateSamplers()的调用再次返回。

  • 220 纹理的使用

    • gvec4 texture(gsampler1D tex, float P[, float bias]);

    • gvec4 texture(gsampler2D tex, vec2 P[, float bias]);

    • gvec4 texture(gsampler3D tex, vec3 P[, float bias]);

    • gvec4 texture(gsamplerCube tex, vec3 P[, float bias]);

    • gvec4 texture(gsampler1DArray tex, vec2 P[, float bias]);

    • gvec4 texture(gsampler2DArray tex, vec3 P[, float bias]);

    • gvec4 texture(gsampler2DRect tex, vec2 P);

    • gvec4 texture(gsamplerCubeArray tex, vec4 P[, float bias]);从名为tex的采样器中采样一个纹素,对应的纹理坐标为P。如果对象支持mipmap。并且设置了bias,那么这个参数江北用于mipmap细节层次(level-of-detail)的偏移量计算,来判断采样应当在哪一层进行。函数的返回值是一个包含了采样后的纹理数据的向量。

    • p222 纹理坐标
      纹理坐标也就是纹理中的坐标,用于对图像进行采样。它们通常是按照逐顶点的方式来设置的,然后对结果几何体区域进行插值来获得逐片段的坐标值。这个坐标值是在片元着色器中使用的,一边读取纹理数据并返回纹理中的颜色值作为结果片元。
    • p229 复杂纹理类型

      • 3D纹理
    • p230 纹理数组

      • gvec4 texture(gsampler2D tex, vec2 P[, float bias]);

      • gvec4 texture(gsampler2DArray tex, vec3 P[, float bias]);我们可以比较二维纹理和二维纹理数组的texture函数原型。第二个函数使用的采样器类型为sampler2DArray,并且纹理坐标P有一个额外的维度。P的第三个分量也就是数组的索引值。或者切片值。

    • p 231 立方体映射纹理

      • p 233 立方体映射示例:天空盒
      • p 234 使用立方体映射实现环境映射
    • p236 阴影采样器

      • float texture(gsampler1DShadow tex, vec3 P[, float bias]);

      • float texture(gsampler2DShadow tex, vec3 P[, float bias]);

      • float texture(gsamplerCubeShadow tex, vec4 P[, float bias]);

      • float texture(gsampler1DArrayShadow tex, vec3 P[, float bias]);

      • float texture(gsampler2DArrayShadow tex, vec4 P[, float bias]);

      • float texture(gsampler2DRectShadow tex, vec3 P);

      • float texture(gsamplerCubeArrayShadow tex, vec3 P, float compare);对绑定到tex所对应的纹理单元的阴影纹理进行采样,纹理坐标通过P来设置。返回值是一个浮点数,用来衡量阴影比较操作的结果,即获取的纹素数据中通过了比较测试的数据所占的分数。

    • p237 深度 - 模板纹理
    • p238 缓存纹理
      缓存纹理是一种特殊形式的纹理,它允许从着色器中直接访问缓存对象的内容,将它当作一个巨大的一维纹理使用。缓存纹理与一般的一维纹理相比有一些限制和不同,但是在代码中所体现出的形态是非常类似的。我们可以把它当作一般的纹理对象创建,绑定到纹理单元,并使用glTextureParameteri()控制他们的参数。但是,纹理数据的存储实际上是有一个缓存对象(它必须是由名称的)来管理和控制的。此外,缓存纹理也没有内置的采样器,并且采样器对象也不会对缓存纹理产生效果。

      • void glTextureBuffer(GLuint texture, GLenum internalformat, GLuint buffer);将缓存对象buffer的存储控制于缓存纹理texture进行关联。buffer的存储数据江北视为一组数据格式为internalformat的元素进行解析,注意数据格式必须是由尺寸后缀的。如果buffer为0,那么缓存纹理texture中当前已经存在的关联信息江北断开,缓存数据将无法再读取。

    • p238 缓存纹理
      如果只需要关联缓存对象的一部分到缓存纹理中,需要使用glTextureBufferRange()函数,它的圆形如下所示:

      • void glTextureBufferRange(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size);将缓存对象buffer中从offset开始,总共size字节的一部分存储区域,关联给缓存纹理texture。buffere的存储数据将被视为一组数据格式为internalformat的元素进行解析,注意数据格式必须是由尺寸后缀的。如果buffer为0,那么缓存纹理texture中当前已经存在的关联信息将被断开,缓存数据将无法再读取。offset必须是一个整数值,并且是系统平台所定义的常量GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT的倍数。

    • p238 缓存纹理
      为了在着色器中访问缓存纹理,还需要创建一个uniform变量samplerBuffer(对于有符号或者无符号整型的变量,还可以使用isamplerBuffer和usamplerBuffer),然后用texelFetch函数来读取单独的纹素采样数据并使用。用于缓存纹理的texelFetch函数的定义如下:

      • vec4 texelFetch(samplerBuffer s, int coord);

      • vec4 texelFetch(isamplerBuffer s, int coord);

      • vec4 texelFetch(usamplerBuffer s, int coord);对一个单独的纹素执行查找,纹理当前绑定到s,而纹理坐标设置为coord。

  • p249 纹理视图
    • void glTextureView(GLuint texture, GLenum target, GLuint origTexture, Glenum internalFormat, Gluint minLevel, GLuint numLevels, GLuint minLayer, GLuint numLayers); …

  • p243 滤波方式 - 纹理贴图可以是线性的、方形的、长方形的,甚至是三维的形式,但是当它被映射到多边形或者物体表面,再变换到屏幕坐标系之后,纹理上的独立纹素几乎不可能直接和屏幕上的最终画面像素直接对应起来。根据所用的变换方法和纹理贴图的不同,屏幕上的一个像素可能对应了一个纹素的已小部分(放大),或者大量的纹素几何(缩小)。无论是哪一种情况,我们都无法精确制导应该使用那些纹素值,以及如何对它们求平均或者插值。因此,OpenGL允许用户从多种不同的滤波选项中进行选择,来完成上述计算需求。不同的滤波选项在速度和画面质量之间做出了不同的权衡。此外,也可以单独为放大和缩小设置设定不同的滤波方式。
    • p243 线性滤波 - 线性滤波技术的含义是:使用坐标值从一组离散的采样信号中选择相邻的采样点,然后将信号曲线拟合成线性近似的形式。

      • p245 高级技巧
        从信号学的角度来说,纹理对原始信号进行采样的时候,至少应该达到最高频率数据的两倍频率。这样才有足够的采样点来精确重建原始的图像。但是,线性滤波不能做到这种级别的重建,因此会产生锯齿走样(aliasing)。另外,如果不能做到原始滤波和2倍频的采样,也会产生走样或者其他的画面瑕疵。我们后面深入讨论这一现象以及mipmap技术的作用。用户还可以使用纹素收集(texel gather)的函数来实现自定义的滤波的方式,尽可能改善线性滤波的瑕疵问题。我们会在后面的部分讨论纹素收集的内容。
    • p245 使用和生成mipmap …
    • p249 计算mipmap层次 …
    • p250 mipmap细节层次的控制 …
  • p250 高级纹理查询函数 - 除了简单的纹理采样函数,例如texture和texelFetch之外,着色语言还支持其他几种纹理的函数。我们后面对他们进行讲解。
    • p250 显示的细节层次控制
      通常来说,使用mipmap的时候,OpenGL会负责计算细节层次并得到mipmap层级的结果,再将采样结果返回给用户。不过,我们也可以自己取代这个计算过程,通过纹理获取函数的参数来显示设置细节层次。函数textureLod就提供了一个lod参数,而不是texture函数通常提供的(可选的)bias参数。和GLSL支持的其他纹理函数类似,textureLod也提供了很多重载的函数原型,适合于不同的数据类型和各种维度的采样器。下面李处了textureLod的一些关键原型函数。

      • gvec4 textureLod(gsampler1D tex, float P, float lod);

      • gvec4 textureLod(gsampler2D tex, vec2 P, float lod);

      • gvec4 textureLod(gsampler3D tex, vec3 P, float lod);

      • gvec4 textureLod(gsamplerCubetex, vec3 P, float lod);

      • gvec4 textureLod(gsampler1DArray, vec3 P, float lod);

      • gvec4 textureLod(gsampler2DArray, vec3 P, float lod);

      • gvec4 textureLod(gsampler2DRect, vec2 P, float lod);

      • gvec4 textureLod(gsampleCubeArray, vec4 P, float lod); 从给定的采样器tex采样一个纹素,纹理坐标为P,显示的细节层次设置为lod。

    • p251 显示的梯度设置
      我们也可以通过另一种方式来实现mipmap的细节层次计算,而不是直接设置细节层次参数。当我们使用梯度纹理函数的时候,会将传递纹理坐标的偏导数作为参数。我们给出了一些关键的函数原型。

      • gvec4 textureGrad(gsampler1D tex, float P, float dPdx, float dPdy);

      • gvec4 textureGrad(gsampler2D tex, vec2 P, vec2 dPdx, vec2 dPdy);

      • gvec4 textureGrad(gsampler3D tex, vec3 P, vec3 dPdx, vec3 dPdy);

      • gvec4 textureGrad(gsamplerCube tex, vec3 P, vec3 dPdx, vec3 dPdy);

      • gvec4 textureGrad(gsampler1DArray tex, float P, float dPdx, float dPdy);

      • gvec4 textureGrad(gsampler2DArray tex, vec3 P, vec3 dPdx, vec3 dPdy);

      • gvec4 textureGrad(gsamplerCbueArray tex, vec4 P, vec4 dPdx, vec4 dPdy);从给定的采样器tex读取纹素,纹理坐标为P,此外P在x和y方向的偏导数分别有dPdx和dPdy给定。

    • p251 带有偏移参数的纹理获取函数
      有的程序需要获取某个区域中的多个纹素数据,或者需要在采样的时候稍微对纹理坐标进行偏移。GLSL中提供了这种功能的函数,并且会比直接在着色器中手动改变纹理坐标的值更为高效。textureOffset函数同样提供了一系列重

      • gvec4 textureOffset(gsampler1D tex, float P, int offset, [float bias]);

      • gvec4 textureOffset(gsampler2D tex, vec2 P, ivec2 offset, [float bias]);

      • gvec4 textureOffset(gsampler3D tex, vec3 P, ivec3 offset, [float bias]);

      • gvec4 textureOffset(gsampler1DArray tex, vec2 P, int offset, [float bias]);

      • gvec4 textureOffset(gsampler2DArray tex, vec3 P, ivec2 offset, [float bias]);

      • gvec4 textureOffset(gsampler2DRect tex, vec2 P, ivec2 offset, [float bias]); 从采样器tex中读取一个纹素,纹理坐标为P。当浮点数类型的纹理坐标被缩放和转换到核实的绝对纹理坐标范围之后,还可以用offset参数对纹理坐标进行偏移,然后再进行读取的工作。 比中着色器中计算偏移纹理左边再去采样的更高效率,比先uv计算,再使用texture函数更加高效

    • p252 投影纹理 – 投影纹理(projective texture)也就是使用一个头饰变换矩阵对纹理坐标进行变换。变换的输入参数是一组齐次坐标,输出结果是变换后的向量,但是最后一个分量不一定是1。textureProj函数就是用来处理最后一个分量的,它会吧结果的纹理坐标投影到纹理的坐标空间中。对于某些场合,例如将花纹投影到平面表面上(或者手电在墙上照出一个光圈),以及阴影图映射,这个技术非常有用。这里我们仅给出一些函数原型。
      • gvec4 textureProj(gsampler1D tex, vec2 P[, float bias]);

      • gvec4 textureProj(gsampler1D tex, vec4 P[, float bias]);

      • gvec4 textureProj(gsampler2D tex, vec3 P[, float bias]);

      • gvec4 textureProj(gsampler2D tex, vec4 P[, float bias]);

      • gvec4 textureProj(gsampler3D tex, vec4 P[, float bias]);

      • gvec4 textureProj(gsamplerRect tex, vec3 P); 执行带有投影信息的纹理查找。纹理坐标为P,但是会除以它的最后一个分量并且通过结果值来执行纹理查找,后半部分与texture函数并无区别。与texture区别就是textureProj先将p坐标除以最后分量,再调用texture函数。

    • p253 在着色器中执行纹理查询
      下面给出的两个内置GLSL函数并不会真的从纹理中读取数据,而是会返回纹理数据处理的想换信息。第一个函数是textureQueryLod,它会获取硬件固定流水线的mipmap计算结果并返回。

      • vec2 textureQueryLod(gsampler2D tex, vec2 p) – 返回mipmap的处理信息,其中x分量是当前访问的mipmap数组,y分量是当前计算得到的细节层(相对纹理base层次的差)。这里的说明有纹理:x是数组?y是相对base层的差值,y的说明是可以理解的,x不能理解,因为返回的是vec2,分量有x,y,而且分别都是浮点数,x是数组的话,这怎么浮点数来代表是数组呢?难不成是:这个浮点数需要先转换为整数,然后该整数就是mipmap数组的首个元素的地址吗?

      • 对于上面的每个textureQueryLod()函数来说,都有一个对应的查询函数textureQueryLevels(),它会返回当前的mipmap层次数。
        • textureQueryLevels(gsampler1D tex);

        • textureQueryLevels(gsampler2D tex);

        • textureQueryLevels(gsampler3D tex);

        • textureQueryLevels(gsamplerCube tex);

        • textureQueryLevels(gsampler1DArray tex);

        • textureQueryLevels(gsampler2DArray tex);

        • textureQueryLevels(gsamplerCubeArray tex);

        • textureQueryLevels(gsampler1DShadow tex);

        • textureQueryLevels(gsampler2DShadow tex);

        • textureQueryLevels(gsamplerCubeShadow tex);

        • textureQueryLevels(gsampler1DArrayShadow tex);

        • textureQueryLevels(gsampler2DArrayShadow tex);

        • textureQueryLevels(gsamplerCubeArrayShadow tex); 返回给定的采样器包含的mipmap层次。

      • 有的时候,我们有必要了解正在采样的纹理尺寸。举例来说,我们可能需要对一个整数纹理坐标(表达绝对的纹素位置)缩放到某个浮点数区间,或者迭代当前纹理中所有的采样器。textureSize函数会返回纹理的某个层次的尺寸。它的圆形如下所示。
        • int textureSize(gsampler1D tex, int lod);

        • ivec2 textureSize(gsampler2D tex, int lod);

        • ivec3 textureSize(gsampler3D tex, int lod);

        • ivec2 textureSize(gsamplerCube tex, int lod);

        • ivec2 textureSize(gsamplerRect tex, int lod);

        • ivec3 textureSize(gsamplerCubeRect tex, int lod);

        • ivec2 textureSize(gsampler1DArray tex, int lod);

        • ivec3 textureSize(gsampler2DArray tex, int lod);

        • int textureSize(gsamplerBuffer tex);返回当前绑定到采样器的纹理tex在某个细节层次lod(如果存在的话)上的尺寸。返回值的分量会按照纹理宽度、高度、深度的顺序进行填充。对于数组形式的纹理来说,返回值的最后一个分量表示数组中的切片数量。

      • 我们还可以在着色器中获取一个多重采样纹理中,每个纹素的采样点数:
        • int textureSamples(gsampler2DMS tex);

        • int textureSamples(gsampler2DMSArray tex); 返回tex中每个纹素对应的采样点数量。

    • p255 纹素收集
      textureGather函数是一个特殊的函数,可以在着色器中直接读取四个采样值,从二维纹理(或者立方体映射、长方形纹理,以及对应的纹理数组)中创建双线性滤波的纹素结果。通常这里会读取单通道的纹理数据,可选的参数comp设置了要读取的分量通道的索引(而不是默认的x或者r分量)。和直接多次采样同一个纹理并且读取它的某个通道的做法相比,这个函数会体现出显著的性能优势来,这里因为它依赖于特殊的接口机制,可以把纹理查找的次数降低到预期的四分之一。

      • gvec4 textureGather(gsampler2D tex, vec2 P[, int comp]);

      • gvec4 textureGather(gsampler2DArray tex, vec3 P[, int comp]);

      • gvec4 textureGather(gsamplerCube tex, vec3 P[, int comp]);

      • gvec4 textureGather(gsamplerCubeArray tex, vec4 P[, int comp]);

      • gvec4 textureGather(gsamplerRect tex, vec2 P[, int comp]);直接从长方形、二维(数组)或者立方体映射(数组)类型的纹理tex中读取和收集4个纹素,以便创建一个双线性滤波的纹素结果,这4各纹素的选定通道数据会被返回,并分别存储在返回结果的4个通道里。如果有必要的话,我们可以用comp参数来指定要获取的通道索引,0、1、2、3分别表示x、y、z、w分量。如果没有指定comp,那么默认返回x分量。

    • p255 组合功能的特殊函数

待后续。。。

OpenGL红宝书的部分学习记录相关推荐

  1. OpenGL红宝书正序解读(一)

    OpenGL红宝书正序解读(一) 第一章:OpenGL简介 第二章:状态管理和绘制几何物体 绘图工具箱 清除窗口 指定颜色 强制完成绘图操作 坐标系统工具箱 描述点.直线.多边形 什么是点.直线.多边 ...

  2. 游戏开发计算机图形学杂项知识系列:OpenGL红宝书中第一个渲染程序Triangles常见问题归总

    游戏开发计算机图形学杂项知识系列:OpenGL红宝书中第一个渲染程序Triangles常见问题归总 声明:未经作者允许,严禁商用,转载请标明出处和来源,谢谢 转载自:https://www.cnblo ...

  3. OpenGL红宝书第九版环境配置

    OpenGL红宝书第九版环境配置(VS2017) 对于很多想要学OpenGL的人来说,环境配置一直是一个大问题,笔者就是其中之一.虽然网络上有许多教程,但是由于不同的教程使用的库并不同,利用这些教程配 ...

  4. 关于在vs2013中配置opengl红宝书第八版环境

    转自 http://blog.csdn.net/qq821869798/article/details/45247241 本人刚开始学习opengl,买了一本opengl红宝书第八版, 第一个例子研究 ...

  5. VS2012通过makefile编译OpenGL红宝书的示例代码

    通过创建新VC项目,然后设置一堆属性,对于懒人来说还是太复杂了.既然它自带了makefile,可以尝试下使用nmake. 需要注意的是VS2012的安装目录里面已经没有GL的头文件和库文件.这个改动应 ...

  6. OpenGL红宝书:第一个渲染程序Triangles常见问题归总

    OpenGL红宝书第八版从shader开始讲起,其实渲染对大多数人来说都是充满吸引力的,但是程序写起来确实比较麻烦,书上面第一示例程序零零散散也弄了好几天.这里写个博客汇总一下,我觉得对所有初学者都有 ...

  7. OpenGL红宝书学习(1、概述)

    OpenGL简介 OpenGL 全称Open Graphics Library,一种用于渲染2D.3D矢量图形的跨语言.跨平台的应用程序编程接口(API).由1992年成立的OpenGL架构评审委员会 ...

  8. OpenGL红宝书8th第一个例子triangles

    年初用Qt涉及过OpenGL,当时只根据Qt自给的库使用,按照Qt给的例子没有出现啥问题,但是最近接触计算机图形学,尝试编译红宝书8th(<OpenGL编程指南>(原书第8版)中文版)第一 ...

  9. 用最简单的方法配置运行OpenGL红宝书第9版源码示例

    笔者真是苦逼啊,之前花了很多时间去学习"基于OpenGL的图形学"的开头部分,包括书本和老师的PPT.但是到自己尝试编译运行示例代码的时候真是困难重重.而且!在自己胡乱摸爬滚打终于 ...

最新文章

  1. ajax默认超时时间多久,请问chrome浏览器的默认超时时间是多久?
  2. HTML Help Workshop制作chm帮助文件和在应用程序中的调用
  3. mysql数据库的后_MySQL数据库误删后的回复技巧
  4. Image Cup,我和几位师兄一起奋战!
  5. Python学习二:词典基础详解
  6. shell 中的参数替换
  7. 快速生成CSS样式语法(HTML、CSS)
  8. 无法访问windows installer服务
  9. LayaAir UI 组件 # Image 位图、Label 标签
  10. 快解析 : 管家婆A8远程访问解决方案
  11. 杀毒软件 对应的进程名称
  12. 低代码快速实现跟进提醒
  13. 用ps制作LOGO(个人向)
  14. VUE实现市、区二级联动
  15. PO BO VO DTO POJO DAO概念及其作用
  16. 关闭windows锁屏,提升开机速度
  17. splash : mouse_click()方法
  18. mysql rpl_mysql5.5 半同步参数rpl_semi_sync_master_timeout 测试解决办法
  19. 云计算的三国演义!华为云、阿里云、腾讯云B端市场策略全解读
  20. 最详细的宝塔青龙面板搭建教程

热门文章

  1. CAD中插入外部参照字体会变繁体_为什么在原点附近的图纸作为外部参照插入后却离插入点很远?...
  2. 获取cookies(pyppeteer)
  3. 基于注解实现SpringMVC的配置文件
  4. 图片转PDF,图片过长智能截取
  5. 【生活】驾照C1-科一手册
  6. 【C++基础入门】C++全栈体系(一)
  7. centos挂载盘到根下_centos挂载磁盘及扩展根目录
  8. CS全球排名44,复旦计科实力如何?
  9. 【jmeter性能测试】模拟多个IP同时登录
  10. Linux ln -s目录,Linux ln 命令的使用