笔者介绍:姜雪伟,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编程时首先要明白其实现原理,我们可以通过可编程流水线了解一下其原理:
上图中显示了TBNMatrix,T表示的是切向量,B表示次法线向量,N表示的是法线向量,这个也是求Bump Mapping必须要计算得到的,TBN Matrix可以将其放到GPU中计算得到的。下面我们就一步步给读者介绍其实现,只用文字很难描述清楚,还是通过图看的比较明白。为了帮助读者理解Tangent 空间向量,先给读者看一下场景中的四边形,纹理图片是要贴到四边形上的,如下图所示的效果:
图中显示的是纹理坐标的关系,左下角是(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平面上的示例图展示如下:
圆环在空间中的效果图如下所示:
接下来在圆环上的材质实现法线和切向量空间的代码如下所示:
for(int i=0; i
#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中运行得到的,它渲染的凹凸效果如下所示:
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();
}
}