OpenGL学习总结-基本渲染流程(三)

一、OpenGL 渲染管线(Rendering Pipeline)的发展

1. 固定功能管线时代

  • 在早期的 OpenGL(1.x 和 2.x 版本),渲染管线是固定的,开发者只能使用一组预定义的图形处理函数来进行渲染。这包括了顶点处理、光照计算、纹理映射、裁剪、混合等功能。这种管线的限制使得开发者无法对渲染过程进行深度控制,限制了创意和渲染效果的多样性。

  • 缺点

    • 由于所有渲染处理都是硬件固定的,开发者无法自由定制处理过程。
    • 光照模型、材质和效果都有限,无法实现复杂的现代渲染效果(如反射、折射、阴影、全局光照等)。

2. OpenGL 2.0:引入着色器

  • 引入的关键特性

    • 着色器(Shaders):OpenGL 2.0 引入了第一个可编程管线的核心组成部分,即 GLSL(OpenGL Shading Language),使得开发者能够编写自己的顶点着色器和片段着色器。通过这些着色器,开发者可以对顶点、像素的处理过程进行自定义,从而实现更丰富的图形效果。
    • 顶点着色器(Vertex Shader):允许开发者控制每个顶点的变换和属性。
    • 片段着色器(Fragment Shader):使开发者能够控制像素的最终颜色,加入光照、纹理采样等操作。
  • 标志性突破

    • 虽然 OpenGL 2.0 引入了着色器,但它仍然保持着固定管线的存在。也就是说,开发者可以在顶点处理和片段处理的阶段使用着色器,但固定管线的其他部分(如光照计算、纹理映射等)仍然由硬件自动完成。

3. OpenGL 3.0-4.0:可编程管线全面升级

  • 引入的关键特性

    • 完全去除固定管线:从 OpenGL 3.0 开始,固定管线彻底被弃用。开发者必须完全依赖着色器来完成图形渲染的各个阶段。
    • 几何着色器(Geometry Shader):在 OpenGL 3.2 中引入,几何着色器允许开发者在顶点着色器和片段着色器之间生成或修改图元(如从点生成三角形)。这极大增强了管线的灵活性。
    • 多渲染目标(MRT,Multiple Render Targets):允许渲染多个图像或缓冲区,同时渲染,通常用于延迟渲染(Deferred Rendering)技术。
    • 纹理映射增强:支持更先进的纹理映射技术,如立方体贴图(Cube Mapping)和多重纹理。
    • 帧缓冲对象(FBO,Framebuffer Objects):允许离屏渲染,将渲染结果存储到纹理而不是屏幕中,用于后期处理或环境映射。
  • 标志性突破

    • OpenGL 3.0 引入了完全可编程的渲染管线。顶点着色器、几何着色器和片段着色器都变成了开发者自定义的模块,光照、材质、几何体生成等都可以完全控制。

4. OpenGL 4.0-4.6:现代渲染技术的支持

  • 引入的关键特性

    • 计算着色器(Compute Shader):OpenGL 4.3 引入了计算着色器,使得 GPU 计算任务不仅限于图形渲染,也可以用于图像处理、物理模拟、粒子系统等领域。这标志着 OpenGL 渲染管线不仅可以用于传统的图形渲染任务,还可以执行通用计算任务。
    • Tessellation(细分):OpenGL 4.0 引入了细分着色器(Tessellation Shader),通过细分控制点,允许在图形管线中动态生成细节。它广泛应用于高质量曲面的渲染,如地形、复杂模型等。
    • OpenGL 4.1-4.5:增强的性能与兼容性:这些版本主要对性能进行了优化,提升了对多核 GPU 的支持和扩展了跨平台的兼容性。
    • OpenGL 4.5-4.6:图形管线增强与扩展:这些版本进一步增强了对先进图形技术的支持,如多维度着色器支持、GPU 资源管理、OpenGL 调试和性能优化工具等。
  • 标志性突破

    • 计算着色器和细分着色器使得 OpenGL 成为一个全面的图形和计算平台,支持更复杂的图形效果和计算任务。
    • 对于虚拟现实、电影制作、游戏开发等行业,OpenGL 4.0-4.6 提供了强大的渲染能力和灵活性。
  • 5. 未来趋势:Ray Tracing 和 AI 加速

  • Ray Tracing(光线追踪):随着硬件支持的改进,OpenGL 在光线追踪方面的支持逐渐增强。虽然 OpenGL 本身并不直接支持光线追踪,但它支持的扩展(如 Vulkan)和结合新的硬件特性(如 NVIDIA 的 RTX 系列显卡)使得开发者能够实现更高质量的光照效果和反射。
  • AI 加速与机器学习:随着人工智能的应用日益增加,OpenGL 及其衍生的 Vulkan 也在支持 GPU 加速的机器学习模型方面有所发展。

二、固定渲染管线流程(3.1版本中已淘汰)

        OpenGL 3.1删除了OpenGL 3.0中几乎弃用的所有功能。包括固定管线渲染。虽然被淘汰了,学习了解对OpenGL 的绘制渲染流程还是有帮助的。

1) Primitive Processing:

        这一步是图元运算过程,所谓图元,其实就是一个点集。在OpenGL ES中,所有的物体,几何元素最终都是以顶点的形式表述的。一般来说,这些顶点将会产生三角形、直线或点。它做的工作就是将顶点提供给顶点处理器进行处理。顶点的数据包括顶点的位置(空间坐标)、大小、颜色、顶点的法向量(用于光照计算)、纹理坐标(可能有多个)等等。

2) Transform and Lighting:

        这一步是转换和光照过程。其中Transform是通过模型、视图、投影变换矩阵,将所有的顶点坐标变换成人眼坐标系下的一致坐标。变换矩阵同样会改变物体的顶点法向量。如果激活了纹理,还可以进行纹理坐标转换,以及自动纹理坐标的生产。Lighting处理的就是光照部分,它会利用光源、材质、转换后的顶点位置和法向量计算每个顶点的颜色值。

3) Primitive Assembly:

        图元装配过程。管线中这个流程是对所有的点数据进行点线面等基础图元的组装。这个过程会对所有的图元进行剪切和筛选。对于不在视区空间中的部分进行剪切,对于不可见的面进行筛选。

4) Resterizar:

        光栅化。光栅化的过程就是对所有的经过Primitive Assembly图元转换成屏幕上可以显示的二维Fragment(片元)。片元和将要显示的像素一一对应。

5) Texture Environment:

        纹理处理。利用纹理坐标来进行纹理的相关处理。

6) Colour Sum:

        颜色叠加。根据纹理颜色等相关属性确定最终的顶点颜色。

7) Fog:

        雾。雾化处理。

8) Alpha Test:

        Alpha测试。判断某些片元是否抛弃。比如可以规定Alpha小于0.2的片元就需要抛弃。

9) Depth Stencil:

        深度测试和模板测试。深度测试需要一个深度缓冲区,是在后面的会被在前的遮盖,需要抛弃。模板测试需要一个模板缓冲区,也就是模板缓冲区中为每个像素保存了一个“模板值”,当像素需要进行模板测试时,将设定的模板参考值与该像素的“模板值”进行比较,符合条件的通过测试,不符合条件的则被丢弃,不进行绘制。条件的设置与Alpha测试中的条件设置相似。但注意Alpha测试中是用浮点数来进行比较,而模板测试则是用整数来进行比较。

10) Color Buffer blend:

        跟颜色缓冲区进行混合。最终生成的片元颜色需要跟颜色缓冲区中本来的进行混合(也可以理解成为跟背景混合,以生成最终的颜色。

11) Dither:

        抖动。在可用颜色数量较少的系统中,可能需要对颜色值进行抖动,在适当损失颜色质量的情况下增加可使用的颜色数量。

12) Frame Buffer:

        最终结果就写进了Frame Buffer。一个流程就算结束了。

  • 三、现代可编程管线流程解析

        OpenGL 的可编程管线发展经历了从固定功能管线到完全可编程管线的变革,逐步引入了着色器、几何着色器、计算着色器和细分着色器等先进功能,使得开发者能够在渲染管线的各个阶段进行自定义控制。这种灵活性使得开发者可以实现高质量的渲染效果,如 PBR(物理基础渲染)、复杂光照、后期处理、粒子系统等,同时也支持复杂的计算任务(如图像处理、AI 加速等)。

图中蓝色部分是可以编程控制的环节。

(一) 顶点处理阶段 (Vertex Processing)

        在OpenGL的顶点处理阶段,主要由顶点着色器(Vertex Shader)负责处理每个输入的顶点数据。最常见的操作是将顶点从模型空间经过一系列变换(如视图变换、投影变换)转换到裁剪空间,同时根据其他顶点属性(如法线、颜色、纹理坐标)计算新的属性值,传递给后续的片段着色器。

输入:

  • 顶点数据:顶点的位置、法线、颜色、纹理坐标等,这些数据通常保存在顶点缓冲对象(VBO)中。
  • 顶点着色器的输入变量:包括位置、法线、颜色等,通过layout关键字来指定。

处理:

  1. 顶点坐标变换

    • 使用**模型矩阵(Model Matrix)**将顶点从模型空间转换到世界空间。
    • 使用**视图矩阵(View Matrix)**将顶点从世界空间转换到相机空间(视图空间)。
    • 使用**投影矩阵(Projection Matrix)**将顶点从视图空间转换到裁剪空间。
  2. 法线变换

    法线需要从模型空间变换到世界空间或视图空间,通常要用到模型矩阵的逆转置矩阵来正确处理缩放等非线性变换。
  3. 顶点属性计算

    顶点着色器可以根据顶点的其他属性(如法线、颜色、纹理坐标)来计算光照、颜色等值,传递给片段着色器。

输出:

  • gl_Position:这是裁剪空间中的顶点位置,是后续阶段(如光栅化)裁剪的基础。
  • gl_PointSize:在绘制点时,控制点的大小。
  • gl_ClipDistance:用于顶点裁剪的距离参数。
  • 其他顶点属性(如颜色、法线、纹理坐标等)可以输出到后续的片段着色器。

代码举例:

#version 460 core

// 顶点着色器输入
layout(location = 0) in vec3 inPosition;  // 顶点位置
layout(location = 1) in vec3 inNormal;    // 顶点法线
layout(location = 2) in vec2 inTexCoord;  // 纹理坐标

// 传递给片段着色器的输出
out vec3 fragNormal;                      // 法线,传递给片段着色器
out vec2 fragTexCoord;                    // 纹理坐标,传递给片段着色器

// 统一变量,用于矩阵变换
uniform mat4 model;      // 模型矩阵
uniform mat4 view;       // 视图矩阵
uniform mat4 projection; // 投影矩阵

void main() {
    // 将顶点位置从模型空间转换到裁剪空间
    vec4 worldPosition = model * vec4(inPosition, 1.0);  // 模型空间到世界空间
    gl_Position = projection * view * worldPosition;     // 世界空间到裁剪空间

    // 将法线从模型空间变换到世界空间
    fragNormal = mat3(transpose(inverse(model))) * inNormal;

    // 传递纹理坐标到片段着色器
    fragTexCoord = inTexCoord;
}

说明:

  1. 矩阵变换

    • 顶点位置inPosition首先通过模型矩阵转换到世界空间,再通过视图矩阵投影矩阵转换到裁剪空间,最终输出给gl_Position
    • 法线使用模型矩阵的逆转置矩阵来正确变换,以防止模型缩放影响法线的方向。
  2. 其他属性传递

    • 顶点的纹理坐标直接传递给片段着色器,用于后续的纹理映射。
  3. 输出

    • gl_Position是每个顶点在裁剪空间中的位置,必须输出。
    • fragNormalfragTexCoord等属性将传递给后续阶段(如片段着色器),用于进一步计算光照、纹理等效果。

(二)细分着色器阶段 (Tessellation Shader)

