导航:首页 > 源码编译 > unity顶点移动计算法线

unity顶点移动计算法线

发布时间:2023-01-10 14:16:22

A. Unity Shader 获取深度纹理和法线纹理

深度纹理实际就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是一个高精度的深度值。由于被存储在一张纹理中,深度纹理里的深度值范围是[0, l], 而且通常是非线性分布的。这些深度值来自于顶点变换后得到的归一化的设备坐标 (Normalized Device Coordinates , 简称NDC) 。一个模型要想最终被绘制在屏幕上,需要把它的顶点从模型空间变换到齐次裁剪坐标系下,这是通过在顶点着色器中乘以 MVP 变换矩阵得到的 。在变换的最后一步,我们需要使用一个投影矩阵来变换顶点,当我们使用的是透视投影类型的摄像机时,这个投影矩阵就是非线性的。

下图 显示了Unity 中透视投影对顶点的变换过程。最左侧的图显示了投影变换前,即观察空间下视锥体的结构及相应的顶点位置,中间的图显示了应用透视裁剪矩阵后的变换结果,即顶点着色器阶段输出的顶点变换结果 ,最右侧的图则是底层硬件进行了透视除法后得到的归一化的设备坐标(NDC)。需要注意的是,这里的投影过程是建立在Unity对坐标系的假定上的,也就是说,针对的是观察空间为右手坐标系,使用列矩阵在矩阵右侧进行相乘,且变换到NDC后 z分量范围将在[-1, l] 之间的情况。而在类似 DirectX 这样的图形接口中,变换后z分量范围将在[0, 1]之间 。如果是在其他图形接口下,需要对一些计算参数做出相应变化。

下图 显示了在使用正交摄像机时投影变换的过程。同样,变换后会得到一个范围为 [-1, 1]的立方体。正交投影使用的变换矩阵是线性的。

在得到NDC后,深度纹理中的像素值就可以很方便地计算得到了,这些深度值就对应了NDC中顶点坐标的z分量的值。由于NDC中 z分量的范围在[-1, I], 为了让这些值能够存储在一张图像中,需要使用下面的公式对其进行映射:

其中,d对应了深度纹理中的像素值, 对应了NDC坐标中的z分量的值。

在Unity中,深度纹理可以直接来自于真正的深度缓存,也可以是由一个单独的 Pass 渲染而得,这取决于使用的渲染路径和硬件。通常来讲,当使用延迟渲染路径(包括遗留的延迟渲染路径)时,深度纹理理所当然可以访问到,因为延迟渲染会把这些信息渲染到 G-buffer 。而当无法直接获取深度缓存时,深度和法线纹理是通过一个单独的Pass渲染而得 。具体实现是Unity会使用着色器替换技术选择那些渲染类型(即 SubShader RenderType 标签)为 Opaque 的物体,判断它们使用的渲染队列是否小于等于2500(内置的 Background Geometry AlphaTest 渲染队列均在此范围内),如果满足条件,就把它渲染到深度和法线纹理中。因此,要想让物体能够出现在深度和法线纹理中,就必须在Shader中设置正确的RenderType标签。

在 Unity中,可以选择让一个摄像机生成一张深度纹理或是一张深度+法线纹理。当选择前者,即只需要 张单独的深度纹理时, Unity 会直接获取深度缓存或是按之前讲到的着色器替换技术,选取需要的不透明物体,并使用它投射阴影时使用的 Pass (即 LightMode 被设置为ShadowCaster Pass)来得到深度纹理。如果 Shader 中不包含这样一个 Pass, 那么这个物体就不会出现在深度纹理中(当然,它也不能向其他物体投射阴影)。深度纹理的精度通常24 位或 16 位,这取决于使用的深度缓存的精度。如果选择生成一张深度+法线纹理, Unity 创建一张和屏幕分辨率相同、精度为 32 位(每个通道为 位)的纹理,其中观察空间下的法线信息会被编码进纹理的 通道,而深度信息会被编码进 通道。法线信息的获取在延迟渲染中是可以非常容易就得到的, Unity 只需要合并深度和法线缓存即可。而在前向渲染中,默认情况下是不会创建法线缓存的,因此 Unity 底层使用了一个单独的 Pass 把整个场景再次渲染一遍来完成。这个 Pass 被包含在 Unity 内置的一个 Unity Shader 中,可以在内置的builtin_shaders-xxx/DefaultResources/Camera-DepthNormaITexture.shader 文件中找到这个用于渲染深度和法线信息的 Pass。

