C for Graphic:语言(二)

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

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

    这次我们就通过观察一个简单的CG shader,跟小时候学习文言文咬文嚼字一样,逐句理解这个CG shader的意义,代码如下:

    

struct app2vert
{
	float2 pos : POSITION;
};

struct vert2frag
{
	float4 pos : POSITION;
	float4 col : COLOR;
};

vert2frag vertexFuc (app2vert appData)
{
	vert2frag ret;
	ret.pos = float4(appData.pos,0,1);
	ret.col = float4(1,0,0,1);
	return ret;
}

float4 fragmentFunc (vert2frag vf) : SV_Target
{
	return vf.col;
}

        以上是最简单的CG shader,能达到的功能只是返回一个三维坐标的齐次形式和一个Red的颜色值。

        接着我们就开始逐步分解这个shader代码的意义:

        ①.结构体app2vert是什么意思?

struct app2vert{
    float2 pos:POSITION;
};

        这个app2vert结构体只定义了一个字段,名为pos的float2类型,float2是个什么类型呢?写c的时候哪里用过这个类型?这里要说的是,CG虽然是用c当做基本编程语言,但是其编译器却是一套自己创造的新东西。编译器这个东西就是解析代码字符串并翻译成机器码的程序,那么假设要我设计CG编译器,我刚好需要一个float数组,且数组的长度是确定的比如长度就是2、3、4,那么我就直接设计一个float2、float3、float4数据类型,省去了写float arr[2]这么长的语句,这里顺便说下为什么要定义float2、float3、float4这些数据类型的编译解析呢?因为这些类型使用的频率是极其高的,在CG中float2可以定义二维顶点坐标,贴图UV等,float3可以定义三维顶点坐标和不带Alpha的颜色值等,float4则可以定义三维坐标的齐次形式和带Alpha的颜色值等。

        解释完float2,那么接下来的:POSITION是什么意思呢?假如小伙伴们学过一些解释型语言,比如js,ts,python什么的,对:这个符号肯定不陌生,这个符号定义了我们字段的具体类型,比如js代码如下:

        var name:String;

        代表定义一个名为name的变量,变量类型规定为字符串类型,这里的:的作用就是约束了具体的变量类型。

       回到CG上来,float2 pos:POSITION;这句代码前面已经限定了具体的变量类型为float2,那么后面还加上一个:POSITION干嘛呢?难道pos即为float2又为POSITION类型?实际上这里的POSITION的真正作用是一种底层数据传递,将该CG Shader作用的模型的所有顶点源坐标数据传递给pos这个float2的变量,假如要我用代码的角度翻译解释,如下:

扫描二维码关注公众号,回复: 3118266 查看本文章

        

void main()
{
	int modelVertexLength = this.RenderModel.Vertexs.Lenght;
	for(int i =0;i<modelVertexLength;i++)
	{
		float2 pos = GetModelVertexXY(i);
	}
}

/*index为从0到模型网格顶点长度的for index++值*/
float2 GetModelVertexXY(int index)
{
	float4 modelVertexs[] = this.RenderModel.Vertexs;  /*Vertexs是CG底层给我们提供的模型网格所有顶点齐次坐标*/
	float2 pos = float2(modelVertexs[index].x,modelVertexs[index].y);
	return pos;
}

<=> 等价于

float2 pos:POSITION;

        上面是我随便写的模拟代码,小伙伴们可以认为:POSITION这个用法经过CG编译器编译后能达到上面两个函数的作用。就是将模型网格顶点的x,y依次传给pos这个float2变量,当然我们还能定义下面这些:

         

float4 singleVertex = this.RenderModel.Vertexs[index];

float3 pos:POSITION; <=> float3 pos = float3(singleVertex.x,singleVertex.y,singleVertex.z);

float4 pos:POSITION  <=> float4 pos = float4(singleVertex.x,singleVertex.y,singleVertex.z,singleVertex.w) <=> singleVertex

         意思就是你定义floatm就返回给你m个对应数据,当然m小于等于4了,毕竟三维坐标的齐次形似也就四个元素。

       ②.结构体vert2frag是什么意思呢?

       

struct vert2frag{
    float4 pos:POSITION;
    float4 col:COLOR;
};

        前面我们讨论了:POSITION,这里又来一个!那么很显而易见了,就是将模型顶点源数据绑定传递给pos这个float4类型,是一个齐次坐标的完整传递,也就是xyzw四个元素。

        接下来就是一个:COLOR了,通过前面对:POSITION的讲解,我们就很好理解了,float4 col:COLOR就是将“某个COLOR”属性绑定传递给col的float4类型,这个“某个COLOR”到底指哪里的颜色呢?其实很好辨识,那就是顶点的颜色值,就是当前pos顶点的颜色值,这两个变量分别被传递赋值了当前的顶点坐标和颜色值,这两个值全由CG运行时底层提供,当然col依然可以被定义为float2或者float3类型的,如下:

       

float4 singleVertexColor = this.RenderModel.VertexColors[index];

float2 col2:COLOR <=> float2 col2 = float2(singleVertexColor.x,singleVertexColor.y);  /*无意义,只取两个颜色值有什么意义?*/

