MLAA:高效地将抗锯齿处理从 GPU 迁移至 CPU

下载文章和源代码

英文版下载 MLAA:高效地将抗锯齿处理从 GPU 迁移至 CPU (PDF 1.2MB)
访问 MLAA 示例页可下载源代码。


简介

高效的抗锯齿技术是进行高品质、实时渲染的重要工具。MSAA(多点采样抗锯齿处理)是目前使用的标准技术,但有一些严重的缺点:

  • 与延迟照明不兼容,后者在实时渲染中使用得越来越多;
  • 高内存和处理开销,这使其在一些流行平台(如索尼 Playstation* PS3*)上的使用受限 [Perthuis 2010]。这种开销还直接与渲染场景的复杂性关联;
  • 除非与阿尔法覆盖通道一起使用,否则无法对非几何边界进行平滑处理。

英特尔实验室开发的一项新技术形态学抗锯齿处理(Morphological Antialiasing,MLAA) [Reshetov 2009] 解决了这些限制。MLAA 是一种基于图像的后处理过滤技术,它可以标识不连续模式,并混合这些模式的相邻模式中的色彩来执行有效的抗锯齿处理。它是新一代实时抗锯齿技术的先驱,可与 MSAA 抗衡 [Jimenez et al., 2011] [SIGGRAPH 2011]。

此示例基于由 Reshetov 提供的基于 CPU 的原始 MLAA 实施,并进行了改进以大幅提高性能。这些改进包括:

  • 集成一种高效且易用的新任务处理系统,在英特尔® 线程构建模块(Threading Building Blocks,TBB)上实施。
  • 集成一种高效且易用的新管道传输系统,用于 CPU 图形任务装载。
  • 通过新转置通道改进数据存取模式。
  • 增加使用英特尔® SSE 指令以优化中断检测和颜色混合。

MLAA 算法

本节将概述 MLAA 算法的原理;请参阅 [Reshetov 2009] 了解详尽说明。从概念上说,MLAA 分三步处理图像缓冲:

  1. 查找指定图像中的像素间中断。
  2. 标识 U 形、Z 形和 L 形模式。
  3. 混合这些模式的相邻模式中的颜色。

第一步(查找中断)通过将每个像素与其相邻像素比较实现。横向中断通过将像素与其下方相邻像素比较进行识别,纵向中断通过比较像素与其右方相邻像素进行识别。在我们的实施中我们比较了颜色值,但适合应用程序特点的任何其他方法也完全有效。

第一步结束时,如果检测到中断,则相应的每个像素都会用横向中断标志和/或纵向标志进行标记。在下一步中,我们会沿标记的像素来确定中断线(标记有相同中断标志的连续像素序列),并确定它们如何组合成 L 形模式,如下图中所示:

Figure 1
图 1: MLAA 图像处理,图像具有左侧原始图像中所示的 Z 形、U 形和 L 形形状

第三步(即最后一步)是对每个标识的 L 形模式进行混合。

一般的想法是将 L 形形状主线段(下图中的水平绿线)的中点连接到次线段(垂直绿线)的中点(连线为红线)。连线将每个像素分成两个梯形;对于每个像素,对应梯形的面积确定混合权重。例如,在下图中,像素 c5 的梯形面积是 1/3;因此,c5 的新颜色计算方式为 2/3 *(c5 的颜色)+ 1/3 *(c5 下面相邻像素的颜色)。


图 2: 计算混合权重

在实践中,为了确保有平滑的轮廓外观,我们需要最大程度地减少连续 L 形形状结合位置的颜色差异。为此,我们根据结合点周围的像素颜色对中间位置周围的 L 形线段上的连接点进行细微调整。

 

使用示例

镜头可以通过拖动和点击围绕场景移动,鼠标滚轮可用于进行放大或缩小。此外,在示例窗口的右侧还有三个控制块:


图 3:操作中示例的屏幕快照

第一个块控制场景渲染:

  • “暂停场景”切换场景动画的开关;
  • “显示缩放框”切换缩放框开关,用户可用于更近距离地观察像素区域以更好地比较抗锯齿处理技术。对于感兴趣的区域,用于可以通过右键单击更改为要检查的新区域;
  • “场景复杂性”通过重绘来模拟提高场景复杂性的效果。值(在 1 和 100 之间,可通过滑块调整)代表场景的每帧渲染次数(通过重绘)。

