光晕效果实现和3D流水线的思考
最近在看阿哲的《3D地形制作全攻略》,写得很好,但是好像网上找不到源码,本来想加QQ请教一下,不过貌似被拒绝了。。。
按照书上的方法想做一个光晕效果,看着其实还是不难,但是动手实现的时候还是遇到了麻烦,特别是进行世界坐标->视角转换->投影转换->视口转换的时候,理解起来还是费了一番功夫。
就此总结一下。
首先说一下光晕实现的思路,就是在世界坐标系中确定太阳的位置,然后通过视角转换->投影转换->视口转换,变换到屏幕坐标,结算其与屏幕中心的连线,在这条直线上面贴上光晕的贴图,光晕的alpha值由太阳(屏幕坐标)到屏幕边界的距离决定,开启alpha融合渲染。
基本思想并不难理解,难点在于蕴含在视角转换->投影转换->视口转换中的3D数学知识。
举例说明:
视角矩阵形如:
-1,0,0,0
0,1,0,0
0,0,-1,0
0,-3347,300,1
视角变换是将摄像机变换到世界坐标系的原点,并将其旋转使摄像机的光轴与世界坐标系z轴方向一致。同时,世界空间中的所以几何体都随着摄像机一同进行变换,以保证摄像机的视场恒定。
投影矩阵:
1.81,0,0,0
0,2.4,0,0
0,0,1,1//注意这一行的最后一个1
0,0,-1,0
投影变换(透视投影),投影变换后,所以的点都被投影到X[-1,1]、Y[-1,1],Z[0,1]的范围。
投影变换定义了视域体,并负责将视域体中的几何体投影到投影窗口中。
视口矩阵:
m_ScreenWidth/2, 0, 0, 0,
0, -m_ScreenHeight/2, 0, 0,
0, 0, 1, 0,
m_ScreenWidth/2, m_ScreenHeight/2, 0, 1
视口变换的任务是将顶点坐标从投影窗口转换到屏幕的一个矩形区域。
下面我们把转换之前的点定为(0,3700,-2000,1)(x,y,z,w)
依次进行视角变换->投影变换
- 视角变换后,w的值依然为1
- 然后进行投影变换,
可以注意到投影变换后w的值由视角变换的结果的z分量决定,即w=z(视角变换后的z)*1;而这个z的几何意义又是什么呢?我们知道,视角变换后摄像机被移到坐标系原点,镜头变换为面朝z的正方向,这时z如果为负值,不就是说明这个点在摄像机的后面不可见吗?
下面继续进行变换,上面说到投影变换后w的值由视角变换的结果的z分量决定,也就是说它跟z的符号相同,如果z为负,w也为负,说明是从背面投影过来,w为正是从正面投影到投影平面。
在光晕的实现中,太阳可能会从摄像机背面投影在z=1的平面上,这时w为负,这时候是不应该渲染光晕的。需要剪裁掉,所以渲染的条件为w>=0.0f
下面给出计算太阳屏幕坐标的代码和光晕渲染代码:
计算太阳屏幕坐标:
void CHaloManager::ComputeSunInScreen()
{
D3DXMATRIX matConcat, matViewport;//视口变化矩阵
D3DXMATRIX view,proj;
D3DXVECTOR4 vResult;
matViewport = D3DXMATRIX(
m_ScreenWidth/2, 0, 0, 0,
0, -m_ScreenHeight/2, 0, 0,
0, 0, 1, 0,
m_ScreenWidth/2, m_ScreenHeight/2, 0, 1);
HRESULT hr;
hr=m_Device->GetTransform(D3DTS_VIEW,&view);
if(FAILED(hr))
MessageBox(0,"视角矩阵获取出错","",MB_OK);
hr=m_Device->GetTransform(D3DTS_PROJECTION,&proj);
if(FAILED(hr))
MessageBox(0,"投影矩阵获取出错","",MB_OK);
D3DXMatrixIdentity(&matConcat);
matConcat*=view;//视角
matConcat*=proj;//投影
matConcat*=matViewport;//视口
D3DXVec3Transform(&vResult,&m_sunpos,&matConcat);
m_x=vResult.x/vResult.w;//太阳屏幕坐标x
m_y=vResult.y/vResult.w;//太阳屏幕坐标y
m_w=vResult.w;
}
渲染光晕
void CHaloManager::Render()
{
ComputeSunInScreen();
if(m_w>=0.0f)//渲染条件为m_w>=0.0f,即太阳是从正面投影
{
int iAwayX;
if(m_x<0)
{
iAwayX=-m_x;
}
else if(m_x>m_ScreenWidth)
{
iAwayX=m_x-m_ScreenWidth;
}
else
{
iAwayX=0;
}
int iAwayY;
if(m_y<0)
{
iAwayY=-m_y;
}
else if(m_y>m_ScreenHeight)
{
iAwayY=m_y-m_ScreenHeight;
}
else
{
iAwayY=0;
}
float fAway=float((iAwayX>iAwayY)?iAwayX:iAwayY);
if(fAway>m_border)return;//m_border为边界距离
float RealIntensity=1.0f-(fAway/m_border);//亮度
int CenterofScreenX=m_ScreenWidth/2;
int CenterofScreenY=m_ScreenHeight/2;
int dx=CenterofScreenX-m_x;
int dy=CenterofScreenX-m_y;
//计算顶点位置,颜色,以及光晕的大小
stSCRVertex* v;
m_vertexbuffer->Lock( 0, (int)m_halolist.size()*6*sizeof(stSCRVertex), (void**)&v, 0);
for(int i=0;i<m_halolist.size();i++)
{
int CenterofHaloX=CenterofScreenX-(float)(dx*m_halolist[i].GetPos());
int CenterofHaloY=CenterofScreenY-(float)(dy*m_halolist[i].GetPos());
int HaloSize=m_ScreenWidth*m_halolist[i].GetSize()/2;
//更新光晕的亮度
D3DXCOLOR color = m_halolist[i].GetColor();
color.a *= RealIntensity;
if (color.a > 1.0f) color.a = 1.0f;
if (color.a < 0.0f) color.a = 0.0f;
v->_color=color;
v->_rhw=1.0f;
v->_x=(float)(CenterofHaloX-HaloSize);
v->_y=(float)(CenterofHaloY-HaloSize);
v->_z=0.0f;
v->_tu=0.0f;
v->_tv=0.0f;
v++;
v->_color=color;
v->_rhw=1.0f;
v->_x=(float)(CenterofHaloX+HaloSize);
v->_y=(float)(CenterofHaloY-HaloSize);
v->_z=0.0f;
v->_tu=1.0f;
v->_tv=0.0f;
v++;
v->_color=color;
v->_rhw=1.0f;
v->_x=(float)(CenterofHaloX+HaloSize);
v->_y=(float)(CenterofHaloY+HaloSize);
v->_z=0.0f;
v->_tu=1.0f;
v->_tv=1.0f;
v++;
v->_color=color;
v->_rhw=1.0f;
v->_x=(float)(CenterofHaloX-HaloSize);
v->_y=(float)(CenterofHaloY-HaloSize);
v->_z=0.0f;
v->_tu=0.0f;
v->_tv=0.0f;
v++;
v->_color=color;
v->_rhw=1.0f;
v->_x=(float)(CenterofHaloX+HaloSize);
v->_y=(float)(CenterofHaloY+HaloSize);
v->_z=0.0f;
v->_tu=1.0f;
v->_tv=1.0f;
v++;
v->_color=color;
v->_rhw=1.0f;
v->_x=(float)(CenterofHaloX-HaloSize);
v->_y=(float)(CenterofHaloY+HaloSize);
v->_z=0.0f;
v->_tu=0.0f;
v->_tv=1.0f;
v++;
}
m_vertexbuffer->Unlock();
//设置融合
m_Device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
m_Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
m_Device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
m_Device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
m_Device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
m_Device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
m_Device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
m_Device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
//m_Device->SetMaterial(&m_mtrl);//这句好像没有什么用
m_Device->SetFVF(D3DFVF_HALO);
m_Device->SetStreamSource(0, m_vertexbuffer, 0, sizeof(stSCRVertex));
for (int n=0; n < m_halolist.size(); n++) {
m_Device->SetTexture(0, m_halolist[n].GetTexture());
m_Device->DrawPrimitive(D3DPT_TRIANGLELIST, n*6, 2);
}
m_Device->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE);
}
}
吃了今天的苦头,我知道了想要走得更远,基础很重要呀。真的有必要软件实现一次3D流水线。