Texture sampler in HLSL does not interpolate
我目前正在处理多纹理的地形,并且Texture2DArray的Sample函数遇到问题。
在我的示例中,我使用Texture2DArray存储一组不同的地形纹理,例如草,沙子,沥青等。我的每个顶点都存储一个纹理坐标(UV坐标)和我要使用的纹理索引。因此,如果索引为0,则使用第一个纹理。如果索引为1,则使用第二个纹理,依此类推。只要我的索引是自然数(0、1,..),这就可以正常工作。但是,如果索引是实数(例如1.5f),则失败。
为了查找问题,我将整个像素着色器缩小为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Texture2DArray DiffuseTextures : register(t0); Texture2DArray NormalTextures : register(t1); Texture2DArray EmissiveTextures : register(t2); Texture2DArray SpecularTextures : register(t3); SamplerState Sampler : register(s0); struct PS_IN { float4 pos : SV_POSITION; float3 nor : NORMAL; float3 tan : TANGENT; float3 bin : BINORMAL; float4 col : COLOR; float4 TextureIndices : COLOR1; float4 tra : COLOR2; float2 TextureUV : TEXCOORD0; }; float4 PS(PS_IN input) : SV_Target { float4 texCol = DiffuseTextures.Sample(Sampler, float3(input.TextureUV, input.TextureIndices.r)); return texCol; } |
下图显示了左侧示例场景的结果。如您所见,使用的纹理之间有一个硬边界。没有插值形式。
为了检查纹理索引,我通过将纹理索引作为颜色返回从上面更改了我的像素着色器:
1 | return float4(input.TextureIndices.r, input.TextureIndices.r, input.TextureIndices.r, 1.0f); |
结果可以在图像的右侧看到。纹理索引是正确的,因为它们的范围为[0,1],您可以清楚地看到该区域边界处的插值。但是,我采样的纹理没有显示任何形式的插值。
由于我的像素着色器非常简单,我不知道是什么原因导致了这种行为? DirextX中是否有任何设置对此负责?
我使用DirectX 11,像素着色器ps_5_0(我也对ps_4_0进行了测试),并且使用了DDS纹理(BC3压缩)。
编辑
这是我正在使用的采样器:
1 2 3 4 5 6 7 8 9 10 11 | SharpDX.Direct3D11.SamplerStateDescription samplerStateDescription = new SharpDX.Direct3D11.SamplerStateDescription() { AddressU = SharpDX.Direct3D11.TextureAddressMode.Wrap, AddressV = SharpDX.Direct3D11.TextureAddressMode.Wrap, AddressW = SharpDX.Direct3D11.TextureAddressMode.Wrap, Filter = SharpDX.Direct3D11.Filter.MinMagMipLinear }; SharpDX.Direct3D11.SamplerState samplerState = new SharpDX.Direct3D11.SamplerState(_device, samplerStateDescription); _deviceContext.PixelShader.SetSampler(0, samplerState); |
解决方案
我使用catflier提供的代码制作了一个函数来获取纹理颜色:
1 2 3 4 5 6 7 8 9 10 11 | float4 GetTextureColor(Texture2DArray textureArray, float2 textureUV, float textureIndex) { float tid = textureIndex; int id = (int)tid; float l = frac(tid); float4 texCol1 = textureArray.Sample(Sampler, float3(textureUV, id)); float4 texCol2 = textureArray.Sample(Sampler, float3(textureUV, id + 1)); return lerp(texCol1, texCol2, l); } |
这样,我可以通过一个简单的函数调用获得所有纹理类型(漫反射,镜面反射,自发光...)所需的纹理颜色:
1 2 3 4 | float4 texCol = GetTextureColor(DiffuseTextures, input.TextureUV, input.TextureIndices.r); float4 bumpMap = GetTextureColor(NormalTextures, input.TextureUV, input.TextureIndices.g); float4 emiCol = GetTextureColor(EmissiveTextures, input.TextureUV, input.TextureIndices.b); float4 speCol = GetTextureColor(SpecularTextures, input.TextureUV, input.TextureIndices.a); |
结果像我希望的那样平滑::-)
纹理数组不会跨切片采样,因此从技术上讲,这是预期的结果。
如果要在切片之间进行插值(例如:1.5f为您提供第二个纹理的"一半"和第三个纹理的"一半"),则可以使用Texture3d代替,它允许这样做(但会花费更多的钱)将执行三线性过滤)
否则,您可以通过以下方式执行采样:
1 2 3 4 5 6 7 8 9 | float4 PS(PS_IN input) : SV_Target { float tid = input.TextureIndices.r; int id = (int)tid; float l = frac(tid); //lerp amount float4 texCol1 = DiffuseTextures.Sample(Sampler, float3(input.TextureUV,id)); float4 texCol2 = DiffuseTextures.Sample(Sampler, float3(input.TextureUV,id+1)); return lerp(texCol1,texCol2, l); |
} ??
请注意,此技术非常灵活,因为您还可以提供非相邻切片作为输入(例如,您可以在切片2和23之间进行lerp),并最终通过更改lerp来使用其他混合模式函数。