我们正在使用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;
其他着色器也使用它。用于使用SpriteFonts
的SpriteBatch
使用默认设置。
生成的纹理
一些瓷砖是即时生成并保存到文件中的。当地图加载时,该文件被加载。这是使用以下方式创建的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有关。
这里有一张图片,可以让你看到我在说什么: .
未受影响的瓷砖似乎是静态瓷砖,不在底层。然而,在这些瓷砖上方的透明瓷砖显示其他图形伪像。它们缺少线条(因此行被删除)。我标记了这段文字,因为我认为它是发生的提示。如果我将mip mag和minfilter设置为Linear,则会出现垂直线。
这里是一个放大的图像(游戏中的缩放),显示第2或第3层瓷砖上的文物
我们已经尝试过了
- 在
Point
或Linear
上使用mipfilter
- 在原始纹理上设置
GenerateMipMaps
- 在生成的纹理上设置
GenerateMipMaps
(RenderTarget
的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
吗?或者...?