文章目录
-
- 第3章 Shader(着色器)中用到的各种空间概念
-
- 3.1 模型空间
-
- 3.1.1 为什么用模型空间
- 3.1.2 在脚本和Shader中进出模型空间
- 3.2 世界坐标空间
-
- 3.2.1 统一表达:世界坐标空间
- 3.2.2 在脚本和Shader中进出世界坐标空间
- 3.3 视空间
-
- 3.3.1 渲染的需要: 视空间
- 3.3.2 在脚本和Shader中进出视空间
- 3.4 空间的一块: 视椎体
- 3.5 剪切空间
-
- 3.5.1 投影
- 3.5.2 脚本和Shader中的投影矩阵
- 如果只是为了编程下面的可以不必看,或者知道这一回事就行,Unity公司花这么多人力物力做出来的你就相信人家吧,当然从学术的角度来说,知道还是有点好处的。
-
-
- 3.5.3 验证NDC
- 3.5.6 NDC之后
-
- 3.5.6.1 NDC之后发生的事情
-
第3章 Shader(着色器)中用到的各种空间概念
说到空间的概念,这里所有的空间都是三维欧几里得空间,也就是三个坐标轴x、y、z的形式,在图形学中为了矩阵计算的方便,外加一个w,也就是齐次坐标表示方法,这个W经常被用作除数,如果不理解的话先把它当成变量1或者无视它
3.1 模型空间
3.1.1 为什么用模型空间
在建模软件中,为了表示的方便性和实用性,每一个物体都有一个以它自己的Pivot为原点的欧几里得三维坐标空间。
3.1.2 在脚本和Shader中进出模型空间
在Unity的脚本中,可以通过transform.worldToLocalMatrix这个矩阵的MultiplyPoint()或者MultiplyVector()方法,把世界坐标表达的矢量转换为此物体的模型空间表达的矢量。在Shader中,可以通过左乘_World2Object这个矩阵来完成此任务。
3.2 世界坐标空间
3.2.1 统一表达:世界坐标空间
模型空间在处理物体自身的一些相对关系时比较有用,如期自身的面、线、点等,但是在更高一个层级上,比如我们有多个物体,这是为了能够表达这些模型的相对关系,就需要一个统一空间坐标,这就是世界坐标空间。
3.2.2 在脚本和Shader中进出世界坐标空间
假设物体上有一个顶点,出于方便,这个点是被以此物体自身的模型空间坐标来表达的,如果我们想让它和另一个物体上的一个点作比较,比如相对位置该怎么办呢?很显然,世界坐标可以把这两个点表达为统一的形式。在脚本中,物体的Transfrom组件的localToWorldMatrix矩阵的MultiplyPoint和MultiplyVector方法能把此物体自身以模型空间坐标表达的矢量变换为以世界坐标表达的矢量。在Shader中,对应的矩阵是_Object2World.
3.3 视空间
3.3.1 渲染的需要: 视空间
视空间,又称相机空间是为了方便表达以相机为世界的中心时,所有物体的相互关系的一个空间。
3.3.2 在脚本和Shader中进出视空间
我们可以通过相机Camera组件的worldToCameraMatrix来把一个世界坐标的向量重新表示为一个以相机为中心的空间的表达形式。如果我们想把一个相机空间的向量重新表示到世界坐标,可以用cameraToWorldMatrix来实现。在Shader中,有一个矩阵UNITY_MATRIX_MV可以把向量一次性从模型空间转换到视空间内。
3.4 空间的一块: 视椎体
看的到的部分:视椎体
Unity引擎不会将整个场景进行渲染,而只是渲染出现在镜头中的物体。如何判断物体是否处于镜头中呢?这既是视椎体的概念,如下图所示:
只有处于视锥体内的物体才会被渲染,而其他部分将处于实现之外,从而得到我们在屏幕上看到的东西。视椎体是远近剪切屏幕以及视角大小所定义的一个平头锥体,而相机就处于这个锥体的顶尖上。这个过程的专业术语叫Culling。
3.5 剪切空间
3.5.1 投影
之后我们需要准备把三维的物体表示到一个二维的平面上,在这个过程首先需要先投影一次,把视椎体变换成一个长方体如下图所示
这次变换是vertex Shader任务的终点,之后引擎会自动处理下面的事情,把坐标转换到NDC(Normalized Device Coordinates),这是一个无量纲的空间,OpenGL和Direct3D稍有不同,OpenGl的值域是(-1,-1,-1)到(1,1,1);Direct3D的值域是(-1,0,-1)到(1,1,1)。
3.5.2 脚本和Shader中的投影矩阵
在脚本中代表这个投影操作的是Camera组件中的projectionMatrix,而在Shader中则是UNITY_MATRIX_MVP矩阵。
我们完全可以把上面这一系列步骤一次完成,也就是把上面提到的几个矩阵组合到一起,从而得到UNITY_MATRIX_MVP这个矩阵。通过这个矩阵,可以直接把物体上的一个点投射到屏幕上,这也是正常显示一个物体的Shader所必须完成的工作。
如果只是为了编程下面的可以不必看,或者知道这一回事就行,Unity公司花这么多人力物力做出来的你就相信人家吧,当然从学术的角度来说,知道还是有点好处的。
3.5.3 验证NDC
NDC是通过UNITY_MATRIX_MVP矩阵变换之后的空间,因为在Shader中无法像普通的程序一样Log输出,所以,有些人常常对此充满疑惑。其实可以在一个脚本中对一个点做同样的变换,然后通过UI,或者Log来观察MVP变换的输出。下面就是这样的一个例子。
void Update(){// trans.localToWorldMatrix;//m of MVP//因为将使用物体的世界坐标(trans.position),所以略去trans.localToWorldMatrix,即_Object2Worldvp = cam.projectionMatrix*cam.worldToCameraMatrix;// vp of MVP//VP矩阵的逆,用于把一个(-1,1)Clip空间的点再转换到世界空间内vpInverse = vp.inverse;//mvp = cam.projectionMatrix*cam.worldToCameraMatrix*trans.localToWorldMatrix;ndcPos = vp.MultiplyPoint(trans.position);//将一个世界坐标的点转换到Clip空间WorldPos = vpInverse.MultiplyPoint(ndcPos);//将一个Clip空间的点转换到世界坐标空间
}
我们首先构造出Shader中使用的MVP,因为是将一个世界坐标中的点进行变换,所以省去了M,然后再构造出这个矩阵的逆,最后分别使用两个矩阵来变换一个点,也就是先将一个点变换到NDC空间然后再将其变换回来,再返回到世界空间。
3.5.6 NDC之后
3.5.6.1 NDC之后发生的事情
在vertex Shader中点被变换到了NDC空间,几何体的顶点在经过变换后,仍会有序,也就是按照他们在Mesh中,被发送到GPU时的顺序,进入到下一个阶段,进行几何体的组装和栅格化。这个时候会使用到被发送到GPU的有关几何体组成信息,如三角面的组成,这些信息也是有序的,和顶点顺序一致。
在应用原始数据生成几何体之后,栅格化之前,会进行Culling操作,也就是根据面的朝向,几何体是否处于视锥体内,来决定是否扔掉这个三角面,这个Culling操作会在栅格化之前进行。
在栅格化之后,GPU会在相关数据,比如一个三角面的3个顶点数据之间进行线性插值,从而为一个像素产生其相关的UV、法线、Z深度等信息。在栅格化之后,像素在屏幕上的位置就已经确定,不能再改变,并且这个位置信息在管线内的流动是固定化的、不可见的,即在fragment函数中见不到经过MTV变换后、语义为Position的那个计算结果,如果要用这个位置信息,就要在vertex函数中显示的写入到一个寄存器。
接下来,GPU就会对每一个潜在像素执行我们在fragment中的代码,如果在fragment函数中写了有关光照、贴图采样等代码,那么这些操作就发生在这个时候。
最后,在我们的代码,也就是fragment函数执行完毕后,GPU还有一些操作要进行如Z测试,Blend等操作。