当将纹理(作为平面3D对象绘制)转换成模拟深度时,黑色线条会随机出现。

50

我们正在使用XNA开发一个顶视角RPG游戏。最近,在编写显示地图的代码时遇到了一些问题。当绘制地图时,使用普通的转换矩阵进行顶视图绘制时,一切似乎都很正常。但是,当使用非平面的转换矩阵,例如将顶部或底部压缩以模拟深度时,会出现黑色线条(当顶部或底部被压缩时为行,当左侧或右侧被压缩时为列),这些线条在相机位置改变时会移动和放置,看起来是随机的。(下方提供了图片)

背景信息

地图由瓷砖组成。原始纹理的瓷砖由32x32像素组成。我们通过创建2个三角形并在这些三角形上显示原始纹理的一部分来绘制瓷砖。着色器为我们完成此操作。有三层三角形。首先,我们绘制所有不透明的瓷砖和所有半透明和部分透明瓷砖的不透明像素,然后绘制所有半透明和部分透明瓷砖和像素。这很好用(但是当我们按浮点因子缩放时,有时在瓷砖行和/或列之间会出现颜色混合的线条)。

渲染状态

我们对所有瓷砖使用相同的光栅化器状态,并在绘制实心或半透明瓷砖时切换两者之间。

_rasterizerState = new RasterizerState();
_rasterizerState.CullMode = CullMode.CullCounterClockwiseFace;

_solidDepthState = new DepthStencilState();
_solidDepthState.DepthBufferEnable = true;
_solidDepthState.DepthBufferWriteEnable = true;

_alphaDepthState = new DepthStencilState();
_alphaDepthState.DepthBufferEnable = true;
_alphaDepthState.DepthBufferWriteEnable = false;

在阴影中,我们将SpriteBlendMode设置如下:

第一个实心层1使用

AlphaBlendEnable = False; 
SrcBlend = One; 
DestBlend = Zero; 

所有其他的实心和透明层(后绘制)使用

AlphaBlendEnable = True; 
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha; 

其他着色器也使用它。用于使用SpriteFontsSpriteBatch使用默认设置。

生成的纹理

一些瓷砖是即时生成并保存到文件中的。当地图加载时,该文件被加载。这是使用以下方式创建的RenderTarget完成的:

RenderTarget2D rt = new RenderTarget2D(sb.GraphicsDevice, 768, 1792, false, 
    SurfaceFormat.Color, DepthFormat.None);
    sb.GraphicsDevice.SetRenderTarget(rt);

生成后,文件将被保存并加载(这样当设备重置时,我们就不会失去它,因为它不再位于RenderTarget上)。我尝试使用mipmapping,但它是一个精灵表。没有关于瓦片放置位置的信息,所以mipmapping是无用的,它没有解决问题。

顶点

我们循环遍历每个位置。目前还没有浮点数,但位置是Vector3(Float3)。

