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

Der*_*Jan 51 c# graphics shader xna

我们正在使用XNA开发一个自上而下的RPG.最近我们在编写显示地图的代码时陷入了挫折.在绘制地图时,使用正常变换矩阵的自上而下视图,一切似乎都很好.当使用非平面变换矩阵时,例如挤压顶部或底部以模拟深度,当相机改变位置时,出现黑线(顶部或底部的行,当向左或向右挤压时的列).移动和放置似乎是随机的.(图片进一步提供.)

背景资料

地图由瓷砖组成.原始纹理具有由32x32像素组成的图块.我们通过创建2个三角形并在这些三角形上显示原始纹理的一部分来绘制切片.着色器为我们做了这个.有三层三角形.首先,我们绘制所有不透明的瓷砖和所有半透明和部分透明瓷砖的所有不透明像素,然后是所有半透明和部分透明的瓷砖和像素.这样可以正常工作(但是当我们使用浮点因子进行缩放时,有时颜色混合的线条位于切片行和/或列之间).

绘制状态

我们对所有瓷砖使用相同的rasterizerState,并且在绘制实体或半透明瓷砖时我们在两者之间切换.

_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;
Run Code Online (Sandbox Code Playgroud)

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

第一个固体层1使用

AlphaBlendEnable = False; 
SrcBlend = One; 
DestBlend = Zero; 
Run Code Online (Sandbox Code Playgroud)

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

AlphaBlendEnable = True; 
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha; 
Run Code Online (Sandbox Code Playgroud)

其他着色器也使用它.在SpriteBatchSpriteFonts使用的,使用默认设置.

生成的纹理

一些图块即时生成并保存到文件中.加载地图时会加载该文件.这是使用RenderTarget如下创建完成的:

RenderTarget2D rt = new RenderTarget2D(sb.GraphicsDevice, 768, 1792, false, 
    SurfaceFormat.Color, DepthFormat.None);
    sb.GraphicsDevice.SetRenderTarget(rt);
Run Code Online (Sandbox Code Playgroud)

生成后,文件将被保存并加载(因此,当设备重置时,我们不会丢失它,因为它不再存在于a RenderTarget).我尝试使用mipmapping,但它是一个spritesheet.没有关于瓷砖放置位置的信息,因此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
Run Code Online (Sandbox Code Playgroud)

要定位切片,请使用以下代码:

tilePosition.X = position.X;
tilePosition.Y = position.Y + position.Z;
tilePosition.Z = position.Z;
Run Code Online (Sandbox Code Playgroud)

如您所知,浮点数为32位,精度为24位.z的最大位值是8位(5 = 00000101).X和Y的最大值分别为16位.24位.我认为在浮点方面没有任何问题.

this.Position = tilePosition;
Run Code Online (Sandbox Code Playgroud)

设置顶点时,它会如下所示(因此它们共享相同的平铺位置)

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;
}
Run Code Online (Sandbox Code Playgroud)

着色器

着色器可以绘制动画图块和静态图块.两者都使用以下采样器状态:

sampler2D staticTilesSampler = sampler_state { 
    texture = <staticTiles> ; magfilter = POINT; minfilter = POINT; 
    mipfilter = POINT; AddressU = clamp; AddressV = clamp;};
Run Code Online (Sandbox Code Playgroud)

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

每次传递,我们使用以下行剪切alpha值(因此我们不会得到黑色像素)

clip(color.a - alpha)
Run Code Online (Sandbox Code Playgroud)

Alpha是1用于固体层1,和几乎任何其它层0.这意味着如果有一小部分alpha,它将被绘制,除非在底层(因为我们不知道如何处理它).

相机

我们使用相机模拟从上到下的瓷砖查找,使它们显得平坦,使用z值通过外部分层数据对它们进行分层(3层并不总是按照正确的顺序排列).这也很好.相机更新转换矩阵.如果你想知道为什么它有一些奇怪的结构,如this.AddChange - 代码是Double Buffered(这也有效).变换矩阵形成如下:

// 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; }); 
Run Code Online (Sandbox Code Playgroud)

推理与流动

有3层瓷砖数据.每个图块由定义IsSemiTransparent.当瓷砖是IsSemiTransparent,它需要在没有的东西之后绘制IsSemiTransparent.加载到SplattedTile实例上时会堆叠平铺数据.因此,即使SplattedTile瓦片数据的第一层是空的,第一层中的第一层也将具有瓦片数据(假设至少一个层具有瓦片数据).原因是,Z-buffer如果它们按顺序绘制,则不知道要混合什么,因为它背后可能没有固体像素.

这些图层没有az值,单个图块数据具有.当它是地砖时,它有Priority = 0.因此,Priority我们在图层(绘制顺序)和不透明(半透明,不透明)之后对相同的图块进行排序.将根据优先级绘制具有不同优先级的瓷砖.

