现代图形渲染管线
在现代图形渲染管线中,顶点处理是一个复杂的过程,涉及多个阶段,主要包括顶点着色器、曲面细分、几何着色器等。以下是这些阶段的流程和细节:
- 顶点着色器(Vertex Shader)
这是顶点处理的第一阶段,也是必须的阶段。顶点着色器是一个可编程阶段,开发者可以编写着色器代码来执行以下任务:
变换:将顶点从模型空间(局部空间)变换到世界空间,再变换到视图空间,最后变换到裁剪空间。这通常涉及模型-视图-投影矩阵的乘法。
光照计算:在顶点级别进行光照计算,如Gouraud着色。
法线变换:如果进行光照计算,需要将法线从模型空间变换到视图空间。
纹理坐标处理:生成或变换纹理坐标。
其他属性处理:如颜色、切线等。
顶点着色器的输出是每个顶点的裁剪空间坐标,以及任何需要传递给后续阶段的顶点属性。
- 曲面细分(Tessellation)【可选】
曲面细分是一个可选的阶段,它可以在硬件层面上动态增加几何细节。这个阶段通常包括两个部分:
曲面细分控制着色器(Tessellation Control Shader, TCS):这个着色器可以控制曲面细分的程度,它会输出细分级别和其他控制点的数据。
曲面细分评估着色器(Tessellation Evaluation Shader, TES):根据TCS的输出,TES会生成细分后的顶点,并进行适当的变换。
3. 几何着色器(Geometry Shader)【可选】
4. 几何着色器(Geometry Shader)是另一个可编程阶段,它在顶点着色器和光栅化之间执行。这个阶段是可选的,不是所有的渲染管线都会使用它。几何着色器的主要功能包括:
图元生成:几何着色器可以从输入的图元(点、线、三角形等)生成新的图元。例如,它可以将一个点扩展成一个四边形,或者根据某些条件丢弃某些图元。
图元变换:它可以对图元的顶点进行进一步的变换,比如实现几何放大效果。
层次渲染:在渲染到3D纹理或层次化帧缓冲对象时,几何着色器可以决定输出到哪一层。
视图管理:在多视图渲染(如立体3D渲染)中,几何着色器可以为每个视图生成不同的图元。
几何着色器的输入是顶点着色器输出的顶点,它们组成的图元,以及任何传递的顶点属性。几何着色器的输出是新的图元,它们将被送入下一个阶段。
- 光栅化(Rasterization)
光栅化不是一个可编程阶段,但它是顶点处理后的重要步骤。在光栅化阶段,几何图元被转换成片元(fragments),这些片元对应屏幕上的像素。光栅化过程包括:
裁剪:在光栅化之前,所有超出视图体积的图元都会被裁剪掉。
透视除法:将顶点的裁剪空间坐标转换为归一化设备坐标(NDC)。
视口变换:将NDC坐标转换为屏幕空间坐标。
深度测试准备:为每个片元计算深度值,以便后续进行深度测试。
5. 片元着色器(Fragment Shader)
片元着色器是光栅化之后的一个可编程阶段,它对每个片元进行处理,确定最终像素的颜色和其他属性。这个阶段可以执行:
纹理映射:将纹理应用到片元上。
光照计算:在片元级别进行更精确的光照计算,如Phong着色。
颜色混合:计算多个片元颜色值的混合。
深度、模板测试:根据深度和模板缓冲区的值决定是否丢弃片元。
6. 输出合并(Output Merging)
这是渲染管线的最后阶段,它负责将片元着色器的输出合并到帧缓冲区。这个阶段包括:
深度和模板测试:根据深度和模板缓冲区的值确定片元是否可见。
颜色混合:将片元的颜色与帧缓冲区中已有的颜色根据特定规则混合。
抖动和伽马校正:对颜色值进行最终的处理,以适应显示设备的特性。
这些步骤共同构成了现代图形渲染管线中的顶点处理流程。开发者可以通过编写着色器代码来自定义顶点着色器、几何着色器、片元着色器等阶段的行为,以实现各种图形效果。在这个过程中,GPU的并行处理能力被充分利用,以高效地处理成千上万的顶点和片元。
-
帧缓冲区(Frame Buffer)
最终,经过输出合并阶段处理后的像素数据会被写入帧缓冲区。帧缓冲区是GPU中的一块内存,用于存储最终渲染的图像。这个图像随后可以被显示在屏幕上,或者作为纹理在后续的渲染过程中使用。 -
后处理(Post-Processing)【可选】
虽然不是传统渲染管线的一部分,但在现代图形渲染中,经常会有一个额外的后处理阶段。在这个阶段,可以对帧缓冲区中的图像进行各种效果处理,如:
色彩校正:调整图像的亮度、对比度、饱和度等。
模糊和滤镜效果:应用高斯模糊、运动模糊、边缘检测等滤镜效果。
抗锯齿处理:减少图像中的锯齿状边缘,如FXAA(快速近似抗锯齿)。
HDR渲染和色调映射:处理高动态范围图像,并将其映射到低动态范围的显示设备上。
后处理通常是通过将帧缓冲区的内容作为纹理输入到一个全屏四边形(或两个三角形)上,并运行一个片元着色器来实现的。这个过程可以重复多次,以应用多个后处理效果。
总结
整个渲染管线是一个从3D模型数据到最终2D图像的转换过程,涉及多个阶段的复杂计算。每个阶段都有其特定的作用,而且很多阶段是可编程的,这给开发者提供了极大的灵活性来创造所需的视觉效果。随着图形硬件的发展,渲染管线也在不断进化,支持更多的功能和更高效的处理方式。
Vertex Shader
顶点着色器(Vertex Shader)是现代图形管线中的第一个可编程阶段,它主要负责处理每个顶点的数据。以下是实现顶点着色器的一般步骤和细节:
- 着色器编写
首先,你需要使用着色器语言(如GLSL、HLSL或Metal Shading Language)编写顶点着色器的代码。这段代码通常包含以下部分:
输入属性:定义顶点的输入属性,如位置、法线、纹理坐标等。
输出属性:定义传递给下一阶段(如曲面细分控制着色器或几何着色器,或者直接到片元着色器)的属性。
统一变量(Uniforms):定义全局常量,如变换矩阵、光源参数等,这些变量在渲染过程中保持不变。
主函数:编写主函数,这是顶点着色器的入口点,它描述了顶点数据如何被处理。
2. 着色器编译
编写完成后,顶点着色器代码需要被编译成GPU可以理解的形式。这通常在应用程序初始化时完成,具体步骤依赖于使用的图形API:
OpenGL:使用glCreateShader、glShaderSource和glCompileShader函数编译着色器。
DirectX:使用HLSL编译器(如fxc)或者运行时编译API(如D3DCompile)编译着色器。
Vulkan:通常使用GLSLang或SPIRV-Cross工具将GLSL代码编译成SPIR-V格式。
3. 着色器链接
编译后的着色器需要与其他着色器(如片元着色器)一起链接成一个着色器程序,这样才能在渲染管线中使用OpenGL:使用glAttachShader和glLinkProgram函数将编译后的着色器附加到一个程序对象并链接它们。
DirectX:在DirectX中,顶点着色器和像素着色器通常分别编译,然后在创建管线状态对象时指定。
Vulkan:在Vulkan中,你需要创建一个着色器模块,并在管线创建时指定它。
4. 设置顶点数据
在GPU中使用顶点着色器之前,需要将顶点数据上传到GPU内存中。这通常涉及以下步骤:
创建并填充顶点缓冲区(Vertex Buffer)。
定义顶点输入布局(Input Layout),这告诉GPU如何解释顶点缓冲区中的数据。
将顶点缓冲区绑定到图形管线。
5. 设置统一变量
在渲染之前,还需要设置顶点着色器中使用的统一变量。这些变量可能包括:
模型变换矩阵(Model Matrix):将模型从模型空间变换到世界空间。
视图变换矩阵(View Matrix):将模型从世界空间变换到观察空间。
投影变换矩阵(Projection Matrix):将模型从观察空间变换到裁剪空间。
6. 渲染调用
一旦顶点着色器准备好,并且顶点数据和统一变量都设置完毕,就可以发出渲染调用了。这个调用会触发图形管线,并执行顶点着色器。
- 顶点处理
在顶点着色器的主函数中,每个顶点都会经历以下处理:
变换:应用模型、视图和投影矩阵变换顶点位置。
光照计算(可选):在顶点级别进行简单的光照计算。
传递数据:计算并传递任何需要在后续阶段使用的输出变量,如变换后的位置、法线、纹理坐标等。
-
输出和传递
顶点着色器处理完毕后,它会输出每个顶点的最终位置以及其他任何传递给下一阶段的变量。这些输出变量随后会被插值处理,作为片元着色器的输入。 -
调试和优化
顶点着色器编写和设置完成后,可能需要进行调试和优化:
调试:确保顶点着色器正确地执行了预期的数学运算和逻辑。可以使用图形调试工具来检查顶点着色器的输出,或者在着色器代码中添加临时输出来帮助理解数据流。
优化:顶点着色器的性能优化可能包括减少计算量、优化数学运算、使用更高效的数据结构等。在某些情况下,也可以考虑使用更低级别的着色器语言或汇编语言来手动优化特定的运算。
10. 状态管理
在实际的渲染循环中,可能需要根据不同的渲染任务切换不同的顶点着色器或着色器程序。因此,状态管理变得非常重要:
切换着色器程序:在渲染不同的物体时,可能需要切换不同的着色器程序。
更新统一变量:在渲染过程中,如果物体的变换或者光照条件发生变化,需要更新顶点着色器中的统一变量。
11. 兼容性和跨平台
编写顶点着色器时,还需要考虑不同平台和不同图形API的兼容性问题。例如,不同的图形API可能有不同的着色器语言和不同的性能特点。
- 清理资源
在应用程序结束或者不再需要某个着色器程序时,应该释放相关资源,如着色器对象和程序对象,以避免内存泄漏。
通过上述步骤,顶点着色器就可以实现并在图形管线中发挥作用了。顶点着色器的实现细节可能会根据具体的应用场景和性能要求有所不同,但基本的原理和流程是相似的。
图元装配(Primitive Assembly)
图元装配(Primitive Assembly)是渲染流水线中的一个阶段,它将顶点数据组装成图元,如点、线、三角形等。这些图元是构成几何形状的基本单元,用于后续的光栅化和片段处理阶段。
在图元装配阶段,顶点数据经过顶点处理阶段的变换和处理后,被传递到图元装配阶段进行组装。具体的图元类型取决于应用程序中使用的几何形状,常见的图元类型包括:
点(Point):由单个顶点组成,通常用于表示粒子、光源等。
线(Line):由两个顶点组成,通常用于表示线段、曲线等。
三角形(Triangle):由三个顶点组成,是最常用的图元类型,用于表示平面形状。
其他复杂的图元类型,如四边形、多边形等,可以通过三角形拆分或其他技术进行处理。
图元装配阶段的主要任务是将顶点数据按照图元类型进行组装,并生成完整的图元数据。这些图元数据将在后续的光栅化阶段使用,用于确定每个像素的位置和覆盖率。
图元装配阶段是渲染流水线中的重要一环,它为后续的光栅化和片段处理阶段提供了几何形状的基本单元。通过图元装配,顶点数据被组装成图元,为最终的图像生成奠定了基础。
在图元装配阶段,顶点数据被组装成图元后,还可以进行一些其他的处理和操作。以下是一些常见的图元装配后处理步骤:
图元剔除(Backface Culling):根据图元的朝向和相机位置,决定是否剔除(丢弃)某些图元。例如,背对相机的三角形可能不会对最终图像产生可见的影响,因此可以被剔除以提高渲染性能。
图元裁剪(Clipping):对于超出视锥体范围的图元,进行裁剪操作,只保留位于视锥体内的部分。这样可以减少后续光栅化和片段处理的计算量。
图元排序(Primitive Sorting):在某些情况下,需要对图元进行排序,以确保正确的渲染顺序。例如,透明物体的渲染需要按照深度排序,以正确处理混合效果。
图元分割(Tessellation):对于需要更高细节级别的曲面或形状,可以使用图元分割技术将原始图元细分为更多的小图元。这可以提供更精细的几何细节和曲面光滑度。
这些图元装配后处理步骤可以根据具体的渲染需求进行选择和应用。它们的目的是优化渲染过程,提高性能和图像质量。在现代图形编程中,还可以使用可编程着色器管线来自定义和扩展图元装配后处理步骤,以实现更高级的图形效果和渲染技术。
当图元装配完成后,接下来的步骤是光栅化(Rasterization)。光栅化将图元转换为屏幕上的像素,确定每个像素的位置和覆盖率。以下是光栅化阶段的主要步骤:
三角形设置(Triangle Setup):对于每个输入的三角形图元,光栅化阶段首先进行三角形设置。这包括计算三角形的边界框(bounding box)和边的斜率(slope)。
扫描线转换(Scanline Conversion):在光栅化过程中,以扫描线为单位进行处理。对于每个扫描线,确定与三角形相交的像素。
插值(Interpolation):在扫描线上,对于每个相交的像素,根据顶点属性的插值,计算像素的颜色、纹理坐标、法线等属性。这样可以在三角形内部实现平滑的渐变效果。
裁剪(Clipping):对于超出屏幕边界的像素,进行裁剪操作,只保留位于屏幕内的像素。
深度测试(Depth Testing):对于每个像素,进行深度测试,比较像素的深度值与帧缓冲中对应位置的深度值。根据测试结果,决定是否更新帧缓冲中的深度值。
覆盖测试(Coverage Testing):对于每个像素,进行覆盖测试,判断像素是否被其他图元覆盖。根据测试结果,决定是否更新帧缓冲中的颜色值。
光栅化阶段是渲染流水线中的关键步骤,将几何形状转换为屏幕上的像素。通过插值、裁剪、深度测试和覆盖测试等操作,确定每个像素的最终颜色和深度值。这些像素数据将在后续的片段处理阶段进行进一步处理,最终生成最终的图像输出。
Rasterization
光栅化(Rasterization)是3D图形渲染管线中的一个核心过程,它负责将顶点着色器处理过的顶点数据转换成片元(也称为像素片段),这些片元随后会被送入片元着色器进行进一步处理。以下是光栅化的具体流程和细节:
-
顶点处理
在光栅化之前,顶点着色器已经处理了每个顶点的数据,包括位置、颜色、纹理坐标等,并且将它们变换到了裁剪空间。 -
裁剪
在光栅化之前,顶点数据会经过裁剪阶段,移除那些位于视锥体外的顶点。这个过程确保只有位于摄像机视野内的几何体会被渲染。 -
屏幕映射
裁剪后的顶点坐标会被映射到屏幕空间。这通常涉及将顶点坐标从裁剪空间转换到标准化设备坐标(NDC),然后根据视口(viewport)大小映射到屏幕坐标。 -
三角形设置
顶点数据会被组织成三角形。在这个阶段,图形硬件会准备三角形的数据,包括边界和属性,以便进行光栅化。 -
三角形遍历
图形硬件会遍历三角形的每个像素位置,确定哪些像素属于三角形内部。这个过程称为三角形遍历或扫描转换。 -
深度测试和剔除
对于每个像素位置,光栅化器会进行深度测试,比较像素的深度值与深度缓冲区中的值。如果像素被遮挡(即深度测试失败),它会被剔除,不会产生片元。 -
片元生成
对于通过深度测试的像素位置,光栅化器会生成片元。每个片元包含了像素的屏幕坐标、深度值以及插值后的顶点属性(如颜色、纹理坐标、法线等)。 -
属性插值
在三角形的顶点属性被传递到片元之前,它们会根据片元在三角形内的相对位置进行插值。这个过程确保了纹理和颜色在三角形内平滑过渡。 -
面向前的片元
在某些情况下,如启用了背面剔除,那些面向远离摄像机的三角形的片元会被剔除,这可以提高渲染效率。 -
片元着色器准备
生成的片元随后会被送入片元着色器。在这个阶段,片元着色器会根据片元的属性计算最终颜色、纹理、光照等效果。 -
其他测试和混合
在片元着色器处理之后,可能会进行其他的测试,如模板测试、alpha测试等。通过这些测试的片元会被送到混合阶段,与颜色缓冲区中的颜色根据特定的混合模式进行混合。 -
写入帧缓冲
最后,处理过的片元颜色会被写入帧缓冲区,这个缓冲区最终会被显示到屏幕上,形成最终的图像。 -
多重采样抗锯齿(MSAA)
如果启用了多重采样抗锯齿,光栅化过程会为每个像素生成多个样本。这些样本会独立进行深度和模板测试,然后在片元着色器中处理。最终,这些样本会被合并(解析)成单个像素颜色,以减少边缘的锯齿效果。 -
透明度处理
对于具有透明度的物体,光栅化过程需要正确处理透明度以确保正确的混合和遮挡关系。这通常涉及到对透明物体进行排序和/或使用特殊的混合模式。 -
逐片元操作
在最终确定片元颜色并写入帧缓冲之前,还会进行一系列逐片元操作,包括:
模板测试:根据模板缓冲区的值和预设的模板操作来决定是否保留片元。
深度测试:更新深度缓冲区,如果片元通过了深度测试。
混合操作:根据当前的混合状态和帧缓冲区中已有的颜色值,计算最终颜色值。
逻辑操作:在某些情况下,可以对颜色值执行逻辑操作(如AND、OR等)。
16. 渲染目标写入
最后,片元的颜色值会被写入到渲染目标(通常是帧缓冲区的颜色附件)中。如果有多个渲染目标,这个过程会对每个目标重复执行。
-
后处理
在所有片元处理完成并写入帧缓冲后,可能会执行一系列后处理操作,如伽马校正、色彩校正、HDR渲染、运动模糊、景深效果等。这些操作通常在渲染管线的最后阶段进行,以增强最终图像的视觉效果或者实现特定的图像风格。 -
帧缓冲区交换
在所有渲染和后处理操作完成后,当前帧缓冲区的内容需要显示到屏幕上。在双缓冲或三缓冲的系统中,这涉及到帧缓冲区的交换。前端缓冲区(当前显示的缓冲区)和后端缓冲区(渲染目标缓冲区)会交换,新渲染的图像出现在屏幕上,而之前显示的图像则成为新的渲染目标。 -
同步和延迟
为了避免撕裂现象(图像的上半部分和下半部分不同步),通常会使用垂直同步(V-Sync)来确保帧缓冲区交换发生在显示器的垂直刷新周期内。在某些高级渲染系统中,还可能使用延迟渲染或其他技术来进一步提高效率和图像质量。 -
资源清理
渲染一帧之后,可能需要清理或更新某些资源,如释放不再需要的纹理或缓冲区,或者为下一帧更新动态纹理等。 -
准备下一帧
最后,渲染管线会开始准备下一帧的渲染,这可能包括更新场景数据、处理用户输入、执行物理模拟等。
光栅化是一个复杂的过程,涉及到许多子步骤和优化。现代图形硬件和图形API(如DirectX 12、Vulkan、OpenGL)都提供了高度优化的光栅化管线,但理解这些基本步骤对于高效使用这些工具和调试图形程序是非常有帮助的。
Fragment Shader
片元着色器(Fragment Shader)是渲染管线中的一个重要部分,它负责处理渲染过程中的每一个像素(或称为片元)。片元着色器的主要任务是计算每个片元的颜色和深度。以下是片元着色器的一般实现步骤和一些细节:
输入:片元着色器的输入通常来自顶点着色器(Vertex Shader)。顶点着色器会处理每个顶点的属性,然后将这些属性插值到每个片元。这些属性可能包括位置、颜色、纹理坐标、法线等。
纹理采样:片元着色器通常会使用纹理坐标来从纹理中采样颜色。这个过程可能会涉及到一些复杂的操作,例如mipmap选择、纹理过滤、纹理重复等。
光照计算:片元着色器通常会根据法线、光源位置、视点位置等信息来计算光照效果。这个过程可能会涉及到一些复杂的光照模型,例如Phong模型、Blinn-Phong模型、PBR模型等。
颜色计算:片元着色器会根据纹理颜色、光照效果、片元的颜色等信息来计算最终的颜色。这个过程可能会涉及到一些复杂的混合操作,例如片元着色器(Fragment Shader)是渲染管线中的一个重要部分,它负责处理每个像素(或称为片元)的颜色和深度。以下是片元着色器的一般实现步骤和一些细节:
输入:片元着色器的输入通常包括从顶点着色器(Vertex Shader)传递过来的插值数据,如颜色、纹理坐标、法线等,以及一些全局的统一变量(Uniforms),如光源位置、材质属性等。
纹理采样:如果物体使用了纹理映射,片元着色器会根据输入的纹理坐标从纹理中采样颜色。这个过程可能会涉及到一些纹理过滤操作,如双线性过滤或三线性过滤。
光照计算:片元着色器通常会进行一些光照计算,以模拟光源对物体的影响。这个过程可能会使用到一些光照模型,如Phong模型或Blinn-Phong模型。
颜色计算:片元着色器会根据采样的纹理颜色、光照计算的结果以及物体的颜色属性来计算最终的颜色。
输出:片元着色器的输出通常是一个RGBA颜色值,这个颜色值会被写入到帧缓冲区的对应像素中。在某些情况下,片元着色器也可能输出深度值或者其他一些自定义的数据。
环境映射:环境映射是一种模拟反射和折射效果的技术。在片元着色器中,可以使用立方体贴图(Cubemap)或者球形贴图(Spheremap)来实现环境映射。
阴影映射:阴影映射是一种模拟阴影效果的技术。在片元着色器中,可以使用深度贴图(Depth Map)或者阴影贴图(Shadow Map)来实现阴影映射。
后处理效果:后处理效果是一种在渲染结果上添加额外效果的技术,例如模糊、色调映射(Tone Mapping)、抗锯齿(Anti-aliasing)等。在片元着色器中,可以通过操作帧缓冲区的像素来实现各种后处理效果。
物理基础渲染(PBR):物理基础渲染是一种更真实地模拟光照和材质的技术。在片元着色器中,可以使用基于物理的光照模型和材质参数来实现PBR。
透明和半透明效果:在片元着色器中,可以通过操作颜色的alpha通道来实现透明和半透明效果。但是需要注意的是,透明和半透明效果可能会引入一些排序和混合的问题。
以上只是一些基本的片元着色器的实现细节和技术,实际上在现代的图形API中,片元着色器可以实现各种复杂和创新的效果。
Output Merger
输出合并(Output Merger)是渲染管线的最后一个阶段,它负责将片元着色器的输出结果(即片元颜色)与帧缓冲区(Frame Buffer)中的现有颜色进行合并。以下是输出合并阶段的一些具体细节:
深度测试(Depth Test):深度测试是用来确定一个片元是否被其他片元遮挡的。每个片元都有一个深度值,这个深度值会与深度缓冲区(Depth Buffer)中的对应值进行比较,如果深度测试失败(例如,新的片元深度值大于深度缓冲区中的值,表示新的片元被其他片元遮挡),那么这个片元就会被丢弃,不会进行后续的处理。
在输出合并阶段,还有一些其他的重要概念和技术:
多重采样抗锯齿(MSAA):多重采样抗锯齿是一种常用的抗锯齿技术。在这个过程中,渲染器会在每个像素内部进行多次采样,然后将这些采样结果进行平均,以得到最终的颜色。这可以有效地减少锯齿效果,使得图像边缘更加平滑。
HDR渲染和色调映射:在现代的图形渲染中,常常会使用高动态范围(HDR)渲染和色调映射技术。HDR渲染可以产生比普通的8位颜色更广的颜色范围,而色调映射则是将HDR渲染的结果映射到显示器可以显示的颜色范围内。
Gamma校正:Gamma校正是一种用来调整图像亮度的技术。由于人眼对亮度的感知是非线性的,因此在将线性的颜色值显示到屏幕上之前,通常需要进行Gamma校正。
帧缓冲区的管理:在输出合并阶段,还需要管理帧缓冲区。例如,可能需要切换前缓冲区和后缓冲区(用于双缓冲技术),或者清除帧缓冲区的颜色、深度和模板值等。
以上就是输出合并阶段的一些更深入的细节和技术。需要注意的是,这些技术的具体实现可能会依赖于特定的图形API和硬件。
模板测试(Stencil Test):模板测试是一种可以自定义的测试,它可以用来实现各种特殊的效果,如镜面反射、阴影卷积等。模板测试会使用一个模板缓冲区(Stencil Buffer),每个片元都有一个模板值,这个模板值会与模板缓冲区中的对应值进行比较,如果模板测试失败,那么这个片元就会被丢弃。
混合(Blending):混合是将片元颜色与帧缓冲区中的现有颜色进行合并的过程。混合可以实现各种效果,如透明、半透明、加亮等。混合通常会使用一些混合方程和混合因子,例如,可以使用源颜色的alpha值作为混合因子,来实现半透明效果。
抖动(Dithering):抖动是一种用来减少颜色带状效应(Color Banding)的技术。抖动会在颜色值上添加一些随机的噪声,使得颜色过渡更加平滑。
以上是输出合并阶段的一些具体细节,但实际上在不同的图形API和硬件中,输出合并阶段可能会有一些不同的实现和特性。
242

被折叠的 条评论
为什么被折叠?