1. 细分控制着色器(TCS)

        TCS 主要负责为每个输入的补丁计算细分级别,控制补丁被细分成多少子面,细分级别通常是动态计算的,可以用于生成更精细的几何细节。

        功能:TCS会根据需求把Patch自己的属性以及它内部的顶点属性做一些修改。然后输出Patch。当然,它也可以不做任何修改,直接传给后面的shader。我们知道Tessellation的作用就是把一个图元分割成很多图元,比如把一个三角形分割成很多更小的三角形。因此,在分割的时候我们得要知道这个三角形的每个边要被分割成多少段,然后在三角形内部,我们还要怎么继续分割,这两个紫色的内容就是存储在 gl_TessLevelOuter 和gl_TessLevelInner。TCS可以根据需要设置这两个值。

         所以,TCS的主要作用是设置Patch以及它内部顶点的属性。同时也是最重要的,设置图元接下来被细分的度。(TCS不做分割动作)

        下面贴了个三角形分割的图片。

输入:

  • gl_PatchVerticesIn:当前补丁的输入顶点数量,通常是3(对于三角形补丁),4(对于四边形补丁)等。
  • gl_InvocationID:当前细分控制着色器实例的调用ID,用于支持多线程计算。
  • 顶点属性:来自顶点着色器的顶点属性,通常是位置、法线、颜色等。

处理:

  • 计算细分级别:细分控制着色器的主要任务是计算细分级别,决定补丁细分的精度。细分级别决定了补丁会被细分为多少个子面。
  • 输出细分级别
    • gl_TessLevelOuter:控制补丁四个边的细分级别。
    • gl_TessLevelInner:控制补丁内部的细分级别。

输出:

  • gl_TessLevelOuter:外部细分级别,控制补丁四个边的细分。
  • gl_TessLevelInner:内部细分级别,控制补丁内部分的细分。

                 

#version 450 core

// 输入:来自顶点着色器的顶点数据
layout(location = 0) in vec3 inPosition[3];  // 假设输入为三角形补丁

// 输出:细分级别
out float tessLevelOuter[3];
out float tessLevelInner;

void main() {
    // 动态计算外部细分级别(基于某些条件,如补丁的大小、形状等)
    tessLevelOuter[0] = 5.0;  // 控制边0的细分级别
    tessLevelOuter[1] = 5.0;  // 控制边1的细分级别
    tessLevelOuter[2] = 5.0;  // 控制边2的细分级别

    // 动态计算内部细分级别
    tessLevelInner = 3.0;  // 内部细分级别

    // 输出细分级别
    gl_TessLevelOuter = tessLevelOuter;
    gl_TessLevelInner = tessLevelInner;
}

解释:

  • 负责计算补丁的细分级别,并通过gl_TessLevelOutergl_TessLevelInner将这些级别传递给后续的细分评估着色器(TES)。

    • tessLevelOuter控制补丁的四个边的细分级别。
    • tessLevelInner控制补丁内部的细分级别。

2. 细分评估着色器(TES)

        TES 负责基于细分控制着色器提供的细分级别和当前顶点的插值坐标,计算细分顶点的位置,并生成平滑曲面或其他复杂形状。

输入:

  • gl_TessCoord:当前顶点在补丁内的插值坐标。对于一个三角形补丁,gl_TessCoord是一个三维坐标,其中的每个分量表示该顶点在补丁内的插值位置(如Bézier曲线)。
  • gl_PatchVerticesIn:当前补丁的输入顶点数量。
  • gl_PrimitiveID:当前补丁的ID。

处理:

  • 计算细分顶点:细分评估着色器通过插值计算细分后的顶点位置,生成更精细的几何形状。
  • 生成平滑曲面:通过细分和插值,可以得到平滑的曲面或更复杂的几何形状。

输出:

  • gl_Position:每个细分后的顶点位置。
  • gl_ClipDistance:根据需要进行裁剪的距离。
#version 450 core

// 输入:来自TCS的细分坐标
in float tessLevelOuter[3];    // 外部细分级别
in float tessLevelInner;       // 内部细分级别

// 输入:当前顶点的插值坐标
in vec3 gl_TessCoord;          // 当前顶点在补丁内的坐标

// 输出:细分后的顶点位置
out vec4 fragColor;

void main() {
    // 使用Bézier曲线的插值计算顶点位置
    vec3 p0 = gl_in[0].gl_Position.xyz;  // 顶点0
    vec3 p1 = gl_in[1].gl_Position.xyz;  // 顶点1
    vec3 p2 = gl_in[2].gl_Position.xyz;  // 顶点2

    // 计算细分后的顶点位置
    vec3 p = (1.0 - gl_TessCoord.x - gl_TessCoord.y) * p0 +
             gl_TessCoord.x * p1 + gl_TessCoord.y * p2;

    gl_Position = vec4(p, 1.0);  // 输出计算的顶点位置

    // 可以根据需要计算颜色等其他属性
    fragColor = vec4(p, 1.0);  // 使用顶点位置作为颜色输出
}

        TES与顶点着色器十分相似,总是只有一个输入(质心坐标)和一个输出(顶点)。TES在每次调用过程中只能生成一个顶点,而且它不能丢弃顶点。OpenGL中曲面细分阶段的TES着色器的主要目的就是借助于PG阶段生成的坐标来生成曲面。简单来说就是将质心坐标变换到表示曲面的多项式中并计算出最终结果。结果就是新的顶点的位置,之后这些顶点就能与普通顶点一样进行变换和投影了。如你所见,在处理几何曲面的时候,如果我们选择的TL值越高,我们获得的区域位置就越多,而且通过在TES中对他们进行计算我们得到的顶点就会更多,这样我们就能更好的表示精细的表面。在这一节中表面的计算公式我们简单的使用一个线性组合公式来代替。

        在TES着色器执行之后,产生的新的顶点会被作为三角形传递到渲染管线的下一阶段。在TES之后接下来不管是GS还是光栅化阶段,都和之前的一样了。

        输入:一系列顶点。这些顶点是三角形被分割后产生的新顶点。下面是每个TES程序都必须有的一段代码:

        layout( triangles, fractional_odd_spacing, ccw ) in;

   它表示TES的输入是三角形(当然你也可以写成其他类型的图元),至于 fractional_odd_spacing,和ccw是什么意思,大家看spec吧,很简单,我怕我解释不清楚而误解大家。最后的那个“in”进一步说明了这是TES的输入。

        输出:也是一系列顶点。

        功能:其实在TCS与TES之间有个过程叫Tessellation Primitive Generator(简称TGP),它首先会去查看TES的输入是什么,哦,它要三角形。那么,TGP就会把TCS传入的Patch内部的顶点看成是若干个三角形(注意Patch内部的顶点不一定只有三个)。然后,TGP每次从当前Patch里面取出三个顶点做一个三角形的分割,直到Patch里面的顶点全部被取出。

        每个三角形具体怎么被分割呢?

        其实,gl_TessLevelOuter 和gl_TessLevelInner会被传入给TGP。它们的作用就被体现出来。这就是为什么我前面说的TCS不做分割,只计算分割的度。(注意TGP不是shader,它只是pipeline里面的一个状态而已)

        现在开始讲TES的功能吧。其实TGP传入的顶点的坐标值并不是世界坐标值,而是一个三角形内部的坐标表示形式,大家看到上面的图了吧,三角形顶点上有坐标的,TGP然后根据这个坐标去计算内部新成立的顶点在该局部坐标系内部的坐标。因此,TES就是要把每个顶点的局部坐标变换成世界坐标,以及把顶点相应属性(颜色,纹理坐标等)转换成真正且有效的属性值。每处理一个顶点就输出一个顶点。

        注意:TES并不知道这些顶点会被组成什么图元,它只要求TGP把patch内部的顶点当成什么图元去分割。TES和VS一样,输入是顶点,输出也是顶点。在TES后面有个图元装配的过程,它会根据TES的输入(看上面的那行代码),转换成相应的图元。这里图元装配器会把TES输出的顶点装配成一个一个的三角形。

