本篇通过一个简单shader代码介绍shader中顶点片元着色器的写法,struct结构体的用法,以及语义和调用unityshader库函数等,如果有对shader结构还是不懂的请看本人的这篇文章介绍unityshader结构https://blog.csdn.net/m0_53377876/article/details/130269867
先上代码
Shader "Unlit/003"
{
Properties
{
_Color("Color" , Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//只有在CGPROGRAM内再次定义一个与属性块内名字与类型相同的变量,属性块对应的变量才能起作用
fixed4 _Color;
struct v2f// v vert dao frag
{
//SV_POSITION语义告诉unity : pos为裁剪空间中的位置信息
float4 pos:SV_POSITION;
//COLOR0 语义可以储存颜色信息
fixed3 color:COLOR0;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//将 【-1,1】转变为【0,1】 x/2 + 0.5;
o.color = v.normal * 0.5 +fixed3(0.5,0.5,0.5);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
fixed3 c = i.color;
//.xyzw .rgba .x .y .xw
c*=_Color.rgb;
//return fixed4(0,0,0,1);
return fixed4(c, 1);
}
ENDCG
}
}
}
效果图
1.#pragma vertex vert/#pragma fragment frag
在渲染管线中,有两个阶段,顶点阶段和片元阶段,程序先进行顶点片理,然后再进行片元着色。顶点阶段处理顶点信息,并把结果返回传给片元阶段。片元阶段是针对单个象素进行的,也称逐象素的,输入的是顶点阶段传过来的信息,输出一个RGBA的颜色值。
#pragma vertex vert 这行代码的作用是指定顶点阶段的函数是 vert,函数名可以自由设定,但是必须和#pragma vertex后面定义的函数名一致
在unityshader中此函数被声明后不需要自己调用,系统自动调用,执行时系统传入对应的值,经过函数处理后将返回值返回系统中(此处先不用在意系统如何辨别该传入什么值,后面会介绍)
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//将 【-1,1】转变为【0,1】 x/2 + 0.5;
o.color = v.normal * 0.5 +fixed3(0.5,0.5,0.5);
return o;
}
#pragma fragment frag 这行代码的作用是指定片元阶段的函数是 frag,命名规则同#pragma vertex vert。
同样此函数也不需要自己调用,系统自动调用,输入的是顶点阶段传过来的信息,例如v2f,输出一个RGBA的颜色值
fixed4 frag(v2f i):SV_TARGET
{
fixed3 c = i.color;
//.xyzw .rgba .x .y .xw
c*=_Color.rgb;
//return fixed4(0,0,0,1);
return fixed4(c, 1);
}
2.语义
为了解决计算机如何知道传入什么值,以及返回值用于什么地方,我们加入了语义,一下是常用语义
顶点着色器输入常用语义
语义 | 描述 |
POSITION | 模型空间中的顶点位置,通常是float4类型 |
NORMAL | 顶点法线,通常是float3类型 |
TANGENT |
顶点切线,通常是float4类型 |
TEXCOORDn,如TEXCOORD0,TEXCOORD1 | 该顶点的纹理坐标,TEXCOORD0表示第一组坐标纹理,依次类推,通常是float2,float4类型 |
COLOR | 顶点颜色,通常是fixed4或float4类型 |
Shader Model版本 |
TEXCOORDn中N的支持个数 |
Shader Model2 |
8 |
Shader Model3 |
8 |
Shader Model4 |
16 |
Shader Model5 |
16 |
顶点着色器输出常用语义
语义 |
描述 |
SV_POSITION |
裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量。等同于DX9中的POSITION。 |
COLOR0 |
通常用于输出第一组顶点颜色,不是必须 |
COLOR1 |
通常用于输出第二组顶点颜色,不是必须 |
TEXCOORD0-TEXCOORD7 |
通常用于输出纹理坐标,不是必须 |
片元着色器输出时常用语义
语义 |
描述 |
SV_Target |
输出值将会储存到渲染目标(render target)中。等同于DX9中COLOR语义。 |
用法:我们写一个简单的例子,我们不必在意里面写的是什么意思,上代码
float4 vert(float4 v:POSITION):SV_POSITION
{
return UnityObjectToClipPos(v);
}
fixed4 frag():SV_TARGET
{
return fixed4(1,1,1,1);
}
在vert函数中,我们传入了一个用POSITION语义表示的float4类型的值,意思是计算机会将模型空间中的顶点位置传入到该float4类型的值中,并返回一个用SV_POSITION语义表示的float4类型的值,意思是返回裁剪空间中的顶点坐标并用float4类型储存
在frag函数中,返回了一个用SV_TARGET语义表示的float4类型的值,意思是输出值将会储存到渲染目标(render target)中,实现的效果就是改变物体的颜色
3.结构体
float4 vert(float4 v:POSITION):SV_POSITION
{
return UnityObjectToClipPos(v);
}
如上述代码所示,如果传入和返回值有很多个怎么办,传入值可以写入多个,但是这样就显得代码异常冗余,而且如果有多个返回值我们也没有方法传出,所以我们引入了结构体,我们可以将参数和返回值定义成结构体以此达到传入和传出多个值
例如文字最开头的代码中存在以下结构体
struct v2f
{
//SV_POSITION语义告诉unity : pos为裁剪空间中的位置信息
float4 pos:SV_POSITION;
//COLOR0 语义可以储存颜色信息
fixed3 color:COLOR0;
};
使用方法如下面代码所示
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//将 【-1,1】转变为【0,1】 x/2 + 0.5;
o.color = v.normal * 0.5 +fixed3(0.5,0.5,0.5);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
fixed3 c = i.color;
c*=_Color.rgb;
return fixed4(c, 1);
}
4.#include "UnityCG.cginc"
unityshader中也有许许多多的库函数,在这里我们举例介绍其中的一个UnityCG.cginc,如果大家有需要使用到其他库函数请查阅unity官方文档
在第三点我们介绍了struct结构体,我们每次写一个新的脚本时我们都要自己定义一个结构体用于管理输入和输出,但是这些结构体他们形式上都是一致的,支持的语义也是固定的,每次写一个新的脚本我们都要重写一次无疑增加了我们的代码量。所以在UnityCG.cginc中封装了很多方法和结构体,我们通过查阅就可以找到我们想要的结构体进行调用即可。
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//将 【-1,1】转变为【0,1】 x/2 + 0.5;
o.color = v.normal * 0.5 +fixed3(0.5,0.5,0.5);
return o;
}
在这里我使用了appdata_full结构体,在UnityCG.cginc中的定义如下
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
这里我们可以看出这个结构体里面定义的东西完全够我们使用,所以我们无需再定义其他结构体
5.其余知识点
到这里文章开头代码的主要部分本人以及全部讲完,剩下的是一些小知识点
首先Properties里面定义的_Color("Color" , Color) = (1,1,1,1)属性块,如果要使用一定要在CGPROGRAM里再定义一个与属性块名字类型相同的变量,这样属性块才能起作用。
同时我们编写的是一个CG语言所写的代码,主要是顶点片源着色器,我们对顶点片源的修改要在CGPROGRAM和ENDCG直接书写。
Shader "Unlit/003"
{
Properties
{
_Color("Color" , Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
//只有在CGPROGRAM内再次定义一个与属性块内名字与类型相同的变量,属性块对应的变量才能起作用
fixed4 _Color;
//...
ENDCG
}
}
}
到这里你以及会写一个简单的shader了