获取深度纹理,先设置摄像机的 depthTextureMode:

然后在 Shader 中通过声明_CameraDepthNormalsTexture 变量来访问它。

同理,如果需要获取获取深度+法线纹理,设置摄像机的 depthTextureMode为:

然后在 Shader 中通过声明_CameraDepthN ormalsTexture 变量来访问它。

还可以组合这些模式,让一个摄像机同时产生一张深度和深度+法线纹理:

在 Shader 中访问到深度纹理_CameraDepthTexture 后,我们就可以使用当前像素的纹理坐标对它进行采样。绝大多数情况下,我们直接使用 tex2D 函数采样即可,但在某些平台(例如 PS3 PSP2) 上,我们需要 一些特殊 处理 Unity 为我们提供了一个统一的宏SAMPLE_DEPTH_TEXTURE, 用来处理这些由于平台差异造成的问题。而我们只需要在 Shader中使用 SAMPLE_DEPTH_TEXTURE 宏对深度纹理进行采样,例如:

其中,i.scrPos是在顶点着色器中通过调用ComputeScreenPos(o.pos)得到的屏幕坐标。上述这些宏的定义,可以在Unity 内置的HLSLSupport.cginc文件中找到。

当通过纹理采样得到深度值后,这些深度值往往是非线性的,这种非线性来自于透视投影使用的裁剪矩阵。然而,在我们的计算过程中通常是需要线性的深度值,也就是说,我们需要把投影后的深度值变换到线性空间下,例如视角空间下的深度值,我们只需要倒推顶点变换的过程即可。下面以透视投影为例,推导如何由深度纹理中的深度信息计算得到视角空间下的深度值。

当我们使用透视投影的裁剪矩阵 对视角空间下的一个顶点进行变换后,裁剪空间下顶点的z和w分量为:

其中,Far和Near分别是远近裁剪平面的距离。然后,我们通过齐次除法就可以得到NDC下的z分量:

而深度纹理中的深度值是通过下面的公式由NDC 计算而得的:

由上面的这些式子,可以推导出用d表示而得的 的表达式:

由于在Unity 使用的视角空间中,摄像机正向对应的z值均为负值,因此为了得到深度值的正数表示,我们需要对上面的结果取反,最后得到的结果如下:

它的取值范围就是视锥体深度范围,即[Near, Far]。如果我们想得到范围在[0, l]之间的深度值,只需要把上面得到的结果除以Far即可。这样,0就表示该点与摄像机位于同一位置,1表示该点位于视锥体的远裁剪平面上。结果如下:

其实,Unity提供了两个辅助函数来为我们进行上述的计算过程LinearEyeDepth 和LinearOlDepth。LinearEyeDepth 负责把深度纹理的采样结果转换到视角空间下的深度值,也就是我们上面得到的 。而Linear01Depth则会返回一个范围在[0, 1]的线性深度值,也就是我们上面得到的 。这两个函数内部使用了内置的_ZBufferParams变量来得到远近裁剪平面的距离。
如果需要获取深度+法线纹理,可以直接使用tex2D函数对_CameraDepthNormalsTexture 进行采样,得到里面存储的深度和法线信息。Unity提供了辅助函数来为我们对这个采样结果进行解码,从而得到深度值和法线方向。这个函数是DecodeDepthNormal,它在UnityCG.cginc 里被定义为:

DecodeDepthNormal 的第一个参数是对深度+法线纹理的采样结果,这个采样结果是 Unity 深度和法线信息编码后的结果 它的 xy 分量存储的是视角空间下的法线信息 而深度信息被编码进了 zw 分量。通过调用 DecodeDepthNormal 函数对采样结果解码后, 我们就可 得到解码后的深度值和法线。这个深度值是范围在[O l] 的线性深度值(这与单独的深度纹理中存储 深度值不同),而得到的法线则是视角空间下的法线方向。同样也可以通过调用 DecodeFloatRG和DecodeViewNormalStereo 来解码深度+法线纹理中的深度和法线信息。

B. 从条带状阴影谈Unity Shadow

不知道你曾经在Unity中遇到过阴影异常没有,比如近处马赛克状的阴影块?亦或是脱离投影物体的“悬浮”阴影?还是稍远处阴影的突然消失?多块阴影互相叠加处颜色异常的亮度等等。我感觉最让人无法直观理解,也相对常见的一种阴影异常要数“条带状阴影”了。请允许我暂时如此称呼它,因为这很形象。

