3D游戏引擎系列七

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN课程视频网址:http://edu.csdn.net/lecturer/144

Jim Blinn在1978发表了一篇名为:“Simulationof Wrinkled Surfaces”,提出了Bump Mapping这个东东。BumpMapping通过一张Height Map记录各象素点的高度信息,有了高度信息,就可以计算HeightMap中当前象素与周围象素的高度差,这个高度差就代表了各象素的坡度,用这个坡度信息去绕动法向量,得到最终法向量,用于光照计算。在前面的博客中也有介绍过高光法线时介绍过TBN,在这里利用TBN渲染Bumpmapping实现原理。

         GPU渲染属于可编程流水线,在使用Shader编程时首先要明白其实现原理,我们可以通过可编程流水线了解一下其原理:

3D游戏引擎系列七_第1张图片

上图中显示了TBNMatrix,T表示的是切向量,B表示次法线向量,N表示的是法线向量,这个也是求Bump Mapping必须要计算得到的,TBN Matrix可以将其放到GPU中计算得到的。下面我们就一步步给读者介绍其实现,只用文字很难描述清楚,还是通过图看的比较明白。为了帮助读者理解Tangent 空间向量,先给读者看一下场景中的四边形,纹理图片是要贴到四边形上的,如下图所示的效果:

3D游戏引擎系列七_第2张图片

图中显示的是纹理坐标的关系,左下角是(0,0),右上角是(1,1)。

现在,我们需要找到两个顶点的切线。“S切线”点的方向的纹理坐标,“T切线”点的方向的纹理坐标。

这两个切线和法线是一个顶点的“基础”。它们定义了一个坐标空间-“切向量空间”。s 切线,t切线和法线分别是用x,y,z轴表示的,TBN矩阵是从物体空间转换到切线空间,矩阵的表示如下所示:

( Sx Sy Sz )
( Tx Ty Tz )
( Nx Ny Nz )

S也是切线空间中的一种,我们也可以将其表示为Binormal,现在的主要问题是求解S,T,N向量。

先抛开GPU编程,先用C++代码实现一遍,首先定义一个类用于存储TBN,类的存储代码如下所示:

class TORUS_VERTEX
{
public:
    VECTOR3D  position;
    float s, t;
    VECTOR3D  sTangent, tTangent;
    VECTOR3D  normal;
    VECTOR3D  tangentSpaceLight;
};

结构体定义好了后,接下来,我们假设在场景中放置一个圆环,这个圆环有48个点组成,用于存储顶点和索引链表, 代码类完整定义如下所示:

#ifndef TORUS_H
#define TORUS_H

class TORUS_VERTEX
{
public:
	VECTOR3D position;
	float s, t;
	VECTOR3D sTangent, tTangent;
	VECTOR3D normal;
	VECTOR3D tangentSpaceLight;
};

class TORUS
{
public:
	TORUS();
	~TORUS();

	bool InitTorus();

	int numVertices;
	int numIndices;

	unsigned int * indices;
	TORUS_VERTEX * vertices;
};

const int torusPrecision=48;

#endif

现在开始计算圆环的顶点位置,法线,切向量空间,首先我们把圆环在XY平面上的示例图展示如下:

3D游戏引擎系列七_第3张图片

圆环在空间中的效果图如下所示:

3D游戏引擎系列七_第4张图片