(三)几何着色器阶段 (Geometry Shader)

        几何着色器(Geometry Shader) 是OpenGL渲染管线中的一个可选阶段,位于顶点着色器和片段着色器之间。其主要功能是处理几何原语(如点、线、三角形),生成新的顶点或修改原有顶点,并可能生成新的几何原语。几何着色器对输入的顶点进行操作,并决定输出的原语类型(例如:点、线、三角形等)。

输入:

  • 来自顶点着色器的顶点数据:几何着色器接收整个几何原语的顶点数据。例如,对于一个三角形,几何着色器将接收到由顶点着色器输出的3个顶点。
  • gl_PrimitiveIDIn:当前原语的ID,用于标识当前处理的原语。
  • gl_InvocationID:当前着色器调用的ID,用于多线程计算,每个线程处理不同的顶点。

处理:

  1. 生成新顶点:几何着色器可以基于输入的顶点生成新的几何细节,或进行几何体的扩展。
    • 例如,生成更多的顶点来细分几何图形,或者将一个线段原语转换为三角形原语。
  2. 修改原有顶点:根据输入顶点计算新的顶点位置或其他属性。
  3. 输出新的几何原语:几何着色器可以改变原始原语类型(例如,将三角形转为点或线段)或将其分割为多个新原语。

输出:

  • gl_Position:每个顶点的新位置,必须输出,用于后续的光栅化阶段。
  • 其他顶点属性(如颜色、法线、纹理坐标等)也可以被输出并传递给片段着色器。

代码举例:

以下是一个简单的几何着色器示例,展示如何将一个三角形原语扩展成多个三角形(类似于几何细分)。

#version 460 core

// 输入顶点:每个几何着色器调用会接收一组三个顶点
layout(triangles) in;   // 输入原语类型为三角形
layout(triangle_strip, max_vertices = 6) out;  // 输出为三角带,最多6个顶点

// 传递给片段着色器的输出
out vec3 fragColor;

void main() {
    // 生成新的顶点,通过顶点位置的微小变动来创建更多的三角形
    for (int i = 0; i < 3; ++i) {
        gl_Position = gl_in[i].gl_Position;  // 保持原顶点位置
        fragColor = vec3(1.0, 0.0, 0.0);  // 传递颜色给片段着色器
        EmitVertex();  // 发射第一个顶点

        // 创建第二个三角形顶点,略微偏移位置
        gl_Position = gl_in[i].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
        fragColor = vec3(0.0, 1.0, 0.0);
        EmitVertex();  // 发射第二个顶点
    }

    EndPrimitive();  // 结束原语生成
}

解释:

  • 输入:几何着色器接收一个三角形原语(layout(triangles) in),即三个顶点。
  • 输出:几何着色器输出一个三角带原语(layout(triangle_strip, max_vertices = 6) out),最多包含6个顶点。通过改变每个顶点的位置和颜色,生成更多的顶点并传递给片段着色器。
  • 处理
    • 在每次循环中,几何着色器通过gl_in[i].gl_Position获取每个输入顶点的坐标,并对其进行偏移(例如,沿X轴偏移0.1)。
    • EmitVertex()发射当前顶点,EndPrimitive()表示当前几何原语的结束。

使用场景:

  1. 细分几何:几何着色器可以用来细分几何图形,增加更多的顶点,甚至创建新的几何形状。
  2. 扩展原语:如将一个线原语扩展成带有宽度的矩形,或将三角形原语转换为多个三角形以增加细节。
  3. 几何体变换:用于复杂的几何变换,如修改法线方向、旋转或缩放原语。

(四) 光栅化阶段 (Rasterization)

光栅化阶段将几何原语(如点、线、三角形等)转换为屏幕空间的片段,并将这些片段传递到片段着色器进行进一步处理。

  • 输入:

    • 来自几何着色器或细分评估着色器的顶点数据。所有的图形原语(点、线、三角形)都将在此阶段被光栅化为片段。
    • 顶点的屏幕空间坐标(gl_Position),用于确定片段在屏幕上的位置。
  • 处理:

    • 光栅化:将图形原语(如三角形)转换为像素。光栅化的过程中,决定哪些片段应该被生成。
    • 裁剪:检查每个片段是否位于视口内,如果不在视口内,则将其丢弃。
    • 深度和模板测试:如果启用了深度和模板测试,光栅化过程会对每个片段进行深度和模板测试,决定是否继续处理该片段。
  • 输出:

    • 片段(fragment):包含颜色、深度等信息的像素候选。