要重现“条带状阴影”其实很简单,我们甚至可以在不修改任何Unity默认设置的情况下获得这种现象。做个实验,新建一个Unity工程(2019),创建2个Quad对象,然后上下堆叠起来,设置顶层Quad的高度(Y轴世界坐标)为0.05,设置底层高度为0,调节一下主方向光,使之保持垂直朝向的姿势,既Rotation: (90, 0, 0),一切OK的话大概能得到下图所示的表现,看起来还不错,姑且称之为 现象<0>

我们保持光照和平面位置不变,接下来我们要调节2个重要参数,叫 bias 和 normal bias ,Unity在标准渲染管线模式下,将这2个参数暴露在了Light组件有关shadow的设置参数中;URP模式则可以在 Render Feature的设置栏中找到它们俩。总之找到并设置 bias = 0 , normal bias = 0 ,我们再看看有什么变化:

简单盘点下,在具有一定倾斜角度直接光照下,观察两块相聚非常近的面片中,上方面板向下方面片的投影,我们会发现“条带状阴影”;在此基础上,我们将 bias 和 normal bias 这两个参数归零,面片间投射的阴影恢复正常,但是面片上原本非阴影区域又出现了“条带状阴影”。

无论如何,在解读之前,我们都应该明确这种现象是Unity在处理阴影效果时产生的,因为我们全程只调节了2个控制阴影显示的参数而已。而对于Unity或者一般游戏引擎是如何渲染物体投影效果这一部分,考虑到网上相关内容的资料非常丰富,这里就不再详细阐述了,凝练一下,可以把通用渲染逻辑整理为3个步骤,罗列如下:

步骤1略微展开一下,它涉及到将在光源位置放置摄像机,然后基于光源的视角生成深度图,场景中光源的阴影区也就是光源位置摄影机看不到的地方。ShadowMap本质上是一张深度图,它记录了从该光源的位置出发,能看到的场景中,距离光源最近的表面的深度值。

上图左侧为深度图。右侧在相机位置绘制场景时,能够看到场景中的点Va,其对应的ShadowMap中的位置为a,Va的深度不大于深度图中a位置存储的深度,因此Va不处于阴影

考虑到上述采用shadowmap技术渲染阴影的技术最早可以追述到1978年,人们已经对这项技术能给图形渲染带来的利与弊有了充分的认识。回到 现象<2> 来,在图形学中描述这类现象自然也有其惯用的术语,叫做“Shadow Acne”或者“Self-Shadowing”。这两个英文单词,前者可以翻译为“阴影粉刺”,后者则一般翻译为“自阴影”,一个表形,一个表意,可以说描述得挺好,后续我们还会提到,如果有兴趣了解更多细节,这里有一篇对各种阴影异常表现总结得很好的 技术博文 可以作为参考。

在有了一定的阴影纹理算法基础,以及对所使用数据结构有一定了解的前提下,我们不妨从复盘光照模型开始研究 现象<2> 的成因。

在PL上红色斑点的右侧标记了另一个黑色斑点,毫无疑问它也落在了可见区域中,而且由于光照是垂直向下的,光源到这个黑色斑点的最短距离和一旁的红色斑点应当是一致的,数值上等于图中红色连线的长度,既{Depth}。

当倾斜光照,使之与物体平面(PL)成一定角度相交后,事情就变得有意思起来了。我们都知道,由于透视原理,摄像机近处的物体(PL)相较于远处的物体(光源)会挤占更多的屏幕空间像素,而生成阴影纹理(ShadowMap)的光源空间也有类似的现象,只不过不是因为透视投影导致的(直接光照使用的是正交投影),而是由角度和分辨率共同作用导致的:

现在考察上图中黄色虚线填充的BCP三角形,在这块区域内的物体(比如顶点P1),到光源处的最短距离必然小于图中红色实线表示的深度值{Depth};同样的在黑色虚线填充的三角形PED中,所有点到光源的距离必然大于深度值{Depth}。根据光照投影算法的定义,在BCP三角形中的点P1,转换到光源空间后的深度值小于该处记录的光源深度,所以P1不在阴影中,这显然是期望的结果;然而另一侧三角形PED中的P2却在相同的算法中被分配到了阴影中,这显然是错误的结论。

通过前面的分析,我们看到,本该处于非阴影状态的三个平面PL上的点 P1、P和P2,在一定光照角度外加一定的纹理分辨率条件下,经过标准光照投影算法处理后会得出错误的结论(三角形PED区域的投影结果),而且导致投影错误的原因也以作图的方法进行的展示。为了能更直观,更量化的反应问题,我们不妨对上述图例作如下简化():

在上图中,黑色粗线表示场景中的平面,黄色粗线表示为光源所对应的近平面。该近平面会对应产生的ShadowMap。AB表示为这个近平面上对应ShadowMap上一个纹素的区域。假设近平面是一个规范尺寸的正方形,且边长为FSize。对应的ShadowMap分辨率被设置为SSize。那么AB所对应的坐标尺寸通过如下计算可获得: AB = FSize / SSize
我们希望通过某些方式,让纹素AB对应区域在物体平面上的投影CD部分,能够完整的处于图中蓝色虚线的左侧。解决的方法也很朴素直观,按道理只需将ShadowMap中存放的深度值稍微增大点即可,最好是大得刚刚好,恰巧确保CD段处于非阴影区域。

这是第一种修正,针对光线的方向进行偏移:

观察线段(向量)GD,这是我们按照光线方向的最短平移距离,蓝色虚线在平移后恰好位于线段CD的右侧,从而确保了CD段在计算阴影深度时,其在光源空间的深度能够小于ShadowMap采样深度(既图中的BD线段长度)。
求解GD:

除了沿着光线方向直接增加深度之外,还可以沿着法线方向作偏移,间接影响深度值,这就是我们接下来要研究的normal bias法线偏移。

法线偏移其实还分两种实现方式,一种是在将物体顶点转换到光源空间之前,先沿着法线正方向作移动,达到减小片元的深度,具体参考下图:

另一种是在生成ShadowMap时,将物体的顶点沿着法线反方向进行偏移,从而变相得增大阴影的深度值,具体参考下图:

先说第一种方式,它的作用机制不是直接修改阴影纹理的深度值,而是尝试缩小物体在光源空间的深度,因此需要在片元着色器中实现。参考图[normal 1],物体表面沿着自身法线方向移动了距离GM,获得新的位置C'G。将原本CM段的区域整体提升到了蓝色虚线的左侧。我们知道C'G区域的深度取值必然小于等于F点的深度值EF,现在会被光源正常照亮了;至于点G右侧的部分线段可以不用担心,它们将会被相邻的ShadowMap覆盖和处理。

此处的α指的还是光线方向和法线方向形成的夹角。

对于第二种方式,理解起来会直观一些,因为它直接作用于ShadowMap,修改(增加)纹素中的深度值。参考图[normal 2],修正后蓝色虚线向左下方偏移了距离FN, 从而确保CD段区域处于阴影深度线的左侧,被正常照亮。至于偏移量MN的计算,需要分为2步:

其中α指光线方向与法线的夹角;φ指光源方向与法线的夹角。

Unity引入2个bias变量作为控制阴影深度参数的理由,我想至少有2层。其一是增加了灵活性,让用户可以依据场景本身的特质调整光源方向或者法线方向的深度,试想如果Unity只提供了normal bias作为参数,在某些极端情况下,过大的normal bias会导致物体投射的阴影与物体本体脱离的现象(Peter Panning)。

至于另一层理由,可以参考上图,bias对于图中的tanθ曲线,normal bias对于sinθ曲线,θ是光源平面与物体平面的夹角。当光线方向与物体表面越加趋向平行时,光源平面越加垂直于物体平面,θ角越接近90度(π/2),此时使用bias计算得出的修正距离也将随着tan一起趋向无穷大(图中斜向上蓝色箭头)。若此时引入sinθ,则可以平衡bias的影响,这是因为很朴素的道理:sin函数是有上界的(图中蓝色水平箭头),我们可以在θ角小于45度(π/4)时优先使用bias的计算结果,而当θ角超过45度后,将计算偏移距离的权重倒向normal bias一边。

也许有人会问,既然bias计算出的偏移会趋于无穷大,为何不只用normal bias呢?对此之前已经有了回答:我们需要避免Peter Panning现象。

Normal Bias的两种移动方式,在实际效果上并不完全相同。采用在顶点着色器中改变ShadowMap深度值的方式,会在某些情况下导致Normal Bias失效。参考上图,最左侧为产生阴影的普通情况,由于整个单元内采用C点的深度进行比较,所以ED段会错误产生阴影。中间示意图为顶点着色器增加 ShadowMap 深度的做法,在这种方法中,已知从C到D的深度差距记为d,光线和AB段的法线角度为θ,则最小的消除Shadow Acne的沿法线反方向的距离计算为:

在这种情况下,当θ趋向于90°(即光线与AD近乎平行时),最小的距离趋向于无穷大,Normal Bias就很难达到消除Acne的大小。右侧示意图为片元着色器移动片元位置的计算方法,在角度变化的情况下也不会出现最小距离趋近无穷大的情况。但在片元着色器上进行这部分计算,就比前一种方式要更多的计算量。

条带状的“亮暗”渐变,在Unity2019.4.13版本下,使用标注渲染管线,开启软阴影后截屏。

“粉刺”状或者“锉刀”状的暗斑,在Unity2019.4.13版本下,使用标注渲染管线,关闭软阴影后截屏。

吐槽:曾经我也很好奇,为何自阴影的别名叫做 Shadow Acne(阴影粉刺),而不是更加常见的阴影条带。直到我关闭了软阴影并清空了bias修正后看到上图。确实这才是二维格子状纹理出问题后该有的表现,就像下图这样,每个独立的纹素所对应的区域应当被分割成了正方形(正交投影),在二维平面上遇到一定角度的入射光,形成了上图这种规则排列的“粉刺”状暗斑。

进过了上面的分析,我们知道 现象<2> 的出现本质是由于标注阴影算法的缺陷,导致物体自己产生的阴影纹理遮蔽了自己(部分),也就是所谓的“自阴影”。现在我们来看看 现象<1> 的成因,不妨先从2种现象在条件上的差异开始入手。

当场景中存在正常产生阴影的物体时(上图蓝色矩形表示障碍物),全局添加一个Bias,在上图情况中解决了 Self Shadow 的问题,但是正常应该产生的阴影也被消除了。当Bias从小到大逐渐变化时,对一个正常产生阴影的物体来说,它的阴影表现为由靠近物体的一端开始,逐渐消失。这种现象有个专用术语,叫“漏光”(Light Bleeding)。

接下来,我们通过上图复现文章开头引入 现象<1> 的场景布局。还是倾斜入射,这次在物体平面(PL)的正下方多了一层新的平面(UD)。如果不对阴影深度进行修正(bias皆为0),那么来自阴影纹理中像素A投射的等深线CD会与PL平面相交于点P,此时我们在PL上能看到自阴影效果,既图中由点BCP包围的黄色三角形以及由点PDH围成的黑色三角形区域。当开启bias后,等深线CD分别沿着光线方向和物体法线的反方向做了2次偏移(图中红色箭头),并来带了线段GI所在之处。当UD平面与PL平面距离足够近时,线段GI将于UD平面相交于点P2,同时形成由点EGP2和IJP2围成了2块不同区域。此时我们说在平面UD上出现了“漏光”,既本来不该被照亮的区域(EP2)现在被错误的照亮了。

取消2个bias,有可能消除下层平面UD上投影异常效果。参考上图线段CD所代表的阴影纹理等深平面,并没有出现在平面UD上EJ段的右侧(或者说与线段EJ没有相交),可见这种情况下,UD上能正确显示来自PL的投影。

还是在取消bias前提下,当我们进一步缩小2个平面之间的距离,可以再次复现异常,此时上下层平面可以近似认为是一个平面,下层平面UD会产生源自PL的自阴影。

现象<1> 成因可以说是Unity为了解决 现象<2> ,在引入bias后间接导致的。下面是我总结的一些有助于减缓 现象<1> 的办法,和大家一起分享,当然问题的解决方案是开放的,如果大家有其他好方案,希望不要吝啬得分享出来。

现象<1> 的成因分析,它只会出现在光照方向与物体表面成较小夹角时,我们可以通过合理的布置场景中会导致此类现象的物件位置,优化它们的迎光角度,从而避免出现透光现象。

如果我们对出现 现象<1> 的问题物体投影质量要求不高,完全可以取消该物体的投影选项(不参与阴影纹理的生成,但是可以被动接受阴影),然后使用结构相对简单的投影代理来为该物体实际生成阴影。简言之,我们解决不了问题,那就解决产生问题的物体就好。

如上图中绿色箭头,我们需要通过某种方法,在片原阶段,将平面UD沿着其法线方向的反方向做一次小偏移,使之远离被下压过来的等深线段GI。这里面有个难点,我们如何判断一个像素需要做额外偏移?或者说我们该如何知道物体平面UD需要做额外偏移,而不是物体平面PL呢?一种最简单的方法是把问题丢给CPU,在预处理阶段对不同顶点进行标记,运行时带入到GPU中参与计算。

C. unity shader 顶点变化之后法线怎么办

BumpMap一种是Emboss Bump Map(浮雕凹凸贴图),它使用的是Height map,原理是在原始图像的基础上,对高度场图像进行复制、轻微平移、差操作。
但它存在很多严重的局限性,它只能用于漫反射表面,对于镜面高光来说是不可能的。

D. Unity Shader:相关数学基础

本文同时发布在我的个人博客上: https://dragon_boy.gitee.io

有左手坐标系和右手坐标系两种,Unity使用的是右手坐标系。

我们使用两个或三个以上的实数来表示一个点的坐标,如 。

矢量是指n维空间中一种包含了模和方向的有向线段。矢量的表示方法和点类似,如 。

矢量通常由一个箭头表示,由起点指向终点。矢量常被用于表示相对于某个点的偏移,只要矢量的模和方向保持不变,无论在哪里,都是同一个矢量。

对乘法,将矢量的每个分量和标量相乘即可:

对除法,同理: ,标量需非零。

两个矢量加减法,把对应的分量进行相加或相减即可:

几何意义上,矢量相加即前者起点连接至后者终点,矢量相减即后者终点指向前者终点。

矢量的模即矢量的长度:

单位矢量即模为1的矢量。将某个非零矢量转化为单位矢量的过程称为归一化。

矢量之间的乘法有两种,点积和叉积。

点积公式有两个:
1:

2( 为两个矢量的夹角):

点积的结果在几何意义上是获得矢量 在 上的投影与 的模的乘积,如果二者都是单位矢量,那么点积结果就是前者在后者上的投影。

叉积公式:

下面是另一种表示:

上述表明矢量叉积的结果的模是两矢量构成平行四边形的面积。

在几何意义的上,叉积的结果是一个新的矢量,分别垂直于 和 ,且沿 、 和叉积的方向可构成一个右手坐标系。

矩阵有行列之分,以 矩阵为例:

矢量可以看作时 的列矩阵或 的行矩阵。

将矩阵的每个元素和标量相乘即可。

两个矩阵相乘,要求前一个矩阵的列数等于后一个矩阵的行数,相乘得到的矩阵的行数是第一个矩阵的行数,列数是第二个矩阵的列数。如 的维度是 , 的维度是 ,那么 的维度是 。

矩阵的乘法即前一个矩阵的每一行( 行)与后一个矩阵的每一列( 列)相乘(可看作行构成的矢量和列构成的矢量点积),每次相乘的结果填写在对应的 行 列上。

注意,矩阵乘法不满足交换律,但满足结合律。

方阵即行列数相等的矩阵。

同时,如果除对角线的元素外全是0的方阵称为对角矩阵。

针对上述的对角矩阵,如果对角线的元素全是0的话,那么就称为单位矩阵。

对于一个 的矩阵 ,它的转置 为 的矩阵。转置运算即将原矩阵的行列翻转。原矩阵的 行变为 列, 列变为 行。

注意,矩阵的转置的转置等于原矩阵。

矩阵的串接的转置等于反向串接各个矩阵的转置:

只有方阵才有逆矩阵。

一个矩阵和它的逆矩阵的乘积为单位矩阵:

零矩阵没有逆矩阵。如果一个矩阵有逆矩阵,那么这个矩阵是可逆的,或非奇异的,相反则是不可逆的或奇异的。

如果一个矩阵的行列式为不为0,那么该矩阵就是可逆的(具体不解释)。

注意,逆矩阵的逆矩阵为原矩阵。

单位矩阵的逆矩阵是它本身。

转置矩阵的逆矩阵是逆矩阵的转置:

矩阵串接相乘后的逆矩阵等于反向串接各个矩阵的逆矩阵:

注意,如果某个矩阵或矢量进行了一个矩阵变化,那么使用逆矩阵可以还原这个变换:

如果一个方阵和它的转置矩阵的乘积是单位矩阵,那么这个方阵就是正交矩阵:

将正交矩阵与逆矩阵的概念结合起来,就可以得到,如果一个矩阵正交,那么它的转置矩阵和逆矩阵相等:

上述式子在实际计算中非常有用,因为逆矩阵的计算量很大(涉及伴随矩阵),所以如果能判断某个变换矩阵是正交的话,可以很简单地用转置矩阵代替它的逆矩阵进行计算。