float3 col3:COLOR <=> float3 col3 = float3(singleVertexColor.x,singleVertexColor.y,singleVertexColor.z);  /*rgb颜色值,去掉了alpha*/

         但是大家发现一个问题没?我们的vert2frag结构体在vertexFunc是一个返回值,是由我们写代码填充vert2frag中pos和col的,那么这两个字段需要CG运行时底层给我们传递数据吗?完全不需要啊!那此时我们用:POSITION和:COLOR去“修饰”这两个字段干嘛?这里要说一下:POSITION和:COLOR的另一个用法,也就是绑定传值,类似我们c中的&引用传递,这个:POSITION和:COLOR直接绑定GPU硬件(比如寄存区,显存等)中的数据,当我们修改我们的pos和col时,就相当于直接修改了硬件资源中的数据。那么此时在我们看来,:POSITION和:COLOR的用法就相当于直接&引用绑定了GPU硬件资源中的模型相应数据,那么即达到了输出(比如将模型顶点源数据比如坐标,颜色传递给顶点函数)同时达到了输入(我们写代码绑定模型的源数据,然后程序修改,相当于直接覆写了模型源数据,或者说输入了新值给模型源数据)

      此时我们基本了解那两个结构体的具体含义了,这里要用很官方的语言来说一下,POSITION和COLOR名为语义(semantics),是不是很简单的解释?当年我第一次看文档知道了这个名词的时候一段时间内都是一脸萌比状态。现在我要说的就是这个语义的具体意义就是将CPU中硬件(比如寄存器,显存等)内数据提供外部编程使用的一种CG编译器限定好的语法规范意义。by the way,语义可不止POSITION和COLOR两个,还有其他的后面我们继续学习并使用。

        那么这里我要画一张图,形象的理解一下语义的作用,如下图:

       

       ③.顶点函数vertexFunc。

       

vert2frag vertexFunc(app2vert appData)
{
    vert2frag ret;
    ret.pos = float4(appData.pos,0,1);
    ret.col = float4(1,0,0,1);
    return ret;
}

      通过我们上面分析结构体数据的意义后,现在再来看这个函数就极其简单了,就是传入一个app2vert的结构体数据,然后返回一个vert2frag的结构体数据,而且函数内部做的操作也极其简单,就是将float2 pos补全z = 0和w = 1后提交给float4 pos(及其绑定的GPU中的模型顶点坐标),同时给col填充一个纯红色的值(及其绑定的GPU中的模型顶点颜色值)。

     这里我要解释一下的是,单独看这个函数,那就很简单,假如回归CG shader上下文来看,那么这个函数的意义就是CG给我们抛出的顶点函数,首先让我们接收一个定义好的app2vert结构体,结构体也是我自己去写的,仅仅绑定了一个POSITION的float2 pos,然后我继续在函数内进行最简单的运算,返回一个vert2frag的结构体,结构体也是我自己去写的,就包含两个数据,一个绑定了POSITION的float4 pos,和一个绑定了COLOR的float4 col。那么这个返回值有什么意义呢?继续从CG shader上下文看,这个顶点函数vertexFunc返回的数据会继续被片段函数fragmentFunc使用。

     ④.片段函数fragmentFunc。

     

float4 fragmentFunc (vert2frag vf) : SV_Target
{
	return vf.col;
}

      这代码一看就更简单了,就是接收了一个vert2frag的结构体数据,然后把颜色值给返回出去了。当然我们继续结合CG shader上下文关系来看,从顶点函数vertexFunc返回的数据要传递给片段函数fragmentFunc使用,那么怎么找到这个对应的传递关系呢?语义SV_Target就起到了作用,标记了:SV_Target的函数就是顶点函数vertexFunc的数据最终传递到的函数了(顺便说一下SV_Target大小写无所谓,反正CG编译器能识别并编译出合理的函数调用关系,比如你写成sv_target也可以,都是代表了(system)specify (value)vertex target),最后将我设置的color(1,0,0,1)也就是纯红色返回并渲染。 

      最后总结一下写两个函数,都需要我们通过CG shader上下文的几何源数据到顶点处理器到栅格化到片段处理器这个渲染流程来理解。

     ⑤.接下来肯定就是代码环节了,写了一大堆文字,最后还是需要实际上程序才是王道,如下:

     

Shader "Unlit/SimpleUnlitShader"
{
	Properties
	{
		
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vertexFuc
			#pragma fragment fragmentFunc

			#include "UnityCG.cginc"

			struct app2vert
			{
				float2 pos : POSITION;
			};

			struct vert2frag
			{
				float4 pos : POSITION;
				float4 col : COLOR;
			};

			vert2frag vertexFuc (app2vert appData)
			{
				vert2frag ret;
				ret.pos = float4(appData.pos,0,1);
				ret.col = float4(1,0,0,1);
				return ret;
			}

			float4 fragmentFunc (vert2frag vf) : SV_Target
			{
				return vf.col;
			}
			ENDCG
		}
	}
}

     

    最简单的CG shader代码,不涉及矩阵运算,特效运算等,只显示纯红色。但是通过以上非常详细的讲解,我相信对大家理解CG shader起到很关键的作用。

     so,下一篇博客我们继续深入。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/82422248