ZFXEngine开发笔记之Bump Mapping(2)

51 篇文章 7 订阅
32 篇文章 1 订阅

作者:i_dovelemon

日期: 2014 / 9 / 13

来源 : CSDN

主题 :Bump Mapping, Tangent Space, Normal Map, Height Map


引言

           在上篇文章中,我讲述了如何根据高度图来创建法线图。并且承诺在后面会讲述3D几何学中的重要工具Tangent Space的相关知识。今天,就在这里,向大家讲述如何接下来的工作部分。


Tangent Space

           我们知道,在前篇文章中,讲述的法线图中的法线是在纹理图的空间中也就是Tangent Space中。当我们进行光照计算的时候,我们需要将光的照射向量和像素的法线放在一个空间中,从而进行光照计算。一般来说光源的照射向量是在世界空间中的,而法线是在Tangent Space中的。如果我们在进行光照计算的时候,将每一个像素的法线从Tangent Space中变换到世界坐标空间的话,对于所有的像素都要进行这样的处理,但是如果我们对光照向量进行这样的操作的话,只需要在Vertex Shader中执行一次就可以了。所以,我们的做法自然就是想办法将光源的照射向量转换到与像素的法线相同的Tangent Space中去。

            进行坐标空间变换是3D图形学的基本知识。如果读者不了解的话,请看我的博客中的3D坐标变换。为了能够进行坐标变换,我们需要像素的法线的坐标空间Tangent Space的基坐标在模型坐标空间的表示,这样,我们就能够很容易的将模型坐标空间中的向量变换到Tangent Space中。

            那么,如何创建Tangent Space的三个基向量了?

            Tangent Space中的x轴和Bump Map的的u轴方向对齐,而y轴和Bump Map的v轴方向对齐。也就是说,如果Q点表示的是三角形中的一个点,那么我们就能够使用如下的方程来表示:

            Q - P0 = (u - u0) T + (v - v0)B,(公式1)

             这里的T和B就是和Bump Map对齐的向量。P0是三角形的一个顶点,(u0,v0)表示的是P0顶点的纹理坐标。而后面的B表示的是bitangent,有些书本上也称之为binormal。

             假设我们有一个三角形,它的三个顶点分别为P0, P1, P2,他们对应的纹理坐标分别为(u0,v0),(u1,v1),(u2,v2)。为了方便起见,我们这里将P0做为参考点。所以,我们假设:

           Q1 = P1 - P0

           Q2 = P2 - P0

并且

           (s1, t1) = (u1 - u0, v1 - v0)

           (s2, t2) = (u2 - u0, v2 - v0)

           由于上面的公式1表示的是一个三角形中所有点的关系,那么对于点P1和P2同样也能够满足这个函数,也就是说:

           Q1 = s1T + t1B

           Q2 = s2T + t2B

            我们使用矩阵来解上面的方程,得到如下的矩阵方程:

           

              两边同时乘以(s,t)矩阵的逆矩阵,得到如下的矩阵方程:

              (公式2)

              好了,从上面的矩阵方程中,我们就可以得到两个向量T和B了。

              在有了T,B,N向量之后,我们就可以使用如下的矩阵,将Tangent Space中的坐标变换到模型的坐标空间中去了:

             

             上面的矩阵,是将Tangent Space中的坐标变换到模型坐标空间中去的,但是在这里我们需要的是将模型坐标空间中的坐标变换到Tangent Space坐标空间中去的。所以我们需要上面矩阵的逆矩阵。(实际上这是不精确的,因为我们在上面使用的N向量是顶点的Normal向量方向,这个向量和实际的Tangent Space中的N向量有细微的差别,但是可以忽略不计。)由于这三个向量相互垂直,那么这个矩阵就是正交矩阵,正交矩阵的逆矩阵等于它的转置矩阵,所以如下所示为最终的将模型坐标空间中的坐标变换到Tangent Space空间中的矩阵:

             


计算Tangent Space

                 下面的两个函数用于计算顶点的Tangent Space坐标向量中的Tangent Vector。

