当转换纹理(绘制为平面 3D 对象)以模拟深度时,黑线随机出现

发布于 2024-11-05 17:19:34 字数 7415 浏览 3 评论 0原文

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

在阴影中,我们设置 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);

生成后,文件将被保存并加载(因此当设备重置时我们不会丢失它,因为它不再位于 渲染目标)。我尝试使用 mipmapping,但它是一个 spritesheet。没有关于图块放置位置的信息,因此 mipmap 没有用,也没有解决问题。

顶点

我们循环遍历每个位置。这里还没有浮点,但位置是 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;

如您所知,浮点数是 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)

对于实心层 1,Alpha is 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; }); 

推理和流程

有 3 层图块数据。每个图块均由 IsSemiTransparent 定义。当图块为 IsSemiTransparent 时,需要在非 IsSemiTransparent 的内容之后绘制它。当加载到 SplattedTile 实例上时,图块数据会被堆叠。因此,即使图块数据的第一层为空,SplattedTile 的第一层也会在第一层中具有图块数据(假设至少有一个层具有图块数据)。原因是如果按顺序绘制,Z 缓冲区不知道要与什么混合,因为它后面可能没有实体像素。

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

第一个实体图层没有目标像素,因此我将其设置为 DestinationBlend.Zero。它也不需要 AlphaBlending,因为没有任何东西可以进行 alphablend。当已经有颜色数据并需要相应混合时,可能会绘制其他图层(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 越低(屏幕上越高),挤压得越多。这确实有效,但现在几乎到处都会出现随机的黑线。它似乎排除了一些瓷砖,但我不明白有什么相关性。我们认为这可能与插值或 mipmap 有关。

这是一张图片来向您展示我在说什么: 带线条的屏幕截图

不受影响的瓷砖似乎是静态瓷砖,不在底层。 但是,上面的透明图块会显示其他图形伪影。他们错过了行(因此行被删除)。我标记了这段文字,因为我认为它暗示了正在发生的事情。如果我将 mip mag 和 minfilter 设置为 Linear,就会出现垂直线。

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

我们已经尝试过

  • PointLinear 上的 mipfilter
  • 在原始纹理上设置 GenerateMipMaps
  • 设置 GenerateMipMaps code> 在生成的纹理上(RenderTarget 的 true 标志构造函数)
  • 打开 mipmapping(仅在缩小时给出更多工件,因为我是 mipmapping spritesheet
  • 不绘制第 2 层和第 3 层(这实际上使所有图块都有黑线)
  • DepthBufferEnable = false
  • 将所有实体图层设置为 SrcBlend = One; DestBlend = Zero;
  • 将所有实体图层设置为 ScrBlend = SrcAlpha;< /code> DestBlend = InvSrcAlpha;
  • 绘制透明层(线条仍然存在)。
  • 不 这只会删除一些行。我们正在进一步调查此事。
  • 在 msdn、stackoverflow 和使用 google 上搜索相同的问题(没有运气)。

有人认识到这个问题吗?最后一点,我们在绘制图块后调用 SpriteBatch,并使用另一个 Shader 作为头像(没有问题,因为它们的高度 > 0)。这会撤销我们的采样器状态吗?或者...?

We are developing a top-down RPG using XNA. Recently we bumped into a setback when writing the code to display our maps. When drawing the map, top-down view with a normal transformation matrix, everything seems to be fine. When using a non-flat transformation matrix, such as squeezing the top or bottom to mimic depth, black lines (rows when top or bottom, column when left or right is squeezed) that move around when the camera changes position, appear. The movement and placement appear to be random. (Image provided further down.)

Background information

The maps consist of tiles. The original texture has tiles consisting of 32x32 pixels. We draw the tiles by creating 2 triangles and displaying part of the original texture on these triangles. A shader does this for us. There are three layers of triangles. First we draw all the opaque tiles and all opaque pixels of all semi-opaque and partial-transparent tiles, then all the semi-opaque and partial-transparent tiles and pixels. This works fine (but when we zoom by a floating point factor, sometimes color-blended lines are in between tile rows and/or columns).

Renderstates

We use the same rasterizerState for all tiles and we switch between two when drawing solid or semi-transparent tiles.

_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;

In the shade we set the SpriteBlendMode as follows:

The first solid layer 1 uses

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

All the other solid and transparent layers (drawn later) use

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

Other shaders use this too. The SpriteBatch for the SpriteFonts used, uses default setting.

Generated Texture

Some tiles are generated on the fly and saved to file. The file is loaded when the map is loaded. This is done using a RenderTarget created as follows:

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

When generated, the file is saved and loaded (so we don't lose it when the device resets, because it no longer will be on a RenderTarget). I tried using mipmapping, but it is a spritesheet. There is no information on where tiles are placed, so mipmapping is useless and it didn't solve the problem.

Vertices

We loop through every position. No floating points here yet, but position is a 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

To position the tiles the following code is used:

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

As you know, floats are 32 bit, with 24 bits for precision. The maximum bit value of z is 8 bits (5 = 00000101). The maximum values for X and Y are 16 bits resp. 24 bits. I assumed nothing could go wrong in terms of floating points.

this.Position = tilePosition;

When the vertices are set, it does so as follows (so they all share the same tile position)

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;
}

Shader

The shader can draw animated tiles and static tiles. Both use the following sampler state:

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

The shader doesn't set any different sampler states, we also don't in our code.

Every pass, we clip at the alpha value (so we don't get black pixels) using the following line

clip(color.a - alpha)

Alpha is 1 for solid layer 1, and almost 0 for any other layer. This means that if there is a fraction of alpha, it will be drawn, unless on the bottom layer (because we wouldn't know what to do with it).

Camera

We use a camera to mimic lookup from top down at the tiles, making them appear flat, using the z value to layer them by external layering data (the 3 layers are not always in the right order). This also works fine. The camera updates the transformation matrix. If you are wondering why it has some weird structure like this.AddChange - the code is Double Buffered (this also works). The transformation matrix is formed as follows:

// 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; }); 

Reasoning and Flow

There are 3 layers of tile data. Each tile is defined by IsSemiTransparent. When a tile is IsSemiTransparent, it needs to be drawn after something not IsSemiTransparent. Tile data is stacked when loaded on a SplattedTile instance. So, even if layer one of tile data is empty, layer one of the SplattedTile will have tile data in the first layer, (given that at least one layer has tile data). The reason is that the Z-buffer doesn't know what to blend with if they are drawn in order, since there might be no solid pixels behind it.

The layers do NOT have a z value, individual tile data has. When it is a ground tile, it has Priority = 0. So tiles with the same Priority we be ordered on layer (draw order) and opaqueness (semi-transparent, after opaque). Tiles with different priority will be drawn according to their priority.

The first solid layer has no destination pixels, so I set it to DestinationBlend.Zero. It also doesn't need AlphaBlending, since there is nothing to alphablend with. The other layers (5, 2 solid, 3 transparent) might be drawn when there is already color data and need to blend accordingly.

Before iterating through the 6 passes, the projection matrix is set. When using no taper, this works. When using a taper, it doesn't.

The Problem

We want to mimic some more depth by applying the taper, using the some matrix. We tried several values but this is an example:

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

The screen (everything with height value 0, all flat stuff) will be squeezed. The lower the y (higher on the screen), the more it's squeezed. This actually works, but now random black lines appear almost everywhere. It seems to exclude a few tiles, but I don't see what's the correlation. We think it might had something to do with interpolation or mipmaps.

And here is an image to show you what I am talking about:
Screenshot with lines.

The tiles not affected seem to be static tiles NOT on the bottom layer. However, transparent tiles on top of those show other graphical artifacts. They miss lines (so rows just get deleted). I marked this text because I think it is a hint to what's happening. The vertical lines appear if I put the mip mag and minfilter to Linear.

Here is an image zoomed in (in game zoom), showing the artifact on tiles on layer 2 or 3
Screenshot with lines, zoomed in

We already tried

  • mipfilter on Point or Linear
  • Setting GenerateMipMaps on the original textures
  • Setting GenerateMipMaps on the generated textures (true flag constructor of RenderTarget)
  • Turning on mipmapping (only gave more artifacts when zoomed out, because I was mipmapping a spritesheet.
  • Not drawing layer 2 and 3 (this actually makes ALL the tiles have black lines)
  • DepthBufferEnable = false
  • Setting all solid layers to SrcBlend = One; DestBlend = Zero;
  • Setting all solid layers to ScrBlend = SrcAlpha; DestBlend = InvSrcAlpha;
  • Not drawing transparent layer (lines are still there).
  • Removing clip(opacity) in the shader. This only removes some lines. We are investigating this further.
  • Searching for the same problem on msdn, stackoverflow and using google (with no luck).

Does anyone recognize this problem? On a final note, we do call the SpriteBatch AFTER drawing the tiles, and use another Shader for avatars (show no problems, because they have height > 0). Does this undo our sampler state? Or...?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

牛↙奶布丁 2024-11-12 17:19:34

看看最后一张图片底部的岩石 - 它有沙色的线条穿过它。据推测,您先画沙子,然后画上面的岩石。

这告诉我,这不是通过纹理“绘制黑线”,而是纹理的某些部分没有被绘制。由于垂直拉伸时会发生这种情况,因此这几乎肯定意味着您正在创建从旧像素到新像素的映射,而无需在新纹理中插入值。

例如,使用映射 (x,y) --> (x, 2y),这些点将被映射为 (0,0) --> (0,0),(0,1) --> (0,2) 和 (0,2) --> (0, 4)。请注意,源纹理中没有点映射到 (0,1)(0,3)。这会导致背景渗透。我敢打赌,如果你将其更改为水平拉伸,你会看到垂直线。

您需要做的是以另一种方式进行映射:给定目标纹理中的每个像素,使用上述变换的逆运算在源图像中找到其值。您可能会得到小数值像素坐标,因此您需要对值进行插值。

我对 XNA 一点也不熟悉,但可能有一种比手动更方便的方法。

Look at the rock of the bottom of that last image - it's got sandy-colored lines going through it. Presumably, you are drawing the sand first, then the rock on top.

This tells me it's not "black lines being drawn" through the textures, but that parts of the textures are not being drawn. Since it happens when you stretch vertically, this almost certainly means you are creating a mapping from old pixels to new pixels, without interpolating values inbetween in the new texture.

For instance, using the mapping (x,y) --> (x, 2y), the points will get mapped like (0,0) --> (0,0), (0,1) --> (0,2), and (0,2) --> (0, 4). Notice that no points in the source texture map to (0,1) or (0,3). This would cause the background to seep through. I bet if you change it to stretch horizonally, you'll see vertical lines.

What you would need to do is map the other way: given each pixel in the target texture, find its value in the source image using the inverse of the above transformation. You will probably get fractional values pixel-coordinates, so you will want to interpolate values.

I am not familiar at all with XNA, but there is probably a more convenient way to do this than by hand.

苏大泽ㄣ 2024-11-12 17:19:34

问题与 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 (type = uint) 的新输入变量。现在 PixelLayer 函数由 3 个变为 1 个。

接下来是 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] <图层 -> 丢弃)。 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);

现在所有类型都是正确的。代码按预期工作,问题已解决。它与我最初指出的 discard(...) 代码无关。

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

The problem has to do with numeric types in HLSL.

First, let's clean up that shader. I will do so, because this is how we found the actual problem. Here is a unified diff for SplattedTileShader.fx before the taperfix was put in:

@@ -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);
    }
 }