那么如何判断一个矩阵是否正交,我们把一个矩阵的列看作是一个矢量,称为基矢量,那么根据正交矩阵定义:

根据上述等式,得:

, , 都是单位矢量,且两两垂直。那么满足这样条件的矩阵的就是正交矩阵。

在Unity中,常将矢量当做是列矩阵来使用,即放到矩阵的右边来进行乘法。这种情况下,矩阵乘法通常是右乘,即:

变换是将一些数据通过某种方式进行转换的过程。

一个非常常见的变换是线性变换,指的是可以保留矢量加和标量乘的变换。数学公式为:

缩放是一种线性变换,旋转也是一种线性变换,我们可以使用一个 的矩阵就可以对一个三维向量进行线性变换。除此之外还有错切,镜像,正交投影。

但平移变换不是线性变换,我们不能使用一个 的矩阵对一个三维向量进行变换。

由此出现仿射变换,即合并线性变换和平移变换的变换类型。仿射变换使用一个 的矩阵来表示,这样我们需要将矢量扩展到四维空间下,即齐次坐标空间。

对于一个三维向量,我们扩展一个维度,分量称为 。对于坐标, 常设为1,而对于方向,常设为0。因为我们只想对位置进行平移变换,方向不行。

我们使用一个 的矩阵来表示平移、旋转和缩放。一个基础的变换矩阵可以分解为4个部分:

表示旋转缩放的矩阵, 表示平移。

下面是一个平移矩阵(针对点)的例子:

如果是方向矢量的话,由于 分量为0,平移变换不会有影响:

平移变换的逆矩阵就是反向平移得到的矩阵:

平移矩阵并不是正交矩阵。

下面是一个缩放矩阵(针对点)的例子:

对方向矢量缩放:

如果 ,那么这样的缩放为统一缩放,否则为非统一缩放。由于非统一缩放会改变角度和比例信息,所以针对方向向量的缩放不能使用上述的方式。

缩放矩阵的逆矩阵如下:

缩放矩阵不是正交矩阵。

上面的矩阵只适合沿坐标轴方向缩放,如果要在任意方向缩放,就要使用一个复合变换,先缩放轴,再沿坐标轴缩放。

绕x轴旋转:

绕y轴旋转:

绕z轴旋转:

旋转矩阵是正交矩阵。

大多数情况下,将平移、旋转、缩放结合起来的顺序往往是:先缩放,后旋转,再平移,也就是:

注意,针对旋转的顺序,Unity的顺序是zxy,即旋转变换矩阵是:

旋转时的坐标系有两种可以选择:

在上述两种方式中,旋转的结果不一样,Unity是第一种。

模型空间即模型的局部坐标系,在Unity中是左手坐标系。

世界空间被用来描述物体在场景中的位置,在Unity中,世界空间是左手坐标系。

将顶点从模型空间变换到世界空间的变换称为模型变换(Model)。这一变换通常由 组成。

也被称为摄像机空间。

摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点,在Unity中,+x指向摄像机的右方,+y指向摄像机的上方,+z指向摄像机的后方,即摄像机指向-z轴。

将顶点从世界空间转换到观察空间的变换为观察变换(View)。

为得到顶点在观察空间中的位置,有两种方法:一是计算观察空间的三个坐标轴在世界空间下的表示,然后构建出从观察空间变换到世界空间的变换矩阵,再对矩阵求逆来得到从世界空间变换到世界空间的矩阵。二是想象平移整个观察空间,让摄像机的原点位于世界坐标的原点,坐标轴与世界空间的坐标轴重合即可。

这里使用第二种方法,即想办法让摄像机变换到世界空间的原点即可(注意,由于观察空间使用右手坐标系,因此需要对z分量取反)。

将顶点从观察空间转换到裁剪空间的变换矩阵称为裁剪矩阵,也被称为投影矩阵(Projection)。裁剪空间由视锥体决定。

视锥体是空间中的一块区域,视锥体内的区域是摄像机可以看到的空间。视锥体由六个平面构成,这些平面称为裁剪平面。视锥体有两种类型,透视投影和正交投影。

在视锥体的裁剪平面中有两块很特殊,分别称为近裁剪平面和远裁剪平面,它们决定了摄像机可以看到的深度范围。

投影矩阵有两个目的:

我们利用上图的参数可以计算出近裁剪平面和远裁剪平面的高度:

裁剪平面的宽度我们可以通过横纵比得到。假设摄像机的横纵比为 :

根据上述信息,投影矩阵为:

使用上述投影矩阵后:

可以看到,投影矩阵本质上是对x、y、z坐标进行一些缩放,同时z分量也进行了平移,缩放的目的是为了方便裁剪。此时w分量不再是1,而是-z,通过这个w,我们可以判断一个顶点是否在视锥体内,在的话必须满足下面条件:

透视投影后,空间从右手坐标系变为左手坐标系。

假设横纵比为 ,那么:

正交投影矩阵:

使用正交投影矩阵后:

使用正交投影后,w分量仍是1。

这一步的变换,我们会得到真正的像素位置。

将顶点从裁剪空间投影到屏幕空间有两个步骤:首先进行透视除法,用x、y、z分量除以w分量。这一步得到的坐标被称为NDC,经过透视除法的裁剪空间变换到一个立方体内:

对于正交投影没什么区别。

在Unity中,屏幕空间左下角的像素坐标是(0,0)。接下来就是将NDC缩放值屏幕坐标系。

透视除法和屏幕映射的过程总结如下:

法线是一个方向矢量,垂直于表面,与其垂直的一个矢量被称为切线。

切线由两个顶点之间的差值计算得来,因此可以使用变换顶点的变换矩阵来变换切线。假设不考虑平移变换,变换后的切线为:

不过,如何直接这么变换的话,法线就有可能不垂直于表面了:

下面我们可以来推导一下正确的法线变换矩阵。

首先,切线与法线垂直,即 ,假设变换法线的矩阵为 ,变换后法线仍与切线垂直,那么:

经推导得:

由于 ,如果 ,则上式成立,即 。

如果变换矩阵 是正交矩阵,那么G = M_{A->B},当然,这限于只包含旋转变换。如果只包含旋转和统一缩放,那么 。

内置变量: Built-in shader variables
内置方法: Built-in shader helper functions

E. 如何在Unity3D中计算“法向量A向任意法向量B旋转,使得两个向量夹角减小N度后的A向量”

其实我不太明白你究竟想要什么……
如果只是返回zero或者B的话,我只需要判断AB方向就可以了,何苦还要计算旋转呢?
判断方向很容易,可以使用
Vector3.Angle()

靠拢的话,有这样一个函数
public static Vector3 RotateTowards(Vector3 current, Vector3 target, float maxRadiansDelta, float maxMagnitudeDelta);

第一个参数是原向量A,第二个是目标向量B,第三个是角度,第四个设置成0.0f就可以了。
(当然,A,B都是单位向量的话。)
但是这个函数看起来是这样的意思,我并没有实践过。

可参考:http://docs.unity3d.com/ScriptReference/Vector3.RotateTowards.html

F. 怎样能将unity3dcube的顶点坐标移动,让正方形成为一个梯形

我用C#写的脚本,JavaScript你自己改一下语法吧
首先那个Cube要有一个Collider,相当于一个碰撞检测范围
你创建了Cube之后点击Add Componet,点Physics,然后点Mesh Collider创建Collider
在Collider的面板中,修改Mesh为你当前的这个Cube
有了Collider便可以在脚本中检测鼠标移进移出
public class MouseOverTest : MonoBehaviour {

bool isShowTip;
// Use this for initialization
void Start () {
isShowTip=false;
}

void OnMouseEnter () {
isShowTip=true;
}

void OnMouseExit () {
isShowTip=false;
}

void OnGUI () {
if (isShowTip){
GUI.Label(new Rect(Input.mousePosition.x,Screen.height-Input.mousePosition.y,100,40),"Tips!!");
}
}
}

阅读全文

与unity顶点移动计算法线相关的资料

热点内容
phpsae源码 浏览:853
为什么安卓手机一直要权限 浏览:227
汇编程序的伪指令 浏览:803
苹果7怎么更新app 浏览:318
c语言常用算法pdf 浏览:960
编程如何让画面动起来 浏览:865
大龄女程序员未来发展 浏览:976
数学书籍pdf 浏览:506
加密门禁卡写入成功无法开门 浏览:464
齿轮传动pdf 浏览:52
alpinelinux 浏览:150
手机端app的扫码功能在哪里 浏览:227
少儿编程中小班英语教案 浏览:452
锁屏密码加密手机怎么解除 浏览:205
linuxlostfound 浏览:135
征途服务器ip地址 浏览:330
git提交代码命令行 浏览:165
什么叫浏览器服务器结构 浏览:157
于谦聊天哪个app 浏览:449
小鹏汽车nlp算法工程师薪资 浏览:881