Real-Time Layered Materials Compositing Using Spatial Clustering Encoding

引言

大多数现代渲染引擎都利用简单的知名材质库和分层材质表示法,来创作细节丰富、高质量的游戏内材质。当今纹理处理管线中流行的工具(例如 Allegorithmic Substance Painter 和 Quixel NDO Painter)也基于分层材质的概念 [Neubelt and Pettineo 2013, Deguy et al. 2016, Karis 2013]。

在本章中,我们提出了一种使用分层材质方法的算法,该算法允许我们在实时环境下使用大量图层来创建复合材质。我们的算法旨在尽可能接近地模拟 Allegorithmic Substance 的纹理管线,但是在实时环境下。所提出的技术基于混合多个知名材质,其中一个共享的材质库定义了合成中使用的每种材质的表面属性。

【注释】

Allegorithmic Substance纹理管线

传统的纹理管线(手绘): 在Photoshop中绘制一张Albedo(漫发射贴图),一张Normal(法线贴图),一张Roughness(粗糙度贴图)。这些贴图是死的,分辨率固定,不宜修改。

Substance工作流(程序化):你创建的是一个材质图 ,由无数个相互连接的节点组成。每个节点执行一个特定的操作(如生成噪声、扭曲图案、混合颜色等)。最终的纹理贴图是这个图表的输出结果

一个具体的例子:制作一面破旧的墙壁

  1. 基础层:Tile Generator节点开始,创建砖块的基本图案和砂浆缝隙。

  2. 添加颜色: 使用Gradient Map为砖块和砂浆赋予基础颜色。

  3. 添加表面细节: 连接各种Noise节点到Normal输入,给砖块表面添加粗糙不平的质感。

  4. 添加破损: 使用DirtGrunge节点作为蒙版,在砖块的边缘和砂浆上添加苔藓或水渍。这个蒙版会同时控制Base Color(变成绿色/深色)和Roughness(苔藓更湿润,更光滑)。

  5. 非破坏性调整: 你觉得砖块太红了?只需回到Gradient Map节点,调整一个色标即可。你觉得破损太多?找到Grunge节点,调低它的强度。

在整个过程中,你没有任何一步是"不可撤销"的。

【注释】

使用我们的方法,每个网格可以拥有一套唯一的 UV 集和若干张唯一的纹理混合遮罩,每张混合遮罩定义了材质库中材质的逐像素混合权重。材质库中的每种材质可以使用细节纹理技术来提高表面细节的分辨率。在合成中使用带有细节纹理的材质的优势在于,它打破了纹理分辨率的限制,使我们能够以非常高的分辨率生成最终的合成结果。在 4K 时代,拥有高分辨率的游戏内材质尤为重要。

我们的方法支持在运行时替换材质库以及修改纹理混合遮罩的透明度。运行时的材质替换会导致最终合成材质产生不同的视觉外观,这对于支持用户生成内容或游戏内自定义的游戏尤为重要。该技术已用于在 My.com 发行的动作多人在线坦克游戏《装甲战争》中渲染装甲车辆。

1.2 现有技术概述

一种流行的合成方法是将多层材质预烘焙成一套纹理(反照率、法线、粗糙度等),供游戏引擎使用。生成的纹理集主要是为特定网格设计的,不能在不同的网格之间共享。我们称这种方法为"静态材质分层" [Neubelt and Pettineo 2013, Deguy et al. 2016]。这种方法能产生良好的结果,但需要大量的 GPU 内存来存储最终的高分辨率纹理。

为了突破这一限制,一些现代渲染引擎使用了一种与已被地形渲染证明有效的"纹理喷溅"技术非常相似的方法 [Bloom 2000]。为了生成最终的合成纹理,这些引擎使用像素着色器和一套定义混合透明度的纹理混合遮罩来混合多个纹理。我们称这种方法为"动态材质分层"[Inside Unreal 2013, Noguer 2016]。如图 1.1 所示,这种方法效果很好,但由于内存和性能的限制,只能同时使用少量材质层。