<span style="font-family:Microsoft YaHei;">void CalcTangent(ZFXTVERTEX* v)
{
    for(int i = 0 ; i < 8 ; i ++)
    {
        CalcTangent(&v[i*3],&v[i*3+1],&v[i*3+2]);
    }
}// end for CalcTangent

void  CalcTangent(ZFXTVERTEX *t1, ZFXTVERTEX *t2, ZFXTVERTEX *t3)
{
    ZFXVector vc, vcA, vcB ;

    float fu21 = t2->tu - t1->tu ,
        fv21 = t2->tv - t1->tv ,
        fu31 = t3->tu - t1->tu ,
        fv31 = t3->tv - t1->tv ;

    //x-component of tangent vector
    vcA.set(t2->x - t1->x, fu21, fv21);
    vcB.set(t3->x - t1->x, fu31, fv31);
    vc.cross(vcA, vcB);
    if(fabs(vc.x) > 0.0001f)
    {
        t1->vcU[0] = -vc.y / vc.x ;
    }

    //y-component of tangent vector
    vcA.set(t2->y - t1->y, fu21, fv21);
    vcB.set(t3->y - t1->y, fu31, fv31);
    vc.cross(vcA, vcB);
    if(fabs(vc.x) > 0.0001f)
    {
        t1->vcU[1] = -vc.y / vc.x ;
    }

    //z-component of tangent vector
    vcA.set(t2->z - t1->z, fu21, fv21);
    vcB.set(t3->z - t1->z, fu31, fv31);
    vc.cross(vcA, vcB);
    if(fabs(vc.x) > 0.0001f)
    {
        t1->vcU[2] = -vc.y / vc.x ;
    }

    ZFXVector normalV(t1->vcU[0],t1->vcU[1],t1->vcU[2]);
    normalV.normalize();

    t2->vcU[0] = t3->vcU[0] = t1->vcU[0] = normalV.x;
    t2->vcU[1] = t3->vcU[1] = t1->vcU[1] = normalV.y;
    t2->vcU[2] = t3->vcU[2] = t1->vcU[2] = normalV.z;
}// end for CalcTangent</span>

               公式完全使用的是在上面结论公式2来进行计算,只要将cross展开,你就可以明白。在函数中,我仅仅计算了T向量,而没有和上面理论那样计算T和B向量了。因为,我将要使用顶点的法线方向做为Tangent Space的N向量,所以在有了T和N之后,只要进行叉积运算就能够得到B向量了。这个决策,也反应在我定义的顶点结构中,如下所示:
<span style="font-family:Microsoft YaHei;">/**
* Define the Tangent Vertex format
*/
typedef struct ZFXTVERTEX_TYPE
{
	float x, y, z ;
	float vcN[3] ;
	float tu, tv ;
	float vcU[3] ;    // Tangent vector
}ZFXTVERTEX;
</span>
             函数很简单,只要按照公式,照搬照抄就可以,就不用多说了。


程序实例

            既然我们知道了如何计算Tangent Space了,那么我们就用此种方法来构建一个Room的模型,一下是Room模型的创建代码:

<span style="font-family:Microsoft YaHei;">HRESULT CreateRoom(ZFXTVERTEX* v, WORD* index)
{
	//Create a Wall
	ZFXVector normal ;

	//Triangle 1
	v[0].x = -100 ; v[0].y = 100 ; v[0].z = 100 ;
	v[0].vcN[0] = 0 ; v[0].vcN[1] = 0 ; v[0].vcN[2] = 1 ;
	v[0].tu = 0.0f ; v[0].tv = 0.0f ;

	v[1].x = 100 ; v[1].y = 100 ; v[1].z = 100 ;
	v[1].vcN[0] = 0 ; v[1].vcN[1] = 0 ; v[1].vcN[2] = 1 ;
	v[1].tu = 1.0f ; v[1].tv = 0.0f ;

	v[2].x = 100 ; v[2].y = -100 ; v[2].z = 100 ;
	v[2].vcN[0] = 0 ; v[2].vcN[1] = 0 ; v[2].vcN[2] = 1 ;
	v[2].tu = 1.0f ; v[2].tv = 1.0f ;

	//Triangle 2
	v[3].x = -100 ; v[3].y = 100 ; v[3].z = 100 ;
	v[3].vcN[0] = 0 ; v[3].vcN[1] = 0 ; v[3].vcN[2] = 1 ;
	v[3].tu = 0.0f ; v[3].tv = 0.0f ;

	v[4].x = 100 ; v[4].y = -100 ; v[4].z = 100 ;
	v[4].vcN[0] = 0 ; v[4].vcN[1] = 0 ; v[4].vcN[2] = 1 ;
	v[4].tu = 1.0f ; v[4].tv = 1.0f ;

	v[5].x = -100 ; v[5].y = -100 ; v[5].z = 100 ;
	v[5].vcN[0] = 0 ; v[5].vcN[1] = 0 ; v[5].vcN[2] = 0 ;
	v[5].tu = 0.0f ; v[5].tv = 1.0f ;

	//Triangle 3
	v[6].x = -100 ; v[6].y = -100 ; v[6].z = 100 ;
	v[6].vcN[0] = 0 ; v[6].vcN[1] = 1 ; v[6].vcN[2] = 0 ;
	v[6].tu = 0.0f; v[6].tv = 0.0f ;

	v[7].x = 100 ; v[7].y = -100 ; v[7].z = 100 ;
	v[7].vcN[0] = 0 ; v[7].vcN[1] = 1 ; v[7].vcN[2] = 0 ;
	v[7].tu = 1.0f ; v[7].tv = 0.0f ;

	v[8].x = 100 ; v[8].y = -100 ; v[8].z = -100 ;
	v[8].vcN[0] = 0 ; v[8].vcN[1] = 1 ; v[8].vcN[2] = 0 ;
	v[8].tu = 1.0f ; v[8].tv = 1.0f ;

	//Triangle 4
	v[9].x = -100 ; v[9].y = -100 ; v[9].z = 100 ;
	v[9].vcN[0] = 0 ; v[9].vcN[1] = 1 ; v[9].vcN[2] = 0 ;
	v[9].tu = 0.0f; v[9].tv = 0.0f ;

	v[10].x = 100 ; v[10].y = -100 ; v[10].z = -100 ;
	v[10].vcN[0] = 0 ; v[10].vcN[1] = 1 ; v[10].vcN[2] = 0 ;
	v[10].tu = 1.0f ; v[10].tv = 1.0f ;

	v[11].x = -100 ; v[11].y = -100 ; v[11].z = -100 ;
	v[11].vcN[0] = 0 ; v[11].vcN[1] = 1 ; v[11].vcN[2] = 0 ;
	v[11].tu = 0.0f ; v[11].tv = 1.0f ;

	//Triangle 5
	v[12].x = -100 ; v[12].y = 100 ; v[12].z = -100 ;
	v[12].vcN[0] = 1 ; v[12].vcN[1] = 0 ; v[12].vcN[2] = 0;
	v[12].tu = 0.0f ; v[12].tv = 0.0f ;

	v[13].x = -100 ; v[13].y = 100 ; v[13].z = 100 ;
	v[13].vcN[0] = 1 ; v[13].vcN[1] = 0 ; v[13].vcN[2] = 0 ;
	v[13].tu = 1.0f; v[13].tv = 0.0f ;

	v[14].x = -100 ; v[14].y = -100 ; v[14].z = 100 ;
	v[14].vcN[0] = 1 ; v[14].vcN[1] = 0 ; v[14].vcN[2] = 0 ;
	v[14].tu = 1.0f; v[14].tv = 1.0f ;

	//Triangle 6
	v[15].x = -100 ; v[15].y = 100 ; v[15].z = -100 ;
	v[15].vcN[0] = 1 ; v[15].vcN[1] = 0 ; v[15].vcN[2] = 0;
	v[15].tu = 0.0f ; v[15].tv = 0.0f ;

	v[16].x = -100 ; v[16].y = -100 ; v[16].z = 100 ;
	v[16].vcN[0] = 1 ; v[16].vcN[1] = 0 ; v[16].vcN[2] = 0 ;
	v[16].tu = 1.0f; v[16].tv = 1.0f ;

	v[17].x = -100 ; v[17].y = -100 ; v[17].z = -100 ;
	v[17].vcN[0] = 1 ; v[17].vcN[1] = 0 ; v[17].vcN[2] = 0 ;
	v[17].tu = 0.0f ; v[17].tv = 1.0f ;

	//Triangle 7
	v[18].x = 100 ; v[18].y = 100 ; v[18].z = 100 ;
	v[18].vcN[0] = -1 ; v[18].vcN[1] = 0 ; v[18].vcN[2] = 0 ;
	v[18].tu = 0.0f; v[18].tv = 0.0f ;

	v[19].x = 100 ; v[19].y = 100; v[19].z = -100 ;
	v[19].vcN[0] = -1 ; v[19].vcN[1] = 0 ; v[19].vcN[2] = 0;
	v[19].tu = 1.0f ;  v[19].tv = 0.0f ;

	v[20].x = 100 ; v[20].y = -100 ; v[20].z = -100 ;
	v[20].vcN[0] = -1 ; v[20].vcN[1] = 0 ; v[20].vcN[2] = 0 ;
	v[20].tu = 1.0f; v[20].tv = 1.0f;

	//Triangle 8
	v[21].x = 100 ; v[21].y = 100 ; v[21].z = 100 ;
	v[21].vcN[0] = -1 ; v[21].vcN[1] = 0 ; v[21].vcN[2] = 0 ;
	v[21].tu = 0.0f; v[21].tv = 0.0f ;

	v[22].x = 100 ; v[22].y = -100 ; v[22].z = -100 ;
	v[22].vcN[0] = -1 ; v[22].vcN[1] = 0 ; v[22].vcN[2] = 0 ;
	v[22].tu = 1.0f; v[22].tv = 1.0f;

	v[23].x = 100 ; v[23].y = -100 ; v[23].z  = 100 ;
	v[23].vcN[0] = -1 ; v[23].vcN[1] = 0 ; v[23].vcN[2] = 0 ;
	v[23].tu = 0.0f ; v[23].tv = 1.0f ;

	//Calculate the tangent vector
	CalcTangent(v);

	for(int i = 0  ; i < 24 ; i ++)
		index[i] = i ;

	return 0;
}//end for CreateRoom</span>

                在构建好了24个顶点数据之后,我们就对数组进行Tangent Space中的T向量的计算,并保存起来。

                好了,计算好了之后,我们通过如下的函数来进行渲染:

<span style="font-family:Microsoft YaHei;">void RenderOmniPass(UINT bID, UINT vsID, UINT psID, UINT nLight)
{
	ZFXMatrix mAttenu;
	static float n = 0.0f ;
	ZFXVector pos_1(100.0f, -100.0f, 0.0f);
	ZFXVector pos_2(-100.0f, 100.0f, 0.0f);
	ZFXVector pos ;

	if(nLight == 1)
		pos =  pos_1 ;
	else
		pos = pos_2 ;

	ZFXMatrix mRotate;
	mRotate.identity();
	mRotate.rotateY(n);
	n += 0.01f;
	pos = pos * mRotate ;

	//Set the help vector to c30
	ZFXVector vHelp(0.5f, 0.5f, 0.5f);
	if(FAILED(g_pDevice->setShaderConstant(SHT_VERTEX,
		DT_FLOAT, 30, 1, &vHelp)))
		return ;

	//Set the light color to c0
	ZFXCOLOR lightColor = {1.0f, 1.0f, 1.0f, 1.0f};
	if(FAILED(g_pDevice->setShaderConstant(SHT_PIXEL,
		DT_FLOAT, 0, 1, &lightColor)))
		return ;

	if(FAILED(g_pDevice->activateVShader(vsID, VID_UT)))
		return ;

	if(FAILED(g_pDevice->activatePShader(psID)))
		return ;

	ZFXMatrix mW;
	mW.identity();
	g_pDevice->setWorldTransform(&mW);

	//Set the light position to c25
	ZFXMatrix invM;
	invM.inverseOf(mW);
	pos = pos * invM ;
	if(FAILED(g_pDevice->setShaderConstant(SHT_VERTEX, DT_FLOAT, 25, 1, &pos)))
		return ;

	//Set the world_transform to c31
	ZFXMatrix transM ;
	transM.transposeOf(mW);
	if(FAILED(g_pDevice->setShaderConstant(SHT_VERTEX,
		DT_FLOAT, 31, 4, &transM)))
		return ;

	g_pDevice->useAdditiveBlending(true);

	if(FAILED(g_pDevice->getVertexCacheManager()->render(bID)))
		return ;

	g_pDevice->useAdditiveBlending(false);
}// end for RenderOmniPass</span>

                 上面的函数没有什么太麻烦的地方。唯一需要注意的是,我们需要将光源变换到模型的坐标空间中去,因为我们要在模型的坐标空间中对光照进行光照计算。下面是这个实例程序的Vertex Shader和Pixel Shader:
<span style="font-family:Microsoft YaHei;">vs.1.1                 
dcl_position  v0
dcl_normal    v3
dcl_texcoord  v7
dcl_tangent   v8
m4x4 oPos, v0, c0  ; transform position

mov r5, v8
mov r7, v3

; calculate binormal V as
; crossproduct of U and N
mul r0,r5.zxyw, -r7.yzxw;
mad r6,r5.yzxw, -r7.zxyw,-r0;

; build vector to_light
sub r2, c25, v0

; transform to_light vector
dp3 r8.x, r5.xyz, r2
dp3 r8.y, r6.xyz, r2
dp3 r8.z, r7.xyz, r2

; normalize transformed to_light vector
dp3 r8.w, r8, r8
rsq r8.w, r8.w
mul r8, r8, r8.w

; *.5 + .5
mad oD0.xyz, r8.xyz, c30.x, c30.x   

mov oT0.xy, v7.xy
mov oT1.xy, v7.xy
</span>

<span style="font-family:Microsoft YaHei;">ps.1.1                 
tex t0 
tex t1                
dp3 r1, t1_bx2, v0_bx2      
mul r0, c0, t0
mul r0, r1, r0
</span>

              我这里使用的是Assembly Shader,如果看不懂的话也没有关系,有了上面关于Tangent Space的计算,读者应该能够完全使用HLSL来进行光照计算了。


最终的程序截图如下所示:


             好了,今天的笔记到此结束!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园信息化系统解决方案旨在通过先进的信息技术,实现教育的全方位创新和优质资源的普及共享。该方案依据国家和地方政策背景,如教育部《教育信息化“十三五”规划》和《教育信息化十年发展规划》,以信息技术的革命性影响为指导,推进教育信息化建设,实现教育思想和方法的创新。 技术发展为智慧校园建设提供了强有力的支撑。方案涵盖了互连互通、优质资源共享、宽带网络、移动APP、电子书包、电子教学白板、3D打印、VR虚拟教学等技术应用,以及大数据和云计算技术,提升了教学数据记录和分析水平。此外,教育资源公共服务平台、教育管理公共服务平台等平台建设,进一步提高了教学、管控的效率。 智慧校园系统由智慧教学、智慧管控和智慧办公三大部分组成,各自具有丰富的应用场景。智慧教学包括微课、公开课、精品课等教学资源的整合和共享,支持在线编辑、录播资源、教学分析等功能。智慧管控则通过平安校园、可视对讲、紧急求助、视频监控等手段,保障校园安全。智慧办公则利用远程视讯、无纸化会议、数字会议等技术,提高行政效率和会议质量。 教育录播系统作为智慧校园的重要组成部分,提供了一套满足学校和教育局需求的解决方案。它包括标准课室、微格课室、精品课室等,通过自动五机位方案、高保真音频采集、一键式录课等功能,实现了优质教学资源的录制和共享。此外,录播系统还包括互动教学、录播班班通、教育中控、校园广播等应用,促进了教育资源的均衡化发展。 智慧办公的另一重点是无纸化会议和数字会议系统的建设,它们通过高效的文件管理、会议文件保密处理、本地会议的音频传输和摄像跟踪等功能,实现了会议的高效化和集中管控。这些系统不仅提高了会议的效率和质量,还通过一键管控、无线管控等设计,简化了操作流程,使得会议更加便捷和环保。 总之,智慧校园信息化系统解决方案通过整合先进的信息技术和教学资源,不仅提升了教育质量和管理效率,还为实现教育均衡化和资源共享提供了有力支持,推动了教育现代化的进程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值