При преобразовании текстур (нарисованных как плоские 3D-объекты) для имитации глубины черные линии отображаются случайным образом

#c# #graphics #xna #shader

#c# #графика #xna #шейдер

Вопрос:

Мы разрабатываем RPG сверху вниз с использованием XNA. Недавно мы столкнулись с проблемой при написании кода для отображения наших карт. При рисовании карты, просмотр сверху вниз с обычной матрицей преобразования, все, кажется, в порядке. При использовании неплоской матрицы преобразования, такой как сжатие верхней или нижней части для имитации глубины, появляются черные линии (строки, когда они расположены сверху или снизу, столбец, когда сжаты влево или вправо), которые перемещаются при изменении положения камеры. Перемещение и размещение кажутся случайными. (Изображение предоставлено ниже.)

Справочная информация

Карты состоят из плиток. Исходная текстура содержит плитки размером 32×32 пикселя. Мы рисуем плитки, создавая 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; 
  

Другие шейдеры тоже используют это. SpriteBatch Для SpriteFonts используемого используется настройка по умолчанию.

Сгенерированная Текстура

Некоторые фрагменты генерируются «на лету» и сохраняются в файл. Файл загружается при загрузке карты. Это делается с помощью RenderTarget , созданного следующим образом:

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

При создании файл сохраняется и загружается (чтобы мы не потеряли его при перезагрузке устройства, потому что его больше не будет на RenderTarget ). Я пытался использовать mipmapping, но это таблица спрайтов. Нет информации о том, где размещены плитки, поэтому mipmapping бесполезен, и это не решило проблему.

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

Рассуждения и поток

Существует 3 слоя данных плитки. Каждая плитка определяется IsSemiTransparent . Когда плитка есть IsSemiTransparent , ее нужно рисовать после чего-то, чего нет IsSemiTransparent . Данные плитки складываются при загрузке в SplattedTile экземпляр. Таким образом, даже если первый слой с данными о плитках пуст, первый слой SplattedTile будет содержать данные о плитках в первом слое (при условии, что по крайней мере один слой содержит данные о плитках). Причина в том, что Z-buffer не знает, с чем смешиваться, если они нарисованы по порядку, поскольку за этим может не быть сплошных пикселей.

Слои не имеют значения z, а отдельные данные плитки имеют. Когда это основная плитка, она имеет Priority = 0 . Таким образом, плитки с одинаковым Priority порядком упорядочиваются по слою (порядок прорисовки) и непрозрачности (полупрозрачные, после непрозрачных). Плитки с разным приоритетом будут отрисовываться в соответствии с их приоритетом.

У первого сплошного слоя нет целевых пикселей, поэтому я установил для него значение DestinationBlend.Zero . В этом также нет необходимости AlphaBlending , поскольку нет ничего, с чем можно было бы использовать alphablend. Другие слои (5, 2 сплошных, 3 прозрачных) могут быть нарисованы, когда уже есть данные о цвете и их необходимо соответствующим образом смешать.

Перед повторением 6 проходов устанавливается projection matrix . При использовании no taper это работает. При использовании конуса этого не происходит.

Проблема

Мы хотим имитировать еще немного глубины, применяя сужение, используя некоторую матрицу. Мы попробовали несколько значений, но это пример:

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

Экран (все, что имеет значение высоты 0, все плоские объекты) будет сжат. Чем ниже y (выше на экране), тем сильнее он сжимается. Это действительно работает, но теперь случайные черные линии появляются почти везде. Кажется, это исключает несколько плиток, но я не вижу, какова корреляция. Мы думаем, что это могло быть как-то связано с интерполяцией или mipmaps.

И вот изображение, чтобы показать вам, о чем я говорю: Скриншот с линиями.

Незатронутые плитки кажутся статичными плитками, КОТОРЫХ НЕТ на нижнем слое. Однако прозрачные плитки поверх них отображают другие графические артефакты. Они пропускают строки (поэтому строки просто удаляются).Я выделил этот текст, потому что думаю, что это подсказка к происходящему. Вертикальные линии появляются, если я помещаю mip mag and minfilter в Linear .