【注释】

//下面是一般的混合遮罩方法

复制代码
// 1. 采样颜色纹理 - 获取“颜料”
vec3 dirt_color = texture2D(u_DirtColorTex, uv).rgb;
vec3 moss_color = texture2D(u_MossColorTex, uv).rgb;
vec3 snow_color = texture2D(u_SnowColorTex, uv).rgb;

// 2. 采样遮罩纹理 - 获取“模板/配方”
float dirt_mask = texture2D(u_DirtMaskTex, uv).r; // 例如:0.2
float moss_mask = texture2D(u_MossMaskTex, uv).g; // 例如:0.7  
float snow_mask = texture2D(u_SnowMaskTex, uv).b; // 例如:0.1

// 3. 根据遮罩的指导,混合颜色
vec3 final_color = vec3(0.0);
final_color += dirt_color * dirt_mask;   // 泥土贡献:20%
final_color += moss_color * moss_mask;   // 青苔贡献:70%
final_color += snow_color * snow_mask;   // 积雪贡献:10%

// 4. 输出最终像素颜色
FragColor = vec4(final_color, 1.0);

//一般遮罩使用的采样次数和纹理通道很多。

【注释】

1.3 术语定义

由于不同的游戏引擎和材质创作管线使用不同的术语,以下是本文中使用的定义。

材质模板: 单一的知名材质,例如黄金、钢铁、木材等。材质模板可以使用平铺的细节纹理来制造材质细节更丰富的假象。材质模板被用作创建复杂多层材质的基本构建块。

材质遮罩: 一种灰度纹理,用于在合成不同材质模板时定义透明度。通常,这些纹理是由现代纹理工具(如 Allegorithmic Substance Painter 和 Quixel NDO Painter)使用半程序化方法创建的。

颜色 ID: 一种颜色编码的纹理,用于定义属于不同不透明材质的 UV 区域。不透明材质没有与之关联的混合遮罩,并且在我们的合成中始终用作底层。使用颜色编码表示(每种独特的颜色代表一种单一材质)可以简化内容管线并减少所需纹理的数量。如图 1.2 所示,每张颜色编码纹理可以表示多种不透明材质。

分层材质: 这是一种用于构建最终合成材质的材质定义。每个分层材质都有一个与之关联的单一颜色 ID,以及一个有序的材质遮罩集合,该集合定义了材质的合成顺序和混合权重。分层材质还有一组与之关联的材质模板,用于定义合成中使用的每种材质的视觉外观。

1.4 算法概述

现有的用于动态材质分层的解决方案 [Inside Unreal 2013, Noguer 2016] 将不同材质的透明度存储在纹理的 RGBA 通道中。当每个材质遮罩仅覆盖纹理的一小部分区域时,这种解决方案在内存消耗方面效率低下。大量的纹理空间未被用于合成而被浪费。

我们观察到,混合遮罩在纹理空间内通常是连贯的,并且仅部分相互重叠。利用这一观察结果,我们提出将不同的非重叠混合遮罩存储在同一纹理通道中。为了实现这一点,我们将若干材质遮罩分组到一个我们称之为"簇"的集合中。我们基于纹理空间中纹素之间的连通性来构建这些簇。这使我们能够更有效地利用纹理空间,因为不同的簇可以将它们的混合遮罩存储在相同的共享纹理通道中。在运行时,我们使用这种簇表示法和材质模板集来进行最终的材质合成,如图 1.3 所示。

由于我们将不同的混合遮罩存储在同一纹理通道中,因此考虑不同簇之间的纹理过滤边界至关重要。不同混合遮罩的纹理过滤会在合成阶段导致错误,这是由于一个材质的纹理混合遮罩泄漏到另一个材质中。在构建材质簇时,我们会考虑哪些相邻像素参与了纹理过滤,并在创建簇时使用此信息。