第一个实体图层没有目标像素,因此我将其设置为DestinationBlend.Zero.它也不需要AlphaBlending,因为没有什么可以与之混淆.当已经有颜色数据并且需要相应地混合时,可以绘制其他层(5,2实体,3透明).

在迭代6遍之前,projection matrix设置.当不使用锥形时,这是有效的.使用锥度时,它不会.

问题

我们想通过使用一些矩阵来应用锥度来模仿更深的深度.我们尝试了几个值,但这是一个例子:

new Matrix(1, 0, 0, 0,
           0, 1, 0, 0.1f,
           0, 0, 1, 0,
           0, 0, 0, 1);
Run Code Online (Sandbox Code Playgroud)

屏幕(高度值为0的所有东西,所有扁平的东西)都会被挤压.y越低(屏幕越高),它被挤压得越多.这实际上有效,但现在几乎随处可见随机黑线.它似乎排除了几个瓷砖,但我没有看到相关性是什么.我们认为它可能与插值或mipmap有关.

这是一张图片,向您展示我在说什么: 带有线条的截图.

不受影响的瓷砖似乎不是底层的静态瓷砖.但是,其上方的透明图块会显示其他图形工件.他们错过了行(所以行只会被删除).我标记了这个文本,因为我认为这是对正在发生的事情的暗示.该垂直线出现,如果我把mip mag and minfilterLinear.

这是放大的图像(在游戏缩放中),显示第2层或第3层上的图块上的工件 带有线条的屏幕截图,放大

我们已经试过了

  • mipfilterPointLinear
  • 设置GenerateMipMaps原始纹理
  • 设置GenerateMipMaps生成的纹理(真正的标志构造函数RenderTarget)
  • 打开mipmapping(缩小时只会产生更多的工件,因为我正在使用spritesheet进行mipmapping.
  • 没有绘制第2层和第3层(这实际上使所有的瓷砖都有黑线)
  • DepthBufferEnable = false
  • 将所有实体图层设置为 SrcBlend = One; DestBlend = Zero;
  • 将所有实体图层设置为 ScrBlend = SrcAlpha; DestBlend = InvSrcAlpha;
  • 没有绘制透明图层(线仍然存在).
  • 删除clip(opacity)shader.这只删除了一些行.我们正在进一步调查此事.
  • 在msdn,stackoverflow和使用谷歌搜索相同的问题(没有运气).

有人认出这个问题吗?最后一点,我们称之为SpriteBatchAFTER绘制瓷砖,并使用另一个Shader用于化身(显示没有问题,因为它们的高度> 0).这会撤消我们的sampler state吗?要么...?

Blu*_*eft 6

看看最后一张图片底部的岩石 - 它有沙色的线条穿过它.据推测,你首先是绘制沙子,然后是顶部的岩石.

这告诉我,不是通过纹理"绘制黑线",而是绘制了部分纹理.由于它是在垂直拉伸时发生的,这几乎可以肯定意味着您正在创建从旧像素到新像素的映射,而无需在新纹理中插入值.

例如,使用映射(x,y) --> (x, 2y),积分将被映射像(0,0) --> (0,0),(0,1) --> (0,2)(0,2) --> (0, 4).请注意,源纹理中没有点映射到(0,1)(0,3).这会导致背景渗透.我敢打赌,如果你把它改成水平伸展,你会看到垂直线条.

您需要做的是以另一种方式映射:给定目标纹理中的每个像素,使用上述变换的反转在源图像中找到它的值.您可能会获得小数值像素坐标,因此您需要插值.

我对XNA一点都不熟悉,但有可能比手动更方便.


Der*_*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);
    }
 }
Run Code Online (Sandbox Code Playgroud)

`

如您所见,有一个名为layer(type = 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)
         );
Run Code Online (Sandbox Code Playgroud)

是的,我们改变了类型!

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

那么,发生了什么?

好的,所以我们过去常丢弃不在图层上的值(if input.TextureInfo[0] < layer -> discard).在input.TextInfo [layer]里面有一个浮点数.现在我们将浮点数与我们的uint图层值进行比较.在这里魔术发生了.一些像素将只是一个完全匹配(或者可能只是在该层值之上),如果类型是(u)int,那么在代码方面会很好,但事实并非如此.

那么如何解决呢?好吧,中途可能有规则.如果移动代码位于中间位置,那么移动代码会渲染一个像素.我们对图层做同样的事情.

这是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);
Run Code Online (Sandbox Code Playgroud)

现在所有类型都是正确的.代码按原样运行,问题解决了.它与discard(...)代码没有任何关系,我最初指出的是.

感谢所有参与帮助我们解决此问题的人.