for (UInt16 x = 0; x < _width;  x++)
{
    for (UInt16 y = 0; y < _heigth; y++)
    {
        [...]
        position.z = priority; // this is a byte 0-5

以下代码用于定位瓦片:

tilePosition.X = position.X;
tilePosition.Y = position.Y + position.Z;
tilePosition.Z = position.Z;

正如您所知,float 类型为32位,其中24位用于精度。变量z的最大比特位值为8位(5 = 00000101)。变量X和Y的最大值分别为16位和24位。我假设在浮点数方面不会出现任何问题。

this.Position = tilePosition;

当顶点被设置时,它会按以下方式执行(这样它们都共享相同的瓷砖位置)

Vector3[] offsets  = new Vector3[] { Vector3.Zero, Vector3.Right, 
    Vector3.Right + (this.IsVertical ? Vector3.Forward : Vector3.Up), 
    (this.IsVertical ? Vector3.Forward : Vector3.Up) };
Vector2[] texOffset = new Vector2[] { Vector2.Zero, Vector2.UnitX, 
    Vector2.One, Vector2.UnitY };

for (int i = 0; i < 4; i++)
{
    SetVertex(out arr[start + i]);
    arr[start + i].vertexPosition = Position + offsets[i];

    if (this.Tiles[0] != null)
        arr[start + i].texturePos1 += texOffset[i] * this.Tiles[0].TextureWidth;
    if (this.Tiles[1] != null)
        arr[start + i].texturePos2 += texOffset[i] * this.Tiles[1].TextureWidth;
    if (this.Tiles[2] != null)
        arr[start + i].texturePos3 += texOffset[i] * this.Tiles[2].TextureWidth;
}

着色器

着色器可以绘制动画瓷砖和静态瓷砖。两者都使用以下采样器状态:

sampler2D staticTilesSampler = sampler_state { 
    texture = <staticTiles> ; magfilter = POINT; minfilter = POINT; 
    mipfilter = POINT; AddressU = clamp; AddressV = clamp;};

着色器没有设置任何不同的采样器状态,我们在代码中也没有这样做。

每个通道,我们使用以下行在 alpha 值处剪裁(以避免出现黑色像素)

clip(color.a - alpha)

Alpha在实心层1中为1,而在其他任何层中几乎为0。这意味着如果有Alpha的一部分,它将被绘制,除非在底层(因为我们不知道该怎么处理它)。

相机

我们使用相机来模拟从上往下查找瓷砖,使它们看起来平坦,并使用z值按外部分层数据对它们进行分层(3个层次不总是按正确顺序排列)。这也很好地发挥作用。相机更新变换矩阵。如果你想知道为什么它有像这样的奇怪结构.AddChange - 代码是双缓冲的(这也有效)。变换矩阵形成如下:

// First get the position we will be looking at. Zoom is normally 32
Single x = (Single)Math.Round((newPosition.X + newShakeOffset.X) * 
    this.Zoom) / this.Zoom;
Single y = (Single)Math.Round((newPosition.Y + newShakeOffset.Y) * 
    this.Zoom) / this.Zoom;

// Translation
Matrix translation = Matrix.CreateTranslation(-x, -y, 0);

// Projection
Matrix obliqueProjection = new Matrix(1, 0, 0, 0,
                                      0, 1, 1, 0,
                                      0, -1, 0, 0,
                                      0, 0, 0, 1);

Matrix taper = Matrix.Identity; 

// Base it of center screen
Matrix orthographic = Matrix.CreateOrthographicOffCenter(
    -_resolution.X / this.Zoom / 2, 
     _resolution.X / this.Zoom / 2, 
     _resolution.Y / this.Zoom / 2, 
    -_resolution.Y / this.Zoom / 2, 
    -10000, 10000);

// Shake rotation. This works fine       
Matrix shakeRotation = Matrix.CreateRotationZ(
    newShakeOffset.Z > 0.01 ? newShakeOffset.Z / 20 : 0);

// Projection is used in Draw/Render
this.AddChange(() => { 
    this.Projection = translation * obliqueProjection * 
    orthographic * taper * shakeRotation; }); 

推理和流程

有三层瓷砖数据。每个瓷砖都由IsSemiTransparent定义。当一个瓷砖是IsSemiTransparent时,它需要在不是IsSemiTransparent的东西之后绘制。当在SplattedTile实例上加载时,瓷砖数据会被堆叠。因此,即使瓷砖数据的第一层为空,SplattedTile的第一层也会有瓷砖数据(假设至少有一层有瓷砖数据)。原因是Z-buffer不知道如果按顺序绘制它们会与什么混合,因为可能没有实心像素在其后面。

这些层没有z值,单个瓷砖数据有。当它是地面瓷砖时,它具有Priority = 0。因此,具有相同Priority的瓷砖将按图层(绘制顺序)和不透明度(半透明,在不透明后)排序。具有不同优先级的瓷砖将根据其优先级绘制。

第一层实心图层没有目标像素,所以我将其设置为DestinationBlend.Zero。它也不需要AlphaBlending,因为没有需要进行颜色混合的对象。其他图层(5、2个实心、3个透明)可能会在已经具有颜色数据并需要相应地混合时绘制。

在迭代6次之前,首先设置投影矩阵。当不使用锥度时,这样可以正常工作。但如果使用了锥度,就无法正常工作。

问题

我们想通过应用锥度来模拟更多的深度,使用某些矩阵。我们尝试了几个值,但以下是一个示例:

new Matrix(1, 0, 0, 0,
           0, 1, 0, 0.1f,
           0, 0, 1, 0,
           0, 0, 0, 1);

屏幕(所有高度值为0的东西,所有平面物体)将被挤压。y越低(在屏幕上越高),它就会被挤压得越多。这实际上是有效的,但现在几乎到处都出现了随机的黑线。它似乎排除了一些瓷砖,但我不知道它们之间的关联。我们认为这可能与插值或mipmaps有关。

这里有一张图片,可以让你看到我在说什么: Screenshot with lines.

未受影响的瓷砖似乎是静态瓷砖,不在底层。然而,在这些瓷砖上方的透明瓷砖显示其他图形伪像。它们缺少线条(因此行被删除)。我标记了这段文字,因为我认为它是发生的提示。如果我将mip mag和minfilter设置为Linear,则会出现垂直线。

这里是一个放大的图像(游戏中的缩放),显示第2或第3层瓷砖上的文物 带有线条的截图,放大

我们已经尝试过了

  • PointLinear上使用mipfilter
  • 在原始纹理上设置GenerateMipMaps
  • 在生成的纹理上设置GenerateMipMapsRenderTarget的true标志构造函数)
  • 打开mipmapping(只有在缩小时会产生更多伪影,因为我正在对精灵表进行mipmapping)。
  • 不绘制第2和第3层(这实际上使所有瓷砖都有黑线)
  • DepthBufferEnable = false
  • 将所有实心层设置为SrcBlend = One; DestBlend = Zero;
  • 将所有实心层设置为ScrBlend = SrcAlpha; DestBlend = InvSrcAlpha;
  • 不绘制透明层(线仍然存在)。
  • shader中删除clip(opacity)。这只能去掉一些线条。我们正在进一步调查此问题。
  • 在msdn、stackoverflow和谷歌上搜索相同的问题(但没有找到解决方法)。
有人认识这个问题吗?最后需要注意的是,在绘制瓷砖后我们调用SpriteBatch,并为头像使用另一个Shader(因为它们的高度> 0,所以没有问题)。这会撤销我们的sampler state吗?或者...?

2
抱歉,系统维基在一定数量的编辑后被取消了。已取消。 - user1228
我在 Meta 上读到了这个,谢谢 @Will 把它改回给我。也许等问题解决了再改回去吧 ;)。 - Derk-Jan
@Derk-Jan:您有没有可能将您的代码发布到某个地方,这样我们就可以直接查看它,更容易帮助调试了呢? - Neil Knight
@Neil,我们将在网站上发布一个仅包含此问题的版本。 - Derk-Jan
@Neil,这里是你要的。 - Derk-Jan
显示剩余5条评论
5个回答