为了创建初始的簇划分,我们对材质遮罩集执行连通性分析。连通性分析将所有用于纹理过滤的纹素分类为已连接。如果纹素被分类为已连接,它们将属于同一个材质簇。当我们执行连通性分析时,还应考虑 Mipmap 纹理过滤。同时,我们应该限制支持的 Mipmap 级别数量,否则在最后的 Mipmap 级别,所有纹素都将被分类为已连接。对于我们的实现,我们决定仅支持前四个 Mipmap 级别。更小的 Mipmap 级别不被我们的实现处理并被丢弃。仅支持前四个 Mipmap 级别足以保持纹理过滤的良好质量,并使生成的簇数量保持较少。不完整的 Mipmap 链可能导致走样,但其程度是可以接受的 [Mittring 2008]。实际上,产生的走样几乎不可见,并且可以被大多数现代抗锯齿算法有效消除。

每个生成的簇不应包含超过有限数量的材质,材质数量取决于我们需要支持的每像素材质层数。实际上,簇中使用的材质数量通常等于四或五种,因为我们将簇混合权重存储在 BC1 或 BC3 纹理格式中。实际上无限的总材质数和每像素五个材质足以表示即使是非常复杂的分层材质。

构建具有有限材质数量的簇并非总是可行,因为我们可能会发现比每个簇允许的最大材质数量更多的连通材质。因此,我们可能会发现一个簇使用的材质超过了最大允许数量。我们可以将此类簇分割成几个较小的簇,以满足我们的初始要求。这将导致簇边缘共享纹素处出现纹理过滤错误。由于在不同簇(其中编码了不同材质)的混合遮罩之间发生错误的纹理过滤,将导致过滤错误,如图 1.4 所示。我们提出了一种解决方案,在分割此类簇时最大限度地减少纹理混合遮罩的泄漏。更多细节请参见第 1.5.7 节。

【注释】

本文作者并没有解决 无法再分却仍超阈值:作者提出的方法是 强制分割,并利用算法的智能性来尽可能地将副作用降到最低。

【注释】

1.4.1 空间聚类编码表示

在预处理阶段,我们使用单一颜色 ID 来定义所有不透明材质,并使用一个有序的材质遮罩集来定义所有透明材质,从而构建簇表示法。预处理的结果是,我们得到一个用于运行时合成的数据集,其中包含三种不同类型的数据。

· 簇间接寻址: 一种间接寻址纹理,用于定义指定纹素的当前簇 ID。簇 ID 使用整数纹理格式存储,并定义了应为给定纹素使用哪组材质。簇间接寻址使用比源材质遮罩分辨率更低的纹理存储。相邻纹素通常使用相同的材质集,并且使用的材质集很少变化,这使我们能够使用较小分辨率的纹理来存储此数据。由于此纹理包含整数数据,因此不能使用任何纹理过滤。簇间接寻址存储在无 Mipmap 的纹理中,并使用 POINT 纹理过滤模式获取。

· 簇权重: 权重纹理定义了由簇 ID 指定的材质集中每种材质的混合权重。我们支持每像素最多五个不同的材质遮罩,权重使用 BC3 纹理格式存储。簇权重使用与输入材质遮罩相同分辨率的纹理存储,尽管使用的材质集很少变化,但混合遮罩的相邻纹素可能差异很大。簇权重可以在同一材质簇内正确过滤。纹理数据存储有 Mipmap,并使用 TRILINEAR 或 ANISOTROPIC 纹理过滤模式获取。

· 簇属性 : 定义了用于最终合成的材质表面属性,例如反照率、粗糙度、金属度等。我们决定使用 Structured Buffer 来存储簇属性。根据实现方式,簇属性也可以存储在Constant Buffer 中。

