一:基础的结构
着色代码一般依托于材质上的体现,在一般的情况下,都需要先创建一个材质,在着色器的代码给与到材质上,再把材质给与到物体上。
js
Shader "myshader/01 myshader" //shader的名字,这个名字可以和文件的名字不一样,斜线前面为文件夹的路径
{ //外部读取的到的文件全部保存到这里
Properties
{
//属性,可以在外面的输入
_Color("Color",Color) = (1,1,1,1) //默认值 第一个_Color是代码中显示的名字,第二个Color是unity显示的名字,第三个Color是类型,由4个只组成rgba
_Vector("Vertor",Vector) = (1,2,3,4) //向量
_Int("Int",Int) = 344 //整数
_Float("Float",Float) = 25.0 //小数
_Range("Range",Range(1,11))=6 //范围数只能是1-11 默认为6
_2D("Texture",2D) = "white"{} //得到图片,没有制定就为白色
_Cube("Cube",Cube) = "white"{} //立方体纹理
_3D("Texture",3D) = "white"{} //3D纹理
}
//SubShader可以有很多个,显卡运行效果的时候从第一个SubShader开始,如果第一个效果可以实现,使用第一个,如果存在一些的效果的实现不了,它会自动运行下一个SubShader
SubShader
{
//pass,至少有一个,这里就是函数
Pass
{
//这里编写Shader代码 HLSLPROGRAM
CGPROGRAM
//使用CG语言编写Shader代码
//分别获得上面的属性值
float4 _Color; //half fixed 和float等价 float使用32位存储,half使用16位来存储 fixed是11位保存
float4 _Vector;
float _Int;
float _Float;
float _Range;
sampler2D _2D;
samplerCube _Cube;
sampler3D _3D;
ENDCG
}
}
Fallback "VertexLit" //如果全部不行,就执行这个shader,unity自己有的shader
}
在这里没有编写顶点着色器和片元的着色器,在下面的编写的过程中会完成,后面的编写的过程,主要是二个着色器的编写。
二:顶点着色器和片元着色器
js
Shader "myshader/02 secondshader can run"
{
SubShader
{
Pass
{
CGPROGRAM
//顶点函数
#pragma vertex vert //声明了定点的函数的函数名,名字为vert,从模型空间转换到剪裁空间的变化
//片元函数
#pragma fragment frag //声明了片元函数的函数名,名字为frag,返回模型中的每一个的像素值
float4 vert(float4 v:POSITION) :SV_POSITION //分别进行说明,通过语义告诉系统干嘛的,POSITION每一个MESH的顶点的数据,SV_POSITION是输出进行说明
{
float4 pos = UnityObjectToClipPos(v); //实际上就是MVP矩阵,这里对于顶点坐标进行MVP变化 //mul(UNITY_MATRIX_MVP,v)
return pos;
}
float4 frag() : SV_Target //函数的实现
{
return float4(0.5,1,1,1); //白色,前面为强制转换
}
ENDCG
}
}
Fallback "VertexLit"
}
在顶点的着色器中,输入为float4 v:POSITION
,输入的是每一个mesh中的顶点信息,:SV_POSITION
是对于顶点着色器的输出进行说明,这个坐标已经完成了MVP变化。float4 pos = UnityObjectToClipPos(v)
;是把输入的坐标进行MVP变换之后的坐标,完成了剪切计算,在计算了顶点的着色器之后,计算进入片元着色器,本例中没有片元着色器的输入变化。使用的相机为场景中的主相机。
三:使用结构体进行传送数据
在进行顶点着色器和片元着色器的过程中,可能需要传送大量不同的数据,一般还是使用结构体进行传送数据:
js
Shader "myshader/03 Use struct"
{
SubShader{
Pass{
CGPROGRAM
//顶点函数
#pragma vertex vert
//片元函数
#pragma fragment frag
//使用结构体 application to vertex
struct a2v {
float4 vertex:POSITION; //给与模型空间下的顶点坐标
float3 normal:NORMAL; //给予模型空间下的法线坐标
float4 texcoord:TEXCOORD0; //给予纹理坐标(第一套),这里使用的是float4,一般常用的UV是2个数据就可以使用,但是这里使用4个数据是为了插值使用
};
//使用结构体 vertex to fragment
struct v2f
{
float4 position:SV_POSITION; //必须要返回位置
float3 temp:COLOR0; //颜色,必须要有语义
};
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
f.temp = v.normal;
return f;
}
float4 frag(v2f f) : SV_Target
{
return float4(f.temp,1);
}
ENDCG
}
}
Fallback "VertexLit"
}
在本篇中分别使用了结构a2v
,用来传递应用端到顶点着色器的数据,其中包含的信息有顶点信息,法线信息,纹理信息。在传送到片元着色器的过程中使用了结构体v2f
,传送到片元的结构之中。
四:片元着色器的漫反射
这里来实现一下基本的漫反射的计算:
js
Shader "Siki/04 Diffuse fragment"
{
Properties
{
_Diffuse("Diffuse Color",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"} //光照模型为前向渲染
CGPROGRAM
#include "Lighting.cginc" //第一个直射光(directional light)的颜色 _LightColor0 光的位置 _WorldSpaceLightPos0
//顶点函数
#pragma vertex vert
//片元函数
#pragma fragment frag
//获得属性值
fixed4 _Diffuse;
//使用结构体 application to vertex
struct a2v
{
float4 vertex:POSITION; //给与模型空间下的顶点坐标
float3 normal:NORMAL; //法线
};
//使用结构体 vertex to fragment
struct v2f
{
float4 position:SV_POSITION; //必须要返回位置
fixed3 worldNormalDir : COLOR;
};
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
f.worldNormalDir = mul(v.normal, (float3x3) unity_WorldToObject);
return f;
}
float4 frag(v2f f) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; //获得环境光
fixed3 normalDir = normalize(f.worldNormalDir);
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //获得平行光的方向,由当前的顶点指向光源的方向
fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir, lightDir), 0); //取得漫反射的颜色
diffuse *= _Diffuse.rgb; //乘上自己带有的颜色,颜色进行融合
fixed3 tempColor = diffuse + ambient;
return float4(tempColor,1);
}
ENDCG
}
}
Fallback "VertexLit"
}
其中关于世界的法线的变化代码为:f.worldNormalDir = mul(v.normal, (float3x3) unity_WorldToObject);
,需要先对于法线也进行世界空间的变化。在片元的着色器中有获得环境的光有:fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
然后获得世界光源的方向fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
,是当前的着色点指向的光源,然后计算漫反射的系数: fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir, lightDir), 0);
,最后计算得出总的光照的效果。
五:半程Lambert模型
js
Shader "myshader/05 Diffuse fragment HalfLambert"
{
Properties
{
_Diffuse("Diffuse Color",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"} //光照模型为前向渲染
CGPROGRAM
#include "Lighting.cginc" //第一个直射光(directional light)的颜色 _LightColor0 光的位置 _WorldSpaceLightPos0
//顶点函数
#pragma vertex vert
//片元函数
#pragma fragment frag
//获得属性值
fixed4 _Diffuse;
//使用结构体 application to vertex
struct a2v
{
float4 vertex:POSITION; //给与模型空间下的顶点坐标
float3 normal:NORMAL; //法线
};
//使用结构体 vertex to fragment
struct v2f
{
float4 position:SV_POSITION; //必须要返回位置
fixed3 worldNormalDir : COLOR;
};
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
f.worldNormalDir = mul(v.normal, (float3x3) unity_WorldToObject);
return f;
}
float4 frag(v2f f) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; //获得环境光
fixed3 normalDir = normalize(f.worldNormalDir);
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //获得平行光的方向,由当前的顶点指向光源的方向
float halfLambert = max(dot(normalDir, lightDir), 0)*0.5f+0.5f;
fixed3 diffuse = _LightColor0.rgb * halfLambert; //取得漫反射的颜色
diffuse *= _Diffuse.rgb; //乘上自己带有的颜色,颜色进行融合
fixed3 tempColor = diffuse + ambient;
return float4(tempColor,1);
}
ENDCG
}
}
Fallback "VertexLit"
}
相比于上一章节,本章主要采用的漫反射的优化过程,float halfLambert = max(dot(normalDir, lightDir), 0)*0.5f+0.5f;
能够优化漫反射的效果。
六:片元的高光反射
在计算了漫反射之后,现在来计算高光反射的效果
js
Shader "myshader/06-Specular Fragment"
{
Properties
{
_Diffuse("Diffuse Color",Color) = (1,1,1,1)
_Specular("Specular Color",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8,200)) = 10
}
SubShader
{
Pass
{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc" //取得第一个直射光的颜色 _LightColor0 第一个直射光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse; //分别获得前面定义的数据,漫反射颜色
fixed4 _Specular; //高光颜色
half _Gloss; //光泽度
//application to vertex
struct a2v
{
float4 vertex:POSITION;//告诉unity把模型空间下的顶点坐标填充给vertex
float3 normal:NORMAL;
};
struct v2f {
float4 position:SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldVertex :TEXCOORD1;
};
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
f.worldNormal = mul(v.normal, (float3x3) unity_WorldToObject);
f.worldVertex = mul(v.vertex, unity_WorldToObject).xyz;
return f;
}
fixed4 frag(v2f f) :SV_Target{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 normalDir = normalize(f.worldNormal);
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//对于每个顶点来说 光的位置就是光的方向 ,因为光是平行光
fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir, lightDir), 0) * _Diffuse.rgb; //取得漫反射的颜色
fixed3 reflectDir = normalize(reflect(-lightDir, normalDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldVertex);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(reflectDir, viewDir), 0), _Gloss);
fixed3 tempColor = diffuse + ambient + specular;
return fixed4(tempColor,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
在v2f
的结构体中包含了顶点信息和法线的信息,在顶点的着色器中都需要进行模型变化,f.worldNormal = mul(v.normal, (float3x3) unity_WorldToObject); f.worldVertex = mul(v.vertex, unity_WorldToObject).xyz;
然后再片元的着色器中计算高光的反射的部分: fixed3 reflectDir = normalize(reflect(-lightDir, normalDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldVertex); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(reflectDir, viewDir), 0), _Gloss);
首先进行计算高光的反射的光线的法相,reflect输入的是一个方向是入射方向到片元。然后计算高光的反射的光线:fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(reflectDir, viewDir), 0), _Gloss);
,然后把所有的结果进行相加。
六:片元的blinn-pong模型
js
Shader "Siki/09-Specular Fragment BlinnPhong"{
Properties
{
_Diffuse("Diffuse Color",Color) = (1,1,1,1)
_Specular("Specular Color",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8,200)) = 10
}
SubShader
{
Pass{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc" //取得第一个直射光的颜色 _LightColor0 第一个直射光的位置_WorldSpaceLightPos0
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
fixed4 _Specular;
half _Gloss;
//application to vertex
struct a2v
{
float4 vertex:POSITION;//告诉unity把模型空间下的顶点坐标填充给vertex
float3 normal:NORMAL;
};
struct v2f
{
float4 position:SV_POSITION;
float3 worldNormal : TEXCOORD0;
float4 worldVertex :TEXCOORD1;
};
v2f vert(a2v v)
{
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
//f.worldNormal = mul(v.normal, (float3x3) _World2Object);
f.worldNormal = UnityObjectToWorldNormal(v.normal);
f.worldVertex = mul(v.vertex, unity_WorldToObject);
return f;
}
fixed4 frag(v2f f) :SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 normalDir = normalize(f.worldNormal);
//fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//对于每个顶点来说 光的位置就是光的方向 ,因为光是平行光
fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex).xyz);
fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir, lightDir), 0) * _Diffuse.rgb; //取得漫反射的颜色
//fixed3 reflectDir = normalize(reflect(-lightDir, normalDir));
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldVertex );
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldVertex));
fixed3 halfDir = normalize(viewDir + lightDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(normalDir, halfDir), 0), _Gloss);
fixed3 tempColor = diffuse + ambient + specular;
return fixed4(tempColor,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
在片元的着色器中
八:带有纹理的模型
js
Shader "myshader/8-Texture"{
Properties
{
//_Diffuse("Diffuse Color",Color) = (1,1,1,1)
_Color("Color",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_Specular("Specular Color",Color) = (1,1,1,1)
_Gloss("Gloss",Range(10,200)) = 20
}
SubShader
{
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
//fixed4 _Diffuse;
fixed4 _Color;
sampler2D _MainTex; //纹理
float4 _MainTex_ST; //获得缩放和偏移
fixed4 _Specular;
half _Gloss;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
struct v2f
{
float4 svPos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float4 worldVertex:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v v)
{
v2f f;
f.svPos = UnityObjectToClipPos(v.vertex);
f.worldNormal = UnityObjectToWorldNormal(v.normal);
f.worldVertex = mul(v.vertex, unity_WorldToObject);
f.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; //更改这里的UV坐标
return f;
}
fixed4 frag(v2f f) :SV_Target
{
fixed3 normalDir = normalize(f.worldNormal);
fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex));
fixed3 texColor = tex2D(_MainTex, f.uv.xy) * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * texColor * max(dot(normalDir, lightDir), 0);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldVertex));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(normalDir, halfDir), 0), _Gloss);
fixed3 tempColor = diffuse + specular + UNITY_LIGHTMODEL_AMBIENT.rgb * texColor;
return fixed4(tempColor, 1);
}
ENDCG
}
}
Fallback "Specular"
}
九:更改带有法线的效果
js
Shader "Siki/13-Rock Normal Map"
{
Properties
{
//_Diffuse("Diffuse Color",Color) = (1,1,1,1)
_Color("Color",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_NormalMap("Normal Map",2D) = "bump"{}
_BumpScale("Bump Scale",Float) = 1
}
SubShader
{
Pass
{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
//fixed4 _Diffuse;
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NormalMap;
float4 _NormalMap_ST;
float _BumpScale;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;//tangent.w是用来确定切线空间中坐标轴的方向的
float4 texcoord:TEXCOORD0;
};
struct v2f
{
float4 svPos:SV_POSITION;
//float3 worldNormal:TEXCOORD0;
//float4 worldVertex:TEXCOORD1;
float3 lightDir : TEXCOORD0;
float4 uv:TEXCOORD1;
};
v2f vert(a2v v)
{
v2f f;
f.svPos = UnityObjectToClipPos(v.vertex);
// f.worldNormal = UnityObjectToWorldNormal(v.normal);
// f.worldVertex = mul(v.vertex, _World2Object);
f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
f.uv.zw = v.texcoord.xy * _NormalMap_ST.xy + _NormalMap_ST.zw;
TANGENT_SPACE_ROTATION;//调用这个后之后,会得到一个矩阵 rotation 这个矩阵用来把模型空间下的方向转换成切线空间下
//ObjSpaceLightDir(v.vertex)//得到模型空间下的平行光方向
f.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
return f;
}
fixed4 frag(v2f f) :SV_Target
{
//fixed3 normalDir = normalize(f.worldNormal);
fixed4 normalColor = tex2D(_NormalMap,f.uv.zw);
// fixed3 tangentNormal = normalize( normalColor.xyz * 2 - 1 ) ; //切线空间下的法线
fixed3 tangentNormal = UnpackNormal(normalColor);
tangentNormal.xy = tangentNormal.xy * _BumpScale;
tangentNormal = normalize(tangentNormal);
fixed3 lightDir = normalize(f.lightDir);
fixed3 texColor = tex2D(_MainTex, f.uv.xy) * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * texColor * max(dot(tangentNormal, lightDir), 0);
fixed3 tempColor = diffuse + UNITY_LIGHTMODEL_AMBIENT.rgb * texColor;
return fixed4(tempColor, 1);
}
ENDCG
}
}
Fallback "Specular"
}
十:包含透明的渲染
js
Shader "Siki/14-Rock Alpha"
{
Properties
{
//_Diffuse("Diffuse Color",Color) = (1,1,1,1)
_Color("Color",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_NormalMap("Normal Map",2D) = "bump"{}
_BumpScale("Bump Scale",Float) = 1
_AlphaScale("Alpha Scale",Float) = 1
}
SubShader
{
Tags{ "Queue" = "Transparent" "IngnoreProjector" = "True" "RenderType" = "Transparent" } //渲染队列,该着色器是透明着色器,是否忽略投影,渲染的方式
Pass
{
Tags{ "LightMode" = "ForwardBase" }
ZWrite Off //关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha //混合分别为 a,1-a
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
//fixed4 _Diffuse;
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NormalMap;
float4 _NormalMap_ST;
float _BumpScale;
float _AlphaScale;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;//tangent.w是用来确定切线空间中坐标轴的方向的
float4 texcoord:TEXCOORD0;
};
struct v2f
{
float4 svPos:SV_POSITION;
//float3 worldNormal:TEXCOORD0;
//float4 worldVertex:TEXCOORD1;
float3 lightDir : TEXCOORD0;
float4 uv:TEXCOORD1;
};
v2f vert(a2v v)
{
v2f f;
f.svPos = UnityObjectToClipPos(v.vertex);
// f.worldNormal = UnityObjectToWorldNormal(v.normal);
// f.worldVertex = mul(v.vertex, _World2Object);
f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
f.uv.zw = v.texcoord.xy * _NormalMap_ST.xy + _NormalMap_ST.zw;
TANGENT_SPACE_ROTATION;//调用这个后之后,会得到一个矩阵 rotation 这个矩阵用来把模型空间下的方向转换成切线空间下
//ObjSpaceLightDir(v.vertex)//得到模型空间下的平行光方向
f.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
return f;
}
fixed4 frag(v2f f) :SV_Target
{
//fixed3 normalDir = normalize(f.worldNormal);
fixed4 normalColor = tex2D(_NormalMap,f.uv.zw);
// fixed3 tangentNormal = normalize( normalColor.xyz * 2 - 1 ) ; //切线空间下的法线
fixed3 tangentNormal = UnpackNormal(normalColor);
tangentNormal.xy = tangentNormal.xy * _BumpScale;
tangentNormal = normalize(tangentNormal);
fixed3 lightDir = normalize(f.lightDir);
fixed4 texColor = tex2D(_MainTex, f.uv.xy) * _Color;
fixed3 diffuse = _LightColor0.rgb * texColor.rgb * max(dot(tangentNormal, lightDir), 0);
fixed3 tempColor = diffuse + UNITY_LIGHTMODEL_AMBIENT.rgb * texColor;
//return fixed4(tempColor,_AlphaScale * texColor.a); //这个的a的值为0
return fixed4(tempColor,0.5);
}
ENDCG
}
}
Fallback "Specular"
}
这些是常用的unity的自带管线的编写,一般使用URP的管线的更多。