第二个块用于选择要应用的抗锯齿处理技术:MLAA、MSAA (4x) 或无抗锯齿处理。这当然也可用于比较各种技术的性能和质量(默认选择为 MLAA);

最后一个控制块仅在使用 MLAA 作为抗锯齿处理技术时可用,用于控制算法的运行方式:

  • “仅复制/映射/解除映射”在 GPU 内存与 CPU 内存之间来回复制颜色缓冲,但不会在两次复制操作之间执行任何 MLAA 处理。这样可以测量复杂操作对整个算法整体性能的影响;
  • “CPU 任务管道传输”为 CPU 图形任务负载打开/关闭管道传输系统(默认为打开),以便可以看到管道传输的优势;
  • “显示找到的边界”运行算法的第一部分(查找像素之间的中断),但混合通道替换成调试通道,其中:
    • 如果发现某个像素与相邻像素间存在横向中断,则该像素变成纯绿色;
    • 如果发现某个像素与相邻像素间存在纵向中断,则该像素变成纯蓝色;
    • 如果发现某个像素与相邻像素间同时存在横向中断和纵向中断,则该像素变成纯红色;
    • 如果未发现中断,则像素无变化。


图 4. 采用 MLAA 并启用“显示找到的边界”选项的齿轮塔场景


架构示例

在没有管道优化的情况下,每个帧的事件序列如下:

 

  1. 激活并渲染测试场景
  2. MLAA 阶段(如果启用了 MLAA)
    • 将渲染场景处的颜色缓存复制到临时缓冲
    • 映射临时缓冲用于 CPU 端访问
    • MLAA 后处理(临时缓冲同时是输入和输出)
    • 取消临时缓冲映射
    • 将临时缓冲复制回 GPU 端颜色缓冲
    • 渲染缩放框(如果适用)
  3. 渲染示例的 UI,呈现帧

除了“执行 MLAA 后处理工作”步骤(暂时不考虑管道),以上所有步骤都使用标准 Microsoft DirectX* 方法实现。

 

任务处理 API

MLAA 算法本质是易于并行处理。对于中断检测和通道混合,我们都可以用独立的块(连续的行或列块)来处理颜色缓冲。MLAA 可采用基于任务的解决方案实现;这种解决可自动充分利用可用的 CPU 核心,同时无需让代码知道核心数量

此示例使用了一个基于 C 语言的简单任务处理 API,在英特尔® 线程构建模块 (Intel® TBB) 调度程序的基础上实现。创建这种封装程序 API 的目的是简化与已经采用类似任务处理 API(如跨平台游戏引擎)的现有代码库的技术集成。其优势是可有效提高主源文件 MLAA.cpp 的可读性。

封装程序 API 的两个重要函数为:

    BOOL CreateTaskSet(
        TASKSETFUNC                 pFunc,      //  Function pointer to the 
                                                //  Taskset callback function
        VOID*                       pArg,       //  App data pointer (can be NULL)
        UINT                        uTaskCount, //  Number of tasks to create 
        TASKSETHANDLE*              pDepends,   //  Array of TASKSETHANDLEs that 
                                                //  this taskset depends on.  The 
                                                //  taskset will not be scheduled
                                                //  until all tasksets in this list
                                                //  complete.
        UINT                        uDepends,   //  Count of the depends list
        OPTIONAL LPCSTR             szSetName,  //  [Optional] name of the taskset
                                                //  the name is used for profiling
        OUT TASKSETHANDLE*          pOutHandle  //  [Out] Handle to the new taskset
        );

和:

    //  WaitForSet will yield the main thread to the tasking system and return
    //  only when the taskset specified has completed execution.
    VOID WaitForSet(
        TASKSETHANDLE               hSet        // Taskset to wait for completion
        );

回调函数具有以下特征:

typedef VOID (*TASKSETFUNC)(VOID* TaskInfo, INT iContext, UINT uTaskId, UINT uTaskCount);

 

MLAA 需要三个连续任务集的相关性图形:

  • 第一个任务集查找颜色缓冲中像素之间的中断;
  • 第二个任务集执行横向混合通道,并依赖于获取中断信息的第一个任务集的完成;
  • 第三个任务集执行纵向混合通道,它依赖于获取中断信息的第一个任务集的完成,但同时还依赖于第二个任务集的完成,因为要进行转置优化(请参阅对应章节以了解详细信息)。

