Shader 笔记二 片段着色器 Fragment Shader

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/wangjiangrong/article/details/89330163

简介

顶点片段着色器,运行于具有可编程渲染管线的硬件上,它包括顶点程序Vertex Programs片段程序Fragment Programs。当在使用顶点程序或片段程序进行渲染的时候,图形硬件的固定功能管线会关闭,具体来说就是编写的顶点程序会替换掉固定管线中标准的3D变换,光照,纹理坐标生成等功能,而片段程序会替换掉SetTexture命令中的纹理混合模式。

顶点着色程序与片段着色程序通常是同时存在,相互配合,前者的输出作为后者的输入不过,也可以只有顶点着色程序。如果只有顶点着色程序,那么只对输入的顶点进行操作,而顶点内部的点则按照硬件默认的方式自动插值。例如,输入一个三角面片,顶点着色程序对其进行phong光照计算,只计算三个顶点的光照颜色,而三角面片内部点的颜色按照硬件默认的算法(Gourand明暗处理或者快速phong明暗处理)进行插值,如果图形硬件比较先进,默认的处理算法较好(快速phong明暗处理),则效果也会较好;如果图形硬件使用Gourand明暗处理算法,则会出现马赫带效应(条带化)。

由于GPU对数据进行并行处理,所以每个数据都会执行一次shader程序。即,每个顶点数据都会执行一次顶点程序;每个片段都会执行一次片段程序。

Vertex And Fragment Shader

和之前一样,我们还是通过一个例子来看,在Project面板中,右击Create->Shader->Image Effect Shader,命名为ImageEffectShaderDemo.shader,如下:

Shader "Custom/ImageEffectShaderDemo" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader {
		// No culling or depth
		Cull Off ZWrite Off ZTest Always

		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;
			};

			v2f vert (appdata v) {
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}
			
			sampler2D _MainTex;

			fixed4 frag (v2f i) : SV_Target {
				fixed4 col = tex2D(_MainTex, i.uv);
				// just invert the colors
				col = 1 - col;
				return col;
			}
			ENDCG
		}
	}
}

前面一些和surface shader相同的内容我们这就不赘述了。关于Cull Off ZWrite Off ZTest Always这块的内容同样我们将单独讲解:https://blog.csdn.net/wangjiangrong/article/details/89335208

 

#pragma

#pragma vertex vert 将函数name的代码编译成顶点程序
#pragma fragment frag 将函数name的代码编译成片段程序

#include

编写Shader的时候,我们可以像使用C++中的头文件一样,使用#include预处理指令来包含其他代码集合。这告诉Unity我们想要当前的Shader使用包含的这些文件中的代码。我们这样做实际上是在相应位置包含了Cg代码片段。

#include "UnityCG.cginc" 引用UnityCg.cginc头文件(Unity安装目录 > Editor > Data > CGIncludes下)就可以使用预先设定好的结构体直接使用,他们分别有appdata_base,appdata_tan和appdata_full:

struct appdata_base {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
};

struct appdata_tan {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
};

struct appdata_full {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    float4 texcoord1 : TEXCOORD1;
    fixed4 color : COLOR;
};

顶点程序输入:

#pragma vertex vert 指定了vert 函数为顶点程序,它有一个输入参数,可以是上面提到的三个CS预定义的输入参数appdata_base,appdata_tan和appdata_full,也可以我们自定义,例如例子中的appdata。需要注意的是自定义的结构中的属性,只能在以下属性中选择(具体类型可以不一样,比如fixed4 color)

float4 vertex:POSITION 顶点位置
float3 normal:NORMAL 顶点法线
float4 texcoord:TEXCOORD0 第一UV坐标
float4 texcoord1:TEXCOORD1 第二UV坐标
float4 color:COLOR   每个顶点(per-vertex)颜色
float4 tangent:TANGENT 切线向量(用在法线贴图中)

知识点1:顶点坐标和正切线是float4,:这里它表示是齐次坐标,比如我们这样表示一个float4(x,y,z,w),当w = 1的时候它表示点(x,y,z),当w= 0的时候它表示一个向量(x,y,z)。区别就在这里,当W为1时表示点,当W为0时表示向量

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

知识点2:副法向量binoraml,它可以通过noraml和tangent计算得出来,公式如下所示:

float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;

知识点3:texcoord0和texcoord1分别表示两层UV,有时候我们模型上的贴图需要多个图片一起贴在一处,那么贴两层就会有两层UV。