接下来在圆环上的材质实现法线和切向量空间的代码如下所示:

    for(int i=0; i

图中实现的是静止不动的圆环,利用for循环实现了TBN向量,转动后的效果计算也是类似的,下面把完整的代码给读者展现一下:

#include 
#include 
#include 
#include "Maths/Maths.h"
#include "TORUS.h"

TORUS::TORUS()
{
	InitTorus();
}

TORUS::~TORUS()
{
	if(indices)
	  delete [] indices;
	indices=NULL;

	if(vertices)
		delete [] vertices;
	vertices=NULL;
}

bool TORUS::InitTorus()
{
	numVertices=(torusPrecision+1)*(torusPrecision+1);
	numIndices=2*torusPrecision*torusPrecision*3;

	vertices=new TORUS_VERTEX[numVertices];
	if(!vertices)
	{
		printf("Unable to allocate memory for torus vertices\n");
		return false;
	}

	indices=new unsigned int[numIndices];
	if(!indices)
	{
		printf("Unable to allocate memory for torus indices\n");
		return false;
	}
	
	//calculate the first ring - inner radius 4, outer radius 1.5
	for(int i=0; i

以上代码实现的圆环效果是在CPU中运行得到的,它渲染的凹凸效果如下所示:

3D游戏引擎系列七_第5张图片

CPU上的效率没有GPU运行效率高,还是建议读者在GPU中实现出来,最后把在GPU中实现的Shader代码给读者展示一下:

// 矩阵
float4x4 g_matWorld		: World;
float4x4 g_matWorldView		: WorldView;
float4x4 g_matWorldViewProj : WorldViewProj;

float4x4 g_matInverWorldView : InverseWorldView;

// 灯光
float4 g_LightPositionViewSpace;
float  g_OneOverSqrLightRadius;

float4 g_EyePosition;

// 材质
float g_MaterialAmbient;
float g_MaterialDiffuse;
float g_MaterialSpecular;
float g_MaterialEmissive;
float g_MaterialShininess;

// 纹理图片
texture g_texEnvMap;
texture g_texNormalMap;
texture g_texHeightMap;

float g_fReflectivity = 0.5f;
float g_fScale = 0.04f; // [0, 0.05]
static float g_fBias  = g_fScale * 0.5f;

// ---------------------------------------------------------------
// Sampler
// ---------------------------------------------------------------

sampler g_EnvMapSampler = sampler_state
{
	Texture = ;
	MinFilter = Linear;
	MagFilter = Linear;
	MipFilter = Linear;
};

sampler g_NormalMapSampler = sampler_state
{
	Texture = (g_texNormalMap);
	MinFilter = Linear;
	MagFilter = Linear;
	MipFilter = Linear;
};

sampler g_HeightMapSampler = sampler_state
{
	Texture = (g_texHeightMap);
	MinFilter = Linear;
	MagFilter = Linear;
	MipFilter = Linear;
};

// ---------------------------------------------------------------
// Utilities
// ---------------------------------------------------------------

half CalAttenuation(half3 lightVec)
{
	return saturate(1 - dot(lightVec, lightVec) * g_OneOverSqrLightRadius);
}

float3x3 CalInvertMatrix3X3(float3x3 M)
{
	float det = dot(cross(M[0], M[1]), M[2]);
	
	float3x3 T = transpose(M);
	
	return float3x3(
			cross(T[1], T[2]),
			cross(T[2], T[0]),
			cross(T[0], T[1])) / det;
}
//计算TBN矩阵
float3x3 CalTangentFrame(float3 N)
{
	float3 binormal;
	float3 tangent;
	
	float3 c1 = cross(N, float3(0.0, 0.0, 1.0)); 
	float3 c2 = cross(N, float3(0.0, 1.0, 0.0)); 
	
	if(length(c1)>length(c2))
	{
		tangent = c1;	
	}
	else
	{
		tangent = c2;	
	}
	
	tangent = normalize(tangent);
	
	binormal = cross(N, tangent); 
	binormal = normalize(binormal);

	return float3x3(tangent, binormal, N); 
}


float3 CalReflect(float4 position, float3 normal)
{
	float3 Normal = mul(normalize(normal), g_matWorld);   
    float3 PosWorld = mul(position, g_matWorld);
    float3 ViewDir = normalize(PosWorld - g_EyePosition.xyz);
	
	//return refract(ViewDir, Normal, .99);
	
	return reflect(ViewDir, Normal);	
}

// ---------------------------------------------------------------
// vertex & pixel shader
// ---------------------------------------------------------------

struct VS_OUTPUT_ENV
{
	float4 position : POSITION;
	float3 texRefCoord : TEXCOORD0;
};

VS_OUTPUT_ENV VS_EnvMapping(float4 position : POSITION, float3 normal : NORMAL)
{
	VS_OUTPUT_ENV OUT = (VS_OUTPUT_ENV)0;
	
	OUT.position = mul(position, g_matWorldViewProj);
	OUT.texRefCoord = CalReflect(position, normal);
	
	return OUT;
}

float4 PS_EnvMapping(float3 tex : TEXCOORD0) : COLOR0
{
	return g_fReflectivity * texCUBE(g_EnvMapSampler, tex);
}

// ---------------------------------------------------------------

struct VS_OUTPUT
{
	float4 position		: POSITION;
	float3 normal		: TEXCOORD0;
	float2 texCoord		: TEXCOORD1;
	float3 worldViewPos : TEXCOORD2;
	float3 texRefCoord  : TEXCOORD3;
};

VS_OUTPUT VS_NormalMapping(float4 pos : POSITION, float3 normal : NORMAL, float2 texCoord : TEXCOORD0)
{
	VS_OUTPUT outData = (VS_OUTPUT)0;
	
	outData.position	 = mul(pos, g_matWorldViewProj);
	outData.normal		 = normalize(mul(normal, (float3x3)g_matWorldView));
	outData.texCoord	 = texCoord;
	outData.worldViewPos = mul(pos, g_matWorldView).xyz;
	outData.texRefCoord  = CalReflect(pos, normal);
	
	return outData;
}

float4 PS_Phong(VS_OUTPUT inData) : COLOR0
{
	
	half3 lightVec = g_LightPositionViewSpace - inData.worldViewPos;
	half3 eyeVec   = -inData.worldViewPos;
	
	half3 normal = normalize(inData.normal);

	half3 lightDir = normalize(lightVec);
	half3 eyeDir   = normalize(eyeVec);
	half3 halfDir  = normalize(eyeDir + lightDir);
	
	half3 light = lit(dot(lightDir, normal), dot(halfDir, normal), g_MaterialShininess);
	half atten	= CalAttenuation(lightVec);
	
	float4 Id = light.y * g_MaterialDiffuse;
	float4 Is = light.z * g_MaterialSpecular;
	
	float4 envColor = g_fReflectivity * texCUBE(g_EnvMapSampler, inData.texRefCoord);
	float4 Ia = envColor * g_MaterialAmbient;
	
	return (Ia + Id + Is) * atten * envColor;
}

float4 PS_NormalMapping(VS_OUTPUT inData) : COLOR0
{
	half3 lightVec = g_LightPositionViewSpace - inData.worldViewPos;
	half3 eyeVec   = -inData.worldViewPos;
	
	// get the normal from normal map
	half3 normal = normalize(tex2D(g_NormalMapSampler, inData.texCoord).xyz * 2.0 - 1.0);
	
	// transform the vector from world space to tangent space
	float3x3 tangent = CalTangentFrame(inData.normal);
	lightVec = mul(tangent, lightVec);
	eyeVec   = mul(tangent, eyeVec);

	half3 lightDir = normalize(lightVec);
	half3 eyeDir   = normalize(eyeVec);
	half3 halfDir  = normalize(eyeDir + lightDir);
	
	half3 light = lit(dot(lightDir, normal), dot(halfDir, normal), g_MaterialShininess);
	half atten	= CalAttenuation(lightVec);
	
	float4 Id = light.y * g_MaterialDiffuse;
	float4 Is = light.z * g_MaterialSpecular * 0.5f;
	
	float4 envColor = g_fReflectivity * texCUBE(g_EnvMapSampler, inData.texRefCoord);
	float4 Ia = envColor * g_MaterialAmbient;
	
	return (Ia + Id + Is) * atten;
}

float4 PS_ParallaxMapping(VS_OUTPUT inData) : COLOR0
{
	half3 lightVec = g_LightPositionViewSpace - inData.worldViewPos;
	half3 eyeVec   = -inData.worldViewPos;
	
	// get the normal from normal map
 	half height = tex2D(g_HeightMapSampler, inData.texCoord);
 	half2 newTexCoord = inData.texCoord + (height * g_fScale - g_fBias) * normalize(eyeVec).xy;

	half3 normal = normalize(tex2D(g_NormalMapSampler, newTexCoord).xyz * 2.0 - 1.0); 

	
	// transform the vector from world space to tangent space
	float3x3 tangent = CalTangentFrame(inData.normal);
	lightVec = mul(tangent, lightVec);
	eyeVec   = mul(tangent, eyeVec);

	half3 lightDir = normalize(lightVec);
	half3 eyeDir   = normalize(eyeVec);
	half3 halfDir  = normalize(eyeDir + lightDir);
	
	half3 light = lit(dot(lightDir, normal), dot(halfDir, normal), g_MaterialShininess);
	half atten	= CalAttenuation(lightVec);
	
	float4 Id = light.y * g_MaterialDiffuse;
	float4 Is = light.z * g_MaterialSpecular * 0.5f;
	
	float4 envColor = g_fReflectivity * texCUBE(g_EnvMapSampler, inData.texRefCoord);
	float4 Ia = envColor * g_MaterialAmbient;
	
	return (Ia + Id + Is) * atten;
}

// ---------------------------------------------------------------
// Technique
// ---------------------------------------------------------------

technique techNormalMampping
{
	pass
	{
		VertexShader = compile vs_1_1 VS_NormalMapping();
		PixelShader  = compile ps_2_0 PS_NormalMapping();
	}
}

technique techParallaxMampping
{
	pass
	{
		VertexShader = compile vs_1_1 VS_NormalMapping();
		PixelShader  = compile ps_2_a PS_ParallaxMapping();
	}
}

上述Shader脚本实现了两种CubeMapping效果,仅供参考。。。。。


你可能感兴趣的:(3D引擎,图形学编程,3D游戏引擎)