Мы разрабатываем RPG сверху вниз, используя XNA. Недавно мы столкнулись с неудачей при написании кода для отображения наших карт. При рисовании карты сверху вниз с нормальной матрицей преобразования все кажется прекрасным. При использовании матрицы без плоской трансформации, такой как сжимая верхнюю или нижнюю сторону до имитирующей глубины, появляются черные линии (строки, когда сверху или снизу, столбец, когда слева или справа сжимаются), которые перемещаются, когда камера меняет положение. Движение и размещение кажутся случайными. (Изображение представлено ниже.)
Фоновая информация
Карты состоят из плиток. У исходной текстуры есть плитки, состоящие из 32x32 пикселей. Мы рисуем плитки, создавая 2 треугольника и отображая часть исходной текстуры на этих треугольниках. Шейдер делает это для нас. Существует три слоя треугольников. Сначала мы рисуем все непрозрачные плитки и все непрозрачные пиксели всех полупрозрачных и частично прозрачных плит, затем все полупрозрачные и частично прозрачные плитки и пиксели. Это прекрасно работает (но когда мы увеличиваем коэффициент с плавающей запятой, иногда цветные линии находятся между рядами строк и/или столбцами).
Renderstates
Мы используем один и тот же элемент растеризатора для всех фрагментов, и мы переключаемся между двумя при рисовании сплошных или полупрозрачных плит.
_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, но это спрайт. Нет информации о том, где размещены плитки, поэтому 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 слоя не всегда в правильном порядке). Это также отлично работает. Камера обновляет матрицу преобразования. Если вам интересно, почему у этого есть какая-то странная структура, такая как 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; });
Рассуждение и поток
Есть 3 слоя данных плитки. Каждая плитка определяется IsSemiTransparent
. Когда плитка IsSemiTransparent
, ее нужно нарисовать после чего-то не IsSemiTransparent
. Данные плитки складываются при загрузке в экземпляр SplattedTile
. Таким образом, даже если слой один из данных плитки пуст, первый слой из SplattedTile
будет иметь данные о плитки в первом слое (при условии, что по крайней мере один слой имеет данные плитки). Причина в том, что Z-buffer
не знает, с чем смешаться, если они нарисованы по порядку, так как за ним не должно быть твердых пикселей.
У слоев нет значения z, данные отдельной плитки. Когда это плитка земли, она имеет Priority = 0
. Итак, плитки с тем же Priority
заказываем на слое (порядок рисования) и непрозрачность (полупрозрачный, после непрозрачности). Плитки с различным приоритетом будут нарисованы в соответствии с их приоритетом.
Первый сплошной слой не имеет целевых пикселей, поэтому я устанавливаю его на DestinationBlend.Zero
. Ему также не нужно AlphaBlending
, так как нет ничего, чтобы alphablend с. Остальные слои (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, все плоские вещи) будут сжаты. Чем ниже y (выше на экране), тем больше он сжимается. Это действительно работает, но теперь случайные черные линии появляются почти везде. Кажется, это исключает несколько фрагментов, но я не вижу, что такое корреляция. Мы думаем, что это могло бы иметь какое-то отношение к интерполяции или мипмапсам.
И вот образ, чтобы показать вам, о чем я говорю:
.
Плитки, не затронутые, по-видимому, являются статическими плитами НЕ на нижнем слое. Однако прозрачные плитки поверх них показывают другие графические артефакты. Они пропускают строки (так что строки просто удаляются). Я отметил этот текст, потому что я думаю, что это намек на то, что происходит. Строки вертикальные появляются, если я помещаю mip mag and minfilter
в Linear
.
Вот изображение, увеличенное (в увеличении игры), показывающее артефакт на плитки на слое 2 или 3
Мы уже пробовали
-
mipfilter
onPoint
илиLinear
- Настройка
GenerateMipMaps
на исходные текстуры - Настройка
GenerateMipMaps
для сгенерированных текстур (конструктор истинного флагаRenderTarget
) - Включение mipmapping (только уменьшилось количество артефактов, поскольку я был mipmapping спрайт.
- Не рисовать слой 2 и 3 (это фактически делает ВСЕ плитки черными линиями)
-
DepthBufferEnable = false
- Установка всех твердых слоев на
SrcBlend = One;
DestBlend = Zero;
- Установка всех твердых слоев на
ScrBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
- Не рисовать прозрачный слой (линии все еще существуют).
- Удаление
clip(opacity)
вshader
. Это удаляет только некоторые строки. Мы изучаем это дальше. - Поиск той же проблемы в msdn, stackoverflow и использовании google (без везения).
Кто-нибудь знает эту проблему? Наконец, мы вызываем SpriteBatch
ПОСЛЕ рисования плит и используем другой shader
для аватаров (не показывайте проблем, потому что они имеют высоту > 0). Это отменит наш sampler state
? Или...?