关于C#:如何手动package纹理坐标?

How to wrap texture coordinates manually?

我正在使用C和HLSL,并且需要package我的纹理坐标,以便将纹理平铺在一个三角形上。

将坐标"package"到0-1范围后,它们将被旋转,所以我不能简单地使用纹理采样器的AddressU和AddressV属性设置为环绕,因为它们需要被环绕然后旋转,因此无法在采样器内部完成。

这里的解决方案很简单,只需使用纹理坐标的小数部分,它将进行package。

这是一个像素着色器的示例,它将对纹理进行36次平铺(6 * 6):

1
2
3
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来package坐标,但得到的结果完全相同。我还尝试过更改纹理过滤方法以及AddressU和AddressV属性,以及无数种不同的调试方法。

我很幸运使用此代码。如果x坐标太高,则将其设置为0,如果坐标太低,则将其设置为1。

1
2
3
4
5
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);

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

这是一张显示纹理的图片(左)和手动包裹时的外观(右)。您可以看到并非所有寄宿生都碰到此错误。

> </p>
<p>我也尝试过不将纹理坐标更改为0-1范围,而是围绕每个图块的中心而不是(0.5,0.5)旋转它们,但是我得到的结果是相同的。同样,我的纹理坐标完全独立于顶点,并在像素着色器内部进行计算。</p>
<p>我所见到的与该问题有关的任何事情都与在一个像素处具有较高的值,然后在下一个像素处具有较低的值有关,例如u = 0.95和下一个像素u = 0.03,这导致它向后插值整个纹理。但是,当我旋转纹理坐标时,根本没有任何改变。即使每个图块都应用了随机旋转。在这种情况下,边缘具有各种彼此相邻的不同值,不仅是左侧的高值和右侧的低值,而且接缝发生的区域从未改变。</p>
<p> <img src=

1
2
3
4
5
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);

现在,您可以应用代码来防止xchange和ychange发生重大变化,从而迫使图形设备使用更高的mipmap。这应该删除您的工件。

如果不需要纹理贴图,因为您正在对齐纹理屏幕,并且纹理尺寸不会导致过采样,则可以使用更简单的替代方法sampleLevel(文档),您可以在其中传递参数,它会选择一个特定的mipmap,您可以自己


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

如果您需要在每个范围内旋转纹理坐标(例如,分别旋转0..1、1..2),则有几种方法可以解决。首先,您可以将插值从线性更改为点,这样可以避免纹理像素之间的插值。但是,如果需要双线性插值,则可能无法接受。在这种情况下,您可以构造一个"网格"网格,并在每个图块上映射输入纹理0..1,而图块纹理坐标在着色器中独立旋转。

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

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

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


Gnietschow发布了该问题的答案,但我将添加一个答案,以确切说明我如何使用该答案。
实际上,我什至不知道为什么这行得通,我什至知道即使使用其他多个平铺,各种纹理和随机旋转也可以做到这一点。

1
2
3
4
5
6
7
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);

如果您根本不需要进行映射,则Gniet提到的另一种方法也可以正常工作

1
2
3
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);