在合成阶段,我们获取每个片元的簇 ID,该 ID 定义了所使用的材质集和混合权重。然后,使用簇属性,我们获取当前簇中使用的每种材质的表面属性。随后,我们使用混合权重和表面属性来合成给定片元的最终表面属性。更多细节参见图 1.5。

1.4.2 混合遮罩的顺序无关表示

对于最终合成,我们需要按照输入数据定义的正确顺序混合材质。最常用的方法是执行 Alpha 混合,并使用以下方程式以从后到前的顺序合成片元:

然后我们对所有用于混合的混合遮罩重复此操作。

这种方法依赖于操作顺序,我们可以不使用 Alpha 混合,而是以加权形式重写混合方程:

其中

由于最终权重是归一化的,我们知道所有权重的总和始终等于一。我们可以利用这个属性在合成像素着色器内部重建其中一个权重,而不是将其存储在纹理通道中:

对于我们的实现,我们决定对纹理混合遮罩使用顺序无关的加权表示。顺序无关表示允许我们在簇内自由交换纹理通道。这个特性对于后续的一些优化非常有用。

1.5 算法实现

1.5.1 提取背景材质

首先,我们为每个纹素定义一种不透明材质。不透明材质也用于确定哪些纹素在 UV 映射内部,哪些不在。然后我们确定应使用哪种纹理分辨率来支持最新的 Mipmap 级别。对于 Mip 级别内的每个纹素,我们从原始高分辨率颜色 ID 纹理生成颜色 ID 值。为了避免最终 Mipmap 纹素使用几种不同不透明材质的情况,我们禁止在相邻纹素中使用不同的颜色 ID。这种自然限制使我们能够在艺术管线的早期阶段发现潜在的聚类问题,并有助于创建可以有效聚类的颜色 ID 贴图。这也使我们能够跳过所有不透明材质的连通性分析,因为不同的不透明材质从不共享相邻纹素。

【注释】

这里的聚类问题 指的是:在颜色ID纹理中,存在两个或多个不同的颜色ID(代表不同的不透明材质)在空间上是相邻的

为什么这会成为一个"问题"?

这个限制是为了确保算法后续能正确、高效地工作。原因如下:

  1. Mipmap兼容性

    • Mipmap是通过对相邻纹素进行下采样生成的。如果两个相邻的原始纹素拥有不同的颜色ID(比如一个红,一个绿),那么在生成Mipmap时,这个位于边界的下级纹素就会接收到混合的颜色值(比如暗黄色)。

    • 这个混合后的颜色值,很可能与预定义材质调色板中的任何颜色都不匹配,导致在查询材质ID时失败或出错。

  2. 算法效率与简化

    • 这个限制使他们能够跳过对所有不透明材质的连通性分析

    • 因为规定了相邻纹素不能有不同的颜色ID,这就意味着每个由相同颜色ID组成的区域,其边界都是清晰、明确的。它们天然就是已经被完美划分好的"簇"。

    • 如果没有这个限制,算法就需要像处理透明混合遮罩一样,对不透明区域也进行复杂的连通性分析和边界处理,这会增加预计算的复杂度。

【注释】

1.5.2 材质层

在此步骤中,我们拥有一个称为材质层的有序纹理混合遮罩集。每个材质层定义了每个纹素的混合透明度。我们对其进行下采样。

【注释】

在这里是对透明混合遮罩集的数据(连续数据)进行下采样

【注释】

1.5.3 加权和表示

cpp 复制代码
For (every texel(X,Y) in opaque layer) 
{ 
	If (texel(X,Y) is empty in opaque layer) 
	{ 
		skip texel 
	} 
	accum = 1.0 
	
	For (every input texture blend mask) 
	{ 
		alpha = blend_mask(X,Y)
		layer_weight(X,Y) = alpha * accum 
		accum = accum * (1.0 - alpha) 
	} 
}
//算法1.1 将纹理混合蒙版转换为归一化加权形式

