C for Graphic:语言(逐句理解)

版权声明: https://blog.csdn.net/yinhun2012/article/details/82586162

       紧接上一篇:https://blog.csdn.net/yinhun2012/article/details/82422248

       之前我们通过观察一个最简单的CG shader,详细的学习了CG shader关键代码的运行机制,这一篇我们就要通过unity引擎的shader上下文环境,观察并理解其他细节代码的作用。

        首先贴上CG shader代码,这一篇主要围绕这个代码进行学习,如下:

        

Shader "Unlit/TextureUnlitShader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}

         上面的CG shader只完成一个简单的图片渲染显示功能,但是细节很值得学习,接下来我就逐一讲解结合unity CG上下文来讲解shader每一句话都是什么意思。

         首先说一下,nvidia是CG的缔造者,其他各个三维引擎或者工具都必须按照CG的基本规范去实现自己的CG编译器以及类似编程语法,比如我最爱的unity3d和rendermonkey(我不爱ue4的原因是ue4实在是太慢了,保存umap慢,编译构建慢等,之前用ue4的日子旁边都是放了一本书,凡是ue4在那操作阻塞着,我就看书玩,实在爱不起来)。那么我们现在用unity写CG shader,就得按照unity的那一套语法编译规范写代码了,上面的代码就是一个很标准的语法对照了,只要我们完全理解代码的每个词汇,就达到目标了。

         so,我们开始咬文嚼字吧:

         ①.Shader{}包体。

         

Shader "Unlit/TextureUnlitShader"
{
     //do something
}

     类似c++或c#的class,将具体的shader代码封装,Shader让CG编译器识别这是一个shader代码的起始标签,“Unlit/TextureUnlitShader”就是shader名称的标识。

        ②.Properties{}属性字段

  

Properties
{
	_MainTex ("Texture", 2D) = "white" {}
}

         类似c++的public:修饰字段,如下:

  

class Texture
{
	//这是一个贴图类
};

class MainTest
{
public:
	float cg_float;
	Texture* cg_MainTex;

};

        这是一段模拟代码,和CG代码没有任何关联,我为什么用c++/c#作为演示代码呢,首先就是我们大概接触最早的就是c/c++了,因为学校里面最开始学习就是从c开始,其次我们作为unity开发,c#当然也是很熟悉啦。最后就算我用很详细的文字讲解代码意义,有的时候还是不如来一段演示代码让人更能理解其中的含义。

          Properties{}就类似Public:,在其中定义我们需要的属性字段,可以供外部访问,比如我们定义一张texture2D。

          _MainTex ("Texture", 2D) = "white" {},此时unity CG编译器在识别出Properties后,紧接着解析其中封装的属性字段,当解析到这段代码后,其中代码意义分解如下:

                 ①._MainTex标识了该texture2D在shader中的字段名称。

                 ②.("Texture", 2D)标识了该texture2D在unity 编辑面板中ui显示字符串"Texture",2D则标识类型为texture2D。

                 ③.= "white" {}标识了该texture2D默认为白色,当然我们可以改成red红色。

         ps:②中我说了unity编辑面板ui显示,这里可以通过官网下载的内置着色器中editor中的StandardShaderGUI.cs文件查看具体显示细节。

          当然,Properties不仅仅只能定义一个texture2D,还能定义其他的,比如_MainFloat("Float",float) = 0.5,定义一个float值,当然还有其它的,我们以后用到的时候再看。

     ③.SubShader{}包体

SubShader
{
    //do something
}

        官方对SubShader的解释是封装一系列渲染pass和所有pass通用的标签和状态的包体,一个主Shader可以包含多个SubShader,为什么主Shader可以包含多个SubShader的原因是,前面我们说了Profile的概念,也就是说因为实际GPU硬件种类太多,为了保证该主Shader可以在多种多样的GPU上运行,所以可能需要写多个可以运行的SubShader,然后实际runtime运行时按照顺序查找到一个能运行SubShader去执行相应的渲染效果,假如都不能执行,就Fallback "Diffuse",意思就是回滚到最基本的Diffuse渲染,保证至少不出问题。

     ④.SubShader中标签和状态

     

