64k简介的纹理:今天如何完成

本文是我们的 H-沉浸式 系列的第二部分 第一部分可以在此处阅读: 浸入式浸入

当创建仅64 KB的动画时,很难使用现成的图像。 我们无法以传统方式存储它们,因为即使您应用压缩(例如JPEG),效率也不够高。 另一种解决方案是过程生成,即编写描述程序执行期间图像创建的代码。 我们对该解决方案的实现是一个纹理生成器-我们的工具链的基本组成部分。 在这篇文章中,我们将解释我们如何在H-沉浸中开发和使用它。


水下泛光灯照亮了海底的细节。

早期版本


纹理生成是我们代码库的最初元素之一:过程纹理已在我们的第一个B-Incubation简介中使用。 该代码由一组填充,过滤,变换和组合纹理的函数以及一个绕过所有纹理的大循环组成。 这些函数是用纯C ++编写的,但是后来添加了C API交互,以便可以由C PicoC解释器进行评估。 那时,我们使用PicoC减少了每次迭代所花费的时间:通过这种方式,我们能够在程序执行期间更改和重新加载纹理。 与现在我们可以更改代码并立即查看结果而不必费心关闭,重新编译和重新加载整个演示这一事实相比,切换到C子集是一个很小的牺牲。


使用简单的图案,少量噪音和变形,我们可以获得风格化的木材纹理。


F-Felix的工作室的场景中, 使用了各种木材纹理。

一段时间以来,我们探索了该生成器的功能,并将其发布到具有小型PHP脚本和简单Web界面的Web服务器上。 我们可以在文本字段中编写纹理代码,然后脚本将其传递给生成器,然后生成器将结果转储为PNG文件以显示在页面上。 很快,我们在午休时间开始在工作中进行素描,并与小组中的其他成员分享我们的小型杰作。 这种互动激励我们进入创作过程。


我们的旧纹理生成器的Web画廊。 可以在浏览器中编辑所有纹理。

全面重新设计


长期以来,纹理生成器几乎保持不变。 我们认为这很好,而我们的效率也不再提高。 但是一旦我们发现互联网论坛上有很多艺术家在展示他们完全按照程序生成的纹理,以及在各种主题上安排挑战 。 程序内容曾经是演示场景的功能,但是AllegorithmicShaderToy和类似的工具使公众可以使用它。 我们没有注意这一点,他们开始轻松地将我们放在肩blade骨上。 不能接受!


布艺沙发 在Substance Designer中创建的完全程序化的织物纹理。 发言者:Imanol Delgado。 www.artstation.com/imanoldelgado

图片

森林地面 由Substance Designer创建的完全程序化的森林土壤质地。 丹尼尔·蒂格(Daniel Thiger)发表。 www.artstation.com/dete

我们长期以来需要重新考虑我们的工具。 幸运的是,使用同一个纹理生成器进行了多年的工作使我们得以认识到它的缺点。 此外,我们新生的网格生成器还告诉我们过程内容管道的外观。

架构上最重要的错误是将生成实现为具有纹理对象的一组操作。 从高级的角度来看,这可能是正确的方法,但是从实现的角度来看,诸如texture.DoSomething()Combine(textureA,textureB)之类的功能存在严重缺陷。

首先,无论它们多么简单,OOP样式都要求您将这些函数声明为API的一部分。 这是一个严重的问题,因为它无法很好地扩展,更重要的是,在创作过程中会产生不必要的摩擦。 我们不想每次需要尝试新事物时都更改API。 这使实验变得复杂,并限制了创作自由。

其次,就性能而言,此方法要求您以周期进行纹理数据处理的次数与操作次数相同。 如果这些操作相对于访问大型内存片段的成本而言代价很高,那么这将不是特别重要,但是通常并非如此。 除了极少的操作(例如,生成Perlin噪声填充)外 ,它们基本上非常简单,并且只需要在纹理点上执行几条指令即可。 也就是说,我们规避了纹理数据来执行琐碎的操作,从缓存的角度来看,这是非常低效的。

新结构通过逻辑重组解决了这些问题。 实际上,大多数功能对每个纹理元素独立执行相同的操作。 因此,我们无需编写绕过所有元素的texture.DoSomething()函数,而是可以编写texture.ApplyFunction(f) ,其中f(元素)仅适用于单个纹理元素。 然后可以根据特定的纹理来写f(元素)

这似乎是一个较小的更改。 但是,这种结构简化了API,使生成代码更灵活,更具表现力,对缓存更友好,并允许轻松进行并行处理。 许多读者已经意识到这实际上是一个着色器。 但是,实际的实现方式仍然是在处理器上执行的C ++代码。 我们仍然保留在循环外执行操作的能力,但是仅在必要时使用此选项,例如通过卷积。

那是:


//     . // API . //    -  API. //      . class ProceduralTexture { void DoSomething(parameters) { for (int i = 0; i < size; ++i) { //   . (*this)[i] = … } } void PerlinNoise(parameters) { … } void Voronoi(parameters) { … } void Filter(parameters) { … } void GenerateNormalMap() { … } }; void GenerateSomeTexture(texture t) { t.PerlinNoise(someParameter); t.Filter(someOtherParameter); … //  .. t.GenerateNormalMap(); } 

它变成了:


 //       . // API . //     . //      . class ProceduralTexture { void ApplyFunction(functionPointer f) { for (int i = 0; i < size; ++i) { //    . (*this)[i] = f((*this)[i]); } } }; void GenerateNormalMap(ProceduralTexture t) { … } void SomeTextureGenerationPass(void* out, PixelInfo in) { result = PerlinNoise(in); result = Filter(result); … //  .. *out = result; } void GenerateSomeTexture(texture t) { t.ApplyFunction(SomeTextureGenerationPass); GenerateNormalMap(t); } 