这种相关性图形在 MLAA.cpp 中表示为对 CreateTaskSet 的三个调用序列;任务集回调函数(在 MLAAPostProcess.cpp 中实现)分别为 MLAAFindDiscontinuitiesTask、MLAABlendHTask、MLAABlendVTask。

如果未启用管道传输,我们要通过使用上一次任务集的句柄调用 WaitForSet 来等待上一个任务集完成。调用返回时,对帧的 MLAA 操作就完成了。在使用管道时,事情的复杂性会稍有提高。

 

CPU/GPU 管道

要从我们的实施中获得最大性能,我们必须保持 CPU 端和 GPU 端都得到充分利用。由于任务集之间存在数据依赖性,因此可以通过穿插多个帧的处理来实现充分利用,如下图中所示:


图 5:通过管道移动的帧。CPU 主线程时间线上的红色块说明主线程将在应用程序受 CPU 限制时执行 MLAA 处理。


换言之,我们必须构建一个管道传输系统,其中的一个管道是一个 CPU 阶段(工作负荷)和 GPU 阶段的序列,可以同时运行前述管道的多个实例。

在我们的示例中,每个管道实例对应于一个帧处理。我们分三个步骤:

第 1 步(GPU 阶段):

  • 创建动画并渲染测试场景;
  • 将颜色缓冲复制到临时缓冲(使用异步 GPU 端 CopyResource)。

第 2 步(CPU 阶段):

  • 映射临时缓冲;
  • 执行 MLAA 后处理(临时缓冲同时是输入和输出)。

第 3 步(GPU 阶段):

  • 取消临时缓冲映射,将临时缓冲复制回 GPU 端颜色缓冲;
  • 完成帧渲染(缩放框、UI)并呈现。

为了实现此概念,我们设计了一个简单的管道类。每个阶段用一个 PipelineFunction 结构代表,该结构指定要调用的函数以及阶段类型。回调函数必须具有以下特征:

typedef void (*PPIPELINEFUNC)( UINT uInstance, ID3D11DeviceContext* pContext )

 

其中 uInstance 指示要从中调用函数的管道实例。GPU 阶段使用 DirectX* 查询(类型为 D3D11_QUERY_EVENT)来发送其完成信号,但 CPU 阶段必须显式调用管道的类 CompleteCPUWait 方法来发送完成信号:

    //  CompleteCPUWait signals an instance that expects an CPU wait in order
    //  to complete. When the Pipeline reaches that instance, it will be complete
    //  for that stage in the pipeline.
    VOID
    CompleteCPUWait(
        UINT                    uInstance );

创建管道实例时需要调用一次 Init 方法。在我们的示例中,代码如下:

	PipelineFunction Func[ 2 ];
	Func[0].Type = PIPELINE_EVENT_COMPLETE;
	Func[0].pFunc = AnimateAndRenderScene;
	Func[1].Type = PIPELINE_CPU_COMPLETE;
	Func[1].pFunc = MLAAPostProcessing;

	g_Pipeline.Init(g_NumPipelineInstances, ARRAYSIZE( Func ), Func, g_pPipelineQueries );

其中 g_NumPipelineInstances 是要创建的管道实例数量(本例中为 3 个)。

为了运行这些管道,我们为每个帧调用 Start 方法:

    //  Start is used to execute the Pipeline.  Processing will continue until
    //  an instance in the pipeline completes.  The index of that instance is
    //  return.  PIPELINE_INVALID is returned if the Pipeline was set to flush
    //  and there are no more instances left in the Pipeline.
    UINT
    Start(
        ID3D11DeviceContext*    pContext,       //  Context to issue Event query on.
        
        BOOL                    bFlush = FALSE  //  If bFlush is TRUE, the Pipeline
                                                //  will not create new instances.
                                                //  Specify TRUE if you want to flush
                                                //  the Pipeline.
        );

因为 Start 方法会返回已完成管道实例的索引,因此不必通过管道类来第三步(即最后一步)。最后一步的代码在调用 Start 方法后立即执行,使用 Start 返回的索引来建立数据结构索引。

管道传输不依赖于任务处理 API WaitForSet 调用,因为它会起阻止作用,不允许发生管道传输。解决方法是使用一个“完成任务集”,这是一种依赖于所有 MLAA 任务完成的任务,其唯一作用是调用 CompleteCPUWait 方法。

 

英特尔® SSE 优化