Tags { "RenderType"="Opaque" }
LOD 100
Cull back

     这些标签和状态键值对,被CG编译器识别,主要作用是标识后续渲染pass该什么时候并且怎么样被CG runtime渲染。

     比如上面三句代码含义如下:

             ①.Tags { "RenderType"="Opaque" } 标识了渲染类型为Opaque,什么意思呢?就是代表这个渲染类型定义为不透明着色器,unity CG库提供了多种适用于不同环境的渲染类型,具体的后面会谈到。

              ②.LOD 100,LOD全称level of detail,,设置一个100是个什么意思呢?这里代表一种shader渲染细节技术,假如我们的主Shader包含多个SubShader,每个SubShader的LOD 为不同的值,假如为100、200、300,那么我们在c#代码中通过Shader.globalMaximumLOD = 100;控制全局LOD最大值,来显示不同LOD shader的渲染。有兴趣的小伙伴可以自己测试下,我就不贴图了。

             ③.Cull back,Cull也是渲染剔除的意思,设置back代表背面剔除渲染,那样的话实际上我们的shader只渲染了一个正面,节省一半的资源,当然我们也可以设置front剔除正面,或者off关闭,直接渲染双面。

             ps:当然还有其他一些不同功能的tag和state,后面具体是用到再讲还是直接一次性列出待定,不过这里我们明白这些tag和state的语法意义就行了。

 ⑤.Pass{}渲染通道

      从这里开始就是正式开始进入CG渲染了,Pass{}封装当前SubShader着色器具体的渲染代码,我们的顶点函数,片段函数,表面光照函数就是在其中具体实现,下面开始逐一分解每一句代码的具体意义:

           ①.CGPROGRAM开始和ENDCG结束,这个只是CG编译器的语法规范之一,代表开始编译或解析CG代码到结束,类似#region #endregion代码块。

           ②.#pragma vertex vert和#pragma fragment frag(当然还包含#pragma surface surf YangLightModel)这里就是预编译顶点函数和片段函数(表面光照函数),代表后面的vert和frag为我们要去具体开发实现的顶点和片段函数。

           ③.#include "UnityCG.cginc"和c包含头文件一样,这里引入了UnityCG.cginc这个文件(这个文件可以在下载的内置着色器CGIncludes文件夹中找到),因为我们需要用到unity CG runtime给我们提供的GPU硬件资源数据和各种图形处理函数,都在这个文件可以看到,这里我也不贴代码了,小伙伴们务必自己打开看一下。

            ④.appdata和v2f结构体,如果小伙伴们是按照顺序看我的博客,那么对这两个结构体及其包含的语义绑定字段有详细的理解。额外要说的另外一个语义TEXCOORD0,前面的TEXCOORD代表了纹理坐标,0则代表了第0通道纹理坐标,大部分情况下显卡支持4套纹理通道给我们开发使用,语义分别是TEXCOORD0,TEXCOORD1,TEXCOORD2,TEXCOORD3,我们可以对纹理坐标进行scale缩放或translate位移操作并储存到任意0-3的TEXCOORD。SV_POSITION则等价于POSITION,SV_前缀代表system value,我查到的解释是,当使用SV_POSITION绑定vertex顶点函数输出时,那么顶点坐标在vertex顶点函数return后就被固定了,直接进入光栅处理阶段,不可更改。

                by the way,顺便画图描叙一下TEXCOORD作为纹理坐标语义输入时的uv:贴图坐标系,如下图:

                

                可以认为,小猫贴图的uv就是从(0,0)到(1,1),(m,n)为其中一个采样颜色color值,绑定TEXCOORD语义输入时,float2 uv值就被传入(0,0)插值到(1,1)。

           ⑤.sampler2D _MainTex;,这句代码要和Properties{}对应,是定义获取_MainTex这个texture2D对象,当然CG代码中类型名为sampler2D,定义好后sampler2D对象后,提供后续函数使用。

           ⑥.float4 _MainTex_ST;,这句代码可能让小伙伴们迷惑,咱们又没在Properties{}中定义_MainTex_ST这个外部变量,怎么突然就蹦出来了这个东西?实际上这是和_MainTex相关的变量,代表了_MainTex的Scale和Translate,这是我从官方看到的解释,Scale缩放则对应unity编辑器ui面板上的Tiling,Translate位移则对应其Offset,属于unity CG编译器为我们提供的texture2D默认附带的变量字段。

           ⑦.vert顶点函数,我直接在代码中进行注释讲解 ,如下:

           

v2f vert (appdata v)
{
	v2f o;
	//将unity CG底层提供的模型顶点坐标源数据变换到裁剪空间
	//UnityObjectToClipPos这个cg内置函数相当于mul(UNITY_MATRIX_MVP,v.vertex);
	o.vertex = UnityObjectToClipPos(v.vertex);
	// Transforms 2D UV by scale/bias property
    // #define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
	//上面是UnityCG.cginc的TRANSFORM_TEX宏函数定义,实际上使用到了_MainTex_ST字段,意思就是将_MainTex_ST带入纹理uv的运算
	o.uv = TRANSFORM_TEX(v.uv, _MainTex);
	return o;
}

            ⑧.frag片段函数,解释如下:

            

fixed4 frag (v2f i) : SV_Target
{
	//tex2D为采样函数,具体意思就是根据uv值对一张纹理进行xy坐标点颜色color值获取,在片段函数返回并渲染
	fixed4 col = tex2D(_MainTex, i.uv);
	return col;
}

           上面那张小猫图,其中以动点(m,n)进行tex2D采样的话,就是返回了不同坐标下小猫图的像素颜色值,并传递给col且return渲染。

          ⑨.Fallback "Diffuse",这就是unity CG编译器就行的容错处理了,假如我们的CG shader运行在一个很低端古老的的GPU而显示不出来,就回滚为Diffuse着色器,反正所有CPU都支持这个。

        最后看一下这个CG shader的呈现效果,如下图:

        以上就是对一个unity CG shader非常详细的讲解,目的就是为了大家能轻松理解后续的复杂的高级着色器。

       最后还要重复罗嗦一句,学习CG shader必须按照图形流水线上下文来观察理解,比如流水线顶点到光栅到片段处理顺序流程,同时顶点函数和片段函数处理的输入输出的GPU硬件资源数据是动态变化的,比如模型顶点源坐标和纹理坐标等动态变化的数据,我们心里要有这些概念才能学好CG shader。

       so,接下来继续深入。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/82586162
今日推荐