Вот увеличенное изображение (в игровом зуме), показывающее артефакт на плитках слоя 2 или 3 Скриншот с линиями, увеличенный

Мы уже пытались

  • mipfilter на Point или Linear
  • Настройка GenerateMipMaps для исходных текстур
  • Настройка GenerateMipMaps для сгенерированных текстур (конструктор true flag для 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 ? Или …?

Комментарии:

1. Извините, системная вики-страница после определенного количества правок. Не вики.

2. Я читал об этом на meta, спасибо @, верну мне его обратно. Возможно, когда это будет решено ;).

3. @Derk-Jan: Есть ли шанс, что вы сможете где-нибудь опубликовать свой код, поскольку будет легче помогать в отладке, когда мы сможем просматривать его напрямую?

4. @Neil, мы разместим сборку с этой проблемой только в Интернете.

5. @Neil, вот что ты делаешь

Ответ №1:

Посмотрите на камень в нижней части последнего изображения — через него проходят линии песочного цвета. Предположительно, сначала вы рисуете песок, а затем камень сверху.

Это говорит мне о том, что через текстуры не «прорисовываются черные линии», а о том, что части текстур не прорисовываются. Поскольку это происходит при растяжении по вертикали, это почти наверняка означает, что вы создаете отображение из старых пикселей в новые пиксели без интерполяции значений между ними в новой текстуре.

Например, при использовании сопоставления (x,y) --> (x, 2y) точки будут отображаться как (0,0) --> (0,0) , (0,1) --> (0,2) и (0,2) --> (0, 4) . Обратите внимание, что ни одна точка в исходной текстуре не соответствует (0,1) или (0,3) . Это привело бы к просачиванию фона. Бьюсь об заклад, если вы измените его на растягивание по горизонтали, вы увидите вертикальные линии.

Что вам нужно было бы сделать, так это отобразить другим способом: учитывая каждый пиксель в целевой текстуре, найдите его значение в исходном изображении, используя обратное приведенному выше преобразованию. Вероятно, вы получите дробные значения пиксельных координат, поэтому вам захочется интерполировать значения.

Я совсем не знаком с XNA, но, вероятно, есть более удобный способ сделать это, чем вручную.

Комментарии:

1. Тот факт, что я могу проходить Под черными линиями (и они заполняются пикселями символов), уже натолкнул меня на эту идею. Вот почему я никогда не говорил, что рисуются черные линии. Более вероятно, что строки пикселей обрезаны или потеряны. Я не знаю. Песок имеет priority 0 (нижний слой). У Rock есть priority 1 . Top of Rock имеет priority 2 . Так что да, ваше предположение верно. Да, вы видите вертикальные линии, если растягиваете их по горизонтали. Разве пиксельный шейдер не должен выполнять эту интерполяцию? Вы знаете, как это сделать в HLSL?

2. Посмотрите на эту преобразованную текстуру на 3D-моделях . Почему на нем нет линий, но оно отображено?

3. Просто прочитайте о фильтрации Шона Харгривза . Мы используем встроенную функцию, так что это должно происходить автоматически (мин, мы уменьшаем масштаб, верно?).

4. Мне придется ответить на этот вопрос. Заслуженно.

5. @JonathanDickinson, это отличный информативный пост, но он не имеет никакого отношения к рассматриваемой проблеме или к ее решению.

Ответ №2:

Проблема связана с числовыми типами в HLSL.

Сначала давайте очистим этот шейдер. Я сделаю это, потому что именно так мы обнаружили реальную проблему. Вот унифицированный diff для SplattedTileShader.fx до того, как было введено taperfix:

 @@ -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 (тип = uint). И теперь есть одна функция PixelLayer вместо трех.

Далее идет унифицированный diff для 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] < layer -> discard) . Внутри ввода.TextInfo [layer] есть плавающий элемент. Теперь мы сравниваем это значение float со значением нашего слоя 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(...) кодом, на который я изначально указал.