`

As you can see there is a new input variable called layer (type = uint). And there is now one PixelLayer function instead of three.

Next is the unified diff for 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)
         );

Yes, we changed the types!

And now the problems come to light. It seems that, because of the way the input is processed, the floats will never be exactly the same as the integer value. The reasoning behind this goes beyond this thread, but maybe I should make a community wiki on it.

So, what was happening?

Ok, so we used to discard the values that were not on the layer (if input.TextureInfo[0] < layer -> discard). Inside input.TextInfo[layer] there is a float. Now we compare that float to our uint layer value. And here the magic happens. Some pixels will be just be an exact match (or maybe just above that layer value) and that would be fine, code-wise, if the type was an (u)int, but it's not.

So how to fix it? Well go with that halfway there is probably there rule. The moving code renders a pixel on a tile, if it's halfway there. We do the same thing with the layers.

Here is the fix (unified diff) for 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);

Now all the types are correct. The code works as it should, and the problem is solved. It had nothing todo with the discard(...) code, what I initially pointed at.

Thanks everyone that participated in helping us solve this.

花海 2024-11-12 17:19:34

我不明白为什么会出现黑线,但我可以为您提供另一种渲染景观的方法,使其看起来正确(并且希望能给您带来一点速度提升)。

精灵

你需要一个纹理图集(又名精灵表)才能工作。您可以将您的阿尔塔分成多个地图集并使用多重纹理。

顶点缓冲区

我要做的就是刮擦 SpriteBatch,你总是知道你的精灵将在哪里 - 在启动时创建一个 VertexBuffer (可能每层一个)并使用它来绘制图层。像这样的东西(这是一个 2D 缓冲区,它只是“看起来”像你的 3d 缓冲区):

Vertex Buffer Sample

顶点定义可能包括:

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

每次景观需要“循环”(稍后会详细介绍),您将在相机下浏览地图并更新 VertexBuffer 中的纹理坐标和/或颜色。不要每帧刷新缓冲区。我不会将 [0, 1] 范围内的纹理坐标发送到 GPU,而是 [0, Number of Sprites] - 计算 [ 0, 1] 在你的顶点着色器中。

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

渲染

您使用的世界矩阵会将 [0, 1] 映射到屏幕尺寸加上图块的尺寸(即简单比例 x = Viewport.Width + 32 > 和y = Viewport.Height + 32)。你的投影矩阵将是一个单位矩阵。

视图矩阵很棘手。想象一下,您的地图正在查看 {0,0} 处的当前图块块(确实如此),您需要做的是计算出与您所在位置的偏移量(以像素为单位)。相机正在寻找。因此本质上它将是一个带有 x = Camera.X - LeftTile.X * (Viewport.Width / NumTiles.X) 的偏移矩阵,与 y 类似。

矩阵是唯一棘手的部分,一旦设置好它们,只需简单的 DrawUserPrimitives() 调用即可完成。

请注意,这仅涉及您的风景,请像今天一样绘制其他精灵。

风景循环

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

或者,

另一种选择是将每个层渲染到 RenderTarget2D 并针对整个层使用一次当前的转换。这要么会解决你的问题,要么会让真正的原因变得非常明显。

旁注:如果这里不是 00h40,我会提供示例代码,这个问题值得。我看看明天晚上有多少时间。

I can't figure out why the black lines are appearing, but I can give you another way to render the landscape that could result in it looking correct (and, hopefully, give you a little speed boost).

Sprites

You will need a texture atlas (a.k.a. sprite sheet) for this to work. You could split your altas into multiple atlases and use multi-texturing.

Vertex Buffer

What I would do is scratch the SpriteBatch, you always know where your sprites are going to be - create a VertexBuffer at startup (possibly one per layer) and use it to draw the layers. Something like this (this is a 2D buffer, it just 'looks' 3d like yours):

Vertex Buffer Sample

The vertex definition would probably consist of:

  • Position (Vector2)
  • Texture Coordinate (Vector2)
  • Color (Vector4/Color)

Each time the landscape needs to be 'cycled' (more on this later) you would go through the map under the camera and update the texture co-ordinates and/or color in the VertexBuffer. Don't refresh the buffer each frame. I wouldn't send the texture co-ordinates to the GPU in [0, 1] range, rather [0, Number of Sprites] - calculate the [0, 1] in your vertex shader.

Important: Don't share vertices (i.e. use an IndexBuffer) because the vertices that are shared by more than two faces need to remain distinct (they have distinct texture co-ordinates) - build up the buffer as though IndexBuffers didn't exist. Using an IndexBuffer is wasteful in this scenario, so just stick with a VertexBuffer alone.

Rendering

The world matrix you use will map [0, 1] to the size of the screen plus the size of a tile (i.e. a simple scale x = Viewport.Width + 32 and y = Viewport.Height + 32). Your projection matrix would be an identity matrix.

The view matrix is tricky. Imagine that your map is looking at a current block of the tiles (which it is) at {0,0}, what you need to do is figure out the offset (in pixels) from that where your camera is looking. So essentially it will be an offset matrix with x = Camera.X - LeftTile.X * (Viewport.Width / NumTiles.X) and similar for y.

The matrices are the only tricky bit, once you have them set up it's a simple DrawUserPrimitives() call and you are done.

Note that this only deals with your landscape, draw your other sprites as you are today.

Landscape Cycling

When the position of your camera changes, you basically need to determine if it's looking at a new block of tiles and update the VertexBuffers appropriately (texture co-ordinates and color - leave the position alone, no need to recalculate it).

Alternatively

Another option is to render each layer to a RenderTarget2D and use your current transformation once against the entire layer. This would either resolve your problem, or, it would make the real reason very apparrant.

Side note: I would provide sample code if it wasn't 00h40 here, this question deserves it. I'll see how much time I have tomorrow night.

§普罗旺斯的薰衣草 2024-11-12 17:19:34

根据您提供给我们的内容,我会对您的分层代码极其表示怀疑。这确实看起来底层有时会穿透本应位于顶部的层并隐藏它们,具体取决于浮点舍入。当您有两个应该完全共面的三角形,但由于某种原因没有完全相同的顶点坐标(例如,一个比另一个大)时,垂直于视角的条纹是一种非常常见的效果。如果您将各个图层之间的距离拉得非常非常小,会发生什么?例如,在 -0.00002 处绘制底部实体层,在 -0.00001 处绘制下一个实体层,在 0 处绘制顶层(假设现在所有三个图层都在 0 处绘制)。

我不太了解 XNA,但分层问题始终是使用浮点表示几何的基本问题,如果 XNA “神奇地”为您避免了它,我会感到惊讶。不知道为什么有些瓷砖很好,但大多数都是用螺丝固定的。也许那些瓷砖只是运气好,或者什么的。由浮点错误引起的问题常常表现得很奇怪。

如果稍微分离各层没有帮助,那么您几乎只能进行基于标准注释的调试;尝试没有精灵,没有动画图块,没有透明层,&c。当它停止发生时,无论你刚刚注释掉什么都会破坏它:P

With what you've given us, I would be extremely suspicious of your layering code. This really looks like the bottom layer is sometimes poking through the layers that are supposed to be on top and hiding them, depending on floating point rounding. Stripes perpendicular to the viewing angle is a very common effect when you have two triangles which are supposed to be exactly coplanar, but for whatever reason don't have exactly the same vertex coordinates (e.g. one is bigger than the other). What happens if you draw the various layers a very very small amount apart from each other? Like, draw the bottom solid layer at -0.00002, and the next at -0.00001, and the top layer at 0 exactly (assuming all three are being drawn at 0 now).

I don't know about XNA specifically but the layering issue is always a fundamental problem of using floating point to represent geometry, and I would be surprised if XNA "magically" avoids it for you. Not sure why some of the tiles are fine but most are screwed. Probably those tiles just got lucky, or something. Problems caused by floating point error often act very strange like this.

If separating the layers slightly doesn't help, then you're pretty much reduced to standard comment-based debugging; try it with no sprites, with no animated tiles, with no transparent layer, &c. When it stops happening, whatever you just commented out is breaking it :P

删除会话 2024-11-12 17:19:34

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

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

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

我认为你做多重纹理的方式太复杂了。

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

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

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

The problem is in your SplattedTileShader.fx shader.

The black lines appears because the discarded pixels.

is difficult to follow your shader code without more info, but the problem is there.

i think that the way you are doing multitexture is too complicated.

maybe it will be easier to make a shader that support multitexture in one pass.

in your vertex you can pass four weight values for each texture and do the mix by yourself in the shader.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文