5
看一下最后一张图片底部的石头 - 它有沙色的线条。 可能,您是先绘制沙子,然后在上面绘制岩石。
这告诉我它不是通过纹理绘制“黑色线条”,而是纹理的某些部分没有被绘制。由于在垂直方向拉伸时会发生这种情况,这几乎肯定意味着您正在创建从旧像素到新像素的映射,而没有在新纹理中插入中间值。
例如,使用映射(x,y)-->(x,2y),点将被映射为(0,0)-->(0,0)、(0,1)-->(0,2)和(0,2)-->(0,4)。注意,在源纹理中没有任何点映射到(0,1)或(0,3)。这会导致背景透过来。 我敢打赌,如果你改成水平拉伸,你会看到垂直线。
您需要做的是反向映射:给定目标纹理中的每个像素,使用上述变换的倒数在源图像中找到其值。您可能会得到小数值的像素坐标,因此您需要插值值。
我对XNA一点都不熟悉,但可能有比手动更方便的方法。

我能够在黑线下面行走(它们会填充字符像素),这已经让我有了那个想法。这就是为什么我从未说过黑线是被画出来的。更可能的是像素行被剪切或丢失了。我不知道。Sandpriority 0(底层)。Rockpriority 1Rock 的顶部具有 priority 2。所以,是的,你的假设是正确的。如果你将其水平拉伸,你会看到垂直线。难道像素着色器不应该进行插值吗?你知道如何在 HLSL 中实现这个吗? - Derk-Jan
看看这个3D模型上的转换纹理。为什么它没有线条,但是被映射了呢? - Derk-Jan
刚刚阅读了Shawn Hargreaves的过滤器相关内容。我们使用内置函数,所以这应该会自动发生(最小值,我们正在缩小比例,对吧?)。 - Derk-Jan
我将不得不对这个答案进行悬赏。非常值得。 - Jonathan Dickinson
@JonathanDickinson,这是一篇非常有信息量的文章,但与手头的问题无关,也没有任何解决方案。 - Derk-Jan

