OpenGL-法线贴图(Normal Mapping)
背景
在一个平面上如果只有一个法线,整个面的光照强度都是相同的,如果是凹凸的表面,无法表现表面的真是光照效果,所以引入了法线贴图。平面内的每一个fragment都存储一个法线向量,通过法线向量计算切线向量和副切向向量。
已知上向量是表面的法线向量。右和前向量是切线(Tagent)和副切线(Bitangent)向量。下面的图片展示了一个表面的三个向量:
计算原理
计算每个表面的切线和副切线可以参照https://learnopengl-cn.github.io/,主要计算过程如下:
顶点:
V3N3UV2 verts[] =
{
{ 0.5f, -0.5f, 0.5f , 1.0, 0.0, 0.0, 0.0f, 0.0f },
{ 0.5f, -0.5f, -0.5f , 1.0, 0.0, 0.0, 1.0f, 0.0f },
{ 0.5f, 0.5f, -0.5f , 1.0, 0.0, 0.0, 1.0f, 1.0f },
{ 0.5f, 0.5f, -0.5f , 1.0, 0.0, 0.0, 1.0f, 1.0f },
{ 0.5f, 0.5f, 0.5f , 1.0, 0.0, 0.0, 0.0f, 1.0f },
{ 0.5f, -0.5f, 0.5f , 1.0, 0.0, 0.0, 0.0f, 0.0f },
{-0.5f, -0.5f, -0.5f , -1.0, 0.0,0.0, 0.0f, 0.0f },
{-0.5f, -0.5f, 0.5f , -1.0, 0.0,0.0, 1.0f, 0.0f },
{-0.5f, 0.5f, 0.5f , -1.0, 0.0,0.0, 1.0f, 1.0f },
{-0.5f, 0.5f, 0.5f , -1.0, 0.0,0.0, 1.0f, 1.0f },
{-0.5f, 0.5f, -0.5f , -1.0, 0.0,0.0, 0.0f, 1.0f },
{-0.5f, -0.5f, -0.5f , -1.0, 0.0,0.0, 0.0f, 0.0f },
{-0.5f, 0.5f, 0.5f , -0.0, 1.0,0.0, 1.0f, 0.0f },
{ 0.5f, 0.5f, 0.5f , -0.0, 1.0,0.0, 0.0f, 0.0f },
{ 0.5f, 0.5f, -0.5f , -0.0, 1.0,0.0, 0.0f, 1.0f },
{ 0.5f, 0.5f, -0.5f , 0.0, 1.0,0.0, 0.0f, 1.0f },
{-0.5f, 0.5f, -0.5f , 0.0, 1.0,0.0, 1.0f, 1.0f },
{-0.5f, 0.5f, 0.5f , 0.0, 1.0,0.0, 1.0f, 0.0f },
{-0.5f, -0.5f, -0.5f , 0.0, -1.0,0.0, 1.0f, 0.0f },
{ 0.5f, -0.5f, -0.5f , 0.0, -1.0,0.0, 0.0f, 0.0f },
{ 0.5f, -0.5f, 0.5f , 0.0, -1.0,0.0, 0.0f, 1.0f },
{ 0.5f, -0.5f, 0.5f , 0.0, -1.0,0.0, 0.0f, 1.0f },
{-0.5f, -0.5f, 0.5f , 0.0, -1.0,0.0, 1.0f, 1.0f },
{-0.5f, -0.5f, -0.5f , 0.0, -1.0,0.0, 1.0f, 0.0f },
{-0.5f, -0.5f, 0.5f, 0.0, 0.0, 1.0, 0.0f, 0.0f },
{ 0.5f, -0.5f, 0.5f, 0.0, 0.0, 1.0, 1.0f, 0.0f },
{ 0.5f, 0.5f, 0.5f, 0.0, 0.0, 1.0, 1.0f, 1.0f },
{ 0.5f, 0.5f, 0.5f, 0.0, 0.0, 1.0, 1.0f, 1.0f },
{-0.5f, 0.5f, 0.5f, 0.0, 0.0, 1.0, 0.0f, 1.0f },
{-0.5f, -0.5f, 0.5f, 0.0, 0.0, 1.0, 0.0f, 0.0f },
{ 0.5f, -0.5f, -0.5f, 0.0, 0.0,-1.0, 0.0f, 0.0f },
{-0.5f, -0.5f, -0.5f, 0.0, 0.0,-1.0, 1.0f, 0.0f },
{-0.5f, 0.5f, -0.5f, 0.0, 0.0,-1.0, 1.0f, 1.0f },
{-0.5f, 0.5f, -0.5f, 0.0, 0.0,-1.0, 1.0f, 1.0 },
{ 0.5f, 0.5f, -0.5f, 0.0, 0.0,-1.0, 0.0f, 1.0 },
{ 0.5f, -0.5f, -0.5f, 0.0, 0.0,-1.0, 0.0f, 0.0 },
};
切线与副切线计算:
void convertTBN(V3N3UV2* vertices,NORMALMAP* nmVerts)
{
for (size_t i = 0; i <36; i += 3)
{
nmVerts[i + 0].x = vertices[i + 0].x;
nmVerts[i + 0].y = vertices[i + 0].y;
nmVerts[i + 0].z = vertices[i + 0].z;
nmVerts[i + 1].x = vertices[i + 1].x;
nmVerts[i + 1].y = vertices[i + 1].y;
nmVerts[i + 1].z = vertices[i + 1].z;
nmVerts[i + 2].x = vertices[i + 2].x;
nmVerts[i + 2].y = vertices[i + 2].y;
nmVerts[i + 2].z = vertices[i + 2].z;
nmVerts[i + 0].nx = vertices[i + 0].nx;
nmVerts[i + 0].ny = vertices[i + 0].ny;
nmVerts[i + 0].nz = vertices[i + 0].nz;
nmVerts[i + 1].nx = vertices[i + 1].nx;
nmVerts[i + 1].ny = vertices[i + 1].ny;
nmVerts[i + 1].nz = vertices[i + 1].nz;
nmVerts[i + 2].nx = vertices[i + 2].nx;
nmVerts[i + 2].ny = vertices[i + 2].ny;
nmVerts[i + 2].nz = vertices[i + 2].nz;
nmVerts[i + 0].u = vertices[i + 0].u;
nmVerts[i + 0].v = vertices[i + 0].v;
nmVerts[i + 1].u = vertices[i + 1].u;
nmVerts[i + 1].v = vertices[i + 1].v;
nmVerts[i + 2].u = vertices[i + 2].u;
nmVerts[i + 2].v = vertices[i + 2].v;
// Shortcuts for vertices
float3 v0 = float3(vertices[i + 0].x,vertices[i + 0].y,vertices[i + 0].z);
float3 v1 = float3(vertices[i + 1].x,vertices[i + 1].y,vertices[i + 1].z);
float3 v2 = float3(vertices[i + 2].x,vertices[i + 2].y,vertices[i + 2].z);
float2 uv0 = float2(vertices[i + 0].u, vertices[i + 0].v);
float2 uv1 = float2(vertices[i + 1].u, vertices[i + 1].v);
float2 uv2 = float2(vertices[i + 2].u, vertices[i + 2].v);
// Edges of the triangle : postion delta
float3 deltaPos1 = v1 - v0;
float3 deltaPos2 = v2 - v0;
// UV delta
float2 deltaUV1 = uv1 - uv0;
float2 deltaUV2 = uv2 - uv0;
float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
float3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y)*r;
float3 binormal = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x)*r;
nmVerts[i + 0].tx = tangent.x; nmVerts[i + 1].tx = tangent.x; nmVerts[i + 2].tx = tangent.x;
nmVerts[i + 0].ty = tangent.y; nmVerts[i + 1].ty = tangent.y; nmVerts[i + 2].ty = tangent.y;
nmVerts[i + 0].tz = tangent.z; nmVerts[i + 1].tz = tangent.z; nmVerts[i + 2].tz = tangent.z;
nmVerts[i + 0].bx = binormal.x; nmVerts[i + 1].bx = binormal.x; nmVerts[i + 2].bx = binormal.x;
nmVerts[i + 0].by = binormal.y; nmVerts[i + 1].by = binormal.y; nmVerts[i + 2].by = binormal.y;
nmVerts[i + 0].bz = binormal.z; nmVerts[i + 1].bz = binormal.z; nmVerts[i + 2].bz = binormal.z;
}
}
顶点着色器:
const char* vs = "#version 330 \n\
in vec3 _position;\n\
in vec3 _normal;\n\
in vec2 _uv;\n\
in vec3 _tagent;\n\
in vec3 _biTagent;\n\
out vec2 _outUV;\n\
out vec3 _outPos;\n\
out mat3 _TBN;\n\
uniform mat4 _mvp;\n\
uniform mat4 _matWorld;\n\
uniform mat3 _normalMatrix;; \n\
uniform vec3 _lightPos;\n\
uniform vec3 _cameraPos;\n\
void main()\n\
{\n\
_outUV = _uv;\n\
vec4 pos = _matWorld*vec4(_position,1);\n\
mat3 norMapT = _normalMatrix;\n\
_outPos = pos.xyz;\n\
vec3 normal = normalize(norMapT * _normal);\n\
vec3 tagent = normalize(norMapT * _tagent);\n\
vec3 biTagent = normalize(norMapT * _biTagent);\n\
_TBN = mat3x3(tagent,biTagent,normal);\n\
gl_Position = _mvp * vec4(_position,1.0);\n\
}";
片段着色器:
const char* ps = "#version 330 \n\
in vec2 _outUV;\n\
in vec3 _outPos;\n\
in mat3 _TBN;\n\
uniform sampler2D _texture;\n\
uniform sampler2D _texNormal;\n\
uniform vec3 _lightPos;\n\
uniform vec3 _cameraPos;\n\
void main()\n\
{\n\
gl_FragColor.rgb = texture2D(_texture, _outUV).rgb;\n\
vec3 normal = normalize((_TBN * (texture2D(_texNormal, _outUV).rgb * 2.0 - 1.0)));\n\
vec3 lightDir = normalize(_lightPos - _outPos);\n\
vec3 cameraDir = normalize(_cameraPos - _outPos);\n\
float NdotLD = max(dot(normal, lightDir), 0.0);\n\
gl_FragColor.rgb *= 0.2 + 0.8 * NdotLD;\n\
vec3 lightDirRef = reflect(-lightDir, normal);\n\
float CDdotLDR = max(dot(cameraDir, lightDirRef), 0.0);\n\
gl_FragColor.rgb += pow(CDdotLDR, 64.0);\n\
}";
完整项目代码:
https://github.com/mc-liyanliang/OpenGL-Shader/tree/master