游戏中的文本有多种实现方式,比如在屏幕上进行几何贴图,使用矢量绘制,甚至仅仅使用标准消息盒。
在2D和3D游戏中,文本通常使用贴图的几何图形进行绘制
动态文本系统通过载入在字体中包含的任何字母的单一贴图来构建那些文本字符串
这里的创建顶点缓存是动态的:
代码设置如下:
HRESULT D3DTextDemo::CreateTextVertices(float width, float height) {
HRESULT d3dResult;
VertexPos vertices[] = { //创建顶点矩阵
{ XMFLOAT3(width, height, 1.0f), XMFLOAT2(1.0f, 0.0f) },
{ XMFLOAT3(width, -height, 1.0f), XMFLOAT2(1.0f, 1.0f) },
{ XMFLOAT3(-width, -height, 1.0f), XMFLOAT2(0.0f, 1.0f) },
{ XMFLOAT3(-width, -height, 1.0f), XMFLOAT2(0.0f, 1.0f) },
{ XMFLOAT3(-width, height, 1.0f), XMFLOAT2(0.0f, 0.0f) },
{ XMFLOAT3(width, height, 1.0f), XMFLOAT2(1.0f, 0.0f) },
};
//创建顶点描述
D3D11_BUFFER_DESC vertexDesc; //创建顶点描述
ZeroMemory(&vertexDesc, sizeof(vertexDesc));
vertexDesc.Usage = D3D11_USAGE_DYNAMIC; //动态缓存 能够通过CPU来动态更新
vertexDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; //动态写 为了让CPU能够对GPU中的资源进行写访问
vertexDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; //绑定顶点缓存
const int sizeOfSprite = sizeof(VertexPos) * 6;
const int maxLetters = 24;
vertexDesc.ByteWidth = sizeOfSprite * maxLetters;
d3dResult = d3dDevice_->CreateBuffer(&vertexDesc, NULL, &vertexBuffer_); //子资源参数设置为NULL 我们对顶点缓存的内容进行动态更新 从而创建时我们不需要设置任何数据
if (FAILED(d3dResult)) {
DXTRACE_MSG("FAILED to create vertex buffer");
return false;
}
return true;
}
这里经过了一些修改:
(1) 设置使用标识为D3D11_USAGE_DYNAMIC 来允许我们的缓存能够通过CPU来动态更新
(2) 设置CPU访问标识为D3D11_CPU_ACCESS_WRITE 为了让CPU能够对GPU中的资源进行持续访问
(3) CreateBuffer的时候,我们将子资源参数设置为NULL
DrawString函数的设置:
sizeOfSprite----以字节为单位单个精灵的尺寸。一个精灵是由6个顶点组成的两个三角形
maxLetters----最多渲染几个
length----我们需要渲染的字符串的长度
charWidth----单个字母在屏幕上的宽度
charHeight----单个字母在屏幕上的高度
texelWidth----单个字母在纹理图像中的宽度
verticesPreLetter-----在每个精灵中存储所有顶点的常量
函数设置如下:
与使用循环索引来计算顶点位置不同的是 我们使用字母本身来产生贴图的坐标
这可以通过使用每个字母的ASCII码来计算出
bool D3DTextDemo::DrawString(char* message, float startX, float startY) {
const int sizeOfSprite = sizeof(VertexPos) * 6; //以字节为单个精灵的尺寸 一个精灵是由6个顶点组成的两个三角形
const int maxLetters = 24; //一次能够简单渲染24个字母
int length = strlen(message); //我们需要渲染的字符串长度
//如果字符长度过长 削减
if (length > maxLetters)
length = maxLetters;
//单个字母在屏幕上的宽度
float charWidth = 32.0f / 800.0f;
//单个字母在屏幕上的高度
float charHeight = 32.0f / 640.0f;
//单个字母在纹理图像中的高度
float texelWidth = 32.0f / 864.0f;
//在每个精灵中存储所有的顶点的常量
const int verticesPerLetter = 6;
//之后更新动态缓存中的内容
D3D11_MAPPED_SUBRESOURCE mapResource;
HRESULT d3dResult = d3dContext_->Map(vertexBuffer_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapResource); //通过MAP来访问缓存中的对象 通过maoResource来进行访问
if (FAILED(d3dResult)) {
DXTRACE_MSG("Failed to map resource");
return false;
}
VertexPos* sprintfPtr = (VertexPos*)mapResource.pData; //获得一个指针指向子资源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; //X的结束位置
float thisEndY = startY + charHeight; //高度设置
sprintfPtr[0].pos = XMFLOAT3(thisEndX, thisEndY, 1.0f);
sprintfPtr[1].pos = XMFLOAT3(thisEndX, startY, 1.0f);
sprintfPtr[2].pos = XMFLOAT3(thisStartX, startY, 1.0f);
sprintfPtr[3].pos = XMFLOAT3(thisStartX, startY, 1.0f);
sprintfPtr[4].pos = XMFLOAT3(thisStartX, thisEndY, 1.0f);
sprintfPtr[5].pos = XMFLOAT3(thisEndX, thisEndY, 1.0f);
int texLookup = 0; //表示第一个字母(A)然后依次递增
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 {
texLookup = (letter - indexA);
}
float tuStart = 0.0f + (texelWidth * static_cast(texLookup));
float tuEnd = tuStart + texelWidth;
sprintfPtr[0].tex0 = XMFLOAT2(tuEnd, 0.0f);
sprintfPtr[1].tex0 = XMFLOAT2(tuEnd, 1.0f);
sprintfPtr[2].tex0 = XMFLOAT2(tuStart, 1.0f);
sprintfPtr[3].tex0 = XMFLOAT2(tuStart, 1.0f);
sprintfPtr[4].tex0 = XMFLOAT2(tuStart, 0.0f);
sprintfPtr[5].tex0 = XMFLOAT2(tuEnd, 0.0f);
sprintfPtr += 6;
}
d3dContext_->Unmap(vertexBuffer_, 0);
d3dContext_->Draw(6 * length, 0);
return true;
}
怎样读取贴图并将贴图中的 文字进行渲染?
(1)首先在d3dContext_中导入贴图
d3dContext_->IASetInputLayout(inputLayout_);
d3dContext_->IASetVertexBuffers(0, 1, &vertexBuffer_, &stride, &offset);
d3dContext_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
d3dContext_->VSSetShader(solidColorVS_, 0, 0);
d3dContext_->PSSetShader(solidColorPS_, 0, 0);
d3dContext_->PSSetShaderResources(0, 1, &colorMapForTextA_); //在这里导入贴图
d3dContext_->PSSetSamplers(0, 1, &colorMapSampler_);
(2)然后通过DrawString中的Map函数
HRESULT d3dResult = d3dContext_->Map(vertexBuffer_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapResource); //通过MAP来访问缓存中的对象 通过maoResource来进行访问
//mapResource会访问资源中的内容
之后我们获得一个指针指向子资源的pData 就可以循环设置需要组装的字符串的几何图案了
我们使用一个for-loop的迭代器来遍历字符串
texLookup的作用:
X的开始位置是精灵的左边 X的结束位置是精灵的右边 这相当于将X的开始位置加上每一个字母的宽度
贴图坐标的设置:将当前字母的ASCII码与A的ASCII码作差 如果当前字母是A得到0 B则得到1
循环索引i以0开始依次递增
而texLookup 变量用0表示并依次递增
texLookup = (letter - indexA);
letter是char类型的字符 这样可以推出字符的ASCII码来判断这是哪一个字符关于透明度的设置:
混合描述对象:
ID3D11BlendState* alphaBlendState_;
然后设置alpha通道:
D3D11_BLEND_DESC blendState;
ZeroMemory(&blendState, sizeof(blendState));
blendState.RenderTarget[0].BlendEnable = TRUE;
blendState.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
blendState.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
blendState.RenderTarget[0].DestBlend = D3D11_BLEND_ONE;
blendState.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
blendState.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO;
blendState.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
blendState.RenderTarget[0].RenderTargetWriteMask = 0x0F;
float blendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
d3dDevice_->CreateBlendState(&blendState, &alphaBlendState_);
d3dContext_->OMSetBlendState(alphaBlendState_, blendFactor, 0xFFFFFFFF);