(五)片元着色器阶段 (Fragment Shader)

片段着色器(Fragment Shader)是OpenGL渲染管线的最后一个着色器阶段,负责为每个片段(像素)计算最终的颜色。这个阶段通常涉及颜色计算、光照计算、纹理映射等操作,最终决定片段在屏幕上的表现。

输入:

  • gl_FragCoord:当前片段的屏幕空间坐标。
  • 顶点属性:从前面的阶段(如顶点或几何着色器)传递下来的插值顶点属性,如颜色、法线、纹理坐标等。
  • 其他输入:片段着色器可以接收纹理、光照参数等作为输入。

处理:

  • 颜色计算:根据纹理映射、材质属性和光照模型,计算片段的颜色。
  • 光照计算:使用片段的法线、光源的位置和视角计算光照效果。常见的光照模型有 PhongBlinn-Phong 等。
  • 其他效果:片段着色器也可以处理阴影、反射、折射等高级效果。

输出:

  • gl_FragColor:片段的最终颜色值。
  • gl_FragDepth(可选):用于深度测试的片段深度值。

代码举例:

简单的片段着色器示例(Phong光照模型):

#version 450 core

// 输入:来自顶点着色器或几何着色器的插值属性
in vec3 fragNormal;      // 片段的法线
in vec3 fragPosition;    // 片段的世界空间位置
in vec2 fragTexCoord;    // 片段的纹理坐标

// 输出:片段的最终颜色
out vec4 FragColor;

// 光照参数
uniform vec3 lightPosition;   // 光源的位置
uniform vec3 viewPosition;    // 摄像机的位置
uniform vec3 lightColor;      // 光源的颜色
uniform vec3 objectColor;     // 对象的颜色

// 纹理
uniform sampler2D textureSampler;  // 纹理采样器

void main()
{
    // 1. 环境光
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
    
    // 2. 漫反射光照 (Diffuse)
    vec3 norm = normalize(fragNormal);
    vec3 lightDir = normalize(lightPosition - fragPosition);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // 3. 镜面反射光照 (Specular)
    float specularStrength = 0.5;
    vec3 viewDir = normalize(viewPosition - fragPosition);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;

    // 4. 纹理采样
    vec3 textureColor = texture(textureSampler, fragTexCoord).rgb;

    // 5. 合成最终颜色
    vec3 resultColor = (ambient + diffuse + specular) * textureColor * objectColor;

    // 输出最终颜色
    FragColor = vec4(resultColor, 1.0);
}

解释:

  1. 环境光(Ambient):表示全局照明,片段在没有直接光照时仍然会受到一些散射光的影响。这里通过一个常量 ambientStrength 控制环境光的强度。
  2. 漫反射光照(Diffuse):基于片段法线和光源方向的点积来计算,反映了光源对物体表面的直接照射。法线与光源方向的点积越大,漫反射光越强。
  3. 镜面反射光照(Specular):基于观察方向和反射方向的点积,模拟了光线反射的高光效果。通过 pow 函数控制高光的亮度和大小。
  4. 纹理映射:使用 texture 函数从 textureSampler 采样纹理颜色,并与物体颜色和光照合成最终颜色。
  5. 最终输出:最终合成的颜色通过 FragColor 输出,作为当前片段的颜色。

扩展:

  • 阴影映射:可以在片段着色器中引入阴影图来检测片段是否在阴影中,进而调整光照效果。
  • 反射与折射:可以在片段着色器中使用环境贴图实现反射效果,或通过折射计算实现玻璃、液体等材质的效果。
  • 多光源支持:可以扩展此片段着色器,支持多个光源的累加计算,使场景光照更加丰富。

总结:

        片段着色器是决定物体最终外观的核心阶段,能够通过光照、纹理、反射等多种技术为片段计算出逼真的效果。

(六)后期处理阶段 (Post-Processing)

后期处理阶段包括深度测试、模板测试、混合、颜色运算等操作。

  • 深度测试:片段的深度值会与当前深度缓冲中的值进行比较。如果片段在视图中较远,它将被丢弃。
  • 模板测试:根据模板缓冲中的值决定是否写入帧缓冲。
  • 混合:通过混合操作合成片段的颜色,例如处理透明度。
  • 最终输出:最终的像素数据写入到帧缓冲,显示到屏幕上。

(七)帧缓冲操作 (Framebuffer Operations)

渲染的最终结果通过帧缓冲输出。这个阶段涉及以下操作:

  • 颜色混合:将片段颜色与帧缓冲中的已有颜色进行混合,常见的混合操作有透明度、加色、减色等。
  • 深度/模板测试:在片段着色器输出后进行深度和模板的比较,决定是否继续进行帧缓冲写入。

以下是包含所有着色器的示例程序:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>

const GLuint WIDTH = 800, HEIGHT = 600;  // 窗口的宽和高

GLuint VAO, VBO, shaderProgram;
GLuint tessControlShader, tessEvaluationShader, geometryShader, fragmentShader, vertexShader;

// 1. 定义顶点数据 (三角形的顶点位置与颜色)
GLfloat vertices[] = {
    // 顶点位置 (x, y, z) 和颜色 (r, g, b)
    -0.6f, -0.6f, 0.0f, 1.0f, 0.0f, 0.0f, // 顶点0:红色
    0.6f, -0.6f, 0.0f, 0.0f, 1.0f, 0.0f,  // 顶点1:绿色
    0.0f, 0.6f, 0.0f, 0.0f, 0.0f, 1.0f   // 顶点2:蓝色
};

// 2. 定义着色器代码 (顶点着色器,细分着色器,几何着色器和片段着色器)

// 顶点着色器:将顶点从模型空间转换到裁剪空间
const char* vertexShaderSource = R"(
#version 450 core
layout(location = 0) in vec3 inPosition;  // 顶点位置
layout(location = 1) in vec3 inColor;     // 顶点颜色
out vec3 fragColor;                       // 输出到片段着色器的颜色
uniform mat4 model;  // 模型矩阵
uniform mat4 view;   // 视图矩阵
uniform mat4 projection;  // 投影矩阵
void main() {
    gl_Position = projection * view * model * vec4(inPosition, 1.0);  // 变换到裁剪空间
    fragColor = inColor;  // 传递颜色
}
)";