5

这个问题与HLSL中的数字类型有关。

首先,让我们清理一下这个着色器。我会这样做,因为这是我们找到实际问题的方式。以下是在修补程序 taperfix 放入之前的 SplattedTileShader.fx 的统一差异:

@@ -37,76 +37,31 @@
    data.Position = mul(worldPosition, viewProjection);

-   
     return data;
 }

-float4 PixelLayer1(VertexShaderOutput input, uniform float alpha) : COLOR0
+float4 PixelLayer(VertexShaderOutput input, uniform uint layer, uniform float alpha) : COLOR0
 {
-   if(input.TextureInfo[0] < 1)
+   if(input.TextureInfo[0] < layer)
        discard;

-    float4 color;
+   float4 color;
+   float2 coord;
+   if(layer == 1)
+       coord = input.TexCoord1;
+   else if(layer == 2)
+       coord = input.TexCoord2;
+   else if(layer == 3)
+       coord = input.TexCoord3;

-   switch (input.TextureInfo[1])
+   switch (input.TextureInfo[layer])
    {
        case 0:
-           color = tex2D(staticTilesSampler, input.TexCoord1);
+           color = tex2D(staticTilesSampler, coord);
            break;
        case 1:
-           color = tex2D(autoTilesSampler, input.TexCoord1);
+           color = tex2D(autoTilesSampler, coord);
            break;
        case 2:
-           color = tex2D(autoTilesSampler, input.TexCoord1 + float2(frame, 0) * animOffset) * (1 - frameBlend) + tex2D(autoTilesSampler, input.TexCoord1 + float2(nextframe, 0) * animOffset) * frameBlend;
-           break;
-   }
-
-   clip(color.a - alpha);
-
-   return color;
-}
-
-float4 PixelLayer2(VertexShaderOutput input, uniform float alpha) : COLOR0
-{
-   if(input.TextureInfo[0] < 2)
-       discard;
-
-    float4 color;
-
-   switch (input.TextureInfo[2])
-   {
-       case 0:
-           color = tex2D(staticTilesSampler, input.TexCoord2);
-           break;
-       case 1:
-           color = tex2D(autoTilesSampler, input.TexCoord2);
-           break;
-       case 2: 
-           color = tex2D(autoTilesSampler, input.TexCoord2 + float2(frame, 0) * animOffset) * (1 - frameBlend) + tex2D(autoTilesSampler, input.TexCoord2 + float2(nextframe, 0) * animOffset) * frameBlend;
-           break;
-   }
-
-   clip(color.a - alpha);
-
-   return color;
-}
-
-float4 PixelLayer3(VertexShaderOutput input, uniform float alpha) : COLOR0
-{
-   if(input.TextureInfo[0] < 3)
-       discard;
-
-    float4 color;
-
-   switch (input.TextureInfo[3])
-   {
-       case 0:
-           color = tex2D(staticTilesSampler, input.TexCoord3);
-           break;
-       case 1:
-           color = tex2D(autoTilesSampler, input.TexCoord3);
-           //color = float4(0,1,0,1);
-           break;
-       case 2:
-           color = tex2D(autoTilesSampler, input.TexCoord3 + float2(frame, 0) * animOffset) * (1 - frameBlend) + tex2D(autoTilesSampler, input.TexCoord3 + float2(nextframe, 0) * animOffset) * frameBlend;
+           color = tex2D(autoTilesSampler, coord + float2(frame, 0) * animOffset) * (1 - frameBlend) + tex2D(autoTilesSampler, coord + float2(nextframe, 0) * animOffset) * frameBlend;
            break;
    }
@@ -125,5 +80,5 @@
        DestBlend = Zero;
         VertexShader = compile vs_3_0 VertexShaderFunction();
-        PixelShader = compile ps_3_0 PixelLayer1(1);
+        PixelShader = compile ps_3_0 PixelLayer(1,1);
     }

@@ -134,5 +89,5 @@
        DestBlend = InvSrcAlpha;
        VertexShader = compile vs_3_0 VertexShaderFunction();
