2015-02-11 66 views
0

编辑:我发现问题类似于问题here,但我仍然不能提出解决方案,因为我的问题有点不同。手动包装纹理坐标

我正在使用C++和HLSL,并需要将纹理坐标包裹起来,以便纹理平铺在三角形上。
坐标被“包裹”到0-1范围后,它们将被旋转,所以我不能简单地使用纹理采样器AddressU和AddressV属性设置为包装,因为它们需要被包装然后旋转,所以它可以在采样器内部不要做。
这里的解决方案很简单,只需使用纹理坐标的小数部分即可。

这里是一个像素着色器的一个例子将瓦片纹理36倍(6×6) -

input.tex *= 6.0f; //number of times to tile^2 
input.tex = frac(input.tex); //wraps texCoords to 0-1 range 
return shaderTexture.Sample(SampleType, input.tex); 

这确实瓦的纹理,但可以在边界其中产生问题纹理包装。这些瓷砖必须均匀分配到它们正在显示的空间上,或者它会在边界相交处形成一个接缝。我的正方形纹理被绘制到800x600像素,所以平铺5将平均分割,但6不会,并将导致沿Y轴的接缝。
我已经尝试使用模运算符input.tex = input.tex % 1来包裹坐标,但我得到完全相同的结果。我也尝试改变纹理过滤方法,AddressU和AddressV属性以及无数种不同的调试方法。

我有一些运气使用此代码。如果x坐标过高它被设置为0,如果是太低,它被设置为1

input.tex *= 6.0f; 
input.tex = frac(input.tex); 
if (input.tex.x > 0.999f) input.tex.x = 0; 
if (input.tex.x < 0.001f) input.tex.x = 1; 
return shaderTexture.Sample(SampleType, input.tex); 

这仅仅修复在特定的地点的问题了,所以它绝对不是一个解决方案。

这张图片显示了一个纹理(左)和手动包装时的样子(右)。你可以看到寄宿生触及的并不是每个地方都有这个错误。 enter image description here

我也试过没有改变纹理坐标到0-1的范围,并围绕每个瓷砖的中心而不是(0.5,0.5)旋转它们,但是我得到了相同的结果。此外,我的纹理坐标完全独立于顶点,并在像素着色器内进行计算。

我看到的与这个问题有关的任何事情都与一个像素具有高值并且接下来具有较低值有关,例如u = 0.95和下一个像素u = 0.03,这导致它向后插入纹理。但是当我旋转我的纹理坐标时,根本没有任何变化。即使每个图块都应用了随机旋转。在这种情况下,边缘具有彼此接近的各种不同的值,不仅左侧高值而右侧低值,而且接缝发生的区域不会改变。 enter image description here

回答

2

正如MuertoExcobito所说,主要问题是在边界纹理坐标在1个像素中从1跳到0。在语义上说,整个纹理在这个像素中取平均值是正确的,但它不是由在这个像素中从1到0的纹理内插引起的。真正的原因是mipmapping。

对于您的纹理,在您加载时会生成mipmap。这意味着纹理获得了多个mipmap级别,这些级别都是前一半的大小。

enter image description here

如果纹理变得扭曲的最高水平的采样会导致过采样(pointfiltering状伪像)。为了对抗过采样,纹理查找会根据屏幕空间中纹理坐标的变化选择合适的mipmaplevel。在你的情况下,边界是一个很小的地方变化很大,这导致使用最低的mipmap可能(这是因为你看到一个小红点,这是红色边界的原因)。

回到您的问题,您应该通过使用纹理查找方法SampleGradDocs)来控制mipmapping。要获得像素纹理坐标的当前更改,可以使用内在方法ddxddy。他们在着色器中返回一个任意变量,它在本地如何变化到相邻像素(正确的解释将深入本主题)。因此,使用下面的代码应该不会改变任何东西,因为它应该是相同的语义:

input.tex *= 6.0f; //number of times to tile^2 
input.tex = frac(input.tex); //wraps texCoords to 0-1 range 
float2 xchange = ddx(input.tex); 
float2 ychange = ddy(input.tex); 
return shaderTexture.SampleGrad(SampleType, input.tex, xchange, ychange); 