片段程序输入:

前文提到了,顶点程序的输出即为片段程序的输入。顶点程序的输出中必须包含POSITION语义变量,该值不能在片段程序中直接使用,它只被用于光栅化。自定义数据可以使用TEXCOORD系列的语义词来表示。

顶点程序:

顶点着色程序从GPU前端模块(寄存器)中提取图元信息(顶点位置、法向量、纹理坐标等),并完成顶点坐标空间转换、法向量空间转换、光照计算等操作,最后将计算好的数据传送到指定寄存器中,供片段程序使用。

v2f vert (appdata v) {
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex);
	o.uv = v.uv;
	return o;
}

例子中v2f即为顶点程序的输出,同时也是片段程序的输入,appdata为顶点程序的输入。顶点程序将输入的顶点位置处理后赋给了v2f,同时将输入的uv值赋给了v2f。

内置函数:

mul(M, V) 是表示矩阵M和向量V进行点乘,得到一个向量Z,这个向量Z就是对向量V进行矩阵变换后得到的值
float4 UnityObjectToClipPos(float3 pos) 等价于:mul(UNITY_MATRIX_MVP, float4(pos, 1.0)),但是更有效率。把顶点从模型空间转换到裁剪空间
float3 UnityObjectToViewPos(float3 pos) 等价于:mul(UNITY_MATRIX_MV, float4(pos, 1.0)),但是更有效率
float3 WorldSpaceViewDir (float4 v) 参数是模型空间下的顶点坐标,取得世界空间下指向摄像机的方向,即视角方向
float3 ObjSpaceViewDir (float4 v) 同上,不过取到的视角方向是在模型空间上的
... ...

内置的矩阵(float4x4):

UNITY_MATRIX_MVP 当前模型观察投影矩阵,用于将顶点/方向矢量从模型空间变换到裁剪空间
UNITY_MATRIX_MV  当前模型观察矩阵,用于将顶点/方向矢量从模型空间变换到观察空间
UNITY_MATRIX_V 当前观察矩阵,用于将顶点/方向矢量从世界空间变换到观察空间
UNITY_MATRIX_P 当前的投影矩阵,用于将顶点/方向矢量从观察空间变换到裁剪空间
UNITY_MATRIX_VP 当前观察投影矩阵,用于将顶点/方向矢量从世界空间变换到裁剪空间
UNITY_MATRIX_T_MV 模型观察矩阵的转置
UNITY_MATRIX_IT_MV 模型观察矩阵的逆转置,用于将法线从模型空间变换到观察空间,也可用于得到UNITY_MATRIX_MV的逆矩阵
_Object2World 当前模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间
_World2Object 当前世界矩阵的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间

空间:

模型空间(model space / object space) 每个模型都有自己的模型空间,当它旋转或移动时,模型空间也会随之移动。
世界空间(world space) 宏观的特殊坐标系,在Unity中模型如果没有父节点,那么它就在世界空间内
观察空间(view space / camera space) 以摄像机为原点
裁剪空间(clip space) 其矩阵为投影矩阵,裁剪图元,并为投影做准备
屏幕空间 屏幕左下角是(0, 0),右上角是(pixelWidth, pixelHeight)

顶点着色器的最基本任务就是把顶点坐标从模型空间转换到裁剪空间中模型空间 -> 世界空间 -> 观察空间 -> 裁剪空间 -> 屏幕空间。

片段程序:

片段着色程序从顶点程序存放数据的寄存器中获取需要的数据,通常为“纹理坐标、光照信息等”,并根据这些信息以及从应用程序传递的纹理信息(如果有的话)进行每个片段的颜色计算,最后将处理后的数据送光栅操作模块。

片段就是所有三维顶点在光栅化之后的数据集合,这些数据没有经过深度值比较,而屏幕显示的像素是经过深度比较的。

片段着色程序是对每个片段进行独立的颜色计算,并且算法由自己编写,不但可控性好,而且可以达到更好的效果。片段程序通常只输出一个COLOR(最终颜色)

fixed4 frag (v2f i) : SV_Target {
	fixed4 col = tex2D(_MainTex, i.uv);
	// just invert the colors
	col = 1 - col;
	return col;
}

这段代码的含义即将贴图中每个顶点的新颜色 = 1 - 原颜色。即黑的变白的,白的变黑的。得到的效果如下:

猜你喜欢

转载自blog.csdn.net/wangjiangrong/article/details/89330163