// 细分控制着色器:控制细分的级别与每个细分片段的颜色
const char* tessControlShaderSource = R"(
#version 450 core
layout(vertices = 3) out;  // 声明一个3顶点的细分
in vec3 fragColor[];  // 输入的顶点颜色
out vec3 tcsColor[];  // 输出到细分评估着色器的颜色
void main() {
    tcsColor[gl_InvocationID] = fragColor[gl_InvocationID];  // 传递颜色
    gl_TessLevelOuter[0] = 4.0;  // 设置外部细分级别
    gl_TessLevelOuter[1] = 4.0;
    gl_TessLevelOuter[2] = 4.0;
    gl_TessLevelInner[0] = 4.0;  // 设置内部细分级别
}
)";

// 细分评估着色器:计算每个细分片段的位置与颜色
const char* tessEvaluationShaderSource = R"(
#version 450 core
layout(triangles, equal_spacing, ccw) in;  // 使用三角形细分,等间隔,逆时针方向
in vec3 tcsColor[];  // 从细分控制着色器传递的颜色
out vec3 fragColor;  // 输出到片段着色器的颜色
void main() {
    vec3 p0 = gl_in[0].gl_Position.xyz;  // 获取每个顶点的坐标
    vec3 p1 = gl_in[1].gl_Position.xyz;
    vec3 p2 = gl_in[2].gl_Position.xyz;
    vec3 color0 = tcsColor[0];  // 获取每个顶点的颜色
    vec3 color1 = tcsColor[1];
    vec3 color2 = tcsColor[2];
    vec3 p = (1.0 - gl_TessCoord.x - gl_TessCoord.y) * p0 +  // 计算细分片段的位置
             gl_TessCoord.x * p1 + gl_TessCoord.y * p2;
    fragColor = (1.0 - gl_TessCoord.x - gl_TessCoord.y) * color0 +  // 计算颜色插值
                gl_TessCoord.x * color1 + gl_TessCoord.y * color2;
    gl_Position = vec4(p, 1.0);  // 设置片段的裁剪空间位置
}
)";

// 几何着色器:用于生成额外的几何图元
const char* geometryShaderSource = R"(
#version 450 core
layout(triangles) in;  // 接收三角形作为输入
layout(triangle_strip, max_vertices = 6) out;  // 输出三角带
in vec3 fragColor[];  // 从细分评估着色器传递的颜色
out vec3 gsColor;  // 输出到片段着色器的颜色
void main() {
    // 遍历每个顶点并生成新位置(做位移)
    for (int i = 0; i < 3; ++i) {
        gl_Position = gl_in[i].gl_Position;  // 原位置
        gsColor = fragColor[i];  // 颜色
        EmitVertex();
        gl_Position = gl_in[i].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);  // 位移后的顶点
        gsColor = fragColor[i];  // 颜色
        EmitVertex();
    }
    EndPrimitive();  // 结束当前几何图元
}
)";

// 片段着色器:最终决定每个像素的颜色
const char* fragmentShaderSource = R"(
#version 450 core
in vec3 gsColor;  // 从几何着色器传递的颜色
out vec4 FragColor;  // 最终输出的颜色
void main() {
    FragColor = vec4(gsColor, 1.0);  // 输出颜色
}
)";

// 3. 编译单个着色器
GLuint compileShader(GLenum shaderType, const char* shaderSource) {
    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &shaderSource, nullptr);
    glCompileShader(shader);
    
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char infoLog[512];
        glGetShaderInfoLog(shader, 512, nullptr, infoLog);
        std::cerr << "Shader Compilation Error: " << infoLog << std::endl;
    }
    return shader;
}

// 4. 创建着色器程序
GLuint createShaderProgram() {
    // 编译各个着色器
    vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
    tessControlShader = compileShader(GL_TESS_CONTROL_SHADER, tessControlShaderSource);
    tessEvaluationShader = compileShader(GL_TESS_EVALUATION_SHADER, tessEvaluationShaderSource);
    geometryShader = compileShader(GL_GEOMETRY_SHADER, geometryShaderSource);
    fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
    
    // 创建并链接着色器程序
    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, tessControlShader);
    glAttachShader(program, tessEvaluationShader);
    glAttachShader(program, geometryShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    
    // 检查程序链接是否成功
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        char infoLog[512];
        glGetProgramInfoLog(program, 512, nullptr, infoLog);
        std::cerr << "Program Linking Error: " << infoLog << std::endl;
    }
    return program;
}

// 5. 初始化OpenGL环境(设置VAO和VBO)
void initOpenGL() {
    glGenVertexArrays(1, &VAO);  // 生成顶点数组对象
    glGenBuffers(1, &VBO);       // 生成顶点缓冲区对象
    
    glBindVertexArray(VAO);      // 绑定VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);  // 绑定VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW());  // 上传数据
    
    // 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);  // 顶点位置
    glEnableVertexAttribArray(0);  // 启用顶点位置属性
    
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));  // 顶点颜色
    glEnableVertexAttribArray(1);  // 启用顶点颜色属性
    
    glBindVertexArray(0);  // 解绑VAO
}

// 6. 渲染循环
void renderLoop(GLFWwindow* window) {
    glm::mat4 model = glm::mat4(1.0f);  // 模型矩阵
    glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));  // 视图矩阵
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)WIDTH / (float)HEIGHT, 0.1f, 100.0f);  // 投影矩阵

    while (!glfwWindowShouldClose(window)) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清屏
        
        glUseProgram(shaderProgram);  // 使用着色器程序
        glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, &model[0][0]);
        glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, &view[0][0]);
        glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, &projection[0][0]);
        
        glBindVertexArray(VAO);  // 绑定VAO
        glPatchParameteri(GL_PATCH_VERTICES, 3);  // 设置细分的顶点数
        glDrawArrays(GL_PATCHES, 0, 3);  // 绘制三角形(细分)
        
        glfwSwapBuffers(window);  // 交换缓冲区
        glfwPollEvents();  // 处理事件
    }
}

