2013-12-13 55 views
1

這是我的第一篇文章,如果我犯了任何錯誤等,我很抱歉。 如果在文章結尾處,您需要更多信息,那麼我會更樂意發佈更多。Xna二維平鋪引擎渲染問題

我一直在研究基於2D平鋪的遊戲一段時間,最近我嘗試了一種不同的方法來渲染平鋪。但是現在我在渲染切片時遇到性能問題。遊戲將其世界分成16 * 256個塊,每個塊都有自己的RenderTarget2D(16 * 256)來保存拼塊數據。我已經將渲染目標稱爲塊緩衝區。

public void updateChunks() 
    { 
     int loadMinX = (int)((game.camera.location.X - game.GraphicsDevice.Viewport.Width/2)/Tile.tileWidth/Chunk.chunkWidth); 
     int loadMaxX = (int)((game.camera.location.X + game.GraphicsDevice.Viewport.Width/2)/Tile.tileWidth/Chunk.chunkWidth); 

     int minX = (int)(game.player.location.X/(Chunk.chunkWidth * Tile.tileWidth)) - (int)Math.Floor(chunkCount/2f); 
     int maxX = (int)(game.player.location.X/(Chunk.chunkWidth * Tile.tileWidth)) + (int)Math.Floor(chunkCount/2f); 

     Chunk currentChunk; 
     int cursorChunk = game.cursor.chunkLocation; 

     for(int c = minX; c <= maxX; c++) 
     { 
      if(chunkBuffer.ContainsKey(c)) 
      { 
       currentChunk = chunkBuffer[c]; 

       if(c >= loadMinX && c <= loadMaxX) 
       { 
        currentChunk.bufferBuilt = false; 
       } 

       if(currentChunk.highlighted == true) 
       { 
        currentChunk.highlighted = false; 
        currentChunk.bufferBuilt = false; 
       } 

       if(chunkBuffer.ContainsKey(cursorChunk) && chunkBuffer[cursorChunk] == currentChunk) 
       { 
        currentChunk.highlighted = true; 
        currentChunk.bufferBuilt = false; 
       } 

       if(currentChunk.bufferBuilt == false) 
       { 
        RebuildChunk(currentChunk); 
       } 
      } 
     } 

     game.tilesLoaded = chunkBuffer.Count * Chunk.chunkWidth * Chunk.chunkHeight; 
    } 

這個方法每調用一次,就決定哪個塊緩衝區需要重建/重繪。目前,在視口中的塊被重新編輯,並且鼠標在裏面的塊也被重建(因爲塊變爲高亮顏色)。

塊存儲在字典中,並且加載如updateCHunks()方法所示,通過遍歷塊的字典的minX - > minY值來訪問塊。

public void RebuildChunk(Chunk chunk) 
    { 
     BuildChunk(chunk); 

     chunk.bufferBuilt = true; 
    } 

    private void BuildChunk(Chunk chunk) 
    { 
     int chunkWidth = Chunk.chunkWidth; 
     int chunkHeight = Chunk.chunkHeight; 
     int chunkLocation = chunk.id * chunkWidth; 

     int loadMinX = (int)((game.camera.location.X - game.GraphicsDevice.Viewport.Width/2)/Tile.tileWidth) - 2; 
     int loadMaxX = (int)((game.camera.location.X + game.GraphicsDevice.Viewport.Width/2)/Tile.tileWidth) + 2; 
     int loadMinY = (int)((game.camera.location.Y - game.GraphicsDevice.Viewport.Height/2)/Tile.tileHeight) - 2; 
     int loadMaxY = (int)((game.camera.location.Y + game.GraphicsDevice.Viewport.Height/2)/Tile.tileHeight) + 2; 

     game.GraphicsDevice.SetRenderTarget(chunk.buffer); 
     game.GraphicsDevice.Clear(Color.CornflowerBlue); 

     game.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied); 

     for(int x = 0; x < chunkWidth; x++) 
     { 
      for(int y = 0; y < chunkHeight; y++) 
      { 
       if(x + chunkLocation > loadMinX && x + chunkLocation < loadMaxX && y > loadMinY && y < loadMaxY) 
       { 
        if(chunk.tiles[x, y].type == 0 && chunk.tiles[x, y].lightSource == false) 
        { 
         chunk.tiles[x, y].lightSource = true; 
         chunk.tiles[x, y].lightComponent = new Light(game, new Point(chunkLocation + x, y), 1.0f, 6); 
        } 

        if(chunk.tiles[x, y].lightSource == true) 
        { 
         if(chunk.tiles[x, y].lightComponent == null) 
         { 
          chunk.tiles[x, y].lightSource = false; 
         } 
         else 
         { 
          ApplyLighting(chunk, x, y, chunk.tiles[x, y].lightComponent); 
         } 
        } 

        float brightness = chunk.tiles[x, y].brightness; 

        if(chunk.tiles[x, y].type == 0) 
        { 
         if(chunk.highlighted == true && game.cursor.tileLocation.X == x && game.cursor.tileLocation.Y == y) 
         { 
          game.spriteBatch.Draw(game.gameContent.airTexture, new Rectangle(x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight), new Color(0.5f, 0.5f, 0.5f)); 
         } 
         else 
         { 
          game.spriteBatch.Draw(game.gameContent.airTexture, new Rectangle(x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight), new Color(brightness, brightness, brightness)); 
         } 
        } 
        else if(chunk.tiles[x, y].type == 1) 
        { 
         if(chunk.highlighted == true && game.cursor.tileLocation.X == x && game.cursor.tileLocation.Y == y) 
         { 
          game.spriteBatch.Draw(game.gameContent.dirtTexture, new Rectangle(x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight), new Color(0.5f, 0.5f, 0.5f)); 
         } 
         else 
         { 
          game.spriteBatch.Draw(game.gameContent.dirtTexture, new Rectangle(x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight), new Color(brightness, brightness, brightness)); 
         } 
        } 
       } 
      } 
     } 

     game.spriteBatch.End(); 

     game.GraphicsDevice.SetRenderTarget(null); 
    } 

