要实现的效果

法线效果

系列教程

  1. 【WebGL】从零开始的Web端渲染器(1)——框架搭建
  2. 【WebGL】从零开始的Web端渲染器(2)——添加阴影
  3. 本篇
  4. 【WebGL】从零开始的Web端渲染器(4)——贴图及阴影抗锯齿(还在写)

思路设计

原理

法线贴图其实是很早的技术了,具体教程可以看下面的OpenGL链接

教程网址:https://learnopengl-cn.github.io/05%20Advanced%20Lighting/04%20Normal%20Mapping/

本质就是,法线贴图储存的是切线空间的法线信息,使用它需要将切线空间转换为世界空间,而确定切线空间必须要两个轴向,一个法线,另一个直接用UV决定就好了。

设计

  1. 在模型导入时,解码计算出切线,并且储存在模型中,渲染时也传入显卡;
  2. 在顶点着色器中,计算出法线切线与福切线,并且传入片元着色器;
  3. 在片元着色器中,使用切线空间计算出世界空间法线,并且使用这个法线计算光照;

实现

计算切线

这里直接学的Learn OpenGL里面写的计算切线的方法,主要就是UV的正交基和切线以及副切线的正交基是线性相关的,解方程就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 计算切线
// https://blog.csdn.net/UnityHUI/article/details/103334574
// https://learnopengl-cn.github.io/05%20Advanced%20Lighting/04%20Normal%20Mapping/#_3
let edge1 = [faceVertices[1].x - faceVertices[0].x, faceVertices[1].y - faceVertices[0].y, faceVertices[1].z - faceVertices[0].z];
let edge2 = [faceVertices[2].x - faceVertices[0].x, faceVertices[2].y - faceVertices[0].y, faceVertices[2].z - faceVertices[0].z];
let dUV1 = [faceTexCoords[1].u - faceTexCoords[0].u, faceTexCoords[1].v - faceTexCoords[0].v];
let dUV2 = [faceTexCoords[2].u - faceTexCoords[0].u, faceTexCoords[2].v - faceTexCoords[0].v];

let f = 1.0 / (dUV1[0] * dUV2[1] - dUV2[0] * dUV1[1]);
let tangent = [0, 0, 0];
tangent[0] = f * (dUV2[1] * edge1[0] - dUV1[1] * edge2[0]);
tangent[1] = f * (dUV2[1] * edge1[1] - dUV1[1] * edge2[1]);
tangent[2] = f * (dUV2[1] * edge1[2] - dUV1[1] * edge2[2]);

tangents[((index_indices / 3) - 1) * 9 + 0] = tangent[0];
tangents[((index_indices / 3) - 1) * 9 + 1] = tangent[1];
tangents[((index_indices / 3) - 1) * 9 + 2] = tangent[2];
tangents[((index_indices / 3) - 1) * 9 + 3] = tangent[0];
tangents[((index_indices / 3) - 1) * 9 + 4] = tangent[1];
tangents[((index_indices / 3) - 1) * 9 + 5] = tangent[2];
tangents[((index_indices / 3) - 1) * 9 + 6] = tangent[0];
tangents[((index_indices / 3) - 1) * 9 + 7] = tangent[1];
tangents[((index_indices / 3) - 1) * 9 + 8] = tangent[2];

后面直接跟法线啥的一样传入shader就行。

构建切线空间标准正交基

用M矩阵计算世界切线,然后叉乘得出副切线:

1
2
3
4
5
6
7
vec3 worldNormal = (vec4(a_Normal.xyz, 0.) * u_Matrix_M_I).xyz;
vec3 worldTangent = (u_Matrix_M * vec4(a_Tangent, 0)).xyz;
vec3 worldBinormal = normalize(cross(worldNormal, worldTangent));

v_WorldNormal = worldNormal;
v_WorldTangent = worldTangent;
v_WorldBinormal = worldBinormal;

解码法线贴图

首先把法线贴图从[0, 1]映射到[-1, 1],然后向切线空间的三个轴向进行偏移(结果跟TBN矩阵一样,但是比TBN矩阵好理解,所以就这么写了,好记),最后直接用这个法线计算光照即可。

1
2
3
4
5
6
7
8
// 法线
vec3 worldNormal = normalize(v_WorldNormal);
vec3 worldTangent = normalize(v_WorldTangent);
vec3 WorldBinormal = normalize(v_WorldBinormal);

vec3 tangentNormal = texture2D(u_TexN, uv).xyz * vec3(2) - vec3(1);
tangentNormal.xy *= 1.0;
vec3 finalNormal = normalize(vec3(tangentNormal.x) * worldTangent + vec3(-tangentNormal.y) * WorldBinormal + vec3(tangentNormal.z) * worldNormal);

结果

image

添加后

结束

上一篇教程:【WebGL】从零开始的Web端渲染器(2)——添加阴影

下一个教程:【WebGL】从零开始的Web端渲染器(4)——贴图及阴影抗锯齿(还在写)