要实现的效果
系列教程
- 【WebGL】从零开始的Web端渲染器(1)——框架搭建
- 【WebGL】从零开始的Web端渲染器(2)——添加阴影
- 本篇
- 【WebGL】从零开始的Web端渲染器(4)——贴图及阴影抗锯齿(还在写)
思路设计
原理
法线贴图其实是很早的技术了,具体教程可以看下面的OpenGL链接
教程网址:https://learnopengl-cn.github.io/05%20Advanced%20Lighting/04%20Normal%20Mapping/
本质就是,法线贴图储存的是切线空间的法线信息,使用它需要将切线空间转换为世界空间,而确定切线空间必须要两个轴向,一个法线,另一个直接用UV决定就好了。
设计
- 在模型导入时,解码计算出切线,并且储存在模型中,渲染时也传入显卡;
- 在顶点着色器中,计算出法线切线与福切线,并且传入片元着色器;
- 在片元着色器中,使用切线空间计算出世界空间法线,并且使用这个法线计算光照;
实现
计算切线
这里直接学的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
|
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);
|
结果
结束
上一篇教程:【WebGL】从零开始的Web端渲染器(2)——添加阴影
下一个教程:【WebGL】从零开始的Web端渲染器(4)——贴图及阴影抗锯齿(还在写)