int main() {
    if (!glfwInit()) {
        std::cerr << "Failed to initialize GLFW" << std::endl;
        return -1;
    }

    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL Tessellation Example", nullptr, nullptr);
    if (!window) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);  // 设置当前窗口的上下文
    glewInit();  // 初始化GLEW

    glEnable(GL_DEPTH_TEST);  // 启用深度测试
    
    initOpenGL();  // 初始化OpenGL
    shaderProgram = createShaderProgram();  // 创建着色器程序
    renderLoop(window);  // 进入渲染循环
    
    glfwTerminate();  // 结束GLFW
    return 0;
}
  • 四、渲染管线各阶段GLSL的内建变量总结

  • 1. Vertex Shaders内建变量

        Vertex Shaders 具有以下内建输入变量:

        in int gl_VertexID;

        in int gl_InstanceID;

        in int gl_DrawID; // Requires GLSL 4.60 or ARB_shader_draw_parameters

        in int gl_BaseVertex; // Requires GLSL 4.60 or ARB_shader_draw_parameters

        in int gl_BaseInstance; // Requires GLSL 4.60 or ARB_shader_draw_parameters

  • in int gl_VertexID
    当前正在处理的顶点索引。在非索引渲染时,它是当前顶点的有效索引(已处理顶点的数量 + 首个值)。在索引渲染时,它是用来从缓冲区提取当前顶点的索引。
    注意:如果渲染命令中有 baseVertex 参数,gl_VertexID 会加上该参数的值。

  • in int gl_InstanceID
    当前实例的索引,在执行某种形式的实例化渲染时使用。实例计数从 0 开始,即使使用了基础实例调用也是如此。在非实例化渲染时,这个值为 0。
    警告:该值不受某些实例化渲染函数提供的 baseInstance 参数的影响。gl_InstanceID 总是落在半开区间 [0, instancecount) 内。如果使用 GLSL 4.60,你可以使用 gl_BaseInstance 来计算正确的实例索引。

  • in int gl_DrawID
    在多次绘制渲染命令(包括间接多次绘制命令)中,当前绘制命令的索引。第一个绘制命令的 ID 为 0,随着渲染器通过绘制命令时索引递增。
    该值始终是一个动态统一表达式。

  • in int gl_BaseVertex
    渲染命令中 baseVertex 参数的值。如果渲染命令中没有包含该参数,则该输入的值为 0。

  • in int gl_BaseInstance
    实例化渲染命令中 baseInstance 参数的值。如果渲染命令中没有包含该参数,则该输入的值为 0。

Vertex Shaders 具有以下预定义输出变量:

out gl_PerVertex { 
        vec4 gl_Position; 
        float gl_PointSize; 
        float gl_ClipDistance[]; 
}; 

gl_PerVertex 定义了一个输出接口块。该块未指定实例名称,因此不需要在名称前加前缀。

只有当这个着色器是最后一个激活的顶点处理阶段,并且栅格化仍然处于激活状态(即未启用 GL_RASTERIZER_DISCARD)时,这些变量才具有以下含义。下面的文本解释了顶点后处理系统如何使用这些变量。这些变量不能重新声明并带有插值限定符。

  • gl_Position
    当前顶点的裁剪空间输出位置。

  • gl_PointSize
    被栅格化的点的像素宽度/高度。只有在渲染点图元时才有意义。它将被限制在 GL_POINT_SIZE_RANGE 范围内。

  • gl_ClipDistance
    允许着色器设置从顶点到每个用户定义的裁剪半空间的距离。非负距离表示顶点在裁剪平面内/后方,负距离表示顶点在裁剪平面外/前方。数组中的每个元素表示一个裁剪平面。为了使用这个变量,用户必须手动为其显式声明大小。使用 GLSL 4.10 或 ARB_separate_shader_objects 时,整个 gl_PerVertex 块需要重新声明。否则,只需重新声明 gl_ClipDistance 内建变量即可。

2. 曲面细分控制着色器(Tessellation Control Shader, TCS)内建变量

曲面细分控制着色器(Tessellation Control Shader, TCS)输入变量:

  • gl_PatchVerticesIn:输入Patch中的顶点数。
  • gl_PrimitiveID:当前Patch在这个渲染命令中的索引。
  • gl_InvocationID:在当前Patch中TCS的调用索引。TCS通过这个索引来写入每个顶点的输出变量。

TCS还接收顶点着色器输出的内置变量:

  • gl_Position:顶点的位置。
  • gl_PointSize:渲染点图元时的像素宽度/高度。
  • gl_ClipDistance[]:顶点到每个用户定义的裁剪平面的距离。

注意:尽管gl_in被定义为有gl_MaxPatchVertices个条目,但这并不意味着你可以访问超过gl_PatchVerticesIn的索引并得到合理的值。这些变量只有顶点着色器赋予它们的意义。

曲面细分控制着色器(TCS)输出变量:

  • gl_TessLevelOuter[4]:定义了用于曲面细分原始生成器的外层细分级别。它们决定了要应用到Patch的细分程度。具体含义取决于Tessellation Evaluation Shader中定义的Patch类型和其他设置。
  • gl_TessLevelInner[2]:定义了用于曲面细分原始生成器的内层细分级别。

注意:如果任何被抽象Patch类型使用的外层级别是0或负数(或NaN),则该Patch将被生成器丢弃,不会为该Patch产生任何TES调用。

TCS还可以提供以下可选的每个顶点的输出变量:

  • gl_Position:顶点的输出位置。
  • gl_PointSize:点图元的像素宽度/高度。
  • gl_ClipDistance[]:顶点到每个用户定义的裁剪平面的距离。

在TCS中使用这些变量完全是可选的。实际上,它们的语义通常对TCS没有实际价值。它们与顶点着色器中的含义相同,但由于TCS总是由评估着色器跟随,TCS不必写入它们。

3.曲面细分评估着色器(Tessellation Evaluation Shader, TES)内建变量