第一个 MLAA 算法通道是确定像素间的中断。从概念上讲,每个像素会检查其颜色并将其与下方相邻像素(在查找横向中断时)或右侧相邻像素(在查找纵向中断时)进行比较 [Reshetov09,第 2.2.1 节]。

在此示例中,我们保留了参考实施的简单中断检测内核。如果两个像素的 RGB 颜色分量差异至少达到 16(采用 RGBA8 格式的 0-255 标量),则这两个像素之间存在中断。

这个定义在示例中有着出色的表现,可以实现非常简洁高效的 SIMD。但是,也可以采用更加复杂的方法(有时是必要的)来获得最佳可能结果。例如,可以使用像素的亮度而不直接比较颜色值,可变阈值重新计算每个帧以考虑场景的整体亮度和对比度 [Luminance]。深度值可以用来帮助进行边界检测,也可使用任何一种自定义数据从处理中排除特定的颜色缓冲区域。

因为这一步是独立于其余算法的,因此将在 CPU 上运行,可以将来自程序的任何数据直接用作输入数据。检测算法仅有的限制包括:

  • 算法必须为每个像素提供一位输出,指示是否检测到中断;
  • 性能与质量/复杂性之间的权衡;
  • 程序员的想像力。

与原始参考实施一样 [Reshetov09,第 2.4 节],我们使用 RGBA8 渲染目标格式,由 MLAA 计算得出的“纵向中断”和“横向中断”位标志按原状存储在像素数据的两个高位中(即,不受 MLAA 混合运算影响的两个阿尔法分量高位中)。这样可让实施保持简单,同时有助于减少内存占用,并可在算法的下一步中实现优化(请参阅下面的“转置优化”一节)。