RebuildChunk負責建設再次塊和復位內置標誌,這樣CPU不浪費時間重建還沒有被編輯或塊是不是在視口中。

BuildChunk是瓷磚繪製到塊緩衝區的位置。渲染目標被交換到塊緩衝區,然後它通過塊瓷磚循環。

if(x + chunkLocation > loadMinX && x + chunkLocation < loadMaxX && y > loadMinY && y < loadMaxY) 

此行檢查瓦片是否在視口中。 如果是,請檢查瓷磚類型,然後爲該瓷磚繪製相應的紋理。那裏也有照明邏輯,但它不影響我的問題。

我主要呈現邏輯是:

protected override void Draw(GameTime gameTime) 
    { 
     frameCounter++; 

     drawScene(gameTime); 

     GraphicsDevice.Clear(Color.White); 

     spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied); 

     spriteBatch.Draw(white, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.CornflowerBlue); 
     spriteBatch.Draw(worldBuffer, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White); 

     spriteBatch.End(); 

     debugUI.Draw(gameTime); 

     base.Draw(gameTime); 
    } 

    private void drawScene(GameTime gameTime) 
    { 
     int loadMinX = (int)((camera.location.X - GraphicsDevice.Viewport.Width/2)/Tile.tileWidth/Chunk.chunkWidth); 
     int loadMaxX = (int)((camera.location.X + GraphicsDevice.Viewport.Width/2)/Tile.tileWidth/Chunk.chunkWidth); 

     int minX = (int)(player.location.X/(Chunk.chunkWidth * Tile.tileWidth)) - (int)Math.Floor(worldManager.chunkCount/2f); 
     int maxX = (int)(player.location.X/(Chunk.chunkWidth * Tile.tileWidth)) + (int)Math.Floor(worldManager.chunkCount/2f); 

     GraphicsDevice.SetRenderTarget(worldBuffer); 
     GraphicsDevice.Clear(Color.CornflowerBlue); 

     spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, camera.GetViewTransformation(GraphicsDevice)); 

     for(int c = minX; c <= maxX; c++) 
     { 
      if(c >= loadMinX && c <= loadMaxX) 
      { 
       if(worldManager.chunkBuffer.ContainsKey(c)) 
       { 
        Chunk currentChunk = worldManager.chunkBuffer[c]; 

        if(currentChunk.bufferBuilt == true) 
        { 
         spriteBatch.Draw(currentChunk.buffer, new Rectangle(c * Chunk.chunkWidth * Tile.tileWidth, 0, Chunk.chunkWidth * Tile.tileWidth, Chunk.chunkHeight * Tile.tileHeight), Color.White); 
        } 
       } 
      } 
     } 

     spriteBatch.Draw(player.material, new Rectangle((int)player.location.X, (int)player.location.Y, 16, 32), Color.White); 

     spriteBatch.End(); 
     GraphicsDevice.SetRenderTarget(null); 
    } 

最後,我輸入管理器裏面,我有以下:

if(mouseState.LeftButton == ButtonState.Pressed) 
     { 
      game.worldManager.RemoveTile(game.cursor.chunkLocation, game.cursor.tileLocation); 
     } 

