解读Unity中的CG编写Shader系列5——理论知识

经过前面的系列文章中的三个例子,尽管代码简单,但是我想应该还有些地方没有100%弄明白,我们现在得回过头来补充一些必备的数学、图形学知识

 

1、图形管道

第一个例子中我有提到顶点着色和片段着色在整个图形绘制过程中属于一个环节,整个过程叫做管道,这个管道的所有环节包括:

在整个管道中,只有顶点着色与片段着色是可编程的,顶点数据和帧缓存是具体的数据,剩下的环节是固定功能的环节,即不能用cg去编程的环节。


2、数据流


3、语义、特殊参数(uniform)
前文已经大量接触到语义,语义的存在意义可以理解为给普通的向量或者标量赋予物理意义,比如一个向量(1,1)如果没有语义,我可以把它当做一个速度为1米/S秒的小球,也可以认为他是直线y=-x的一条法向量。而如果我们给这个向量(1,1)后面加上语义,例如(1,1) :SPEED,或者(1,1):NORMAL,那我们的程序就知道了这个向量的物理特性,至少不会混淆他们引起混乱。

uniforms是unity提供给我们的特定参数,他们也有向量、标量和矩阵,他独立于片段、顶点、图元之外而存在,如果将他们组成的网格mesh理解为一个庞大的宇宙,这些uniforms就好似大宇宙中的物理法则,对于任何的顶点、片段、图元都适用,且数值相同。


4、顶点变换

在了解顶点变换之前,我们要明白顶点着色器以及其后续的几个环节的最终目的是要将几何图元(例如三角形)的顶点从模型坐标系变换至显示屏坐标系。

这一点对于初接触Unity的人应该有很深的影像。你在场景中创建一个立体图形,然后创建一个主摄像头,那么最终游戏所看到的画面到底通过这个立体图形以及相机的种种参数经历了怎样的计算与交织呢?

整个顶点变换过程分为5个步骤:


需要注意的是,前3个变换是在顶点着色器中完成的,而透视变换域视窗变换是在后续的环节中完成的。也就是说只有前3个变换过程是可编程的。

前3个变换所用到的3个矩阵均可以通过uniform参数获取,并且unity还提供了一个MVP参数,即整合了这3个矩阵,直接完成从模型坐标系至裁剪坐标系的变换。


5、变换矩阵

写到这里,这里的知识已经跨越了语言的界限,无论是ios、andorid、pc、web(css)中的语言都有transform3D的概念。也就是通过一个4X4矩阵来定义一个物体的变换状况。
1.单位矩阵

单位矩阵是这个4x4矩阵的初始状态,默认情况下没有进行平移、旋转、缩放(还有仿射变换)的时候,transform3D矩阵都是单位矩阵。

我们用m11~m44来表示任何状态下的变换矩阵,其中单位矩阵是最特殊的一种矩阵:

其中m14 m24 m34表示该立体图形的平移状态,m11,m22,m33表示缩放状态,而旋转比较特殊,我们先来看平移

单位矩阵的特性:任何矩阵乘以单位矩阵,还是得到原矩阵

从物理特性上理解:单位矩阵的m11~m33均为1,由于他们表示缩放状态,意思是对于任何矩阵在x,y,z方向上进行原尺寸100%的缩放,变换后的图形无任何变化。
2.平移
在3维直角坐标系中,平移向量T有3个分量,即Tx,Ty,Tz,即T=(Tx,Ty,Tz);
将任何变换矩阵进行向量T的平移过程,由于向量T可以认为是一个1x3矩阵,而变换矩阵是4x4矩阵,所以需要将平移向量构建成一个4x4矩阵,再来跟要变换的矩阵进行相乘:
与向量T(Tx,Ty,Tz)等价的4x4矩阵

Mt:=

任何平移过程都是变换矩阵乘以Mt矩阵的过程


3.缩放
同理,缩放Scaling也可以表示为一个向量S=(Sx,Sy,Sz);

同样的,与这个1x3矩阵等价的4x4矩阵应该为:
Ms=

任何缩放过程都是变换矩阵乘以Ms矩阵的过程  特殊的就好比 任何矩阵乘以Sx~Sz都为1的矩阵Ms,图形不发生变换


4.旋转
旋转比较复杂,任意旋转向量R=(Rx,Ry,Rz)分别表示绕x,y,z轴旋转的弧度数,将这个1X3矩阵等价的变换为4x4矩阵,
Mr=  太复杂了 我就不写了

