像素扩展到背景中无缝绘画的方式 VR开发资源

manew_JR 2017-07-19 16:39:07
当使用不透明mask处理纹理时,VR开发资源将边缘像素扩展或扩展到背景中是非常重要的。 对于掩蔽材料,这可以防止下面的mip-maps中的边缘出血。 对于动态网格绘画效果,扩展位置纹理贴图将允许在UV边界之间进行无缝绘画。 位置纹理贴图对扩张工件非常敏感,因此对工作进行正确的扩张是很重要的。
 
 
 
 
在这篇文章中,我们将研究一种为静态网格创建位置纹理映射的方法,并在使用Unreal Engine 4的单个步骤中使用像素着色器来放大它们。提出了一种斜率外推法,用于更精确地重构边缘附近的像素点,就好像它们是在边缘上进行的一样。首先,我将简单地概述一下这个问题和使用屏蔽材料的纹理扩张。
 
 
上面的图片显示的是一个没有膨胀的纹理,与一个16和64像素半径的膨胀相比。下面一行显示了应用于一种蒙面材料的材料,并演示了低的mip映射显示了没有膨胀的背景出血。扩张的版本将保持它的外观,没有黑色的轮廓从远处。
除了上面的mip - map问题之外,在不使用膨胀时,甚至可能出现最高的mip问题。当渲染多边形到纹理贴图时,多边形的边缘倾向于从像素处切割成角度,留下像素在阶梯的阶梯上横跨多边形的边缘。这些像素要么是白色的,要么是黑色的,这样楼梯就会在纹理的分辨率下可见。
 
 
 
在实时呈现这个工件被称为混叠。蛮力解决方案是在更高的分辨率下渲染,然后将图像的图像分解为需要的分辨率。在许多情况下,这类作品可以通过纹理过滤工作和纹理来渐变,但你可能不会有足够高分辨率的原始资源。扩张解决这个问题的方法是将像素扩展到边缘,这样它们就可以与其他类似颜色的像素相混合,而不是纹理背景颜色。下面是一个使用Matinee相机静态网格的例子,它通过导出和重新导入fbx来生成lightmap UVs(这不是原始资产的一部分):
 
 
大多数图像编辑程序都有可以处理扩展的插件,而且大多数都能很好地处理颜色信息,但处理位置或规范时却不那么好。对于我自己的特殊情况,我需要在飞行中创建未包装的映射,并自动地将它们作为这个过程的一部分进行操作,因此我研究了创建自己的扩张式着色器。其结果是一个处理位置和正常数据的扩张器,比photoshop的xNormal膨胀过滤器更好,而且速度更快。
我的用例正在渲染使用UE4网格的动画因果纹理。这样做是为了在某种程度上控制因果关系,从而可以与呈现的水着色器进行同步,以及测试最近添加的“绘制目标”渲染管道在UE4中的极限。在下图中,用一个未包装的局部UVW位置映射渲染了因果关系,这张地图需要扩张来移除边缘工件。
 
 
 
有一个未包装的位置映射意味着您可以在静态网格上执行射线跟踪类型操作,例如通过在世界上进行行跟踪来绘制球形请求。这里有一个例子,展示了一个使用lightmap UVs和一个顶点着色器,并将位置编码成发射体的网格。从位置映射中计算出的第二个图像中显示了一个球形请求,这意味着这可以用一个呈现目标来迭代完成。
 
 
 
 
 
下面是一些基本的扩张器的代码。它在指定的距离上搜索8个相邻的方块模式。注意,在变量“偏移量”中,主轴在对角线偏移之前列出来。这只是一种简单的方法来确保最短距离的枢轴值被优先使用。
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
float texelsize = 1 / TextureSize;
float2 offsets[8] = {float2(-1,0), float2(1,0), float2(0,1), float2(0,-1), float2(-1,1), float2(1,1), float2(1,-1), float2(-1,-1)};
float3 sample = Tex.SampleLevel(TexSampler, UV, 0);
 
if(sample.x != 0 || sample.y != 0 || sample.z != 0) return sample;
int i, j = 0;
 
while(i < MaxSteps)
{
    i++;
    while (j < 8)
    {
        float2 curUV = UV + offsets[j] * texelsize * i;
        float3 offsetsample = Tex.SampleLevel(TexSampler, curUV, 0);
         
        if(offsetsample.x != 0 || offsetsample.y != 0 || offsetsample.z != 0) return offsetsample;
 
        j++;
    }
}
 
return MinSample;
 
