ZFXEngine开发笔记之Bump Mapping(2)

作者: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来进行光照计算了。

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

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

时间: 2024-10-07 06:13:27

ZFXEngine开发笔记之Bump Mapping(2)的相关文章

ZFXEngine开发笔记之Bump Mapping(1)

作者:i_dovelemon 日期:2014 / 9 / 7 来源:CSDN博客 主题:Bump Mapping, Tangent Space, Normal Map, Height Map 引言 我们知道,在真实世界中,任何物体的表面都不是非常光滑的,这种粗糙的表面效果,在光源的照射下会有明暗的感觉.那么,如何在3D图形中模拟这种效果了?是通过建模的方法,为模型的表面建造出粗糙的表面?这种方法很难做出真实的粗糙感出来,毕竟表面的粗糙程度是很细微的效果,很难用建模的方式建模出来.所以,只能用其他

ZFXEngine开发笔记-SSE版本的ZFXVector

SSE介绍 在学习3D游戏编程大师技巧的时候,就了解到,可是使用一种称之为"单指令,多数据(SIMD)"的技术来编写3D数学库.通过这样的方法,可以将我们经常使用的诸如向量计算,矩阵变换等操作加快很多倍.这次,在学习3D引擎开发的时候,也用到了这个技术.SIMD是一种技术的名称,而并不是具体的工具.实现这种技术,不同的CPU厂商推出了不同的技术,像MMX, 3DNow!, SSE, SSE2, SSE3....由于我的计算机上使用的是Intel的处理器,它支持MMX,SSE,SSE2,

ZFXEngine开发笔记之3D模型格式支持(1)

作者:i_dovelemon 来源:CSDN 日期:2014 / 9 / 17 主题:3D Format, Milk 3D Shape, Chunk Based System, Skeleton Animation (文中以红色中字标示的文字,是整个文章的注意项,请读者留心) 引言 在3D游戏领域,艺术家们通过3D建模软件建立自己的艺术品模型和绚丽的3D场景.当我们想要在自己的游戏中使用这些模型的时候,我们就需要将这种模型变成能够被我们的引擎所识别的文件格式.每一个建模软件都有自己的文件格式,不

ZFXEngine开发笔记之Shadow Volume

作者:i_dovelemon 来源:CSDN 日期:2014 / 10 / 20 主题: Shadow Volume 引言 游戏中,往往有很多的光影效果.想要营造出很好的光影效果,物体在光源照射下的阴影就必不可少.本节内容就向大家讲述如何构建阴影. Shadow Volume 构建阴影的方法有很多,常用的两种方法是Shadow Volume和Shadow mapping.本片博文将向大家讲述使用Shadow Volume来构建阴影的方法. Shadow Volume的算法是由Frank Crow

ZFXEngine开发笔记之Omni Light

作者:i_dovelemon 日期:2014 / 9 / 5 来源:CSDN博客 主题:Per-pixel lighting, Omni light, early-z, Multi-pass, Assembly Shader 引言 在第一人称射击游戏中,经常需要点光源来完成光照计算.所以,这篇文章中将会讲述如何创建这个点光源,并且如何设计让引擎可以支持多光源渲染. Omni light介绍 所谓的Omni light,就是指点光源.不过在这里,我们进行点光源的光照计算的时候,没有用到顶点的法线来

ZFXEngine开发笔记- GPU硬件架构

作者:i_dovelemon 日期:2014 / 8 / 31 来源: CSDN博客 文章: GPU硬件架构 引言 在3D图形学中,可编程渲染管道的出现,无疑是一种创举.在下面的文章中,会向大家简要的介绍现如今的可编程渲染管道中最重要的Vertex Shader和Pixel Shader的硬件架构以及如何使用汇编语言来编写Shader. Vertex Shader 在硬件上,Vertex Shader中所有的运算都在一个名为vertex arithmetic logic unit(ALU)中进行

张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231

原文:张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231 GitHub:https://github.com/ZhangGaoxing/windows-iot-demo/tree/master/DS3231 注意:不包含闹钟设置

Android开发笔记(一百零三)地图与定位SDK

集成地图SDK 国内常用的地图SDK就是百度和高德了,二者的用法大同小异,可按照官网上的开发指南一步步来.下面是我在集成地图SDK时遇到的问题说明: 1.点击基本地图功能选项,不能打开地图,弹出"key验证出错!请在AndroidManifest.xml文件中检查key设置的"的红色字提示.查看日志提示"galaxy lib host missing meta-data,make sure you know the right way to integrate galaxy&

微信订阅号开发笔记(二)

微信开发的流程其实很简单 o(∩_∩)o 哈哈!在微信网站的编辑操作 额,就不说了.虽然有人问过.下面是我的微信开发过程,简单记录下. 成为开发者 材料:1.自己的服务器资源,百度的BAE,新浪的SAE都不错. 2.懂那么点编程语言. 3.注册微信公众号. 上面的都有了之后,就可以自己动手开发了.哇咔咔,好兴奋.有木有. 在登录进去之后,怎么成为开发者?不知道,自己看去. 开始coding吧. 1.验证 if (! empty ( $_GET ['echostr'] ) && ! empt