因为每个像素是 32 位数据,并且可在此步骤中互相独立地处理每个像素,因此我们可以使用英特尔® SSE 内部指令同时处理 4 个像素。检测代码很短。

	
	//---------------------------------------------------------------------------------------
	// Given a row of pixels, check for color discontinuities between a pixel and its
	// neighbor. Intel SSE implementation, so we process all 4 pixels in the row at a time.
	// If a discontinuity is detected, the corresponding flag is set in the alpha component of the pixel.
	//---------------------------------------------------------------------------------------
	inline void ComparePixelsSSE(__m128i& vPixels0, __m128i vPixels1, const INT EdgeFlag)
	{
	// Each byte of vDiff is the absolute difference between a pixel's color channel value, and the one from its neighbor.
		__m128i vDiff = _mm_sub_epi8(_mm_max_epu8(vPixels0, vPixels1), 
									 _mm_min_epu8(vPixels0, vPixels1));
	// We are only interested if the diff. is >16, and not interested in alpha differences.
		const INT Threshold = 0x00F0F0F0;
		__m128i vThresholdMask = _mm_set1_epi32(Threshold);
		vDiff = _mm_and_si128(vDiff, vThresholdMask);
	// Each of the four lanes of the selector is 0 if no discontinuity detected RGB, 0xFFFFFFFF else.
		__m128i vSelector = _mm_cmpeq_epi32(vDiff, _mm_setzero_si128());

然后,参考实施继续更新每个像素的阿尔法分量,但使用低效串行代码来执行此运算。我们可以使用以下英特尔® SSE 代码优化此序列:

	
	__m128i vEdgeFlag = _mm_set1_epi32(EdgeFlag);
	vEdgeFlag = _mm_andnot_si128(vSelector, vEdgeFlag);
	// vEdgeFlag now contains EdgeFlag for the pixels where a discontinuity was detected, 0 otherwise.
	vPixels0 = _mm_or_si128(vPixels0, vEdgeFlag);

我们还替换了 MixColors 函数(用于计算两种颜色的线性插值)以使用完整的英特尔® SSE 实施内容。

 

转置优化

算法的下一步是查找“中断行”,即在处理颜色缓冲的行和列过程中标记了相同中断标志(横向混合通道的横向标志,纵向混合通道的纵向标志)的连续像素序列 [Reshetov09,第 2.1 节]。

因为中断行一般都很短,凭直觉可以知道(并通过分析数据确认)此运算开销最大的部分是在中断行之间扫描,即扫描没有设置中断标志的大范围连续像素区域。

有利的一面是,当满足以下条件时,我们可以用 _mm_movemask_ps Intel® SSE 内部指令一次检查 4 个像素:

  1. 正在扫描存储在连续地址上的 4 个像素;
  2. 第一个像素的地址是“英特尔® SSE 对齐的”(16 字节对齐);
  3. 中断标志存储为 32 位像素数据的高位。

在横向混合通道传送过程中,(1) 为真(我们扫描以像素数据的 2D 线性阵列表示的颜色缓冲中的横向像素行);(2) 几乎总是为真(请记住,当缓冲正确对齐时,16 字节对齐等效于“缓冲中起始像素的索引是 4 的倍数);当我们选择位 31 来表示横向中断标志时 (3) 为真。

如果所有条件都为真,我们将计算标志:

	__m128 PixelFlags = *(reinterpret_cast<__m128*>(WorkBuffer + OffsetCurrentPixel));
	// Creates a 4-bit mask from the most significant bits
	int HFlags = _mm_movemask_ps(PixelFlags);	

可能会有五个结果,具体取决于 Hflags 的值:

  • 0(目前为止最常见的情况):在这组 4 个像素中没有设置中断标志,转至下一组 4 个像素。
  • 设置位 0:中断行始于该组的第一个像素。
  • 设置位 1:中断行始于该组的第二个像素。
  • 设置位 2:中断行始于该组的第三个像素。
  • 设置位 3:中断行始于该组的第四个像素。

此项优化是参考实施内容的一部分,适用于横向混合通道传送,但不适用于纵向混合通道传送。当代码对缓冲进行纵向扫描时,(1) 为假(列中的相邻像素不是存储在连续地址中),(3) 也为假(纵向中断标志存储在像素数据的位 30 上)。

如果我们处理的是纵向标志,则通过引入简单的“向左转换一位”运算轻松解决 (3) 的问题,将上面的代码转换为:

	int Shift = (EdgeFlag == EdgeFlagV) ? 1 : 0;
	__m128 PixelFlags = *(reinterpret_cast<__m128*>(WorkBuffer + OffsetCurrentPixel));
	PixelFlags = _mm_castsi128_ps( _mm_slli_epi32(_mm_castps_si128(PixelFlags), Shift) );
	int HFlags = _mm_movemask_ps(PixelFlags);

但 (1) 仍然是个问题。此外,纵向扫描对缓存的要求极高。由于存在以上两个问题,在参考实施中纵向混合通道传送的开销大约是横向混合通道传送的 3 倍(300%!),如数据分析所示。

我们解决此问题的方法是让纵向混合通道传送使用缓存以及适合英特尔® SSE 指令的横向混合通道传送数据存取模式,将颜色缓冲看作像素阵列并将其在通道之间进行转置:

  • 执行横向混合通道传送
  • 转置(横向混合的)颜色缓冲
  • 执行纵向混合通道传送
  • 逆转置颜色缓冲

这样一来,两种混合通道传送的代码就完全相同了(增加了简单性/可读性方面的优势),唯一的差异是要扫描的标志,我们将受益于所有优化以及较低的横向通道传送缓存要求。

在实践中,转置运算并不以单独的通道/任务集形式实施,而是作为其对应混合通道的最后一部分实施。这样可让我们利用缓存结果。

两种转置运算都需要开销。开销包括额外的代码执行时间、一个额外的工作缓冲以及横向和纵向通道之间的一个同步点(我们必须等到所有横向任务完成后才能开始任何纵向任务,因为我们必须等到颜色缓冲完全转置后才能开始纵向通道传送工作)。

即使有这些额外的开销,整体性能也明显好于以前的方法。数据分析结果与预期相符,两种混合通道有同等的性能。

 

性能结果

MLAA 性能是在以下两种配置上测量的:

  • 代码名称“Huron River”:英特尔® 酷睿™ i7-2820QM 处理器(英特尔® 微架构代码名称 Sandy Bridge,4 核 8 线程,2.30 GHz)以及 GT2 图形处理器,4 GB 内存,Windows 7 旗舰版 64 位 Service Pack 1
  • 代码名称“Kings Creek”:英特尔® 酷睿™ i5-660 处理器(代码名称“Clarkdale”,双核 4 线程,3.33 Ghz),采用 GMA HD 图形处理器(代码名称“Ironlake”),2 GB 内存,Windows 7 旗舰版 64 位 Service Pack 1

我们测量了示例代码的平均帧渲染时间,并以不同抗锯齿处理设置的场景复杂性函数表示。我们还测量了“仅复制/映射/取消映射”模式的渲染时间,以突出颜色缓冲复制运算对算法整体性能的影响。

 

结果

数据显示,对于 Huron River 计算机,MSAA 4x 和额外开销随场景复杂性呈线性上升(事实上,对于所有分辨率,使用 MSAA 4x 渲染测试场景时的帧时间几乎是不使用抗锯齿处理时帧时间的两倍)。相比之下,MLAA 的开销几乎不变(在 1280x800 分辨率下为 4 毫秒/帧左右)。这与我们的预期一致,因为 MLAA 不像 MSAA 4x,它是每帧仅执行一次,不考虑场景复杂性/绘制调用次数。

我们还观察到,除了很低的复杂性值(即小于 5 左右),不管何种分辨率,MLAA 的表现都始终优于 MSAA 4x,性能上的差异随复杂性增加(因为如前所述,MSAA 4x 的开销随复杂性呈线性上升,而 MLAA 的开销则不会)。

对于 Kings Creek 计算机,我们无法比较 MLAA 与 MSAA 4x 的开销,因为 Ironlake 硬件未提供后者。因此,此项测量的目的是确定 MLAA 是否允许我们提供软件抗锯齿处理作为替代以获得可接受的性能。我们在 1280x800 分辨率下的测量显示,MLAA 的开销仍然主要与场景复杂性相关,平均值为 7.5 毫秒左右(不考虑复杂性 = 100 时的外部数据点)。

有趣的是,如果我们将此结果与具有和 Huron River 几乎相同的性能配置文件(MSAA 4x 帧时间约等于 2 倍无锯齿处理的帧时间)的假设 MSAA 4x 实施比较,我们再次注意到 MLAA 在几乎所有复杂性值(本例中为 ≥4)下的表现都优于 MSAA 4x。


表 1. 我们在 Kings Creek 计算机上的测试场景的渲染时间。


表 2.我们在 Huron River 计算机上的测试场景的渲染时间。


图 6:我们针对每种抗锯齿处理技术的测试场景的渲染时间,以场景复杂性的函数表示。在底部,平缓曲线是“打开管道的 MLAA”曲线与“无抗锯齿处理”曲线之间的差异,测量使用 MLAA 的开销 [Huron River,分辨率 1280x800]


图 7:我们针对每种抗锯齿处理技术的测试场景的渲染时间,以场景复杂性的函数表示。在底部,平缓曲线是“打开管道的 MLAA”曲线与“无抗锯齿处理”曲线之间的差异,测量使用 MLAA 的开销 [Huron River,分辨率 1600x1200]


图 8:我们打开和关闭 MLAA 的测试场景的渲染时间,以场景复杂性的函数表示。在底部,平缓曲线是“打开管道的 MLAA”曲线与“无抗锯齿处理”曲线之间的差异,测量使用 MLAA 的开销 [Kings Creek,分辨率 1280x800]

参考资料

[Reshetov 2009] RESHETOV, A. 2009. “Morphological Antialiasing

[Perthuis 2010] PERTHUIS, C. 2010. MLAA in God of War 3. Sony Computer Entertainment America, PS3 Devcon, Santa Clara, July 2010.

[Jimenez et al., 2011] JIMENEZ, J., MASIA, B., ECHEVARRIA, J., NAVARRO, F. and GUTIERREZ, D. 2011. Practical Morphological Anti-Aliasing. In Wolfgang Engel, ed., GPU Pro 2. AK Peters Ltd.

[SIGGRAPH 2011] JIMENEZ, J., GUTIERREZ D., YANG, J., RESHETOV, A., DEMOREUILLE, P., BERGHOFF, T., PERTHUIS, C., YU, H., MCGUIRE, M., LOTTES, T., MALAN, H., PERSSON, E., ANDREEV, D. and SOUSA T. 2011. Filtering Approaches for Real-Time Anti-Aliasing. In ACM SIGGRAPH 2011 Courses.

[Luminance] CRT 类设备的照明清晰度:

国际照明委员会。1971 年。统一颜色空间、色差方程式、心理颜色推荐术语。对 CIE 15 号出版物 15 (E.-1.3.1), TC-1.3, 1971 的第二个补充。

针对 LCD:

ITU-R 推荐。BT.709-5. 2008. 第 18 页 1.3 条和 1.4 条

有关编译器优化的更完整信息,请参阅优化通知