Спасибо всем, кто помог нам решить эту проблему.

Комментарии:

1. Хотел бы я дать вам награду — но SE вынуждает вас вознаградить награду через (я думаю) 2 недели. Рад знать, что вы наконец-то пришли к правильному выводу!

2. @JonathanDickinson хаха, спасибо. На самом деле меня не волнует вознаграждение, но меня волнует решение :). Нам потребовалось некоторое время, хорошо.

Ответ №3:

Я не могу понять, почему появляются черные линии, но я могу предложить вам другой способ визуализации ландшафта, в результате которого он будет выглядеть правильно (и, надеюсь, немного ускорит работу).

Спрайты

Для этого вам понадобится атлас текстур (он же лист спрайтов). Вы могли бы разделить свои альты на несколько атласов и использовать мультитекстурирование.

Буфер вершин

Что я бы сделал, так это поцарапал SpriteBatch, вы всегда знаете, где будут ваши спрайты — создайте VertexBuffer при запуске (возможно, по одному на слой) и используйте его для рисования слоев. Что-то вроде этого (это 2D-буфер, он просто «выглядит» 3d как ваш):

Образец буфера вершин

Определение вершины, вероятно, будет состоять из:

  • Положение ( Vector2 )
  • Координата текстуры ( Vector2 )
  • Цвет ( Vector4/Color )

Каждый раз, когда ландшафт нужно «циклически изменять» (подробнее об этом позже), вы должны просматривать карту под камерой и обновлять координаты текстуры и / или цвет в VertexBuffer . Не обновляйте буфер каждый кадр. Я бы не отправлял координаты текстуры на графический процессор в [0, 1] диапазоне, скорее [0, Number of Sprites] — рассчитайте [0, 1] в вашем вершинном шейдере.

Важно: Не разделяйте вершины (т. Е. Не используйте IndexBuffer ), потому что вершины, которые являются общими для более чем двух граней, должны оставаться разными (у них разные координаты текстуры) — создайте буфер, как если бы IndexBuffer s не существовало. Использование 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 s (координаты текстуры и цвет — оставьте положение в покое, его не нужно пересчитывать).

Альтернативно

Другой вариант — отобразить каждый слой в RenderTarget2D и использовать текущее преобразование один раз для всего слоя. Это либо решило бы вашу проблему, либо сделало бы реальную причину очень очевидной.

Примечание сбоку: я бы предоставил пример кода, если бы здесь не было 00h40, этот вопрос заслуживает этого. Я посмотрю, сколько времени у меня есть завтра вечером.

Комментарии:

1. Спасибо, что уделили время написанию этого поста. Несколько вещей: 1. при таком подходе я не могу просто обрабатывать 3D-модели, 2. Часть рендеринга — это, по сути, то же самое, что и моя матрица преобразования. 3. Мы уже используем таблицы спрайтов <— multiple.

Ответ №4:

Учитывая то, что вы нам дали, я бы с крайним подозрением отнесся к вашему многоуровневому коду. Это действительно выглядит так, что нижний слой иногда пробивается сквозь слои, которые должны быть сверху, и скрывает их, в зависимости от округления с плавающей запятой. Полосы, перпендикулярные углу обзора, являются очень распространенным эффектом, когда у вас есть два треугольника, которые должны быть точно копланарными, но по какой-либо причине не имеют точно одинаковых координат вершин (например, один больше другого). Что произойдет, если вы нарисуете различные слои на очень-очень небольшом расстоянии друг от друга? Например, нарисуйте нижний сплошной слой с точностью до -0.00002, следующий — с точностью до -0.00001 и верхний слой с точностью до 0 (предполагая, что все три слоя сейчас рисуются с точностью до 0).

Я не знаю конкретно о XNA, но проблема наслоения всегда является фундаментальной проблемой использования плавающей точки для представления геометрии, и я был бы удивлен, если XNA «волшебным» образом избежит этого для вас. Не уверен, почему некоторые плитки в порядке, но большинство испорчено. Вероятно, этим плиткам просто повезло или что-то в этом роде. Проблемы, вызванные ошибкой с плавающей запятой, часто ведут себя очень странно, как это.