-        PixelShader = compile ps_3_0 PixelLayer2(0.00001);
+        PixelShader = compile ps_3_0 PixelLayer(2,0.00001);
    }

@@ -143,5 +98,5 @@
        DestBlend = InvSrcAlpha;
        VertexShader = compile vs_3_0 VertexShaderFunction();
-        PixelShader = compile ps_3_0 PixelLayer3(0.00001);
+        PixelShader = compile ps_3_0 PixelLayer(3,0.00001);
    }
 }
@@ -155,5 +110,5 @@
        DestBlend = InvSrcAlpha;
         VertexShader = compile vs_3_0 VertexShaderFunction();
-        PixelShader = compile ps_3_0 PixelLayer1(0.000001);
+        PixelShader = compile ps_3_0 PixelLayer(1,0.000001);
     }

@@ -164,5 +119,5 @@
        DestBlend = InvSrcAlpha;
        VertexShader = compile vs_3_0 VertexShaderFunction();
-        PixelShader = compile ps_3_0 PixelLayer2(0.000001);
+        PixelShader = compile ps_3_0 PixelLayer(2,0.000001);
    }

@@ -173,5 +128,5 @@
        DestBlend = InvSrcAlpha;
        VertexShader = compile vs_3_0 VertexShaderFunction();
-        PixelShader = compile ps_3_0 PixelLayer3(0.00001);
+        PixelShader = compile ps_3_0 PixelLayer(3,0.00001);
    }
 }

如您所见,有一个名为“layer”的新输入变量(类型为uint)。现在只有一个PixelLayer函数,而不是三个。

接下来是SplattedTileVertex.cs的统一差异。