这种方法适用于某些情况,但这种简单方法的问题在于,它只是将边缘像素从它们所在的地方拉伸。这意味着边缘上的任何被过滤的文本将会被插入到不正确的值中,因为相邻的像素将不会趋向于实际运行的方向。本例比较了一种与单纯的扩张没有膨胀的原因的因果关系:
 
 
 
为了解决这个问题,我们可以引入一些斜率外推的数学运算。这意味着代码在任何时候都能找到一个可能的像素,而不是仅仅使用那个值,然后再检查另一个像素,并稍微偏移一点,从而得到这两个像素之间的差异。利用两个采样点之间的距离,你可以建立一个斜率,然后用它来将样本外推到你实际写的象素上。
 
 
 
在上面的图像中,您可以想象蓝色阴影多边形是一些高度图或位置纹理的侧面轮廓视图。绿点是我们想要扩张的像素。通过简单的扩张,这个值直接复制到最近的找到的邻居(上面的蓝点)。这就导致了红色的线是平的并且不会继续保持原始数据的斜率。通过取一个额外的偏移量(上面的橙色点),可以计算斜率,最后的值可以是原始数据的连续斜率。注意图像使用“投影”这个词,但DanielW告诉我“外推”更准确,所以我把它换成了文本。
要将这个添加到上面的着色器中只需要几行。这发生在最后一个if语句中,如果找到的邻居不等于0,它将检查。
 
1
2
3
4
float2 projectUV = curUV + offsets[i] * texelsize * j * 0.25;
float3 direction = Tex.SampleLevel(TexSampler, projectUV, 0);
 
float3 delta = offsetsample - direction;
MinSample = offsetsample + delta * 4;
 
这只会把另一个样本推到0.25乘以电流偏移量。选择这个值是为了避免跳过太长时间,并可能在目标像素的另一边读取黑色值。这可以很容易地用另一个迭代检查来改进,以找到理想的新示例位置,但是在我的测试中,这个工作很好,因为我在UV布局上没有任何非常薄的部分。注意,δx4,考虑到偏移量是0.25倍。
 
以下是如何改进的源头:
 
 
 
现在让我们快速看一下这是如何改进膨胀的结果。一个只有8个样本方向的标准膨胀看起来并不高,因为你很快就会发现8个样本方向与距离和一条硬线的形式有差异。边坡外推完全消除了显示8个样本方向的工件,甚至在这个孤立的紫外环内完美地连接了UVs:
 
 
在上面的图像中很难看到,但是在使用这个位置映射时,中心峰值的平滑实际上并不是使它工作得更好的原因。它是原始彩色像素外数据的斜率和曲率的延续。在下面的例子中,我增加了图像的对比度和指向感兴趣区域的箭头。注意在左边,值停止在原始波段的两边增加,这导致了某种可见的缩放,而在右边整个区域是连续的。
 
 
执行“自动”的扩张
在UE4内部,通过使用顶点着色器和渲染目标,几乎可以实现这个过程的自动化。为了做到这一点,我们需要提前建立一些资产。我们需要创建两个渲染目标,两个材料,一个演员蓝图。您想要打开的任何网格都需要有唯一的lightmap UVs。
 
The Render Targets
在内容浏览器中,点击添加新的- >材质和纹理- >渲染目标。创建两个。在他们被创建后,打开他们,并把分辨率设置为你需要的分辨率。一个好的开始是1024x1024。您可以任意命名它们,但是我建议给它们命名:RT_SceneCapture,然后是RT_Dilated,以避免稍后出现混淆。
 
The Unwrap Material
制作一种新的材料,并将其设置为无灯和双面(以防有任何光映射的UVs被镜像,这将导致它们在打开的情况下向后移动)。然后将这些节点插入到世界位置,以抵消材料的输入。“大小”将是您设置场景捕获参与者的正射线宽度。在本例中,我将网格的局部位置编码为0 - 1,并将网格的边界框按到渲染目标中,使用由Jon Lindquist创建的一个由UE4包含的材料函数。这是有用的,因为它可以用来检查射线追踪或光子映射时的射线位置。
 
 
 
The Dilation Material
膨胀材料将需要放置自定义节点。代码的全文可以在本文的末尾找到。现在简单地设置所有输入插脚来匹配这个图像,您还可以设置纹理对象来指向上面的RT_SceneCapture(或者您可以将这个纹理对象参数设置为更大的灵活性,但它不是必需的)。为了防止GPU崩溃,如果你不小心键入了一个像50000这样的疯狂值,那么它就会设置一个夹。2048选择的值是任意大的,但我只是想挑一些比你需要的更大的东西,这不会杀死GPU。当我输入这一点时,我意识到它应该更像64或128,但我不想把图像写出来。您可以使用纹理属性节点,它可以自动给出纹理大小,但是它似乎有一些与纹理对象参数相关的错误,所以我在这里没有包含它。
 
 
 