Если небольшое разделение слоев не помогает, то вы в значительной степени ограничиваетесь стандартной отладкой на основе комментариев; попробуйте без спрайтов, без анимированных плиток, без прозрачного слоя и т.д. Когда это перестает происходить, все, что вы только что прокомментировали, нарушает это: P

Комментарии:

1. 1 . если бы это было так иногда , разве это не происходило бы и без преобразования? 2 . Я обновлю свой пост, чтобы показать расположение плиток. Каждый многослойный тайл находится в одном и том же месте (в c # используется одна и та же память, ранее им были присвоены отдельные адреса в графическом процессоре). Перемещение невозможно. 3 . Я попробую использовать предложенное вами наложение небольшими объемами.

2. Я разместил код наложения слоев. Как вы можете видеть, при разных значениях z отображаются разные приоритеты. Я все равно собираюсь попробовать то, что вы предложили, но даже если бы это было так, должно быть меньше плиток с линиями, поскольку во многих позициях слои фактически имеют разные значения z. Кроме того, размер каждой плитки составляет РОВНО 32×32 пикселя. Это установлено. Никаких переменных.

3. Хорошо, я пробовал не рисовать слои 2 и 3. Теперь строки заполнены слева направо, без пробелов. Таким образом, незатронутые плитки являются сплошными плитками на слое 2 или 3. Частично сплошные плитки (например, прозрачные плитки и т.д.) Действительно отображают линии, из-за чего ряды пикселей исчезают.

4. @Derk если это проблема с глубиной, как предлагает Пол, можете ли вы отключить чтение в глубину? ( DepthBufferEnable = false ). Для этого вам придется самостоятельно сортировать геометрию по глубине, вместо того чтобы полагаться на буфер глубины.

5. @Andrew Когда я рисую только один слой, я устранил этот случай. Но, чтобы удовлетворить вас и меня, я могу отключить это, и установка значения DepthBufferEnable = false только испортит порядок теней и спрайтов, но не удалит линии. Насколько мне известно, это ожидаемое поведение. Я добавлю ваше предложение в список проверенных.

Ответ №5:

Проблема в вашем шейдере SplattedTileShader.fx.

Черные линии появляются из-за отброшенных пикселей.

сложно следовать вашему коду шейдера без дополнительной информации, но проблема есть.

я думаю, что способ, которым вы делаете мультитекстуру, слишком сложен.

возможно, будет проще создать шейдер, поддерживающий мультитекстуру, за один проход.

в вашей вершине вы можете передать четыре значения веса для каждой текстуры и самостоятельно выполнить смешивание в шейдере.

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

Комментарии:

1. Это действительно было так, но вы, кажется, ничего не объясняете в своем ответе. О каких пикселях вы говорите? И дополнительная информация? Я практически объяснил каждую строку кода. Что еще вы хотели бы знать? Спасибо за ссылку, но я посмотрю на нее!

2. ммм … удаленные пиксели удаляются вместе с предложением clip… вы можете прокомментировать предложения из клипа, и это будет прорисовано не правильно, но лучше. И ваш шейдерный код не очень хорошо объяснен, я не понял, что он делает.

3. Нет, эта часть работает правильно. Шейдер обрезает все невидимые пиксели. Плюс отрисовка каждого из трех слоев. Сначала непрозрачные пиксели, затем (полу) прозрачные пиксели.

4. это заставляет меня задуматься, прочитали ли вы сообщение полностью. Да, мы сделали: — Удаление клипа (непрозрачности) в шейдере. Это удаляет только некоторые линии. Мы исследуем это дальше. В конце концов, не делал shizzle, потому что это не было проблемой!

5. клип работал некорректно, проблема была в том, что вы работали с поврежденными данными… вам нужно было только следить за темой … И нет, я не видел вашего поста…