的呼叫:

public void RemoveTile(int chunkLocation, Point location) 
    { 
     if(chunkBuffer.ContainsKey(chunkLocation) && location.X >= 0 && location.X < Chunk.chunkWidth && location.Y >= 0 && location.Y < Chunk.chunkHeight) 
     { 
      if(chunkBuffer[chunkLocation].tiles[location.X, location.Y].type != 0) 
       chunkBuffer[chunkLocation].tiles[location.X, location.Y].type = 0; 
     } 
    } 

這只是我的測試方法來改變不同瓦片的狀態,在這種情況下,它將類型形式的污垢改變爲空氣以模擬去除一塊瓷磚。

現在的問題。當遊戲第一次運行時很好,並保持在60fps。我把我的角色移動到右邊,用瓷磚填充屏幕寬度,然後挖下來用瓷磚填充屏幕高度。

http://i.imgur.com/NBJ8qRj.png

http://i.imgur.com/N9i8asW.png (注:照明第二圖像中關閉)

遊戲仍在運行良好的第一形象,但是當我開始去除環境中的玩家磚第二張圖片,過了一段時間,幀率從60下降到10以下,下降到2-3幀/秒左右,並且保持在這個速率,並且不會恢復,即使我已經停止點擊/執行任何操作。

的問題發生在全屏幕分辨率和性能分析說,大量的CPU時間在BuildChunk()方法中的抽獎調用中使用:

else 
        { 
         game.spriteBatch.Draw(game.gameContent.dirtTexture, new Rectangle(x * Tile.tileWidth, y * Tile.tileHeight, Tile.tileWidth, Tile.tileHeight), new Color(brightness, brightness, brightness)); 
        } 

因此可以說,它是通過不斷引起交換紋理繪製?即;從繪製airTexture到dirtTexture等交換?因爲我發現如果我將airTexture改爲dirtTexture,問題也會消失。

如果我再往下挖掘所有的瓷磚,fps恢復到60以下,所以我的猜測是它與在重建階段繪製紋理有關。

如果有人可以查看這段代碼,也許指出它的任何缺陷,將不勝感激,可能的解決方案將是偉大的。

另外,如果有人對瓷磚的這種情況有所瞭解,並且提出建議將是無價的!

+1

Nex時間試圖發佈只有相關的代碼,這個問題是巨大的,我不知道有多少人會閱讀它。 – pinckerman

回答

3

我的媽呀,這是一個徹底的問題!向我們提供大量的信息是值得讚賞的,但是在將來,嘗試將你的問題削減到與那些直接相關的問題可能是一個好主意。否則,人們將不知所措。

我可以告訴你的是,我認爲你是在正確的軌道與分析。來回交換紋理可以像你猜測的那樣對性能產生重大影響。

2D渲染性能的最大限制之一是所謂的批量限制。基本上,每次你告訴圖形設備繪製一些東西 - 也就是說,每次你打電話給DirectX的DrawFooPrimitives函數之一時,都會有一定的固定開銷。因此,進行大量和大量的繪製調用,其中每個僅繪製少量的多邊形,因此效率非常低。您的GPU閒置時,您的CPU攪動處理繪製調用!

XNA的SpriteBatch類存在解決此問題。它將大量的精靈組裝成一個單獨的緩衝區,可以通過一次調用DrawIndexedPrimitives來繪製。但是,如果它們全都共享同一個圖形設備狀態,則它只能批量生成精靈。這包括諸如您在SpriteBatch.Begin()中設置的採樣器和光柵化器狀態,​​但它也包含精靈紋理。更改紋理將強制SpriteBatch刷新當前批處理並啓動一個全新的紋理。

這就是說,你正在犯另一個相關的錯誤,那種渲染上述點無關緊要。在您的渲染方法中,您使用SpriteSortMode.Immediate調用SpriteBatch.Begin()。這對於大量的精靈來說效率會很低,因爲你在做的是說:「不要批量處理這些數據;請致電DrawIndexedPrimitives獲得我繪製的每個精靈。」精靈是4個頂點。您的圖形卡可能會通過Draw呼叫處理數十萬個。巨大的浪費!

因此,要總結,這裏就是我會在你的代碼更改:

  1. 變化SpriteSortMode.ImmediateSpriteSortMode.Deferred。使用此選項,SpriteBatch將累積您繪製到緩衝區中的精靈,然後在致電End()時一次繪製它們。
  2. 對所有瓷磚使用單一紋理。這通常使用所謂的texture atlas完成。你可以在SpriteBatch.Draw()中指定一個源矩形,它會告訴它只使用較大圖像的一小部分。