文章目录
- [1. Shader](#1. Shader)
-
- [1.1 基本格式](#1.1 基本格式)
-
- [1.1.1 SubShader](#1.1.1 SubShader)
- [1.1.2 Pass](#1.1.2 Pass)
- [1.1.3 CG语言](#1.1.3 CG语言)
- [1.1.4 语义](#1.1.4 语义)
- [1.1.5 引入](#1.1.5 引入)
- [1.2 着色](#1.2 着色)
-
- [1.2.1 顶点着色器](#1.2.1 顶点着色器)
- [1.2.2 片元着色器](#1.2.2 片元着色器)
- [1.3 物体着色流程](#1.3 物体着色流程)
-
- [1.3.1 新建Shader](#1.3.1 新建Shader)
- [1.3.2 新建材质](#1.3.2 新建材质)
- [1.3.3 挂载材质](#1.3.3 挂载材质)
- [2. 结构体](#2. 结构体)
-
- [2.1 编写结构体](#2.1 编写结构体)
-
- [2.1.1 简单结构体](#2.1.1 简单结构体)
- [2.1.2 引入结构体后的Shader代码](#2.1.2 引入结构体后的Shader代码)
- [2.2 unity封装的结构体](#2.2 unity封装的结构体)
-
- [2.2.1 查看unity结构体](#2.2.1 查看unity结构体)
- [2.2.2 常用的结构体](#2.2.2 常用的结构体)
- [2.2.3 应用unity结构体](#2.2.3 应用unity结构体)
- [2.3 案例1-小彩球](#2.3 案例1-小彩球)
-
- [2.3.1 顶点着色器](#2.3.1 顶点着色器)
- [2.3.2 片元着色器](#2.3.2 片元着色器)
- [2.3.3 完整脚本](#2.3.3 完整脚本)
- [3. Properties](#3. Properties)
-
- [3.1 常用外部资产](#3.1 常用外部资产)
- [3.2 外部着色](#3.2 外部着色)
-
- [3.2.1 _Color](#3.2.1 _Color)
- [3.2.2 完整脚本](#3.2.2 完整脚本)
- [3.3 贴图](#3.3 贴图)
-
- [3.3.1 _MainTex](#3.3.1 _MainTex)
- [3.3.2 完整脚本](#3.3.2 完整脚本)
- [3.4 案例2-图片与外部颜色叠加显示](#3.4 案例2-图片与外部颜色叠加显示)
- [4. 漫反射](#4. 漫反射)
-
- [4.1 基本储备](#4.1 基本储备)
-
- [4.1.1 appdata_full](#4.1.1 appdata_full)
- [4.1.2 相关代码](#4.1.2 相关代码)
- [4.2 兰伯特算法](#4.2 兰伯特算法)
-
- [4.2.1 公式](#4.2.1 公式)
- [4.2.2 逐顶点脚本](#4.2.2 逐顶点脚本)
- [4.2.3 逐像素脚本](#4.2.3 逐像素脚本)
- [4.4 半兰伯特算法-案例3-漫反射材质球](#4.4 半兰伯特算法-案例3-漫反射材质球)
-
- [4.4.1 公式](#4.4.1 公式)
- [4.4.2 完整脚本](#4.4.2 完整脚本)
- [5. 表面着色器](#5. 表面着色器)
-
- [5.1 使用格式](#5.1 使用格式)
- [5.2 viewDir](#5.2 viewDir)
-
- [5.2.1 viewDir](#5.2.1 viewDir)
- [5.2.2 完整脚本](#5.2.2 完整脚本)
- [5.3 案例4-渐变发光小球](#5.3 案例4-渐变发光小球)
1. Shader
1.1 基本格式
1.1.1 SubShader
001是Shader的文件名
Shader有很多影响其行为的块SubShader
csharp
Shader "Custom/001"
{
SubShader{ } //干预着色器
}
在unity上方工具栏点击扩展,选择管理扩展,搜索ShaderlabVS,下载安装后编辑代码时会提示是否有误
1.1.2 Pass
同一个物体有很多状态
csharp
Shader "Custom/001"
{
SubShader
{
//假如这里写水的渲染
pass{ } //通道
//假如这里写石头的渲染
pass{ }
}
}
1.1.3 CG语言
直接和着色器对接的部分CG语言
csharp
Shader "Custom/001"
{
SubShader
{
pass
{
CGPROGRAM //开始 CG 语言了
ENDCG // CG 语言结束了
}
}
}
1.1.4 语义
shader规定了一些词汇,获得一些信息,也可以去传递一些信息,这些词汇就叫做语义,用法就是
:语义词汇
CG语言:在CG语言里,Vector2的表达是float2,Vector3的表达是float3,Vector4的表达是float4
csharp
//声明变量float4 //变量名称v //POSITION就是语义,你只要:POSITION就能获取到模型的顶点
float4 v:POSITION
常用语义
:POSITION 获取到模型的顶点坐标
:NORMAL 法线坐标
:TEXCOORD0 第一套纹理坐标 //纹理坐标就UV坐标
:SV_POSITION 输出给像素着色器的屏幕坐标
:SV_TARGET 输出值直接用于渲染了
1.1.5 引入
shader引入后需要给引入的内容起个名字
csharp
#pragma vertex vert //引用顶点着色器
#pragma fragment frag //引用片元着色器
1.2 着色
1.2.1 顶点着色器
1)坐标转换
模型的顶点位置是世界坐标下的,屏幕不一定能显示全面,shader只需要管屏幕看得到的东西就够了
世界坐标转屏幕坐标代码如下:
csharp
//最后会得到,模型的屏幕坐标
UnityObjectToClipPos(这里输入模型的顶点世界坐标)
2)指定着色的点
shader里是不好随便去声明数据的,传入数据时,用的是括号传入

1.2.2 片元着色器
csharp
//引入fragment //起名叫frag
#pragma fragment frag
//片元着色器方法 //直接输出渲染
float4 frag():SV_TARGET
{
//输出白色
return float4(1,1,1,1);
}
1.3 物体着色流程
1.3.1 新建Shader
新建一个Shader,这几个Shader任选一个,并将其重命名为001

点开Shader,脚本如下
csharp
Shader "Custom/001"
{
SubShader
{
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v :POSITION):SV_POSITION
{
if (v.x <= 0)
{
v.x = 0; //x负方向的点不着色
}
return UnityObjectToClipPos(v);
}
float4 frag():SV_TARGET
{
return float4(1,1,1,1);
}
ENDCG
}
}
}
1.3.2 新建材质
新建一个材质

在材质的shader处找到前面新建的001

1.3.3 挂载材质
选中需要着色的物体,在对象的材质处挂载刚刚新建的材质

2. 结构体
当需要继承很多数据时,一个括号就不方便管理,于是引入新语法,结构体
2.1 编写结构体
2.1.1 简单结构体
csharp
//这里结构体的名字是可以自己起的
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
}
2.1.2 引入结构体后的Shader代码
修改前面1.3.1里的shader脚本,引入结构体后如下:
csharp
Shader "Custom/001"
{
SubShader
{
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//这里结构体的名字是可以自己起的
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
//传入并声明结构体
float4 vert(a2v v):SV_POSITION
{
if (v.vertex.x <= 0)
{
v.vertex.x = 0; //x负方向的点不着色
}
//调用结构体的vertex
return UnityObjectToClipPos(v.vertex);
}
float4 frag():SV_TARGET
{
return float4(1,1,1,1);
}
ENDCG
}
}
}
2.2 unity封装的结构体
2.2.1 查看unity结构体
进入unity网站,在下载unity的界面处,选择对应的版本,在Win 处选择Bulit in shaders

下载完后解压,找到UnityCG.cginc文件,该文件里就可以看见unity已有的结构体

2.2.2 常用的结构体
csharp
struct appdata_base {
float4 vertex : POSITION;//顶点坐标
float3 normal : NORMAL;//法线
float4 texcoord : TEXCOORD0;//第一纹理坐标
UNITY_VERTEX_INPUT_INSTANCE_ID //ID信息
};
struct appdata_tan {
float4 vertex : POSITION;//顶点坐标
float4 tangent : TANGENT;//切线
float3 normal : NORMAL;//法线
float4 texcoord : TEXCOORD0;//第一纹理坐标
UNITY_VERTEX_INPUT_INSTANCE_ID //ID信息
};
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 //ID信息
};
2.2.3 应用unity结构体
引用结构体
#include"UnityCG.cginc"
csharp
Shader "Custom/001"
{
SubShader
{
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include"UnityCG.cginc"
//传入上述结构体
float4 vert(appdata_base v):SV_POSITION
{
//调用结构体的vertex
return UnityObjectToClipPos(v.vertex);
}
float4 frag():SV_TARGET
{
return float4(1,1,1,1);
}
ENDCG
}
}
}
2.3 案例1-小彩球
之前是最后传了一个颜色,不管哪个点,都显示一个颜色,这次是每个顶点需要不同的颜色
2.3.1 顶点着色器
声明了这个结构体,这个结构体会自带值,这个值就是后面的语义里所带的值
如果我们改变了这个结构体,在把它return出去,改变的值就会自己输出到后面的语义里,进行下一轮计算
csharp
appdata_base vert(appdata_base v)
{
//我们在这里把顶点坐标改成了屏幕坐标,传回了appdata_base里
v.vertex =UnityObjectToClipPos(v.vertex);
//因为最后return了,所以v.vertex会被语义POSITION接收
return v;
}
2.3.2 片元着色器
1)在顶点着色器里修改的值,实际上是储存在语义里了,再次声明也是从语义里接收数据,所以只需要再次声明appdata_base,就可以接收到顶点着色器中修改过的数据
2)每一个顶点,对应着一个法线,每一个法线值,会映射成不同的【0,1】之间的值
csharp
float4 frag(appdata_base v):SV_TARGET
{
//-1,1 → 0,1
//因为你是一组数,不是一个数,所以不能直接加0.5,让颜色随顶点坐标的法向量变化而变化
float3 n = v.normal/2+float3(0.5,0.5,0.5);
//n是float3,但返回的是float4,所以后面补一个1
return float4(n,1);
}
2.3.3 完整脚本
csharp
Shader "Custom/001"
{
SubShader
{
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include"UnityCG.cginc"
appdata_base vert(appdata_base v)
{
v.vertex =UnityObjectToClipPos(v.vertex);
return v;
}
float4 frag(appdata_base v):SV_TARGET
{
//因为你是一组数,不是一个数,所以不能直接加0.5
float3 n = v.normal/2+float3(0.5,0.5,0.5);
return float4(n,1);
}
ENDCG
}
}
}
3. Properties
3.1 常用外部资产
代码格式:
内部名称 外部名称 数据类型 赋值
csharp
Properties
{
//颜色
_Color("颜色", Color) = (1,1,1,1)
//图片
_Tex("2D图片",2D) = "white"{}
_Tex3("3D图片",3D) = "white"{}
_Cube("Cube图片",CUBE)=""{}
//数字
_Float("Float数字",Float) = 0.5
_Int("Int数字",Int) = 1
_Range("范围",Range(0,100))=1
_Vector("坐标",Vector)=(1,1,1,1)
}

后面的数字设置也是,如果是没打钩 =0,打钩了就 =1
csharp
Properties
{
[Toggle] _MyToggle("MyToggle",float) =0
}
引入内部,完整脚本
csharp
Shader "Custom/NewSurfaceShader"
{
Properties
{
//颜色
_Color ("颜色", Color) = (1,1,1,1)
//图片
_Tex("2D图片",2D) = "white"{}
_Tex3("3D图片",3D) = "white"{}
_Cube("Cube图片",CUBE)=""{}
//数字
_Float("Float数字",Float) = 0.5
_Int("Int数字",Int) = 1
_Range("范围",Range(0,100))=1
_Vector("坐标",Vector)=(1,1,1,1)
}
SubShader
{
//这里是CG语言
CGPROGRAM
//****************************
//这里就是把格式重写一遍,注意看,名称和上面是一样的
//四个数字代表颜色
fixed4 _Color;
//普通图片
sampler2D _Tex;
//3D图片
sampler3D _Tex3;
//Cube图片
samplerCUBE _Cube;
//一个数字
float _Float;
int _Int;
//这里是不需要很大的小数
half _Range;
float4 _Vector;
//********************************
ENDCG
}
}
3.2 外部着色
3.2.1 _Color
_Color是脚本里使用的名称
"Color"是外部使用的名称
Color是声明的数据类型
csharp
Properties
{
//内部使用的名称 //外部使用的名称 //声明的数据类型 //赋值(1,1,1,1)
_Color("Color",Color)=(1,1,1,1)
}
3.2.2 完整脚本
用的时候在SubShader中还是要重新声明一下
csharp
Shader "Custom/001"
{
Properties
{
//内部使用的名称 //声明的数据类型
_Color("Color",Color)=(1,1,1,1)
//外部使用的名称 //赋值(1,1,1,1)
}
SubShader
{
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include"UnityCG.cginc"
//重新声明_Color
float4 _Color ;
appdata_base vert(appdata_base v)
{
v.vertex =UnityObjectToClipPos(v.vertex);
return v;
}
float4 frag():SV_TARGET
{
//直接将接到的数据输出
return _Color;
}
ENDCG
}
}
}
3.3 贴图
3.3.1 _MainTex
2D 2D图片资源
"white" {} 这个是unity之前准备好的图片资源,就是一个白图
备注:因为shader是一直运行的,不能加空的进去,会出问题,所以先加个白图
1)编辑脚本
csharp
Properties
{
_Color("Color",Color)=(1,1,1,1)
//本节新增
_MainTex("MainTex",2D) = "white" {}
}
2)挂载图片
将图片拖拽至Shader的MainTex处
3.3.2 完整脚本
csharp
Shader "Custom/001"
{
Properties
{
_MainTex("MainTex",2D) = "white"{}
}
SubShader
{
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include"UnityCG.cginc"
//承接2D图片在CG语言里是sampler2D //起名为MainTex
sampler2D _MainTex;
appdata_base vert(appdata_base v)
{
v.vertex =UnityObjectToClipPos(v.vertex);
return v;
}
float4 frag(appdata_base v):SV_TARGET
{
//转换贴图数据 //appdata_base中的贴图数据,取出xy轴数据(这里也可以理解为uv数据)
float4 c =tex2D(_MainTex,v.texcoord.xy);
return c;
}
ENDCG
}
}
}
3.4 案例2-图片与外部颜色叠加显示
csharp
float4 c =tex2D(_MainTex,v.texcoord.xy)*_Color;
颜色和颜色的叠加一般相乘即可
完整的代码如下:
csharp
Shader "Custom/001"
{
Properties
{
_Color("Color",Color)=(1,1,1,1)
_MainTex("MainTex",2D) = "white"{}
}
SubShader
{
pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include"UnityCG.cginc"
float4 _Color ;
sampler2D _MainTex;
appdata_base vert(appdata_base v)
{
v.vertex =UnityObjectToClipPos(v.vertex);
return v;
}
float4 frag(appdata_base v):SV_TARGET
{
float4 c =tex2D(_MainTex,v.texcoord.xy)*_Color;
return c;
}
ENDCG
}
}
}
4. 漫反射
4.1 基本储备
4.1.1 appdata_full
所需结构体
csharp
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 //ID信息
};
4.1.2 相关代码
引入光
csharp
#include "Lighting.cginc"
所用算法
csharp
UnityObjectToWorldNormal() //把物体的法线坐标,换算到世界坐标下
normalize() //把任何一个向量变成单位向量
dot() //点乘
max() //上文讲过
//以下要引用#include "Lighting.cginc"才能找到
_WorldSpaceLightPos0 //世界坐标下的光线坐标
_LightColor0 //光线的颜色
4.2 兰伯特算法
4.2.1 公式

屏幕上对应点的颜色 = (光的颜色*物体的颜色)max(0,该点的法向量该点的光照方向)
4.2.2 逐顶点脚本
白色和黑色交界处有些方块块的感觉、照不到的地方全黑
csharp
Shader "Custom/001"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//新的引用
#include "Lighting.cginc"
//返回结构体 //引用结构体
appdata_full vert (appdata_full v)
{
//模型顶点坐标转屏幕坐标
v.vertex = UnityObjectToClipPos(v.vertex);
//获取法线坐标并转换成世界坐标下的法线坐标
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
//世界坐标下的光线坐标 //单位化坐标 //获取世界坐标下的光线坐标
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//上面的公式
float3 diffuse =_LightColor0.rgb * v.color.rgb * max(0,dot(worldNormal,worldLight));
//算出的值给颜色
v.color = float4(diffuse,1);
return v;
}
float4 frag (appdata_full v) : SV_Target
{
//输出颜色
return float4(v.color) ;
}
ENDCG
}
}
}
4.2.3 逐像素脚本
白色和黑色交界处平滑过渡、照不到的地方全黑
顶点算法是在顶点着色器中写的,像素算法是在片元着色器中写的
顶点是初始值,经过一系列计算后,数据就会和想要的有些偏差
像素着色器离最后的显示比较近,所以出来的结果和想要的更一致
csharp
Shader "Unlit/001"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
appdata_full vert (appdata_full v)
{
v.vertex = UnityObjectToClipPos(v.vertex);
return v;
}
float4 frag (appdata_full v) : SV_Target
{
//法线世界坐标
float3 worldNormal = v.normal;
//光线世界坐标
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算颜色
float3 diffuse =_LightColor0.rgb * v.color.rgb * max(0,dot(worldNormal,worldLight));
//把颜色传进去
return float4(diffuse,1) ;
}
ENDCG
}
}
}
4.4 半兰伯特算法-案例3-漫反射材质球
白色和黑色交界处平滑过渡、照不到的地方不是全黑
在光照不到的地方也能看见,只是比较暗,而不是全黑
4.4.1 公式

最终颜色 = 环境光+Cdiffuse
4.4.2 完整脚本
csharp
Shader "Unlit/001"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
appdata_full vert (appdata_full v)
{
v.vertex = UnityObjectToClipPos(v.vertex);
return v;
}
float4 frag (appdata_full v) : SV_Target
{
float3 worldNormal = v.normal;
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//本节变动
//获取环境光
float3 anbient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//计算范围
float halfLamient = dot(worldNormal,worldLight)*0.5+0.5;
//计算反射强度
float3 diffuse =_LightColor0.rgb * v.color.rgb *halfLamient;
//反射光加光照强度
float3 c = anbient + diffuse;
return float4(c,1) ;
}
ENDCG
}
}
}
5. 表面着色器
5.1 使用格式
csharp
SubShader
{
CGPROGRAM
#pragma surface surf Standard
//给表面着色器配的输入结构体
struct Input
{
};
//表面着色器
void surf (Input IN, inout SurfaceOutputStandard o)
{
}
ENDCG
}
表面着色器的结构体
csharp
struct Input
{
float3 viewDir //包含视图方向,用于计算视差效果、边缘光照等等。
具有 COLOR 语义的 float4 - 包含插值的每顶点颜色。
float4 screenPos //包含反射或屏幕空间效果的屏幕空间位置。请注意,这不适合 GrabPass;您需要使用 ComputeGrabScreenPos 函数自己计算自定义 UV。
float3 worldPos //包含世界空间位置。
float3 worldRefl //在_表面着色器不写入 o.Normal_ 的情况下,包含世界反射矢量。有关示例,请参阅反光漫射 (Reflect-Diffuse) 着色器。
float3 worldNormal // 在_表面着色器不写入 o.Normal_ 的情况下,包含世界法线矢量。
float3 worldRefl; // INTERNAL_DATA - 在_表面着色器写入 o.Normal_ 的情况下,包含世界反射矢量。要获得基于每像素法线贴图的反射矢量,请使用 WorldReflectionVector (IN, o.Normal)。有关示例,请参阅反光凹凸 (Reflect-Bumped) 着色器。
float3 worldNormal; // INTERNAL_DATA - 在_表面着色器写入 o.Normal_ 的情况下,包含世界法线矢量。要获得基于每像素法线贴图的法线矢量,请使用 WorldNormalVector (IN, o.Normal)。
};
5.2 viewDir
5.2.1 viewDir
viewDir是一个向量,当它和法线向量点乘时:
答案>0 在该点的正对面
答案=0 和该点成90度
答案<0 和该点大于90度(在这个情况下,一般就是看不见了)
5.2.2 完整脚本
csharp
Shader "Custom/001"
{
Properties
{
//放图片进入
_MainTex("MainTex",2D)="white"{}
}
SubShader
{
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
//直接获取viewDir
float3 viewDir;
};
void surf(Input IN,inout SurfaceOutput o)
{
//点乘在shader里的写法
half dotp =dot(IN.viewDir,o.Normal);
//如果大于0才会显示颜色,否则是黑色
o.Albedo =tex2D(_MainTex,IN.uv_MainTex).rgb*dotp;
}
ENDCG
}
}
5.3 案例4-渐变发光小球
csharp
Shader "Custom/001"
{
Properties
{
_MainTex("MainTex",2D)="white"{}
_Color("Color",Color)=(0,0.5,0.5,0)
//边缘光范围参数
_Power("Power",Range(0.8,8)) = 3
}
SubShader
{
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
float4 _Color;
//接入参数
float _Power;
struct Input
{
float2 uv_MainTex;
float3 viewDir;
};
void surf(Input IN,inout SurfaceOutput o)
{
o.Albedo =tex2D(_MainTex,IN.uv_MainTex).rgb;
half dotp =dot(IN.viewDir,o.Normal);
//重新设置范围变化曲线
o.Emission =_Color.rgb*pow((1-dotp),_Power);
}
ENDCG
}
}
参考专栏
参考视频