Мы разрабатываем нисходящую RPG с использованием XNA. Недавно мы столкнулись с неудачей при написании кода для отображения наших карт. При рисовании карты, вид сверху вниз с нормальной матрицей преобразования, все вроде бы нормально. При использовании неплоской матрицы преобразования, такой как сжатие верхней или нижней частей для имитации глубины, появляются черные линии (строки сверху или снизу, столбец слева или справа), которые перемещаются при изменении положения камеры. Движение и размещение кажутся случайными. (Изображение предоставлено ниже.)
Справочная информация
Карты состоят из тайлов. Оригинальная текстура имеет плитки, состоящие из 32x32 пикселей. Мы рисуем плитки, создавая 2 треугольника и отображая часть оригинальной текстуры на этих треугольниках. Шейдер делает это для нас. Есть три слоя треугольников. Сначала мы рисуем все непрозрачные плитки и все непрозрачные пиксели всех полупрозрачных и частично прозрачных плиток, затем все полупрозрачные и частично прозрачные плитки и пиксели. Это работает хорошо (но когда мы масштабируем с помощью коэффициента с плавающей запятой, иногда смешанные цветом линии находятся между рядами плиток и / или столбцами).
Renderstates
Мы используем один и тот же 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;
Другие шейдеры тоже используют это. SpriteBatch
для используемого SpriteFonts
, используется настройка по умолчанию.
Сгенерированная текстура
Некоторые плитки генерируются на лету и сохраняются в файл. Файл загружается при загрузке карты. Это делается с помощью RenderTarget
, созданного следующим образом:
RenderTarget2D rt = new RenderTarget2D(sb.GraphicsDevice, 768, 1792, false,
SurfaceFormat.Color, DepthFormat.None);
sb.GraphicsDevice.SetRenderTarget(rt);
При создании файл сохраняется и загружается (поэтому мы не теряем его при перезагрузке устройства, поскольку он больше не будет на RenderTarget
). Я пытался использовать 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;
Как вы знаете, числа с плавающей запятой 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;
}
Shader
Шейдер может рисовать анимированные и статические плитки. Оба используют следующее состояние сэмплера:
sampler2D staticTilesSampler = sampler_state {
texture = <staticTiles> ; magfilter = POINT; minfilter = POINT;
mipfilter = POINT; AddressU = clamp; AddressV = clamp;};
Шейдер не устанавливает никаких других состояний сэмплера, мы также не включаем его в наш код.
Каждый проход мы обрезаем на альфа-значение (чтобы мы не получали черные пиксели), используя следующую строку
clip(color.a - alpha)
Альфа равен 1 для твердого слоя 1, а почти 0 для любого другого слоя. Это означает, что если есть доля альфы, она будет нарисована, если только не на нижнем слое (потому что мы не будем знать, что с ней делать).
Камера
Мы используем камеру, чтобы имитировать поиск сверху вниз на плитках, делая их плоскими, используя значение 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-buffer
не знает, с чем смешиваться, если они нарисованы по порядку, так как позади него могут не быть сплошных пикселей.
Слои НЕ имеют значения z, отдельные данные мозаики имеют.Когда это наземная плитка, она имеет 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);
Экран (все со значением высоты 0, все плоские вещи) будет сжат.Чем ниже у (выше на экране), тем больше он сжимается.Это на самом деле работает, но теперь случайные черные линии появляются почти везде.Кажется, это исключает несколько плиток, но я не вижу, в чем корреляция.Мы думаем, что это может иметь отношение к интерполяции или мип-картам.
А вот изображение, показывающее, о чем я говорю: .
Не затронутые плитки кажутся статическими плитками, а НЕ нижним слоем. Тем не менее, прозрачные плитки поверх них показывают другие графические артефакты.Они пропускают строки (поэтому строки просто удаляются). Я пометил этот текст, потому что я думаю, что это намек на то, что происходит. вертикальные линии появляются, если я поставлю mip mag and minfilter
на Linear
.
Вот изображение, увеличенное (при увеличении игры), показывающее артефакт на плитках на слое 2 или 3
Мы уже попробовали
mipfilter
на Point
или Linear
- Настройка
GenerateMipMaps
наисходные текстуры - Установка
GenerateMipMaps
для сгенерированных текстур (конструктор истинного флага RenderTarget
) - Включение мипмаппинга (давало больше артефактов только при уменьшении, потому что я был мипмаппингтаблица спрайтов .
- Не рисуется слой 2 и 3 (это фактически заставляет ВСЕ плитки иметь черные линии)
DepthBufferEnable = false
- Установка всех сплошных слоев на
SrcBlend = One;
DestBlend = Zero;
- Установка для всех сплошных слоев
ScrBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
- Не рисуется прозрачный слой (все еще есть линии).
- Удаление
clip(opacity)
inshader
. Это удаляет только некоторые строки. Мы исследуем это далее. - Поиск такой же проблемы в msdn, stackoverflow ииспользуя гугл (без удачи).
Кто-нибудь знает эту проблему?В заключение отметим, что мы вызываем SpriteBatch
ПОСЛЕ рисования плиток и используем еще один Shader
для аватаров (без проблем, потому что они имеют высоту> 0).Это отменяет нашу sampler state
?Или ...?