一、简介
在上节中,我们介绍了与NDK和着色器相关的内容,SDK中对此提供了很多的便利,我们只要熟悉了SDK的框架流程,就可以很轻易的实现图案绘制。

二、例程分析
在Initialize函数中,此例程加载了Font、resource、texture等资源,同时调用Shader初始化函数对Shader进行了初始化。资源的加载过程中,我们这里主要分析一下它的流程:
首先在Scene.cpp中,调用了FrmFontGLES.cpp中的Create函数,来实现它的字体创建,首先他传入的为:m_Font.Create( “Samples/Fonts/Tuffy12.pak” );之后在Create函数中首先调用到的是FrmPackedResource.cpp中的LoadFromFile函数,如下:
m_ResourceGLES.LoadFromFile( strFontFileName );
之后通过GetData函数获取到font data:
FRM_FONT_FILE_HEADER_GLES* pFontData;
pFontData = (FRM_FONT_FILE_HEADER_GLES*)m_ResourceGLES.GetData( “FontData” );
接下来当然就是检查font data是否可用,并加载字体相关的Shader等:

m_nGlyphHeight = pFontData->nGlyphHeight;
 m_pGlyphsGLES = pFontData->pGlyphs;// Create the font texture
 m_pTexture = m_ResourceGLES.GetTexture( “FontTexture” );
 if( NULL == m_pTexture )
 {
 FrmLogMessage( “ERROR: CFrmFontGLES::Create( const CHAR* strFontFileName ) failed. - if( NULL == m_pTexture )”);
 return FALSE;
 }// Compile the font shaders
 if( FALSE == FrmCompileShaderProgram( g_strFontVertexShader, g_strFontFragmentShader, &m_hShader ) )
 {
 FrmLogMessage( “ERROR: CFrmFontGLES::Create( const CHAR* strFontFileName ) failed. - if( FALSE == FrmCompileShaderProgram( g_strFontVertexShader, g_strFontFragmentShader, &m_hShader ) )”);
 return FALSE;
 }m_locVertexPos = glGetAttribLocation( m_hShader, “g_vVertexPos” );
 m_locVertexColor = glGetAttribLocation( m_hShader, “g_vVertexColor” );
 m_locVertexTex = glGetAttribLocation( m_hShader, “g_vVertexTex” );m_locScale = glGetUniformLocation( m_hShader, “g_vScale” );
 m_locOffset = glGetUniformLocation( m_hShader, “g_vOffset” );

这里的FrmCompileShaderProgram使用的是FrmShader.cpp中定义到的,这里的Shader是FrmFontGLES.cpp中实现的,如下:

const CHAR* g_strFontVertexShader =
 “#version 300 es\n”
 “uniform vec4 g_vScale; \n”
 “uniform vec4 g_vOffset; \n”
 “in vec2 g_vVertexPos; \n”
 “in vec4 g_vVertexColor; \n”
 “in vec2 g_vVertexTex; \n”
 “out vec4 g_vColor; \n”
 “out vec2 g_vTexCoord; \n”
 " \n"
 “void main() \n”
 “{ \n”
 " // Transform the position \n"
 " vec2 vVertex = g_vVertexPos * g_vScale.xy + g_vOffset.xy; \n"
 " gl_Position = vec4( vVertex, 0.0, 1.0 ); \n"
 " \n"
 " // Pass through color and texture coordinates \n"
 " g_vColor = g_vVertexColor; \n"
 " g_vTexCoord = g_vVertexTex * g_vScale.zw + g_vOffset.zw; \n"
 “} \n”;

然后在FrmShader.cpp中设置着色器程序。到这里,这些与字体相关的资源加载就已经完成了。

接下来就是图像的绘制工作了,这里我们需要绑定Buffer:
FrmSetVertexBuffer( pMesh->m_hVertexBuffer );
FrmSetVertexLayout( pMesh->m_VertexLayout, pMesh->m_nVertexSize );
FrmSetIndexBuffer( pMesh->m_hIndexBuffer );
然后是设置Shader中的变量,以及纹理等,最后是Draw:
FrmDrawIndexedVertices( pSubset->m_nPrimType, pSubset->m_nNumIndices,
pMesh->m_nIndexSize, pSubset->m_nIndexOffset );

这些实现统统都在FrmResourceGLES.h中,当然我们也可以自己动手来实现都是可以的。这里就有问题了,设置buffer的函数我们直接放在了Draw函数里面,而它有肯定是要被循环调用的,我们是否可以把它提取出来,那么我们接着往下走。
在Render函数中:

VOID CSample::Render()
 {
 // Set default states
 glEnable( GL_DEPTH_TEST );
 glEnable( GL_CULL_FACE );// Clear the backbuffer and depth-buffer
 glClearColor( 0.2f, 0.2f, 0.6f, 0.0f );
 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );// Draw the skinned mesh
 DrawSkinnedMesh( m_Mesh[m_nActiveMesh].m_pFrames[0].m_pMesh,
 m_hSkinningShader, &m_SkinningConstants,
 m_matView, m_matProj );// Update the timer
 m_Timer.MarkFrame();// Render the user interface
 m_UserInterface.Render( m_Timer.GetFrameRate() );
 }

我们可以看到最后有一个UserInterface.Render函数,这里有这样一个调用关系。在这个Render函数里面,它会绘制Background和Font,而这些都拥有自己独立的着色器程序,那么我们就可以发现,在最后这里,其实也就是整个框架最后的绘制部分,存在多个着色器程序的同时,也会存在着其相应的buffer。因此,为了便于理解,背景和字体绘制这块,它们也都是每绘制一次便初始化一次其对应的buffer。
最终将会在我们的测试手机上呈现出绘制出来的图案及Logo等。

三、总结
好了,到这里大家应该都对这个框架有了简单的了解了。从始至终,我们只是调用了一下SDK提供的内容,完全不需要去实现复杂的着色器程序,最终却能够绘制出大量复杂多变的图像出来,这就是它的魅力所在。