并行化


纹理生成需要时间,而减少此时间的明显选择是并行代码执行。 至少,您可以学习如何一次生成多个纹理。 这正是我们为F - Felix的工作室所做的工作 ,这大大减少了加载时间。

但是,这不能节省最需要的时间。 生成一个纹理仍然需要很多时间。 这适用于更改,因为我们在每次修改之前都会一次又一次地重新加载纹理。 相反,最好并行处理内部纹理生成代码。 由于现在代码基本上由一个大函数组成,该大函数循环应用于每个纹理像素,因此并行化变得简单而有效。 降低了实验,调整和草稿的成本,而这直接影响了创作过程。




我们探索并抛弃了H-浸入的想法的图示:带有orichalcon衬里的马赛克装饰。 此处显示在我们的交互式编辑工具中。

GPU侧面生成


如果这仍然不明显,那么我会说纹理生成完全在CPU中执行。 也许你们中的一些人现在正在阅读这些文章,并感到困惑“但是为什么?!”。 似乎显而易见的步骤是在视频处理器中生成纹理。 首先,它将使生成速率提高一个数量级。 那为什么不使用它呢?

主要原因是我们小的重新设计目标是保留在CPU上。 切换到GPU将意味着更多工作。 我们将不得不解决其他经验不足的问题。 使用CPU,我们对需要的内容有了清楚的了解,并且知道如何解决以前的错误。

但是,好消息是,由于采用了新的结构,现在尝试使用GPU显得微不足道。 测试这两种类型的处理器的组合将是未来有趣的实验。

纹理生成和物理上精确的阴影


旧设计的另一个限制是纹理仅被视为RGB图像。 如果我们需要生成更多信息,比如说同一个表面的漫反射纹理和法线纹理,那么什么也不能阻止我们这样做,但是API并没有太大帮助。 在基于物理的阴影(PBR)的背景下,这一点尤其重要。

在没有PBR的传统管道中,通常使用颜色纹理,其中烘焙了大量信息。 这样的纹理通常代表表面的最终外观:它们已经具有一定的体积,裂纹变暗,甚至可能在其上反射。 如果同时使用多个纹理,则通常将大比例尺细节和小比例尺细节组合在一起以添加法线贴图或表面反射率。

平面PBR输送机通常使用代表物理值而不是所需艺术效果的多个纹理集。 最接近通常称为表面“颜色”的漫反射色纹理通常是平坦且无趣的。 彩色镜面反射由表面的折射率确定。 大多数细节和可变性均来自法线和粗糙度(粗糙度)(有人可以认为相同,但具有两个不同的比例)。 表面的感知反射率成为其粗糙度级别的结果。 在这个阶段,考虑的不是材料而是材料。










新的结构使我们可以声明纹理的任意像素格式。 使其成为API的一部分之后,我们允许它处理所有样板代码。 声明像素格式后,我们可以专注于创意代码,而无需花费太多精力来处理这些数据。 在运行时,它将生成多个纹理并将它们透明地传输到GPU。

在某些PBR管道中,漫反射和镜面反射颜色不会直接传输。 取而代之的是使用“基础色”和“金属度”参数,这有其优点和缺点。 在“ H-浸入式”中,我们使用“漫反射+镜面反射”模型,材质通常由五层组成:

  1. 漫反射颜色(RGB; 0: Vantablack ; 1: 新鲜雪 )。
  2. 镜面反射的颜色(RGB:在90°反射的光的分数,也称为F0R0 )。
  3. 粗糙度(A; 0:完全光滑; 1:类似橡胶)。
  4. 法线(XYZ;单位向量)。
  5. 地形高程(A;用于视差遮挡贴图)。

使用时,发光信息直接添加到着色器中。 我们发现没有必要进行环境光遮挡,因为在大多数场景中根本没有环境光。 但是,对于其他层或其他类型的信息(例如,各向异性或不透明性),我不会感到惊讶。



上图显示了根据海拔高度生成局部环境光遮挡的最新实验。 对于每个方向,我们都要经过预定的距离并保持最大的斜率(高度差除以距离)。 然后,我们根据平均斜率计算遮挡。

制约因素和未来的工作


如您所见,新结构已成为旧结构的重大改进。 另外,她鼓励创造性表达。 但是,她仍然有我们将来要消除的限制。

例如,尽管本简介没有任何问题,但我们注意到内存分配可能是一个障碍。 生成纹理时,使用一组float值。 对于具有许多层的大型纹理,您可以快速解决内存分配问题。 有多种解决方法,但是它们都有缺点。 例如,我们可以逐块生成纹理,而可伸缩性会更好,但是某些操作(例如卷积)的实现变得不太明显。

此外,在本文中,尽管使用了“材料”一词,但我们仅谈论纹理,而不涉及着色器。 但是,使用材料也应导致着色器。 这种矛盾反映了现有结构的局限性:纹理生成和阴影是由桥分隔的两个独立部分。 我们试图使过桥变得容易,但实际上我们希望这些部分成为一体。 例如,如果一种材料同时具有静态和动态参数,那么我们想在一个地方描述它们。 这是一个复杂的主题,我们尚不知道是否有一个好的解决方案,但让我们不要超越自己。

图片

进行类似于上图所示的Imadol Delgado作品的织物纹理实验。

Source: https://habr.com/ru/post/zh-CN419007/


All Articles