我正在尝试实现sprite批处理,但我不太确定应该怎么做。
纹理批处理不是很难,我只是按纹理ID对所有内容进行分组,但我不确定如何处理顶点数据。
我可以这样做
texture.bind();
gl_quad.bind();
for(auto& quad: quads){
send(quad.matrix);
draw();
}
我只是将1个四边形上传到GPU,然后将矩阵作为一个统一变量发送并绘制四边形,但是对于我想要绘制的每个精灵,我都会有一个绘制调用,这可能不是很聪明。
或者我可以让每个精灵有4个顶点,然后我会在CPU上更新它们,然后我会收集所有精灵并将所有顶点上传到一个大缓冲区并绑定它。
texture.bind();
auto big_buffer = create_vertex_buffers(quads).bind();
draw();
big_buffer.delete();
我也可以使用实例渲染。只上传一个四元组,每个精灵都有一个矩阵,然后将所有矩阵上传到一个缓冲区并调用drawIndirect
。我必须发送9个浮点数而不是8个浮点数(使用big_buffer版本),我认为drawIndirect
比简单的draw
命令要贵得多。
还有其他方法我错过了吗?你会推荐什么?
答案 0 :(得分:2)
我可以向您展示一些适用于批次及其实现的类;但他们确实依赖其他课程。此作品受每个文件的标题部分中的版权保护。
<强> CommonStructs.h 强>
// Version: 1.0
// Copyright (c) 2012 by Marek A. Krzeminski, MASc
// http://www.MarkeKnows.com
#ifndef COMMON_STRUCTS_H
#define COMMON_STRUCTS_H
namespace vmk {
// GuiVertex ------------------------------------------------------------------
struct GuiVertex {
glm::vec2 position;
glm::vec4 color;
glm::vec2 texture;
GuiVertex( glm::vec2 positionIn, glm::vec4 colorIn, glm::vec2 textureIn = glm::vec2() ) :
position( positionIn ),
color( colorIn ),
texture( textureIn )
{}
}; // GuiVertex
// BatchConfig ----------------------------------------------------------------
struct BatchConfig {
unsigned uRenderType;
int iPriority;
unsigned uTextureId;
float fAlpha;
BatchConfig( unsigned uRenderTypeIn, int iPriorityIn, unsigned uTextureIdIn, float fAlphaIn ) :
uRenderType( uRenderTypeIn ),
iPriority( iPriorityIn ),
uTextureId( uTextureIdIn ),
fAlpha( fAlphaIn )
{}
bool operator==( const BatchConfig& other ) const {
if ( uRenderType != other.uRenderType ||
iPriority != other.iPriority ||
uTextureId != other.uTextureId ||
glm::abs( fAlpha - other.fAlpha ) > 0.004f )
{
return false;
}
return true;
}
bool operator!=( const BatchConfig& other ) const {
return !( *this == other );
}
}; // BatchConfig
} // namespace vmk
#endif // COMMON_STRUCTS_H
<强> Batch.h 强>
// Version: 1.0
// Copyright (c) 2012 by Marek A. Krzeminski, MASc
// http://www.MarkeKnows.com
#ifndef BATCH_H
#define BATCH_H
#include "CommonStructs.h"
namespace vmk {
class ShaderManager;
class Settings;
class Batch sealed {
private:
static Settings* m_pSettings;
static ShaderManager* m_pShaderManager;
unsigned m_uMaxNumVertices;
unsigned m_uNumUsedVertices;
unsigned m_vao;
unsigned m_vbo;
BatchConfig m_config;
GuiVertex m_lastVertex;
// For Debugging Only
unsigned m_uId; // Batch Id
std::vector<std::string> m_vIds; // Id's Of What Is Contained In This Batch
public:
Batch( unsigned uId, unsigned uMaxNumVertices );
~Batch();
bool isBatchConfig( const BatchConfig& config ) const;
bool isEmpty() const;
bool isEnoughRoom( unsigned uNumVertices ) const;
Batch* getFullest( Batch* pBatch );
int getPriority() const;
void add( const std::vector<GuiVertex>& vVertices, const BatchConfig& config );
void add( const std::vector<GuiVertex>& vVertices );
void addId( const std::string& strId );
void render();
private:
Batch( const Batch& c ); // Not Implemented
Batch& operator=( const Batch& c ); // Not Implemented
void cleanUp();
}; // Batch
} // namespace vmk
#endif // BATCH_H
<强> Batch.cpp 强>
// Version: 1.0
// Copyright (c) 2012 by Marek A. Krzeminski, MASc
// http://www.MarkeKnows.com
#include "stdafx.h"
#include "Batch.h"
#include "Logger.h"
#include "Property.h"
#include "Settings.h"
#include "ShaderManager.h"
namespace vmk {
Settings* Batch::m_pSettings = nullptr;
ShaderManager* Batch::m_pShaderManager = nullptr;
// ----------------------------------------------------------------------------
// Batch()
Batch::Batch( unsigned uId, unsigned uMaxNumVertices ) :
m_uMaxNumVertices( uMaxNumVertices ),
m_uNumUsedVertices( 0 ),
m_vao( 0 ),
m_vbo( 0 ),
m_config(GL_TRIANGLE_STRIP, 0, 0, 1.0f ),
m_lastVertex( glm::vec2(), glm::vec4() ),
m_uId( uId ) {
if ( nullptr == m_pSettings ) {
m_pSettings = Settings::get();
}
if ( nullptr == m_pShaderManager ) {
m_pShaderManager = ShaderManager::get();
}
// Optimal Size For A Batch Is Between 1-4MB In Size. Number Of Elements That Can Be Stored In A
// Batch Is Determined By Calculating #Bytes Used By Each Vertex
if ( uMaxNumVertices < 1000 ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " uMaxNumVertices{" << uMaxNumVertices << "} is too small. Choose a number >= 1000 ";
throw ExceptionHandler( strStream );
}
// Clear Error Codes
glGetError();
if ( m_pSettings->getOpenglVersion().x >= 3 ) {
glGenVertexArrays( 1, &m_vao );
glBindVertexArray( m_vao );
}
// Create Batch Buffer
glGenBuffers( 1, &m_vbo );
glBindBuffer( GL_ARRAY_BUFFER, m_vbo );
glBufferData( GL_ARRAY_BUFFER, uMaxNumVertices * sizeof( GuiVertex ), nullptr, GL_STREAM_DRAW );
if ( m_pSettings->getOpenglVersion().x >= 3 ) {
unsigned uOffset = 0;
m_pShaderManager->enableAttribute( A_POSITION, sizeof( GuiVertex ), uOffset );
uOffset += sizeof( glm::vec2 );
m_pShaderManager->enableAttribute( A_COLOR, sizeof( GuiVertex ), uOffset );
uOffset += sizeof( glm::vec4 );
m_pShaderManager->enableAttribute( A_TEXTURE_COORD0, sizeof( GuiVertex ), uOffset );
glBindVertexArray( 0 );
m_pShaderManager->disableAttribute( A_POSITION );
m_pShaderManager->disableAttribute( A_COLOR );
m_pShaderManager->disableAttribute( A_TEXTURE_COORD0 );
}
glBindBuffer( GL_ARRAY_BUFFER, 0 );
if ( GL_NO_ERROR != glGetError() ) {
cleanUp();
throw ExceptionHandler( __FUNCTION__ + std::string( " failed to create batch" ) );
}
} // Batch
// ----------------------------------------------------------------------------
// ~Batch()
Batch::~Batch() {
cleanUp();
} // ~Batch
// ----------------------------------------------------------------------------
// cleanUp()
void Batch::cleanUp() {
if ( m_vbo != 0 ) {
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glDeleteBuffers( 1, &m_vbo );
m_vbo = 0;
}
if ( m_vao != 0 ) {
glBindVertexArray( 0 );
glDeleteVertexArrays( 1, &m_vao );
m_vao = 0;
}
} // cleanUp
// ----------------------------------------------------------------------------
// isBatchConfig()
bool Batch::isBatchConfig( const BatchConfig& config ) const {
return ( config == m_config );
} // isBatchConfigh
// ----------------------------------------------------------------------------
// isEmpty()
bool Batch::isEmpty() const {
return ( 0 == m_uNumUsedVertices );
} // isEmpty
// ----------------------------------------------------------------------------
// isEnoughRoom()
// Returns True If The Number Of Vertices Passed In Can Be Stored In This Batch
// Without Reaching The Limit Of How Many Vertices Can Fit In The Batch
bool Batch::isEnoughRoom( unsigned uNumVertices ) const {
// 2 Extra Vertices Are Needed For Degenerate Triangles Between Each Strip
unsigned uNumExtraVertices = ( GL_TRIANGLE_STRIP == m_config.uRenderType && m_uNumUsedVertices > 0 ? 2 : 0 );
return ( m_uNumUsedVertices + uNumExtraVertices + uNumVertices <= m_uMaxNumVertices );
} // isEnoughRoom
// ----------------------------------------------------------------------------
// getFullest()
// Returns The Batch That Contains The Most Number Of Stored Vertices Between
// This Batch And The One Passed In
Batch* Batch::getFullest( Batch* pBatch ) {
return ( m_uNumUsedVertices > pBatch->m_uNumUsedVertices ? this : pBatch );
} // getFullest
// ----------------------------------------------------------------------------
// getPriority()
int Batch::getPriority() const {
return m_config.iPriority;
} // getPriority
// ----------------------------------------------------------------------------
// add()
// Adds Vertices To Batch And Also Sets The Batch Config Options
void Batch::add( const std::vector<GuiVertex>& vVertices, const BatchConfig& config ) {
m_config = config;
add( vVertices );
} // add
// ----------------------------------------------------------------------------
// add()
void Batch::add( const std::vector<GuiVertex>& vVertices ) {
// 2 Extra Vertices Are Needed For Degenerate Triangles Between Each Strip
unsigned uNumExtraVertices = ( GL_TRIANGLE_STRIP == m_config.uRenderType && m_uNumUsedVertices > 0 ? 2 : 0 );
if ( uNumExtraVertices + vVertices.size() > m_uMaxNumVertices - m_uNumUsedVertices ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " not enough room for {" << vVertices.size() << "} vertices in this batch. Maximum number of vertices allowed in a batch is {" << m_uMaxNumVertices << "} and {" << m_uNumUsedVertices << "} are already used";
if ( uNumExtraVertices > 0 ) {
strStream << " plus you need room for {" << uNumExtraVertices << "} extra vertices too";
}
throw ExceptionHandler( strStream );
}
if ( vVertices.size() > m_uMaxNumVertices ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " can not add {" << vVertices.size() << "} vertices to batch. Maximum number of vertices allowed in a batch is {" << m_uMaxNumVertices << "}";
throw ExceptionHandler( strStream );
}
if ( vVertices.empty() ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " can not add {" << vVertices.size() << "} vertices to batch.";
throw ExceptionHandler( strStream );
}
// Add Vertices To Buffer
if ( m_pSettings->getOpenglVersion().x >= 3 ) {
glBindVertexArray( m_vao );
}
glBindBuffer( GL_ARRAY_BUFFER, m_vbo );
if ( uNumExtraVertices > 0 ) {
// Need To Add 2 Vertex Copies To Create Degenerate Triangles Between This Strip
// And The Last Strip That Was Stored In The Batch
glBufferSubData( GL_ARRAY_BUFFER, m_uNumUsedVertices * sizeof( GuiVertex ), sizeof( GuiVertex ), &m_lastVertex );
glBufferSubData( GL_ARRAY_BUFFER, ( m_uNumUsedVertices + 1 ) * sizeof( GuiVertex ), sizeof( GuiVertex ), &vVertices[0] );
}
// TODO: Use glMapBuffer If Moving Large Chunks Of Data > 1MB
glBufferSubData( GL_ARRAY_BUFFER, ( m_uNumUsedVertices + uNumExtraVertices ) * sizeof( GuiVertex ), vVertices.size() * sizeof( GuiVertex ), &vVertices[0] );
if ( m_pSettings->getOpenglVersion().x >= 3 ) {
glBindVertexArray( 0 );
}
glBindBuffer( GL_ARRAY_BUFFER, 0 );
m_uNumUsedVertices += vVertices.size() + uNumExtraVertices;
m_lastVertex = vVertices[vVertices.size() - 1];
} // add
// ----------------------------------------------------------------------------
// addId()
void Batch::addId( const std::string& strId ) {
m_vIds.push_back( strId );
} // addId
// ----------------------------------------------------------------------------
// render()
void Batch::render() {
if ( m_uNumUsedVertices == 0 ) {
// Nothing In This Buffer To Render
return;
}
bool usingTexture = INVALID_UNSIGNED != m_config.uTextureId;
m_pShaderManager->setUniform( U_USING_TEXTURE, usingTexture );
if ( usingTexture ) {
m_pShaderManager->setTexture( 0, U_TEXTURE0_SAMPLER_2D, m_config.uTextureId );
}
m_pShaderManager->setUniform( U_ALPHA, m_config.fAlpha );
// Draw Contents To Buffer
if ( m_pSettings->getOpenglVersion().x >= 3 ) {
glBindVertexArray( m_vao );
glDrawArrays( m_config.uRenderType, 0, m_uNumUsedVertices );
glBindVertexArray( 0 );
} else { // OpenGL v2.x
glBindBuffer( GL_ARRAY_BUFFER, m_vbo );
unsigned uOffset = 0;
m_pShaderManager->enableAttribute( A_POSITION, sizeof( GuiVertex ), uOffset );
uOffset += sizeof( glm::vec2 );
m_pShaderManager->enableAttribute( A_COLOR, sizeof( GuiVertex ), uOffset );
uOffset += sizeof( glm::vec4 );
m_pShaderManager->enableAttribute( A_TEXTURE_COORD0, sizeof( GuiVertex ), uOffset );
glDrawArrays( m_config.uRenderType, 0, m_uNumUsedVertices );
m_pShaderManager->disableAttribute( A_POSITION );
m_pShaderManager->disableAttribute( A_COLOR );
m_pShaderManager->disableAttribute( A_TEXTURE_COORD0 );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
}
if ( m_pSettings->isDebugLoggingEnabled( Settings::DEBUG_RENDER ) ) {
std::ostringstream strStream;
strStream << std::setw( 2 ) << m_uId << " | "
<< std::left << std::setw( 10 );
if ( GL_LINES == m_config.uRenderType ) {
strStream << "Lines";
} else if ( GL_TRIANGLES == m_config.uRenderType ) {
strStream << "Triangles";
} else if ( GL_TRIANGLE_STRIP == m_config.uRenderType ) {
strStream << "Tri Strips";
} else if ( GL_TRIANGLE_FAN == m_config.uRenderType ) {
strStream << "Tri Fan";
} else {
strStream << "Unknown";
}
strStream << " | " << std::right
<< std::setw( 6 ) << m_config.iPriority << " | "
<< std::setw( 7 ) << m_uNumUsedVertices << " | "
<< std::setw( 5 );
if ( INVALID_UNSIGNED != m_config.uTextureId ) {
strStream << m_config.uTextureId;
} else {
strStream << "None";
}
strStream << " |";
for each( const std::string& strId in m_vIds ) {
strStream << " " << strId;
}
m_vIds.clear();
Logger::log( strStream );
}
// Reset Buffer
m_uNumUsedVertices = 0;
m_config.iPriority = 0;
} // render
} // namespace vmk
BatchManager.h
// Version: 1.0
// Copyright (c) 2012 by Marek A. Krzeminski, MASc
// http://www.MarekKnows.com
#ifndef BATCH_MANAGER_H
#define BATCH_MANAGER_H
#include "Singleton.h"
#include "CommonStructs.h"
namespace vmk {
class Batch;
class BatchManager sealed : public Singleton {
private:
std::vector<std::shared_ptr<Batch>> m_vBatches;
unsigned m_uNumBatches;
unsigned m_maxNumVerticesPerBatch;
public:
BatchManager( unsigned uNumBatches, unsigned numVerticesPerBatch );
virtual ~BatchManager();
static BatchManager* const get();
void render( const std::vector<GuiVertex>& vVertices, const BatchConfig& config, const std::string& strId );
void emptyAll();
protected:
private:
BatchManager( const BatchManager& c ); // Not Implemented
BatchManager& operator=( const BatchManager& c); // Not Implemented
void emptyBatch( bool emptyAll, Batch* pBatchToEmpty );
//void renderBatch( const std::vector<GuiVertex>& vVertices, const BatchConfig& config );
}; // BatchManager
} // namespace vmk
#endif // BATCH_MANAGER_H
<强> BatchManager.cpp 强>
// Version: 1.0
// Copyright (c) 2012 by Marek A. Krzeminski, MASc
// http://www.MarekKnows.com
#include "stdafx.h"
#include "BatchManager.h"
#include "Batch.h"
#include "Logger.h"
#include "Settings.h"
namespace vmk {
static BatchManager* s_pBatchManager = nullptr;
static Settings* s_pSettings = nullptr;
// ----------------------------------------------------------------------------
// BatchManager()
BatchManager::BatchManager( unsigned uNumBatches, unsigned numVerticesPerBatch ) :
Singleton( TYPE_BATCH_MANAGER ),
m_uNumBatches( uNumBatches ),
m_maxNumVerticesPerBatch( numVerticesPerBatch ) {
// Test Input Parameters
if ( uNumBatches < 10 ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " uNumBatches{" << uNumBatches << "} is too small. Choose a number >= 10 ";
throw ExceptionHandler( strStream );
}
// A Good Size For Each Batch Is Between 1-4MB In Size. Number Of Elements That Can Be Stored In A
// Batch Is Determined By Calculating #Bytes Used By Each Vertex
if ( numVerticesPerBatch < 1000 ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " numVerticesPerBatch{" << numVerticesPerBatch << "} is too small. Choose A Number >= 1000 ";
throw ExceptionHandler( strStream );
}
// Create Desired Number Of Batches
m_vBatches.reserve( uNumBatches );
for ( unsigned u = 0; u < uNumBatches; ++u ) {
m_vBatches.push_back( std::shared_ptr<Batch>( new Batch( u, numVerticesPerBatch ) ) );
}
s_pSettings = Settings::get();
s_pBatchManager = this;
} // BatchManager
// ----------------------------------------------------------------------------
// ~BatchManager()
BatchManager::~BatchManager() {
s_pBatchManager = nullptr;
m_vBatches.clear();
} // ~BatchManager
// ----------------------------------------------------------------------------
// get()
BatchManager* const BatchManager::get() {
if ( nullptr == s_pBatchManager ) {
throw ExceptionHandler( __FUNCTION__ + std::string( " failed, BatchManager has not been constructed yet" ) );
}
return s_pBatchManager;
} // get
// ----------------------------------------------------------------------------
// render()
void BatchManager::render( const std::vector<GuiVertex>& vVertices, const BatchConfig& config, const std::string& strId ) {
Batch* pEmptyBatch = nullptr;
Batch* pFullestBatch = m_vBatches[0].get();
// Determine Which Batch To Put The Vertices Into
for ( unsigned u = 0; u < m_uNumBatches; ++u ) {
Batch* pBatch = m_vBatches[u].get();
if ( pBatch->isBatchConfig( config ) ) {
if ( !pBatch->isEnoughRoom( vVertices.size() ) ) {
// First Need To Empty This Batch Before Adding Anything To It
emptyBatch( false, pBatch );
if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_RENDER ) ) {
Logger::log( "Forced batch to empty to make room for vertices" );
}
}
if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_RENDER ) ) {
pBatch->addId( strId );
}
pBatch->add( vVertices );
return;
}
// Store Pointer To First Empty Batch
if ( nullptr == pEmptyBatch && pBatch->isEmpty() ) {
pEmptyBatch = pBatch;
}
// Store Pointer To Fullest Batch
pFullestBatch = pBatch->getFullest( pFullestBatch );
}
// If We Get Here Then We Didn't Find An Appropriate Batch To Put The Vertices Into
// If We Have An Empty Batch, Put Vertices There
if ( nullptr != pEmptyBatch ) {
if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_RENDER ) ) {
pEmptyBatch->addId( strId );
}
pEmptyBatch->add( vVertices, config );
return;
}
// No Empty Batches Were Found Therefore We Must Empty One First And Then We Can Use It
emptyBatch( false, pFullestBatch );
if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_RENDER ) ) {
Logger::log( "Forced fullest batch to empty to make room for vertices" );
pFullestBatch->addId( strId );
}
pFullestBatch->add( vVertices, config );
} // render
// ----------------------------------------------------------------------------
// emptyAll()
void BatchManager::emptyAll() {
emptyBatch( true, m_vBatches[0].get() );
if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_RENDER ) ) {
Logger::log( "Forced all batches to empty" );
}
} // emptyAll
// ----------------------------------------------------------------------------
// CompareBatch
struct CompareBatch : public std::binary_function<Batch*, Batch*, bool> {
bool operator()( const Batch* pBatchA, const Batch* pBatchB ) const {
return ( pBatchA->getPriority() > pBatchB->getPriority() );
} // operator()
}; // CompareFunctor
// ----------------------------------------------------------------------------
// emptyBatch()
// Empties The Batches According To Priority. If emptyAll() Is False Then
// Only Empty The Batches That Are Lower Priority Than The One Specified
// AND Also Empty The One That Is Passed In
void BatchManager::emptyBatch( bool emptyAll, Batch* pBatchToEmpty ) {
// Sort Bathes By Priority
std::priority_queue<Batch*, std::vector<Batch*>, CompareBatch> queue;
for ( unsigned u = 0; u < m_uNumBatches; ++u ) {
// Add All Non-Empty Batches To Queue Which Will Be Sorted By Order
// From Lowest To Highest Priority
if ( !m_vBatches[u]->isEmpty() ) {
if ( emptyAll ) {
queue.push( m_vBatches[u].get() );
} else if ( m_vBatches[u]->getPriority() < pBatchToEmpty->getPriority() ) {
// Only Add Batches That Are Lower In Priority
queue.push( m_vBatches[u].get() );
}
}
}
// Render All Desired Batches
while ( !queue.empty() ) {
Batch* pBatch = queue.top();
pBatch->render();
queue.pop();
}
if ( !emptyAll ) {
// When Not Emptying All The Batches, We Still Want To Empty
// The Batch That Is Passed In, In Addition To All Batches
// That Have Lower Priority Than It
pBatchToEmpty->render();
}
} // emptyBatch
} // namespace vmk
现在这些类不会直接编译它们依赖并依赖于其他类对象:设置,属性,ShaderManager,Logger以及这些对象也依赖于其他对象。这是来自大规模工作的OpenGL图形渲染&amp;使用OpenGL着色器的游戏引擎。这是工作源代码,最佳无bug。
这可以作为人们如何设计批处理的指南。并且可以深入了解要考虑的事项,例如:正在渲染的顶点类型{Lines,Triangles,TriangleStrip,TriangleFan等},基于是否具有透明度绘制对象的优先级,处理退化三角形创建批处理对象时的顶点。
设计这种方式的方法是只匹配的批次类型适合同一个桶,并且桶会尝试填充自己,如果它太满而无法容纳顶点,那么它将寻找另一个桶来查看是否一个是可用的,如果没有可用的桶,它将搜索以查看哪个是最充分的,它将从优先级队列中清空它们以将顶点发送到要呈现的视频卡。
这与ShaderManager相关联,该ShaderManager管理OpenGL如何定义和设置着色器程序并将它们链接到程序,它还绑定到AssetStorage类,该类在此处找不到但在ShaderManager中找到。该系统处理完整的自定义GUI,精灵,字体,纹理等。
如果您想了解更多信息,我强烈建议您访问www.MarekKnows.com并查看他在OpenGL上的视频教程系列;对于这个特定的应用程序,您需要遵循他的Shader Engine系列!
答案 1 :(得分:1)
值得注意的是,从每个渲染的精灵之间的上下文切换的角度来看,精灵渲染只是非常昂贵。相比之下,为每个精灵渲染一个四边形往往是一个微不足道的花费。
此处对几何数据的实例化可能比阻力更大,因为每个四边形使用单独的变换矩阵的成本往往超过仅为每个四边形上传一组新的顶点属性的费用。当您至少具有中等复杂的几何体(例如数百到数千个顶点)时,实例化效果最佳。
通常,如果速度是您的主要目标,那么此处的首要任务是将纹理数据合并到&#34; sprite sheet&#34; -style纹理图集中。第一个目标是尽可能少地进行纹理上下文切换,并且通常远远超出每个精灵/帧的单独纹理图像。这也会使实例化进一步变得不切实际,因为你渲染的每个四边形或一对三角形在纹理坐标方面往往会变化很大。
如果你实际上达到了这样一个点,即你拥有尽可能少的纹理上下文切换,并希望更快速地获得一堆动态精灵,那么下一个实际步骤(但收益递减)可能是使用流式VBO。您可以使用渲染当前帧的tris /四边形(具有不同的顶点位置和纹理坐标)所需的顶点属性来填充流VBO,然后绘制VBO。为了获得最佳性能,可能有助于对VBO进行分块,而不是使用每帧填充和绘制,填充和绘制,填充和绘制多次的策略来填充每帧的整个场景的所有几何数据。
尽管如此,由于你询问了实例化(这意味着你对每个精灵使用一个单独的图像),你的第一个也是最大的增益可能来自于使用纹理图集并进一步减少纹理上下文切换。几何方面的优化是一个完全独立的过程,即使在这里使用立即模式,你也可以在很长一段时间内完成。如果您开始优化流式VBO,那将是优化的最后一步。
这就是假设动态精灵在屏幕上移动或更改图像。对于永不改变的静态平铺样式图像,您可以将它们的顶点属性存储到静态VBO中,并可能从实例化中受益(但是我们每次静态VBO都会实现一大堆平铺,因此每个静态VBO可能有数百个每个数千个顶点。)