The Actor Blueprint
 
 
 
创建一个新的Actor蓝图,并添加一个“场景捕获组件2D”组件。选择该组件,并将投影类型设置为正射,并将标准宽度设置为1000,以匹配1000的UV外包装尺寸。将纹理目标设置为我们在第一步中创建的RT_SceneCapture。也将位置设置为0,0500,并将旋转设置为- 90,- 90,0。这是为了匹配从上往下看的UV的方向。最后,把“捕捉每一个帧”设置为假,只是为了防止它连续地捕捉。
接下来,向blueprint添加一个“静态网格”组件,并将其所有设置设置为默认。然后添加一个新变量,使其成为“静态网格”,使其可编辑(这个小的眼球按钮在变量右边)。
 
现在转到blueprint的构建脚本,并放置一个“Set静态网格”节点,该节点将静态网格组件设置为使用所创建的“静态网格”变量。接下来,添加一个“Set材质”节点,并指定前面提到的unwrap内容。使用多个材质元素的静态网格可以使用for循环,使用“get num materials”- 1作为最后一个索引,将unwrap材质分配给所有元素。


 
现在我们只需要向事件图添加一些blueprint节点。首先我们放置一个“自定义事件”,并命名为“Dilate”。在这个事件中,我们只做两件事。首先,我们告诉场景捕捉以捕获图像。然后我们称之为“绘制材料渲染目标”。我们使用我们的膨胀材料作为材料,我们的第二个渲染目标,RT_Dilate,作为指定的渲染目标。


 
 
 
现在选择自定义事件,在blueprint编辑器的details面板中,将“调用编辑器”设置为True。
 

 
现在你所要做的就是把这个蓝图的一个实例放到世界上,选择它,然后从bl功利小组的details面板中运行“Dilate”事件:


 
 
 
现在只需按下“Run”按钮,我们就可以得到指定静态网格的膨胀纹理映射。在这里,飞机被放置在设计图旁边的世界中,在运行了Dilate事件后显示RT_Dilated:


 
 
 
一旦你将这些数据写入渲染目标,你通常可以执行你需要的任何操作,因为位置映射在HDR表单中效果最好。如果您想要创建一个静态纹理,您可以通过右键单击内容浏览器中的渲染目标并选择“创建静态纹理”来实现。


 
 
 
实现膨胀的代码。请记住,对于所有类型的UV布局,此代码可能不灵活,并且可以通过在偏移邻居查找代码中添加进一步检查来改进。它可以通过修改一些修改来处理常规的颜色纹理,通过将示例变量更改为float4并使用alpha代替rgb作为0检查。两种偏移样本甚至可以采用曲率而不是斜率作为输入,从而得到更精确的结果。
 
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//////////////// UV Positional Dilation ///////////////////////////
//** Tex **// Input Texture Object storing Volume Data
//** UV **// Input float2 for UVs
//** TextureSize **// Resolution of render target
//** MaxSteps **// Pixel Radius to search
 
 
float texelsize = 1 / TextureSize;
float mindist = 10000000;
float2 offsets[8] = {float2(-1,0), float2(1,0), float2(0,1), float2(0,-1), float2(-1,1), float2(1,1), float2(1,-1), float2(-1,-1)};
 
float3 sample = Tex.SampleLevel(TexSampler, UV, 0);
float3 curminsample = sample;
 
if(sample.x == 0 && sample.y == 0 && sample.z == 0)
{
    int i = 0;
    while(i < MaxSteps)
    {
        i++;
        int j = 0;
        while (j < 8)
        {
            float2 curUV = UV + offsets[j] * texelsize * i;
            float3 offsetsample = Tex.SampleLevel(TexSampler, curUV, 0);
 
            if(offsetsample.x != 0 || offsetsample.y != 0 || offsetsample.z != 0)
            {
                float curdist = length(UV - curUV);
 
                if (curdist < mindist)
                {
                    float2 projectUV = curUV + offsets[j] * texelsize * i * 0.25;
                    float3 direction = Tex.SampleLevel(TexSampler, projectUV, 0);
                    mindist = curdist;
 
                    if(direction.x != 0 || direction.y != 0 || direction.z != 0)
                    {
                        float3 delta = offsetsample - direction;
                        curminsample = offsetsample + delta * 4
                    }
                   else
                    {
                        curminsample = offsetsample;
                    }
                }
            }
            j++;
        }
    }
}
 
return curminsample;
99VR视界二维码
热门推荐
Hot Recommended
在线客服