这节教程的结构如下:
一,什么是广告板(Billboarding)?
首先在介绍
广告板(Billboarding)前说一下这种情况,假设在一个3D游戏中有一大片森林,每个树都是一个3D模型,一棵树的面数也许不大,但是你想想一座森林有几千甚至几万棵树,加起来的面数如此之多以至于能让你的显卡爆炸。这时候,广告板的技术诞生了,我们可以将做出一张四方形的纹理贴图,上面有一颗树,假设那张树的纹理贴图离相机比较远,在游戏中远远的看去,你无法分辨到底那棵树到底是3D模型还是一张树纹理,但是你一走近的话,也许会暴露这个缺点,这时假设树或者树纹理贴图与Y轴平行,树纹理贴图的中心点跟相机在XZ平面垂直,可以大大降低被玩家发现是假3D的几率
如图:
如图,假设树的纹理贴图的中心点在XZ面的投影为
(Xc,0,Zc), 相机的位置在XZ面的投影点为
(Xe,0,Ze),
那么向量W=(Xc-Xe,Zc-Ze)垂直于向量u(向量u为四方形的树的纹理图在XZ面投影的线段)
下面上我画的图:
刚开始时位置是这样的:
后面相机向X轴正方向平移是这样的:
Θ=arctan(AE/AC);
bool GraphicsClass::Render()
{
//三个变换矩阵
XMMATRIX WorldMatrix, ViewMatrix, ProjMatrix;
XMFLOAT3 CameraPostion, BillBoardModelPostion;
double angle; //广告板旋转角度(围绕Y轴)
float rotation; //旋转量
bool result;
//第一,清除缓存开始绘制场景
mD3D->BeginScene(0.3f, 0.4f, 0.0f, 1.0f);
//第二,生成ViewMatrix(根据CameraClass的mPostion和mRotation来生成的)
mCamera->Render();
//第三,获取三个变换矩阵(WorldMatrix和ProjMatrix来自mD3D类,ViewMatrix来自CameraClass)
WorldMatrix = mD3D->GetWorldMatrix();
ProjMatrix = mD3D->GetProjMatrix();
ViewMatrix = mCamera->GetViewMatrix();
//第四,把地面的顶点数据和索引数据放入3D渲染流水线
mFloorModel->Render(mD3D->GetDeviceContext());
//第五,用ColorShader对底面进绘制
result = mColorShader->Render(mD3D->GetDeviceContext(),mFloorModel->GetIndexCount(), WorldMatrix, ViewMatrix, ProjMatrix,mFloorModel->GetTexture(),mLight->GetDiffuseColor(),mLight->GetLightDirection());
if (!result)
{
MessageBox(NULL, L"mFloorModel Render failure", NULL, MB_OK);
return false;
}
//第六,获取相机的位置
XMStoreFloat3(&CameraPostion, mCamera->GetPostion());
//第七,设置广告板的位置
BillBoardModelPostion = XMFLOAT3(0.0f, 1.5f, 0.0f);
//第八,计算广告板旋转角度,由于广告板围绕Y轴旋转,则广告板旋转时其上面的顶点Y坐标不变,X和Z坐标改变 用ant2计算
angle = atan2(BillBoardModelPostion.x - CameraPostion.x, BillBoardModelPostion.z - CameraPostion.z);
//将角度转换为弧度,乘以2*PI/360 XMMatrixRotationY的参数为角度而不是弧度
//rotation = (float)(angle* 0.0174532925f);
//第九,求出旋转矩阵和移动矩阵,并求出最终的世界矩阵,负角度代表逆时针旋转,正角度代表顺时针旋转
WorldMatrix = WorldMatrix*XMMatrixRotationY(angle)*XMMatrixTranslation(BillBoardModelPostion.x, BillBoardModelPostion.y, BillBoardModelPostion.z);
//第十,把广告板模型的顶点数据和索引数据放入3D渲染流水线
mBillBoardingModel->Render(mD3D->GetDeviceContext());
//第十一,用ColorShader进行渲染
result = mColorShader->Render(mD3D->GetDeviceContext(), mBillBoardingModel->GetIndexCount(), WorldMatrix, ViewMatrix, ProjMatrix, mBillBoardingModel->GetTexture(), mLight->GetDiffuseColor(), mLight->GetLightDirection());
if (!result)
{
MessageBox(NULL, L"mBillBoardingModel Render failure", NULL, MB_OK);
return false;
}
//把渲染的场景呈献给屏幕
mD3D->EndScene();
return true;
}
还有一点注意的是XNAMATH库的XMMatrixRotationY的参数是弧度。
D3DXMATRIXRotationY的参数也为弧度
程序运行图
(1)当相机在X轴中间位置时
(2)当相机往X轴负方向运动时,
(3)当相机往X轴右边运动时
这里我在PixelShader里用
clip 函数剔除了树纹理中黑色的像素
Shader代码:
Texture2D ShaderTexture:register(t0); //纹理资源
SamplerState SampleType:register(s0); //采样方式
//VertexShader
cbuffer CBMatrix:register(b0)
{
matrix World;
matrix View;
matrix Proj;
matrix WorldInvTranspose;
};
cbuffer CBLight:register(b1)
{
float4 DiffuseColor;
float3 LightDirection;
float pad;
}
struct VertexIn
{
float3 Pos:POSITION;
float2 Tex:TEXCOORD0; //多重纹理可以用其它数字
float3 Normal:NORMAL;
};
struct VertexOut
{
float4 Pos:SV_POSITION;
float2 Tex:TEXCOORD0;
float3 W_Normal:NORMAL; //世界空间的法线
};
VertexOut VS(VertexIn ina)
{
VertexOut outa;
outa.Pos = mul(float4(ina.Pos,1.0f), World);
outa.Pos = mul(outa.Pos, View);
outa.Pos = mul(outa.Pos, Proj);
outa.W_Normal = mul(ina.Normal, (float3x3)WorldInvTranspose); //此事世界逆转置矩阵的第四行本来就没啥用
outa.W_Normal = normalize(outa.W_Normal);
outa.Tex= ina.Tex;
return outa;
}
float4 PS(VertexOut outa) : SV_Target
{
float4 TexColor; //采集的纹理颜色
float LightFactor; //灯光因子
float4 color = {0.0f,0.0f,0.0f,0.0f}; //最终输出的颜色
//第一,获取采样颜色
TexColor = ShaderTexture.Sample(SampleType, outa.Tex);
color = TexColor;
clip(color.a - 0.1); //剔除接近于黑色的像素
//第二,求出灯光因子
float3 InvLightDir = -LightDirection;
LightFactor = saturate(dot(InvLightDir,outa.W_Normal));
//第三,求出灯光照射颜色
if (LightFactor>0)
{
color += LightFactor*DiffuseColor; //saturate(float1*float4)
}
color = saturate(color);
//第四,用灯光颜色调节纹理颜色
color = color*TexColor;
return color;
}
这里明显看到随着观察角度不同,树始终面向我们的视角,树的纹理图就像真的3D树模型一样欺骗了我们的眼镜,当然在游戏中一片森林还是提倡几十棵3D模型棵树和几千棵假树一起配合效果会更好。
下面我的源代码链接如下: