OpenGL 3D渲染技术:切线空间与法向量变换

大家仔细观察可以发现,TB的计算仅依赖模型中的数据,因此可以提前算好存储在模型中,glTF格式也支持这种做法,以下是glTF格式attribute中对tangent数据的说明:

NameAccessor Type(s)Component Type(s)Description
TANGENT"VEC4"5126 (FLOAT)XYZW vertex tangents where the w component is a sign value (-1 or +1) indicating handedness of the tangent basis

来源:github.com/KhronosGrou… 看一些glTF官方的样例模型也能找到这样的字段:


“meshes” : [
{
“name” : “TwoSidedPlane”,
“primitives” : [
{
“attributes” : {
“NORMAL” : 2,
“POSITION” : 1,
“TANGENT” : 3,
“TEXCOORD_0” : 4
},
“indices” : 0,
“material” : 0,
“mode” : 4
}
]
}
]

注意glTF只支持tangent,不支持同时存tangent和bitangent,我猜测原因是bitangent能够通过normal和tangent叉乘得到,所以不需要存,如果能存的话也能少掉这个叉乘的计算,但模型也会变大些。

将光照计算转换到切线空间

要得到法向纹理中的法向量,要对法向纹理进行采样,最简单直接的做法就是在fragment shader中对一个片元计算光照时从法向纹理中采样得到法向量,再用TBN矩阵转换到模型坐标系,最终用模型矩阵转到世界坐标系中计算光照,但由于fragment shader执行的次数一般比较多,就会导致这样的TBN矩阵变换次数较多,有一个常用的优化方法,就是将光照计算放到切线空间中:

// vertex shader

out vec3 v_tangentLightPos;
out vec3 v_tangentViewPos;
out vec3 v_tangentFragPos;
void main() {

mat3 normalMatrix = transpose(inverse(mat3(u_modelMatrix)));
// 将TBN三个坐标轴转到世界坐标系
mat3 worldTBNMatrix = mat3(
normalize(normalMatrix * tangent),
normalize(normalMatrix * bitangent),
normalize(normalMatrix * a_normal.xyz)
);
// 将模型坐标系下的顶点坐标转换到世界坐标系
vec3 fragPos = (u_modelMatrix * a_position).xyz;
// 求世界坐标系下TBN三个坐标轴矩阵的逆矩阵
mat3 inversedWorldTBNMatrix = transpose(worldTBNMatrix);
// 将光源位置从世界坐标系转换到切线空间
v_tangentLightPos = inversedWorldTBNMatrix * u_lightPos;
// 将观察点位置从世界坐标系转换到切线空间
v_tangentViewPos = inversedWorldTBNMatrix * u_viewPos;
// 将模型顶点坐标从世界坐标系转换到切线空间
v_tangentFragPos = inversedWorldTBNMatrix * fragPos;

}

// fragment shader

in vec3 v_tangentLightPos;
in vec3 v_tangentViewPos;
in vec3 v_tangentFragPos;

void main() {

// 从法向纹理中读取法向量坐标,转成-1~1范围并归一化
vec3 normal = texture(normalTexture, v_texCoord0).rgb;
normal = normalize(normal * 2.0 - 1.0);
// 光源方向
vec3 lightDir = normalize(tangentLightPos - tangentFragPos);
// 视点方向
vec3 viewDir = normalize(tangentViewPos - tangentFragPos);
// 反射光方向
vec3 reflectDir = reflect(-lightDir, normal);
// 光照计算

}

vertex shader中一开始求了一下模型矩阵的逆矩阵,然后又转置了一下,是干什么用的?这个矩阵是为了解决有些情况下将模型的法线与顶点一起变换后,法线不再垂直于三角形平面的问题。

举个简单的例子:

WX20210529-170130@2x.png

假设有左图中的三角面片及法向量,施加一个模型矩阵变换,此模型矩阵的变换为将x缩放0.5,缩放后法向量n=(0.5,1,1)A'C=(-0.5,0,1),显然n与A’C已经不垂直了,因此变换后法向量不再垂直于这个三角面片。为什么将模型矩阵的逆矩阵转置了一下能解决这个问题?给大家找了一篇文章:zhuanlan.zhihu.com/p/72734738, 感兴趣的朋友可以去研究下。
我们把tangent和bitangent也像a_normal那样用normalMatrix变换到世界坐标系,变换后的tangent、bitangent和a_normal组成的矩阵我称为worldTBNMatrix,它的作用从原来的从切线空间转换到模型坐标系变成了从切线空间转换到世界坐标系,为什么?回想文章前面提到的线性代数知识点,变换后的tangent、bitangent和a_normal已经是世界坐标系下的了。我们的目标是希望在切线空间计算光照,现在有了一个能从切线空间转换到世界坐标系的矩阵,那么它的逆矩阵就能从世界坐标系转换到切线空间,又因为worldTBNMatrix是由三个两两垂直的坐标基组成的,因此它是正交矩阵,由线性代数基础知识可知道正交矩阵有一个性质,就是正交矩阵的逆矩阵等于它的转置,因此直接转置就得到了它的逆矩阵,省去了复杂的求逆计算。v_tangentLightPos、v_tangentViewPos和v_tangentFragPos通过varying的方式传递到fragment shader中,由于v_tangentLightPos和v_tangentViewPos分别对于一个三形的3个顶点是一样的,所以插值后也还是一样的,而v_tangentFragPos是会在3个顶点坐标之间插值得到不一样的坐标。
上面的过程稍微有些复杂,我画张图表示下:
WX20210529-221629@2x.png
好了,这篇文章就先说到这,谢谢阅读!
参考:
learnopengl-cn.github.io/05%20Advanc…
zhuanlan.zhihu.com/p/69069042
zhuanlan.zhihu.com/p/72734738

尾声

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。

不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

image

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《2017-2021字节跳动Android面试历年真题解析》

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

bs.csdn.net/topics/618156601)**

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值