在此步骤中,我们有几个下采样后的纹理混合遮罩,按照指定的顺序定义,用于 Alpha 混合。我们使用算法 1.1 将纹理混合遮罩转换为归一化的加权形式。归一化的加权表示还有助于我们丢弃完全被其他材质覆盖且对最终合成没有贡献的纹素。

1.5.4 无向图表示

在此步骤中,我们从输入数据的位图表示转向无向图表示。与位图表示相比,图表示的优势在于我们可以利用图论来分析并构建具有特定特性的簇。对于每个纹素,我们找到所有对该纹素有贡献的纹理混合遮罩,并分配一个唯一的纹素标识符,该标识符对应于所使用的混合遮罩的唯一组合。然后,我们使用类似于洪水填充算法的算法找到连通区域,并从每个唯一组合中生成一个独立的图顶点。算法产生的结果如图 1.6 所示。对于每个生成的图顶点,我们存储一个分配的标识符,该标识符编码了用于构建此图顶点的混合遮罩。

1.5.5 纹理过滤需求分析

在此步骤中,我们根据以下规则在图顶点之间构建边:如果两个图顶点拥有用于纹理过滤的相邻像素,则这些顶点是连接的。

因此,我们从源位图数据构建了一个连通的无向图 G = (V, E)。V 表示受不同输入纹理混合遮罩组合影响的区域。E 表示这些区域之间的纹理过滤关系,如图 1.7 所示。用于纹理过滤的纹素数量决定了边的权重,并将在后续的图分区中使用。

1.5.6 寻找连通分量数量

生成的无向图通常具有多个连通分量。我们的目标是找到连通分量的数量,并将图分割成若干个子图,这些子图之间不需要过滤(参见图 1.8)。每个子图在后续算法步骤中作为独立的图进行处理。如果生成的图已经符合我们的初始要求且未超过最大允许材质数量,则下一步是冗余的,可以跳过。否则,下一个算法步骤将根据我们的初始要求,将图分割成几个具有特定属性的子图。

1.5.7 解决图分区问题

在此步骤中,我们需要解决图分区问题,并将图 G = (V, E)(其中 V 是顶点集,E 是边)分割成更小的、具有特定属性的组件。通常,图分区问题是 NP 难问题,因此我们应该使用启发式方法和近似算法来解决图分区问题。为了找到最优解,我们使用迭代贪心算法来寻找具有最小权重的边集进行切割。我们的解决方案受到 Kernighan 和 Lin [1970] 提出的图分区启发式算法的启发。

我们的目标是将图划分为子集 A 和 B,其中子集 A 满足初始要求,并且从 A 到 B 的边权重之和最小化。由于边的权重是用于纹理过滤的像素数量,通过最小化从 A 到 B 的边权重之和,我们减少了最终的过滤误差。我们的多遍算法维护并改进分区,在每一遍中使用贪心算法将 A 的顶点与 B 的顶点配对,以便将配对的顶点从分区的一侧移动到另一侧可以改进分区。

接下来,我们的算法从所有已尝试的解决方案中选择最佳方案。因此,我们的算法试图找到具有最小切割边权重之和的最优子集 A。实现细节见算法 1.2。为了演示算法的一个迭代步骤,请参见图 1.9。

1.5.8 生成最终数据

在最后一步,我们拥有一组无向图,其中每个图都符合我们的初始要求。实际上,许多生成的图使用的材质数量少于最大允许数量。为了减少最终簇的数量,只要合并后的结果满足初始要求,我们就将此类图合并为更大的图。