现在还能用自己的代码以防止在交换网和ychange迫使图形设备使用更高的纹理贴图大的变化。这应该删除你的工件。

如果你的纹理不需要纹理映射,因为你是渲染纹理画面对准和纹理大小不会导致过采样,可以使用更简单的替代sampleLevelDocs)在那里,你可以传递参数,这选择一个特定的mipmap,你可以自己决定。

+0

我一直在想我希望有一种方法来找出像素之间的变化,这工作完美,并保持mipmap水平在一个常数0工作,所以我相信你是正确的,这是一个mipmapping问题。非常感谢! – Frobot 2015-02-17 18:28:38

+0

我添加了一个答案,显示了我是如何实现这个 – Frobot 2015-02-17 22:43:25

1

此处的代码导致在单个像素范围内对整个纹理进行采样。例如,对于接缝处的两个相邻像素,一个“u”样本可能为1.0-eps,下一个样本为0.0+eps,其中eps为小于纹理元素宽度的数字。当输出像素被内插时,您将从1.0.0.0插值,对这两个样本之间的整个纹理进行采样。即使您的输入纹理实际上不包含任何完全灰色的像素,整个纹理的平均值也会导致“灰度”。

如果您需要旋转每个范围内的纹理坐标(例如,0..1,1..2独立旋转),有几种方法可以解决这个问题。首先,您可以将插值从线性更改为点,这将避免纹理元素之间的插值。但是,如果您需要双线性插值,这可能是不可接受的。在这种情况下,您可以构造一个“网格”网格,并将输入纹理0..1映射到每个瓦片上,并且瓦片纹理坐标在着色器中独立旋转。

另一种可能的解决方案是将坐标转换为0..1空间,执行旋转,然后将它们转换回原始空间。例如,你会这样做:

// pseudo-code: 
int2 whole; 
input.tex = modf(input.tex, whole); 
input.tex = Rotate(input.tex); // do whatever rotation is needed 
input.tex += whole; 

这将确保包装没有任何不连续性。或者,您可以让您的旋转代码考虑非统一空间纹理坐标。

+0

是每个区块被独立地被随机量旋转。我真的需要双线性插值来保持质量。我可以看到如何使用网格网格来解决这个问题,但它不适合我的目的。我的纹理坐标独立于顶点位置,并在PS中计算。我的算法在网格上铺贴纹理,随机地旋转每个拼贴,并在一个方格之间平滑地变形,从而基本上创建一个不明显的平铺的无限无缝纹理。除了这个问题之外,它的运作非常好。 – Frobot 2015-02-11 18:50:33

+0

谢谢你的发布,你理解我的问题完美。我认为有一种方法可以保持u,v坐标在不添加顶点的情况下跳跃,但这可能需要一些沉重的思考。 – Frobot 2015-02-11 18:51:20

+0

增加了另一种可能的解决方案,这将允许您保留bilerp,而不需要创建网格网格。 – MuertoExcobito 2015-02-11 19:03:22

0

Gnietschow发布了这个问题的答案,但我将添加一个答案,显示我如何使用答案。 我其实并不确定为什么这样工作,我只知道它甚至可以用其他倍数的平铺,各种纹理和随机旋转。

input.tex *= 6.0f; //number of times to tile^2 
input.tex = frac(input.tex); //wraps texCoords to 0-1 range 
float2 xchange = ddx(input.tex); 
float2 ychange = ddy(input.tex); 
if ((xchange.x < 0)) xchange = float2(0, 0); 
if ((ychange.y < 0)) ychange = float2(0, 0); 
return shaderTexture.SampleGrad(SampleType, input.tex, xchange, ychange); 

如果你不需要mipmapping贴图可言,这另一种方法Gniet提到的优秀作品也

input.tex *= 6.0f; //number of times to tile^2 
input.tex = frac(input.tex); //wraps texCoords to 0-1 range 
return shaderTexture.SampleLevel(SampleType, input.tex, 0);