如何手动包装纹理坐标?

问题描述 投票:0回答:3

我正在使用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);

这只能解决某些问题,所以绝对不是解决方案。

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

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

我所看到的与此问题相关的任何事情都与在一个像素处具有高值然后在下一个像素处具有低值有关,例如u = 0.95并且下一个像素u = 0.03,这导致其在纹理上向后插值。但是,当我旋转纹理坐标时,根本没有任何变化。即使每个瓷砖都应用了随机旋转。在这种情况下,边缘具有彼此接壤的各种不同的值,不仅左侧的值高而右侧的值低,而且接缝发生的区域永远不会改变。

c++ filter textures hlsl
3个回答
2
投票

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

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

如果纹理变得失真,最高级别的采样将导致过采样(像工件那样的点过滤)。为了对抗过采样,纹理查找根据屏幕空间中纹理坐标的变化选择适当的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);

现在,您可以应用代码来阻止交换和更改中的重大更改,以强制图形设备使用更高的mipmap。这应该删除你的工件。

如果你的纹理不需要mipmapping,因为渲染纹理屏幕并且纹理大小不会导致过采样,你可以使用更简单的替代sampleLevelDocs)你可以传递一个参数,它选择一个特定的mipmap ,你可以自己决定。


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
投票

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);
© www.soinside.com 2019 - 2024. All rights reserved.