cpp 复制代码
ForEach (source layers identifier existing in the graph G(V,E)) 
{ 
	Begin (split graph into initial subsets A and B) 
	{ 
		Add all graph vertices with same identifier to subset A. 
		Add all other graph vertices into subset B. 
	} 
	Loop 
	{ 
		Calculate sum of edges weights between subset A and B
		and store as solution. 
		Find a vertex inside a subset B that has the largest sum of 
		edges crossing between subsets and can be moved to a 
		subset A without violating our constraints. 
		
		If (such vertex found) 
		{ 
			Move all vertices with same identifier as a found vertex 
			from the subset B to the subset A. 
		} 
		Else 
		{ 
			break 
		} 
	} 
} 
Return solution with the minimal weight between subsets.

                //算法1.2. 图分割算法。

接下来,我们按照第 1.5.3 节所述构建归一化加权表示,但这次是针对全分辨率纹理数据。对于每个生成的图,我们将图顶点所使用的所有权重纹理中的纹素复制到簇权重纹理的独立通道中。然后,我们使用 R8_UINT 格式将所使用的图索引存储到簇间接寻址纹理中。接着,对于簇权重,我们生成一个部分 Mipmap 链(其长度受支持的 Mipmap 数量限制),然后使用 BC1 或 BC3 纹理格式压缩生成的纹理。生成的纹理集示例如图 1.10 所示。

1.5.9 运行时合成

为了在运行时最终合成材质,我们使用以下方法:

间接寻址纹理获取编码的簇 ID。

从权重纹理获取材质混合权重。

使用获取的簇 ID 读取存储在结构化缓冲区中的表面属性。

使用获取的表面参数和材质混合权重进行最终合成。基础合成着色器示例参见代码清单 1.1。

由于我们使用归一化加权表示来存储混合权重,我们可以更改任何单个材质层的混合权重并重新归一化权重的总和。这允许我们在运行时更改单个图层的透明度。

用于合成的纹理通常分辨率不足。为了提高合成的最终分辨率,我们使用存储在纹理数组中的细节纹理,如 Hamilton 和 Brown [2016] 所述。为了生成细节纹理的 UV 集,我们将原始 UV 集乘以平铺因子。细节 UV 集的平铺因子可以按材质指定。我们使用两组纹理数组:一组用于表面参数(反照率、粗糙度、金属度),另一组用于法线贴图。合成中使用的每种材质可以为表面属性使用任意细节贴图,为表面法线使用任意细节贴图。使用和未使用细节纹理的合成示例如图 1.11 所示。

对于法线贴图混合,我们使用如 "Blending in Detail" [Barré-Brisebois and Hill 2012] 中所述的偏导数的加权混合。此外,我们可以在中等距离上仅混合两个贡献权重最高的细节贴图,并在远距离完全禁用细节贴图,以提高合成性能。

cpp 复制代码
struct SurfaceParameters 
{ 
	float3 albedo; 
}; 
struct ClusterParameters 
{ 
	SurfaceParameters layer0; 
	SurfaceParameters layer1; 
	SurfaceParameters layer2; 
	SurfaceParameters layer3; 
}; 
// Weights texture 
Texture2D cWeights; 
// Indirection texture 
Texture2D cIndirection; 
// Material parameters (stored per cluster) 
StructuredBuffer<ClusterParameters> clusterParameters; 

float4 DecodeAndComposition(float2 uv) : SV_Target0 
{ 
	float4 weights; 
	// Fetch weights 
	weights.xyz = cWeights.Sample(samplerTrilinear, uv).rgb; 
	// Reconstruct weight 
	weights.w = 1.0 - weights.x - weights.y - weights.z; 
	// Fetch index 
	uint clusterIndex = cIndirection.Sample(samplerPoint, uv).r; 
	// Get material params 
	ClusterParameters params = clusterParameters[clusterIndex]; 
	// Use the material parameters and weights 
	// for a final composition
	float3 albedo = params.layer0.albedo * weights.x + 
	params.layer1.albedo * weights.y + 
	params.layer2.albedo * weights.z + 
	params.layer3.albedo * weights.w; 
	return float4(albedo, 1.0); 
}

    //清单 1.1 集群解码示例

1.5.10 源代码

