Android最全OpenGL 3D渲染技术:切线空间与法向量变换,2024年最新面试经验分享平台

学习宝典

对我们开发者来说,一定要打好基础,随时准备战斗。不论寒冬是否到来,都要把自己的技术做精做深。虽然目前移动端的招聘量确实变少了,但中高端的职位还是很多的,这说明行业只是变得成熟规范起来了。竞争越激烈,产品质量与留存就变得更加重要,我们进入了技术赋能业务的时代。

不论遇到什么困难,都不应该成为我们放弃的理由!

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我针对Android程序员,我这边给大家整理了一套学习宝典!包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

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

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

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

当然也可以做6个法向图,每个面用自己的那个,也能解决这个问题,但是这样又会引入一些缺点,比如资源占用比较大,现在一般都不这样做了,那么只有一张法向纹理的情况下,是怎么实现在不同的面上能正确使用的呢?这里的关键点就在于,存储在里面的法向量的数据,它不是模型坐标系下的,直接读出来并不能使用。

切线空间

为了能实现法向量在不同的面上都能正确使用,引入了切线空间(Tangent Space)这个概念,特别注意一点,切线空间定义在每个三角面片上,切线空间通常也叫作TBN坐标系,T是切线(Tangent),B是副切线(BiTangent),N是法线(Normal),T与纹理坐标系的U轴相同,B与纹理坐标系的V轴相同,法向贴图中的RGB颜色值读出来后,从01范围稍微转换一下到-11,就是对应切线空间中的xyz

WX20210529-215229@2x.png

tips:法向纹理中存储的法向量是切线空间下的,和模型具体如何旋转无法,大致都是指向切线空间的z轴方向,因此z分量也就是RGB的B分量一般要比其它2个分量要大很多,因此法向纹理通常看起来是偏蓝色的。

那么问题来了,既然法向贴图中的RGB颜色值是表示切线空间中的xyz,也就是说它是切线空间下的法向量,而光源位置、模型顶点都在模型坐标系下,怎样才能用这个切线空间下的法向量呢?那就要把他们转换到同一个坐标系下。这里就要用到线性代数的一个知识点,如果知道一组坐标基在另一个坐标系下的表示,那用这组坐标基下的坐标乘上这组坐标基在另一个坐标系下的表示,就可以将这组坐标基下的坐标转换到另一个坐标系下,听起来有点绕,给大家找了一个学习资料:zhuanlan.zhihu.com/p/69069042

具体到我们这里,TBN就是一组在模型坐标系下的基向量,因此用TBN去变换法向纹理中的法向量,结果就是得到了模型坐标系下的法向量,这样就都在同一个坐标系中,之后就可以使用了,比如都变换到世界坐标系中去。

那么如何计算得到TBN三个向量呢?网上也有不少的推导文章,网上大多数文章的推导过程,是以T与U方向相同、B与V方向相同从而构造出2个纹理坐标转换到模型顶点坐标的方程,从而求解出T和B的,本篇文章也是类似,但是这样直接推导,会造成些疑惑,至少我看起来会有一些疑惑,也有可能是我个人没理解,但我想跟大家分享一下自己的理解。

这里是一篇LearnOpenGL中的推导:learnopengl-cn.github.io/05%20Advanc… 网上很多文章都是这样推导的,先来看两张图:

企业微信20210524-132417@2x.png

T与U方向相同、B与V方向相同,由这两张图可得:

企业微信20210524-132556@2x.png

单看这步其实也好理解,E是模型坐标系中两个顶点之间构成的向量,这两个式子含义其实就是前面提到的线性代数的知识点,它将纹理坐标系下的表示,通过T和B的变换,转成了模型坐标系下的表示,换句话说,T和B的作用就是把纹理坐标转换成了模型坐标。再回想我们一开始要的是什么,我们是想要这样一个TBN,它能把切线空间下的法向量转换成模型坐标系下的法向量,而我们求解T和B时却是用纹理坐标转换成模型坐标建议等式来建立约束,看起来我们是在求一个能把纹理坐标转换成了模型坐标的TB。

为什么这样求得的T和B,再加上N,为什么就能实现能把切线空间下的法向量转换成模型坐标系下的法向量?

这一点我没看到有文章能说清楚,这里我尝试按我的理解来解释下,重新强调一下我们的求解目标:求TBN坐标系的三个基向量,用它能把切线空间下的法向量转换成模型坐标系下的法向量。

WX20210530-084429@2x.png

我的思路是对于模型的某个三角形,把法向量纹理图按这个三角形的对应的纹理坐标对齐,再把T轴B轴分别与U轴V轴对齐,大概会得到上图第3个图那个样子,这时TBN坐标系就固定了下来,就是我们最终要求的,只是现在还不知道TBN三个坐标基是多少,然后我们把TBN坐标系及法向纹理图直接抽走,保留这个形态,得到上图最右边的样子。那么对于那个黄色三角形以及绿色的法向量来说,从上图第3个图到上图最右图的转换实际上就是从切线空间到模型坐标系的转换,转换的方法就是前面提到的线性代数知识点,将TBN坐标系下的坐标值,用TBN三个基在模型坐标系中对应的值来变换。

现在要求TBN,我们来列些式子,我们不知道绿色的法向量变换后的坐标,因为它就是我们最终想通过TBN变换求得的,但是我们知道黄色三角形变换后的坐标,也就是三角形对应的模型顶点在模型坐标系下的坐标,这样就能通过把TBN坐标系下的那个三角形变换到模型坐标系下的那个三角形建立变换等式,也就是前面这个式子:

企业微信20210524-132556@2x.png

通过这两个式子就能求解出T和B:

企业微信20210527-130406@2x.png

求解过程并不复杂,就是简单的线性代数计算,LearnOpenGL中步骤也比较清楚了,这里不再展开。求得T和B之后,再拼上模型的法向量,就得到TBN矩阵,此时就求解完成了。

虽然说最后得到的这个式子不算太复杂,但是如果在计算光照时,每个片元的都这么算一下,开销还是不少的,毕竟fragment shader的执行次数会比较多,一般来说有2个优化策略:一个将TB提前算好,另一个是将光照计算转换到切线空间进行。

提前计算TB

大家仔细观察可以发现,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);

要如何成为Android架构师?

搭建自己的知识框架,全面提升自己的技术体系,并且往底层源码方向深入钻研。
大多数技术人喜欢用思维脑图来构建自己的知识体系,一目了然。这里给大家分享一份大厂主流的Android架构师技术体系,可以用来搭建自己的知识框架,或者查漏补缺;

对应这份技术大纲,我也整理了一套Android高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,希望能帮你突破瓶颈,跳槽进大厂;

最后我必须强调几点:

1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。

你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境

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

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

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

习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值