5.复合矩阵
前面提到的 位于顶点着色器管辖范围内的3个4X4变换矩阵MobjectToWorld(modelToWorld),简写Mm,MworldToView,简写Mv,Mprojection,简写Mp。

顶点着色器将输入参数中的顶点坐标按照这3个矩阵进行连续变换即得到剪裁坐标系中的矩阵。


其过程为:


M原始矩阵*Mm*Mv*Mp=M剪裁坐标系中的矩阵


上面的式子中我们可以将Mm*Mv*Mp先组合起来成为一个组合矩阵Mcombine:


那么任意对象/模型坐标系中的原始矩阵M 与Mcombine相乘可以得到剪裁坐标系中的矩阵,因此Unity提供的MVP参数正是这样一个复合矩阵

与此类似的不需要导入UnityCG.cginc就可以直接使用的参数还有:

uniform float4x4 UNITY_MATRIX_MVP; // model view projection 矩阵
uniform float4x4 UNITY_MATRIX_MV; // model view 矩阵
uniform float4x4 UNITY_MATRIX_P; // projection 矩阵
uniform float4x4 UNITY_MATRIX_T_MV;
//  model view 矩阵的转置(transpose)矩阵
uniform float4x4 UNITY_MATRIX_IT_MV;
// model view 矩阵的逆矩阵的转置矩阵
uniform float4x4 UNITY_MATRIX_TEXTURE0; // 纹理矩阵
uniform float4x4 UNITY_MATRIX_TEXTURE1; // 纹理矩阵
uniform float4x4 UNITY_MATRIX_TEXTURE2; // 纹理矩阵
uniform float4x4 UNITY_MATRIX_TEXTURE3; // 纹理矩阵
uniform float4 UNITY_LIGHTMODEL_AMBIENT; // 环境颜色


 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是Unity Shader使用CG语言实现SAMPLE_DEPTH_TEXTURE的示例代码。 在vertex shader,我们需要将顶点坐标和投影矩阵相乘得到裁剪坐标,然后将裁剪坐标传递给fragment shader。 ``` v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } ``` 在fragment shader,我们首先需要从深度纹理采样得到深度值,然后将其转换为线性深度值。转换方法可以根据具体场景进行调整。接着,我们可以根据深度值计算出该像素的位置,并将该位置作为颜色输出。 ``` half4 frag (v2f i) : SV_Target { float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UNITY_PROJ_COORD(i.vertex)).r); float linearDepth = 2.0 * _ZBufferParams.w / (_ZBufferParams.y + _ZBufferParams.x - depth * (_ZBufferParams.y - _ZBufferParams.x)); float4 clipPos = float4(ComputeGrabScreenPos(i.vertex.xy, linearDepth), linearDepth, 1.0); float4 viewPos = mul(_InvProjMatrix, clipPos); viewPos /= viewPos.w; return float4(viewPos.xyz, 1.0); } ``` 注意,这里我们还需要使用Unity提供的函数Linear01Depth将深度值从非线性转换为线性。同时,我们需要使用_ZBufferParams和_InvProjMatrix这两个Unity内置的变量来进行后续计算。 最后,在主程序,我们需要将深度纹理绑定到对应的纹理单元,并将投影矩阵和_ZBufferParams传递给shader。 ``` Shader "Custom/DepthShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _CameraDepthTexture; float4 _ZBufferParams; float4x4 _InvProjMatrix; struct appdata_base { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } half4 frag (v2f i) : SV_Target { float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UNITY_PROJ_COORD(i.vertex)).r); float linearDepth = 2.0 * _ZBufferParams.w / (_ZBufferParams.y + _ZBufferParams.x - depth * (_ZBufferParams.y - _ZBufferParams.x)); float4 clipPos = float4(ComputeGrabScreenPos(i.vertex.xy, linearDepth), linearDepth, 1.0); float4 viewPos = mul(_InvProjMatrix, clipPos); viewPos /= viewPos.w; return float4(viewPos.xyz, 1.0); } ENDCG } } FallBack "Diffuse" } ``` 注意,我们需要使用Unity内置的宏UNITY_PROJ_COORD来将裁剪坐标转换为纹理坐标。另外,由于_CameraDepthTexture是Unity自动渲染深度纹理的结果,因此无需手动绑定纹理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值