本文共 4082 字,大约阅读时间需要 13 分钟。
由于Direct3D11中微软移除了ID3DXFont这个在Direct3D 9中非常好用的字体接口,这样导致了目前的Direct3D11中竟然没有一个官方的字体解决方案。所以如果要绘制字体,必须要采取手动绘制文本的方式。
我们这里介绍一种图形贴图的方式来绘制字体,即将游戏中要绘制的字体做成一副图片(见下图),然后在游戏里面切开一个个字符图片,按字符串所需字符来绘制。
其实我们并没有真正的切开一幅字体纹理,而是通过修改UV来选中不同的字符。实际上,我们只需要增加一些代码来将之前 Demo 中的顶点缓存修改为动态缓存,和增加一个函数来将我们的贴图精灵填充到缓存中即可。 动态缓存对于我们需要修改一块缓存中的内容的这种情况来说是很合适的。 不推荐多次创建和销毁静态缓存块,特别是逐帧这样做,你应该使用动态缓存来做这样的任务(上一节已经介绍过如何创建动态缓存)。
也就是说我们就修改动态顶点缓存中的顶点位置(字符的位置)和UV(字符的纹理)就可以了。
想修改动态缓存,首先要调用D3D设备的Map函数,来获得子资源的指针(ID3D11Buffer继承自 ID3D11Resource,所以缓存也是一种资源)。
Map函数的原型如下:HRESULT Map( [in] ID3D11Resource *pResource, [in] UINT Subresource, [in] D3D11_MAP MapType, [in] UINT MapFlags, [out, optional] D3D11_MAPPED_SUBRESOURCE *pMappedResource);第一个参数pResource,是映射的源头。
第二个参数Subresource,是子资源的索引(设置为 0,因为我们没有多个子资源)。 第三个参数D3D11_MAP,在本 Demo 中映射类型是 D3D11_MAP_WRITE_DISCARD,它指示 Direct3D 将缓存中的之前的值作为未定义考虑。 第四个参数MapFlags,映射标识。对于其它的映射类型,映射标识可以是 D3D11_MAP_FLAG_DO_NOT_WAIT。但是当使用映射类型D3D11_MAP_WIRTE_DISCARD,则映射标识必须是 0,因为D3D11_MAP_FLAG_DO_NOT_WAIT标识不能用于此种映射类型。 第五个参数pMappedResource,用一个D3D11_MAPPED_SUBRESOURCE结构体类型的指针来保存该映射子资源。(我们通过它来修改动态缓存。为了更新缓存,我们只需要简单的拷贝任何数据给 D3D11_MAPPED_SUBRESOURCE 结构的 pData 成员。 )
(完整代码太多就不贴了,请自行查看该书附带源码)
bool D3DTextDemo::DrawString( char* message, float startX, float startY ){ // Size in bytes for a single sprite. const int sizeOfSprite = sizeof( VertexPos ) * 6; // Demo's dynamic buffer setup for max of 24 letters. const int maxLetters = 24; int length = strlen( message ); // Clamp for strings too long. if( length > maxLetters ) length = maxLetters; // Char's width on screen. float charWidth = 32.0f / 800.0f; // Char's height on screen. float charHeight = 32.0f / 640.0f; // Char's texel width. float texelWidth = 32.0f / 864.0f; // verts per-triangle (3) * total triangles (2) = 6. const int verticesPerLetter = 6; D3D11_MAPPED_SUBRESOURCE mapResource; HRESULT d3dResult = d3dContext_->Map( vertexBuffer_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapResource ); if( FAILED( d3dResult ) ) { DXTRACE_MSG( "Failed to map resource!" ); return false; } // Point to our vertex buffer's internal data. VertexPos *spritePtr = ( VertexPos* )mapResource.pData; const int indexA = static_cast( 'A' ); const int indexZ = static_cast ( 'Z' ); for( int i = 0; i < length; ++i ) { float thisStartX = startX + ( charWidth * static_cast ( i ) ); float thisEndX = thisStartX + charWidth; float thisEndY = startY + charHeight; spritePtr[0].pos = XMFLOAT3( thisEndX, thisEndY, 1.0f ); spritePtr[1].pos = XMFLOAT3( thisEndX, startY, 1.0f ); spritePtr[2].pos = XMFLOAT3( thisStartX, startY, 1.0f ); spritePtr[3].pos = XMFLOAT3( thisStartX, startY, 1.0f ); spritePtr[4].pos = XMFLOAT3( thisStartX, thisEndY, 1.0f ); spritePtr[5].pos = XMFLOAT3( thisEndX, thisEndY, 1.0f ); int texLookup = 0; int letter = static_cast ( message[i] ); if( letter < indexA || letter > indexZ ) { // Grab one index past Z, which is a blank space in the texture. texLookup = ( indexZ - indexA ) + 1; } else { // A = 0, B = 1, Z = 25, etc. texLookup = ( letter - indexA ); } float tuStart = 0.0f + ( texelWidth * static_cast ( texLookup ) ); float tuEnd = tuStart + texelWidth; spritePtr[0].tex0 = XMFLOAT2( tuEnd, 0.0f ); spritePtr[1].tex0 = XMFLOAT2( tuEnd, 1.0f ); spritePtr[2].tex0 = XMFLOAT2( tuStart, 1.0f ); spritePtr[3].tex0 = XMFLOAT2( tuStart, 1.0f ); spritePtr[4].tex0 = XMFLOAT2( tuStart, 0.0f ); spritePtr[5].tex0 = XMFLOAT2( tuEnd, 0.0f ); spritePtr += 6; } d3dContext_->Unmap( vertexBuffer_, 0 ); d3dContext_->Draw( 6 * length, 0 ); return true;}