3D游戏引擎系列七

时间:2023-02-03 22:45:54

笔者介绍:姜雪伟,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游戏引擎系列七

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

3D游戏引擎系列七

图中显示的是纹理坐标的关系,左下角是(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_Hclass 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游戏引擎系列七

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

3D游戏引擎系列七

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

    for(int i=0; i<torusPrecision+1; i++)    {        vertices[i].position=VECTOR3D(1.5f, 0.0f, 0.0f).GetRotatedZ(i*360.0f/torusPrecision)+VECTOR3D(4.0f, 0.0f, 0.0f);        vertices[i].s=0.0f;        vertices[i].t=(float)i/torusPrecision;        vertices[i].sTangent.Set(0.0f, 0.0f, -1.0f);        vertices[i].tTangent=VECTOR3D(0.0f, -1.0f, 0.0f).GetRotatedZ(i*360.0f/torusPrecision);        vertices[i].normal=vertices[i].tTangent.CrossProduct(vertices[i].sTangent);    }

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

#include <windows.h>#include <stdio.h>#include <GL\gl.h>#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.5for(int i=0; i<torusPrecision+1; i++){vertices[i].position=VECTOR3D(1.5f, 0.0f, 0.0f).GetRotatedZ(i*360.0f/torusPrecision)+VECTOR3D(4.0f, 0.0f, 0.0f);vertices[i].s=0.0f;vertices[i].t=(float)i/torusPrecision;vertices[i].sTangent.Set(0.0f, 0.0f, -1.0f);vertices[i].tTangent=VECTOR3D(0.0f, -1.0f, 0.0f).GetRotatedZ(i*360.0f/torusPrecision);vertices[i].normal=vertices[i].tTangent.CrossProduct(vertices[i].sTangent);}//rotate this to get other ringsfor(int ring=1; ring<torusPrecision+1; ring++){for(int i=0; i<torusPrecision+1; i++){vertices[ring*(torusPrecision+1)+i].position=vertices[i].position.GetRotatedY(ring*360.0f/torusPrecision);vertices[ring*(torusPrecision+1)+i].s=2.0f*ring/torusPrecision;vertices[ring*(torusPrecision+1)+i].t=vertices[i].t;vertices[ring*(torusPrecision+1)+i].sTangent=vertices[i].sTangent.GetRotatedY(ring*360.0f/torusPrecision);vertices[ring*(torusPrecision+1)+i].tTangent=vertices[i].tTangent.GetRotatedY(ring*360.0f/torusPrecision);vertices[ring*(torusPrecision+1)+i].normal=vertices[i].normal.GetRotatedY(ring*360.0f/torusPrecision);}}//calculate the indicesfor(int ring=0; ring<torusPrecision; ring++){for(int i=0; i<torusPrecision; i++){indices[((ring*torusPrecision+i)*2)*3+0]=ring*(torusPrecision+1)+i;indices[((ring*torusPrecision+i)*2)*3+1]=(ring+1)*(torusPrecision+1)+i;indices[((ring*torusPrecision+i)*2)*3+2]=ring*(torusPrecision+1)+i+1;indices[((ring*torusPrecision+i)*2+1)*3+0]=ring*(torusPrecision+1)+i+1;indices[((ring*torusPrecision+i)*2+1)*3+1]=(ring+1)*(torusPrecision+1)+i;indices[((ring*torusPrecision+i)*2+1)*3+2]=(ring+1)*(torusPrecision+1)+i+1;}}return true;}

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

3D游戏引擎系列七

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 = <g_texEnvMap>;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 maphalf3 normal = normalize(tex2D(g_NormalMapSampler, inData.texCoord).xyz * 2.0 - 1.0);// transform the vector from world space to tangent spacefloat3x3 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 spacefloat3x3 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效果,仅供参考。。。。。