@@ -11,5 +11,5 @@
     {
         internal Vector3 vertexPosition;
-        internal byte textures;
+        internal float textures;
         /// <summary>
         /// Texture 0 is static tiles
@@ -17,7 +17,7 @@
         /// Texture 2 is animated autotiles
         /// </summary>
-        internal byte texture1;
-        internal byte texture2;
-        internal byte texture3;
+        internal float texture1;
+        internal float texture2;
+        internal float texture3;
         internal Vector2 texturePos1;
         internal Vector2 texturePos2;
@@ -27,8 +27,8 @@
         (
             new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
-            new VertexElement(12, VertexElementFormat.Byte4, VertexElementUsage.PointSize, 0),
-            new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
-            new VertexElement(24, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 1),
-            new VertexElement(32, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 2)
+            new VertexElement(12, VertexElementFormat.Vector4, VertexElementUsage.PointSize, 0),
+            new VertexElement(28, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
+            new VertexElement(36, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 1),
+            new VertexElement(44, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 2)
         );

是的,我们更改了类型!

现在问题暴露了出来。由于输入处理方式的原因,浮点数永远不会与整数值完全相同。这背后的推理超出了本主题,但也许我应该在社区维基上记录一下。

那么,到底发生了什么?

好的,所以我们曾经丢弃那些不在图层中的值 (if input.TextureInfo[0] < layer -> discard)。在 input.TextInfo[layer] 中有一个浮点数。现在我们将该浮点数与 uint 图层值进行比较。这里就发生了魔法。某些像素刚好匹配(或者可能刚好高于该图层值),如果类型是 (u)int,那么它就很好,代码-wise,但实际上并不是。

那么如何解决呢?嗯,走一半可能是规则的路吧。移动代码在瓦片上渲染一个像素,如果它在一半的位置上。我们对图层做同样的事情。

以下是 SplattedTileShader.fx 的修复(统一差异):

@@ -42,28 +42,24 @@
 float4 PixelLayer(VertexShaderOutput input, uniform uint layer, uniform float alpha) : COLOR0
 {
-   if(input.TextureInfo[0] < layer)
+   if(input.TextureInfo[0] < (float)layer - 0.5)
        discard;

    float4 color;
    float2 coord;
-   if(layer == 1)
+   if(layer < 1.5)
        coord = input.TexCoord1;
-   else if(layer == 2)
+   else if(layer < 2.5)
        coord = input.TexCoord2;
-   else if(layer == 3)
+   else
        coord = input.TexCoord3;

-   switch (input.TextureInfo[layer])
-   {
-       case 0:
-           color = tex2D(staticTilesSampler, coord);
-           break;
-       case 1:
-           color = tex2D(autoTilesSampler, coord);
-           break;
-       case 2:
-           color = tex2D(autoTilesSampler, coord + float2(frame, 0) * animOffset) * (1 - frameBlend) + tex2D(autoTilesSampler, coord + float2(nextframe, 0) * animOffset) * frameBlend;
-           break;
-   }
+   float type = input.TextureInfo[layer];
+
+   if (type < 0.5)
+       color = tex2D(staticTilesSampler, coord);
+   else if (type < 1.5)
+       color = tex2D(autoTilesSampler, coord);
+   else
+       color = tex2D(autoTilesSampler, coord + float2(frame, 0) * animOffset) * (1 - frameBlend) + tex2D(autoTilesSampler, coord + float2(nextframe, 0) * animOffset) * frameBlend;

    clip(color.a - alpha);

现在所有类型都是正确的。代码按照预期工作,问题得到了解决。这与我最初指出的discard(...)代码无关。
感谢所有参与帮助我们解决问题的人。

我希望我能给你赏金 - 但是SE强制你在(我想)2周内奖励赏金。很高兴知道你最终成功了! - Jonathan Dickinson
@JonathanDickinson 哈哈,谢谢。我并不在乎赏金,但我确实关心解决方案:)。花了我们一些时间没错。 - Derk-Jan

3
我不知道为什么会出现黑线,但我可以给你另一种渲染景观的方法,这可能会使其看起来正确(并且希望能够提高速度)。
精灵
为了使其工作,您需要一个纹理集(也称为精灵表)。您可以将您的纹理集分成多个纹理集并使用多重纹理。
顶点缓冲区
我会放弃SpriteBatch,因为您始终知道精灵要去的位置 - 在启动时创建一个VertexBuffer(可能是每层一个),并使用它来绘制层。像这样(这是一个2D缓冲区,只是像您的3D缓冲区一样“看起来”):

Vertex Buffer Sample

顶点定义可能包括:

  • 位置(Vector2
  • 纹理坐标(Vector2
  • 颜色(Vector4/Color

每次需要“循环”地形时(稍后会详细说明),您将浏览相机下的地图并更新VertexBuffer中的纹理坐标和/或颜色。不要每帧刷新缓冲区。我不会将纹理坐标发送到GPU中的[0,1]范围内,而是使用[0,精灵数量] - 在顶点着色器中计算[0,1]

重要提示:不要共享顶点(即使用IndexBuffer),因为被多于两个面共享的顶点需要保持不同(它们具有不同的纹理坐标)- 建立缓冲区时,就好像不存在IndexBuffer一样。在这种情况下,使用IndexBuffer是浪费的,因此只需使用VertexBuffer

渲染

您使用的世界矩阵将把[0,1]映射到屏幕大小加上一个图块的大小(即一个简单的比例x = Viewport.Width + 32y = Viewport.Height + 32)。您的投影矩阵将是一个单位矩阵。
视图矩阵有些棘手。想象一下,您的地图正在查看当前瓦片块(实际上就是这样)在{0,0}处,您需要做的是找出从那里您的相机正在查看的像素偏移量。因此,它将是一个带有x = Camera.X - LeftTile.X * (Viewport.Width / NumTiles.X)的偏移矩阵,y类似。
矩阵是唯一棘手的部分,一旦设置好了,就可以简单地调用DrawUserPrimitives(),然后完成了。
请注意,这仅涉及您的景观,按您今天的方式绘制其他精灵。

景观循环

当相机位置更改时,您基本上需要确定它是否正在查看新的瓦片块,并相应地更新VertexBuffer(纹理坐标和颜色-保留位置不变,无需重新计算)。

或者

另一个选择是将每个图层渲染到RenderTarget2D,并对整个图层使用当前变换一次。这可能会解决您的问题,或者让真正的原因非常明显。

附注:如果现在不是00:40,我会提供示例代码的。这个问题值得。我会看看明晚有多少时间。


谢谢您抽出时间撰写这篇文章。有几点需要说明:1.采用这种方法,我不能简单地放入3D模型;2.渲染部分本质上与我的变换矩阵完全相同;3.我们已经使用了多个精灵表。 - Derk-Jan

2
根据你提供的信息,我非常怀疑你的分层代码。这看起来像是底层有时会穿过应该在顶部隐藏它们的层,这取决于浮点舍入。当你有两个三角形应该完全共面但由于某种原因没有完全相同的顶点坐标(例如一个比另一个大),垂直于观察角度的条纹是一种非常常见的效果。如果你将各个图层相距非常小地绘制出来,会发生什么情况?比如说,将底部实心层绘制在-0.00002处,下一层绘制在-0.00001处,顶层绘制在0处(假设现在所有三层都在0处绘制)。
我不知道XNA具体情况,但分层问题总是使用浮点表示几何体时的一个基本问题,如果XNA为你“神奇”避免了这个问题,我会感到惊讶。不确定为什么有些瓷砖没问题,但大多数瓷砖都有问题。可能是那些瓷砖运气好,或者其他原因。由浮点误差引起的问题通常表现得非常奇怪。
如果稍微分离一下各个层不起作用,那么你就只能使用标准的基于注释的调试方法了;尝试没有精灵、没有动画瓷砖、没有透明层等。当它停止发生时,你刚刚注释掉的部分就是导致问题的原因:P

  1. 如果“有时”是这种情况,那么不进行转换也会发生吗?
  2. 我将更新我的帖子以显示瓷砖的定位。每个分层瓷砖都在完全相同的位置上(在C#中共享相同的内存,在GPU中分配到单独的地址之前)。没有移动的可能性。
  3. 我将尝试您给我的少量分层提案。
- Derk-Jan
我放置了分层代码。正如您所看到的,不同的优先级在不同的z值上绘制。我仍然会尝试您建议的内容,但即使是这种情况,也应该有更少的带有线条的瓷砖,因为在很多位置上,图层实际上是在不同的z值上的。此外,每个瓷砖都恰好是32x32像素。这是固定的,没有变量。 - Derk-Jan
好的,我尝试不绘制第二层和第三层。现在从左到右,行已经完整,没有显示间隙。因此,未受影响的瓷砖是第二层或第三层上的实心瓷砖。部分实心瓷砖(如透明瓷砖等)会显示线条,使像素行消失。 - Derk-Jan
2
@Derk 如果这是深度冲突问题,正如Paul所建议的那样,您能否关闭深度读取?(DepthBufferEnable = false)。为此,您将不得不自己对几何图形进行深度排序,而不是依赖深度缓冲区。 - Andrew Russell
@Andrew 当我只绘制一个图层时,我排除了这种情况。但为了满足你和我自己,我可以关闭它,并将其设置为DepthBufferEnable = false,这只会破坏一些阴影和精灵的顺序,而不是删除线条。据我所知,这是预期的行为。我会将您的建议添加到尝试列表中。 - Derk-Jan
显示剩余2条评论

0

问题出在您的SplattedTileShader.fx着色器中。

黑线出现是因为已丢弃的像素。

没有更多信息很难跟踪您的着色器代码,但问题就在那里。

我认为您处理多纹理的方式过于复杂。

也许制作一个支持一次性多纹理的着色器会更容易。

在您的顶点中,您可以传递每个纹理的四个权重值,并在着色器中自行混合。

http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series4/Multitexturing.php


这确实是情况,但您似乎没有在您的回复中解释任何内容。您所说的像素是哪些?还有更多信息吗?我几乎解释了每一行代码。您还想知道什么?不过谢谢您提供的链接,我会查看的! - Derk-Jan
嗯...被丢弃的像素是用裁剪语句丢弃的...你可以注释掉裁剪语句,这样绘制出来可能不正确,但会更好。而且你的着色器代码没有很好地解释,我不理解它在做什么。 - Blau
不,那部分代码运行正确。该着色器对不可见的像素进行了裁剪,并绘制了三个图层中的每一个像素。首先是不透明的像素,然后是(半)透明的像素。 - Derk-Jan
这让我想知道你是否完全阅读了帖子。是的,我们做了:- 在着色器中删除剪辑(不透明度)。这只会删除一些线条。我们正在进一步调查此问题。最终,我们没有做任何事情,因为那不是问题的所在! - Derk-Jan
剪辑功能没有正常工作,问题出在你使用了损坏的数据... 你只需要按照线程来操作就可以了... 不,我没有看到你的帖子... - Blau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接