曲面细分评估着色器(Tessellation Evaluation Shader, TES)输入变量:

  • gl_TessCoord:在细分的抽象Patch中的这个特定顶点的位置。除了这个参数之外,其他每个TES调用中的所有TES调用的输入参数都是相同的。这个vec3的哪些分量有有效值取决于抽象Patch的类型。
  • gl_PatchVerticesIn:正在处理的Patch的顶点计数。这是由TCS指定的输出顶点计数,或者如果没有活动的TCS,则由glPatchParameter指定的Patch顶点大小。
  • gl_PrimitiveID:当前Patch在这次绘制调用中处理的Patch系列的索引。

TES还可以访问TCS或OpenGL提供的细分级别:

  • gl_TessLevelOuter[4]:外层细分级别。
  • gl_TessLevelInner[2]:内层细分级别。

只有抽象Patch实际使用的外层和内层级别才是有效的。

TES还接收TCS输出的每个顶点的内置变量:

  • gl_Position:顶点的位置。
  • gl_PointSize:点图元的像素宽度/高度。
  • gl_ClipDistance[]:顶点到每个用户定义的裁剪平面的距离。

注意:尽管gl_in被定义为有gl_MaxPatchVertices个条目,但这并不意味着你可以访问超过gl_PatchVerticesIn的索引并得到合理的值。

曲面细分评估着色器(TES)输出变量:

  • gl_PerVertex:定义了输出的接口块。这个块被定义为没有实例名称,因此不需要前缀名称。
    • gl_Position:当前顶点的剪辑空间输出位置。
    • gl_PointSize:正在光栅化的点图元的像素宽度/高度。只有在渲染点图元时才有意义,这在TES中需要使用point_mode输入布局限定符。
    • gl_ClipDistance[]:允许着色器设置顶点到每个用户定义的裁剪平面的距离。正值意味着顶点在裁剪平面内部/后方,负值意味着它在裁剪平面外部/前方。数组中的每个元素代表一个裁剪平面。要使用此变量,用户必须手动用明确的尺寸重新声明它。

4.几何着色器(Geometry Shader, GS)内建变量

几何着色器(Geometry Shader, GS)输入变量:

  • gl_PerVertex:由之前的着色器阶段传递的变量,如顶点位置、点大小和裁剪距离。
  • gl_PrimitiveIDIn:基于GS处理的原始数量的当前输入图元的ID。
  • gl_InvocationID:当前实例的索引,定义在使用实例化几何着色器时。

几何着色器(GS)输出变量:

  • gl_PerVertex:定义了输出的接口块。这个块被定义为没有实例名称,因此不需要前缀名称。
    • gl_Position:当前顶点的剪辑空间输出位置。如果你正在流0中发射顶点,除非关闭光栅化,否则必须写入此值。
    • gl_PointSize:正在光栅化的点图元的像素宽度/高度。仅在输出点图元时需要写入。
    • gl_ClipDistance[]:允许着色器设置顶点到每个用户定义的裁剪平面的距离。要使用此变量,用户必须手动用明确的尺寸重新声明它。

某些预定义的输出具有特殊的含义和语义:

  • gl_PrimitiveID:将传递给片段着色器。特定线/三角形的Primitive ID将取自该线/三角形的provoking顶点,因此确保为正确的provoking顶点写入正确的值。

分层渲染在GS中通过两个特殊的输出变量工作:

  • gl_Layer:定义了原始数据在分层图像中要去的层。每个原始数据中的顶点必须获得相同的层索引。
  • gl_ViewportIndex:指定了与此原始数据一起使用的视口索引。

5.片段着色器(Fragment Shader)内建变量

片段着色器(Fragment Shader)输入变量:

  • gl_FragCoord:片段在窗口空间中的位置。X、Y和Z组件是片段的窗口空间位置。如果此着色器阶段没有写入gl_FragDepth,则Z值将写入深度缓冲区。gl_FragCoord的W组件是1/Wclip,其中Wclip是从最后一个顶点处理阶段输出到gl_Position的剪辑空间顶点位置的插值W组件。
  • gl_FrontFacing:如果片段由图元的背面生成,则此变量为false;否则为true。
  • gl_PointCoord:在点图元内定义片段相对于点边的位置。点有效地被光栅化为窗口空间的某个像素大小的正方形。由于点由单个顶点定义,唯一的方法是使用gl_PointCoord来告诉特定的片段在该正方形内的位置。

OpenGL 4.0及以上版本定义了额外的系统生成的输入值:

  • gl_SampleID:当前样本的整数标识符。
  • gl_SamplePosition:当前样本在片段内的像素区域的位置,值在范围[0, 1]内。
  • gl_SampleMaskIn[]:在使用多样本时,此变量包含正在生成的片段的样本掩码的位字段。

一些片段着色器内置输入将采用OpenGL指定的值,但这些值可以被用户控制的值覆盖:

  • gl_ClipDistance[]:此数组包含由最后一个顶点处理阶段的顶点输出的插值裁剪平面半空间。
  • gl_PrimitiveID:这个值是由本次绘制命令渲染的当前图元的索引。这包括应用于网格的任何细分,因此每个单独的图元都将有一个唯一的索引。

片段着色器(FS)输出变量:

  • gl_FragDepth:片段的深度。如果着色器没有静态写入此值,则它将取gl_FragCoord.z的值。

6. 计算着色器(Compute Shader)内建变量

计算着色器(Compute Shader)输入变量:

  • gl_NumWorkGroups:传递给调度函数的工作组数量。
  • gl_WorkGroupID:当前工作组的索引。
  • gl_LocalInvocationID:当前工作组内的着色器调用的索引。
  • gl_GlobalInvocationID:这个值在所有计算调度调用的所有着色器调用中唯一标识了这个计算着色器调用。
  • gl_LocalInvocationIndex:这是gl_LocalInvocationID的一维版本。它标识了工作组内这个调用的索引。

计算着色器其他变量:

  • gl_WorkGroupSize:一个常量,包含着色器的工作组大小,以三维形式存在。

7. 着色器统一变量:

  • gl_DepthRange:提供对glDepthRange近端和远端值的访问。
  • gl_NumSamples:当前帧缓冲区的样本数量。如果帧缓冲区不是多样本的,则此值为1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值