为了演示目的,我们使用 C# 实现了我们的预处理方法,并使用 Unity Technologies 的 Unity 游戏引擎实现了运行时材质合成。完整源代码可在本书的补充材料中找到。源代码也可在 https://github.com/SergeyMakeev/GpuZen2 获取。

1.6 结果

我们在《装甲战争》游戏中使用本文描述的方法来渲染装甲车辆。在我们的游戏中,用户可以自定义用于渲染装甲车辆的着色和材质。所提出的技术能够在支持高质量纹理并允许自定义视觉外观的同时,最大限度地减少存储在磁盘上的纹理数量。您可以在图 1.12 中看到使用我们技术的一些结果。我们比较了我们的技术产生的指令数量与虚幻引擎 4 材质分层技术产生的指令数量,参见表 1.1。表 1.2 显示了为装甲车辆构建的生成时间和生成的材质簇数量。

1.7 结论与未来工作

本章描述的方法有助于有效地存储和使用比现有方法允许的更多的纹理混合遮罩,尽管存在一些自然限制。

此外,所提出的方法支持使用所提出的数据表示进行实时材质重组。为了最有效地使用我们的方法,必须在艺术管线的最早阶段考虑不同纹理混合遮罩在纹理空间中的连通性。同时,所提出的方法适用于任何现有的艺术资源,无需额外准备,仅伴有可容忍的纹理过滤误差。我们继续开发和改进所提出的技术。以下是一些有待进一步发展的领域:

如 Hardy 和 McRoberts [2006] 所提出的,用于材质合成的非线性混合。

在分割簇时减少纹理过滤误差。由于纹理混合遮罩是顺序无关的,我们可以在簇内交换纹理通道。使用沿"接缝"边界的最小二乘最小化技术,如 Iwanicki [2013] 所提出的,我们可以将纹理过滤误差几乎减少到零。

在评估 BRDF 之后进行合成,而不是合成表面属性。使用这种方法,我们可以创建具有多个高光瓣的精确多层材质。

使用顶点颜色作为混合权重修改器,用于局部动态材质重组(动态污垢、划痕等)。

参考

BARRÉ-BRISEBOIS, C. AND HILL, S. 2012. Blending in Detail. URL: http://blog.selfshadow.com/publications/blending-in-detail.;

BLOOM, C. 2000. Terrain Texture Compositing by Blending in the Frame-Buffer. URL:http://www.cbloom.com/3d/techdocs/splatting.txt.;

DEGUY, S., OLGUIN, R., AND SMITH, B. 2016. Texturing Uncharted 4: a matter of Substance.Game Developers Conference 2016.

HAMILTON, A. AND BROWN, K. 2016. Photogrammetry and Star Wars Battlefront. Game Developers Conference 2016.

HARDY, A. AND MCROBERTS, D. 2006. Blend maps: enhanced terrain texturing. In SAICSIT 2006.

INSIDE UNREAL. 2013. A Look at Unreal Engine 4 Layered Materials. URL: https://www.unrealengine.com/news/look-at-unreal-engine-4-layered-materials.;

IWANICKI, M. 2013. Lighting technology of The Last of Us. SIGGRAPH '13.

KARIS, B. 2013. Real Shading in Unreal Engine 4 : Physically Based Shading in Theory and Practice. SIGGRAPH '13.

KERNIGHAN, B. AND LIN, S. 1970. An efficient heuristic procedure for partitioning graphs. In The Bell System Technical Journal, 49, pp. 291--307.

MITTRING, M. 2008. Advanced Virtual Texture Topics. SIGGRAPH '08.

NEUBELT, D. AND PETTINEO, M. 2013. Crafting a Next-Gen Material Pipeline for The Order:1886. SIGGRAPH '13.

NOGUER, J. 2016. The Next Frontier of Texturing Workflows. URL: https://www.allegorithmic.com/blog/next-frontier-texturing-workflows.