笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
模型材质的高光、法线渲染在游戏中使用的最多,加上高光、法线的材质凹凸感更强。UE4(虚幻4引擎)对模型材质的渲染非常逼真,它在对材质渲染方面做的更好,算法更强大。效果如下图:
下面利用Cocos2d-x引擎实现材质的高光和法线渲染,具体实现步骤:首先用max建模工具导出模型fbx格式,在导出模型时需将“Tangents and Binormals项”选上,然后用fbx-conv工具将其转化成c3t或者c3b格式,在转化时的参数设置为“-p -b”或者是“-p -t”,”-p”的含义就是能够将模型的切向量导出,“-b”表示的是二进制数据。接下来就可以得到计算模型所需要的法线向量、切线向量了,如果不加“-p” 指令需要自己计算模型的切向量,既不准确又麻烦。由于法线是在切向量空间计算得到的,而切向量空间需要求得TBN矩阵,TBN矩阵的含义分别代表tangent、bitangent和normal向量。这是建构矩阵所需的向量。要建构这样一个把切线空间转变为不同空间的转换矩阵,需要三个相互垂直的向量,它们沿一个表面的法线贴图对齐于:上、右、前。向上的向量是表面的法线向量。右和前向量分别是切线(Tagent)和副切线(Bitangent)向量。计算向量的目的是构建TBN矩阵,TBN向量效果如下图:
在导出的模型中,不需要开发者计算切向量可直接从模型的数据中拿到模型的法线向量,切线向量,次法线切向量。从而可以快速的生成TBN矩阵,获取到TBN矩阵后将其传入到GPU中计算,实现材质的高光、法线渲染,在做渲染之前,首先需要准备三张纹理贴图,分别表示diffuse,normal,specular贴图如下:
这三张贴图最终全部应用到模型渲染上,贴图的制作是美术事先在做好的,常用工具是PS(PhotonShop),准备工作完成后,开始编写Shader,顶点着色器完整代码如下所示:
attribute vec3 a_normal;
attribute vec3 a_tangent;
attribute vec3 a_binormal;
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 UV;
varying vec3 Position_worldspace;
varying vec3 EyeDirection_cameraspace;
varying vec3 LightDirection_cameraspace;
varying vec3 LightDirection_tangentspace;
varying vec3 EyeDirection_tangentspace;
//灯光位置
vec3 LightPosition=vec3(1000,1000,1000);
mat3transpose(mat3 inMatrix) {
vec3 i0 = inMatrix[0];
vec3 i1 = inMatrix[1];
vec3 i2 = inMatrix[2];
mat3 outMatrix = mat3(
vec3(i0.x, i1.x, i2.x),
vec3(i0.y, i1.y, i2.y),
vec3(i0.z, i1.z, i2.z)
);
return outMatrix;
}
void main(void)
{
vec3 vertexTangent_modelspace = a_tangent;
vec3 vertexBitangent_modelspace = a_binormal;
vec3 vertexNormal_modelspace = a_normal;
// 输出顶点的位置,在透视空间中
gl_Position = CC_MVPMatrix * a_position;
// 在相机空间变换
vec3 vertexPosition_cameraspace = (CC_MVMatrix * a_position).xyz;
//从顶点到相机的向量空间指向
EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;
// 从顶点到相机的向量
LightDirection_cameraspace = LightPosition + EyeDirection_cameraspace;
// 顶点的UV坐标
UV = a_texCoord;
// 模型视口矩阵
mat3 MV3x3 = mat3(CC_MVMatrix[0].xyz, CC_MVMatrix[1].xyz, CC_MVMatrix[2].xyz);
vec3 vertexTangent_cameraspace = MV3x3 * vertexTangent_modelspace;
vec3 vertexBitangent_cameraspace = MV3x3 * vertexBitangent_modelspace;
vec3 vertexNormal_cameraspace = MV3x3 * vertexNormal_modelspace;
// TBN矩阵形成
mat3 TBN = transpose(mat3(
vertexTangent_cameraspace,
vertexBitangent_cameraspace,
vertexNormal_cameraspace
));
// 灯光的切向空间计算和相机的切线空间计算
LightDirection_tangentspace = TBN * LightDirection_cameraspace;
EyeDirection_tangentspace = TBN * EyeDirection_cameraspace;
}
顶点着色器主要是计算TNB矩阵,从而得到灯光和相机在TBN矩阵中的表示,下面是片段着色器完整代码如下所示:
varying vec2 UV;
varying vec3 EyeDirection_cameraspace;
varying vec3 LightDirection_cameraspace;
varying vec3 LightDirection_tangentspace;
varying vec3 EyeDirection_tangentspace;
// 纹理取样声明
uniform sampler2D DiffuseTextureSampler;
uniform sampler2D NormalTextureSampler;
uniform sampler2D RoughnessTextureSampler;
uniform sampler2D SpecularTextureSampler;
void main(){
// 灯光自发光属性和灯光强度
vec3 LightColor = vec3(1,1,1);
float LightPower = 40.0;
// 材质属性
vec3 MaterialDiffuseColor = texture2D( DiffuseTextureSampler, vec2(UV.x, 1-UV.y)).rgb;
vec3 MaterialAmbientColor = vec3(0.588,0.588,0.588) * MaterialDiffuseColor;
vec3 MaterialSpecularColor = texture2D( SpecularTextureSampler, vec2(UV.x, 1-UV.y)).rgb;
// 纹理的法向量空间
vec3 TextureNormal_tangentspace = normalize(texture2D( NormalTextureSampler, vec2(UV.x,1-UV.y) ).rgb*2.0 - 1.0);
// 在相机空间中的法线
vec3 n = TextureNormal_tangentspace;
// 灯光方向
vec3 l = normalize(LightDirection_tangentspace);
// 在法线和灯光方向之间的系数
float cosTheta = max( dot( n,l ), 0.0 );
// 朝向相机向量
vec3 E = normalize(EyeDirection_tangentspace);
// 反射方向
vec3 R = reflect(-l,n);
// 眼睛向量和灯光方向之间的夹脚系数。
float cosAlpha = clamp( dot( E,R ), 0.0,1.0);
vec3 color =
// Ambient: 模拟方向光
MaterialAmbientColor +
// Diffuse : 模型材质的颜色
vec3(0.588,0.588,0.588) * MaterialDiffuseColor * LightColor * cosTheta +
// Specular : 高光的反射
vec3(1.50,1.50,1.50) * MaterialSpecularColor * LightColor * pow(cosAlpha,4.0);
gl_FragColor = vec4(color,1.0);
}
material base
{
technique normal
{
pass 0
{
shader
{
vertexShader = astronaut/base_v.vert
fragmentShader = astronaut/base_f.frag
sampler DiffuseTextureSampler
{
path=astronaut/Astronaut_Base_Color.png
}
sampler NormalTextureSampler
{
path=astronaut/Astronaut_Normal_OpenGL.png
}
sampler SpecularTextureSampler
{
path = astronaut/BaseLightingMap.png
}
}
}
}
}
材质文件内容已经在第九章给读者介绍过了,这里就不多说了,材质文件中包含了顶点着色器和片段着色器脚本文件以及需要加载的diffuse、normal、specular三张纹理贴图,下面开始将顶点着色器和片段着色器应用到模型上面了,执行代码段如下:
//加载模型
auto sprite_Base = Sprite3D::create("astronaut/Base.c3t");
this->addChild(sprite_Base);
sprite_Base->setPosition3D(Vec3(0, 0, 0));
sprite_Base->setRotation3D(Vec3(-90, 0, 0));
//加载材质
auto mat_Base = Sprite3DMaterial::createWithFilename("astronaut/base.material");
auto _state_sprite1 = mat_Base->getTechniqueByIndex(0)->getPassByIndex(0)->getGLProgramState();
sprite_Base->setMaterial(mat_Base);
用同样的原理,可以实现多个模型的高光和法线渲染,将渲染Shader应用到其他模型上同样可以实现高光、法线效果,如下图:
模型的高光法线渲染效果已经完成,接下来给读者介绍反射渲染。