前言

如果你对cg glsl hlsl 顶点着色器 片段着色器 表面着色器 固定渲染管线 等等有所疑惑,或是想学会unity的渲染,看这一篇就足够了。另外我博客的shader分类中还有很多shader教程和源码,每篇源码都有实现思路、语法功能注释,还在不断更新添加中。感兴趣的请自行查看

正文

让vs支持shader

点击vs的工具>扩展和更新>联机>visual studio marketplace
搜素shader
点击下载
重启vs
弹出installer,点击修改 即可。

一,基础知识

关于着色器语言与Shader Lab

着色器语言有三种:
hlsl(DirectX)
glsl(OpenGL)
cg(支持以上两种)
Shader Lab 是unity封装了cg、hlsl、glsl的unity专有着色器语言。shaderlab具有跨平台、图形化编程、便于着色器与unity通信等优点。在unity2018.3.5f以后版本,可以使用图形化工具shader graph来大幅缩减shader编程成本,使这个令人头疼的语言走入寻常百姓家。。。。。但作为一个unityer最好具有基本的Shader Lab编程能力,至少了解基本效果的代码实现过程。

mesh、材质、着色器的与模型的关系

把模型比作一只兔子,那mesh就是兔子的骨架,顶点就是骨架的端点,片段就是由顶点组成的面,材质就是皮肉用来装饰片段的,shader就是用来控制如何显示材质的。

关于顶点、片段处理器

顶点着色器和片段着色器都有自己独立的硬件处理单元。该硬件处理单元拥有非常强大的并行运算能力,非常擅长矩阵计算,片段处理器还可以告诉查询纹理信息。

白话:顶点着色器负责定位像素位置!片段着色器负责修改像素颜色!!

顶点着色程序与片断着色程序通常是同时存在,相互配合,前者的输出作为后者的输入。不过,也可以只有顶点着色程序。如果只有顶点着色程序,那么只对输入的顶点进行操作,而顶点内部的点则按照硬件默认的方式自动插值。例如,输入一个三角面片,顶点着色程序对其进行phong光照计算,只计算三个顶点的光照颜色,而三角面片内部点的颜色按照硬件默认的算法(Gourand明暗处理或者快速phong明暗处理)进行插值,如果图形硬件比较先进,默认的处理算法较好(快速phong明暗处理),则效果也会较好;如果图形硬件使用Gourand明暗处理算法,则会出现马赫带效应(条带化)。
而片断着色程序是对每个片断进行独立的颜色计算,并且算法由自己编写,不但可控性好,而且可以达到更好的效果。
由于GPU对数据进行并行处理,所以每个数据都会执行一次shader程序程序。即,每个顶点数据都会执行一次顶点程序;每个片段都会执行一次片段程序。
片段就是所有三维顶点在光栅化之后的数据集合,这些数据没有经过深度值比较,而屏幕显示的像素是经过深度比较的。

顶点着色器、片段着色器与表面着色器的关系

作用

顶点着色器负责顶点坐标变换,片段着色器负责像素的颜色计算。顶点着色器计算好坐标信息后传入片段着色器计算颜色。所以顶点着色器和片段着色器是合作关系。
表面着色器是封装了顶点和片段着色器的新api。与他们属于上下层关系。
shader编译时会将表面渲染代码编译成多个pass代码块,再分解成顶点/片元着色器。

区别

顶点着色器用于处理顶点。片段着色器用于处理面。
表面着色器是对顶点着色器与片段着色器的进一步封装。
即是说,表面着色器有一套即成的处理办法,不用去搞那些细节。
而顶点着色器和片段着色器更接近底层,可以处理一些细节问题。

着色器的工作流程

着色器的工作流程

二,Shader Lab语法

基本结构

1
2
3
4
5
6
7
8
9
Shader "path/name" {
Properties{
//_CG变量名 ("unity可见的变量名", 属性类型) = 值
_Color ("My Color", Color) = (1, 1, 1, .5)
}
Subshader{
}
}
12345678
  • path/name :unity编辑器中检索的位置和shader名。shader名尽量与shader文件名一样。
  • Properties:shader属性,用于cg与unity通信。
  • Subshader:shader解决方案。每个shader程序包含至少一个subshader,用于解决硬件性能兼容问题。shader的主代码部分都写在这里

Properties

属性的结构:_CG变量名 (“unity可见的变量名”, 属性类型) = 值
例:_Color (“My Color”, Color) = (1, 1, 1, .5)

属性类型表

类型 说明 实例
Int 整型 (.1, 2)
Float 浮点数 .5
Vector 四维向量 (.5, 1 , 1, 0.5)
Range 范围1-2.3的浮点数 (1, 2.3)
Color RGBA颜色 (1,1,1,.5)
2D 2d贴图, 2d纹理,默认值可以为一个代表默认tint颜色的字符串,可以是空字符串或者”white”,”black”,”gray”,”bump”中的一个 ”white”{}
3D 3d贴图
Cube 6面立方贴图 ”white”{}
Rect 矩形贴图 ”white”{}

SubShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SubShader { 
Tags { "RenderType" = "Opaque" "ForceNoShadowCasting" = "True" "IgnoreProjector" = "True"}
LOD 100
Pass{
Fog{Mode OFF}

//固定渲染器
Material{
Diffuse[_Color] //设置漫反射
}
Lighting On
SeparateSpecular On //启用高光颜色
//设置纹理
SetTexture[_MainTex]{
//表面渲染器
#pragma surface surf Lambert
//顶点渲染器
#pragma vertexvert
//片段渲染器
#pragma fragment frag
}
Pass{
}
}

Tags

tags可以填写多个命令,可以控制渲染时机。可以通过在unity的asset窗口中点击shader查看tags生效情况。

标签 说明
“RenderType”=“Opaque” 系统在渲染不透明物体时调用该shader
“RenderType” = “Transparent” 系统在渲染透明物体时调用该shader,绝大部分透明的物体、包括粒子特效都使用这个
“RenderType” = “Background” 系统渲染背景时调用,天空盒都使用这个
“RenderType” = “Overlay” 系统渲染gui镜头时调用,GUI、镜头光晕都使用这个
“IgnoreProjector”=“True” 忽略Projectors
“ForceNoShadowCasting”=“True” 不生成阴影
“Queue”=“xxx” 指定渲染队列顺序,下面有详细说明

Queue的说明

关键字 说明
Background 最先调用的,用来渲染天空盒或背景
Geometry 默认值,用来渲染非透明物体(一般情况下,场景中的绝大多数物体应该是非透明的)
AlphaTest 用来渲染经过Alpha Test的像素,单独为AlphaTest设定一个Queue是出于对效率的考虑
Transparent 以从后往前的顺序渲染透明物体
Overlay 用来渲染叠加的效果,是渲染的最后阶段(比如镜头光晕等特效)

这些预定义的值本质上是一组定义整数,Background = 1000, Geometry = 2000, AlphaTest = 2450, Transparent = 3000,最后Overlay = 4000。在我们实际设置Queue值时,不仅能使用上面的几个预定义值,我们也可以指定自己的Queue值,写成类似这样:“Queue”=“Transparent+100”,表示一个在Transparent之后100的Queue上进行调用。通过调整Queue值,我们可以确保某些物体一定在另一些物体之前或者之后渲染,这个技巧有时候很有用处。(比如遮挡描边效果,应该就是这么来的)

LOD

细节等级。大家玩吃鸡的时候,从飞机上跳下,这时看到地图上的建筑都是比较粗糙的块,当距离慢慢拉近,建筑模型变得越发精致,这就是LOD技术,根据不同的范围使用不同的模型。shader的LOD也是同样用法,不同细节等级,使用不同的LOD。在Unity的Quality Settings中可以设定最大LOD值,当当前LOD小于shader LOD时,那个sub shader就会失效.
VertexLit及其系列 = 100
Decal, Reflective VertexLit = 150
Diffuse = 200
Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
Bumped, Specular = 300
Bumped Specular = 400
Parallax = 500
Parallax Specular = 600### 三、实例

pass

pass是实现着色器具体代码的地方。一个subshader内可以有多个pass。但尽可能用较少的pass实现是对性能的考虑。

pass内的tags说明
pass内的tags有别与subshader中的tags

取值 例子 说明
Always “LightMode”=“Always” 不管是用哪种渲染路径,该pass总是会被渲染。但不计算任何光照
Forwardbase “LightMode”=“ForwardBase” 用于向前渲染,该pass会计算环境光,重要的平行光,逐顶点/SH光源和lightmaps
ForwardAdd “LightMode”=“ForwardAdd” 用于向前渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源
Deferred “LightMode”=“Deferred” 用于向前渲染,该pass会渲染G缓冲,G-buffer
ShadowCaster “LightMode”=“ShadowCaster” 把物体的深度信息渲染到盈盈映射纹理(shadowmap)或一张深度纹理中,用于渲染产生阴影的物体
ShadowCollector “LightMode”=“ShadowCollector” 用于收集物体阴影到屏幕坐标Buff里
PrepassBase 用于遗留的延迟渲染,该pass会渲染法线和高光反射的指数部分
PrepassFinal 用于遗留的延迟渲染,该pass通过合并纹理、光照和自发光来渲染得到最后的颜色
Vertex、VertexLMRGBM和VertexLM 用于遗留的顶点照明渲染

pass 内的代码分为:固定渲染管线、可编程顶点/片段渲染管线,可编程表面渲染管线。
下面是对三种渲染管线的详细介绍。

pass中的ragma

ragma用于对渲染器的控制。
ragma 参数表

命令 参数 实例 说明
vertex #pragma vertex name 将函数name的代码编译为顶点程序
fragment #pragma fragment name 将函数name的代码编译为片元程序
geometry #pragma geometry name 将函数name的代码编译为DX10的几何着色器
hull #pragma hull name 将函数name 的代码编译为DX11hull着色器
domain #pragma domain name 将函数name 的代码编译为DX11 domain着色器
fragmentoption option #pragma fragmentoption option 添加选项到编译的OpenGL片段程序。对顶点程序或编译目标不是opengl的程序无效
target target 2.0、target 3.0、target 4.0、target 5.0 #pragma target name 设置着色器的编译目标,对应不同版本的着色器模型
only_renderers space separated d3d9(direct3d 9)、d3d11、opengl、gles(opengl 2s 2.0)、xbox360、ps3、flash #pragma only_renderers space separated names 仅编译到指定的渲染平台
exclude_renderers space separated d3d9(direct3d 9)、d3d11、opengl、gles(opengl 2s 2.0)、xbox360、ps3、flash #pragma exclude_renderers space separated names 不编译到指定的渲染平台
glsl #pragma glsl 为桌面系统的opengl进行编译时,将cg/hlsl代码转为glsl代码
glsl_no_auto_normalization #pragma glsl_no_auto_normalization name 编译到移动平台glsl时(ios/android), 关闭在定点着色器中对法线向量和切线向量自动进行规范化

着色器中的参数

从应用阶段传递模型数据给顶点着色器时 常用的语义

命令 实例 说明
POSITION 模型空间中的顶点位置,一般是float4类型
NORMAL 顶点法线,float3类型
TANGENT 顶点切线 float4
TEXCOORD0~N 该顶点纹理坐标,0是第一组,一般是flkoat2 或float4类型
COLOR 定点颜色,通常是fixed4或float4类型

从顶点着色器传递给片元着色器时 常用的语义

命令 实例 说明
SV_POSITION 裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量
COLOR0 用于输出第一组顶点颜色
COLOR1 通常用于输出第二组顶点颜色
TEXCOORD0~TEXCOORD7 通常用于输出纹理坐标

片元着色器输出给unity时常用的语义

命令 实例 说明
SV_Target 输出值将会存到渲染目标(render target)中

unity 内置的矩阵变换

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

unity 顶点转换函数

命令 说明 实例
float4 UnityObjectToClipPos(float3 pos) 将一个点从object空间转换成相机在均匀坐标下的剪辑空间。这就相当于 mul(UNITY_MATRIX_MVP, float4(pos, 1.0)), 应该在它的位置上使用。
float3 UnityObjectToViewPos(float3 pos) 将一个点从object空间转换为view空间。这就相当于mul(UNITY_MATRIX_MV, float4(pos, 1.0)).xyz, 应该在它的位置上使用。

辅助函数

命令 说明 实例

unity 内置的摄像机和屏幕参数

命令 实例 说明
float3 _WorldSpaceCameraPos 该摄像机在世界空间中的位置
float4 _ProjectionParams x=1.0 或-1.0(使用反转的投影矩阵渲染时是负数),y=Near,z=Far,w= 1.0+1.0/Far, 其中near和far分别是近裁剪平面和远裁剪平面与摄像机的距离
float4 _ScreenParams x=width,y=height,z=1.0+1.0/width,w=1.0+1.0/height, 其中width和height分别是该摄像机的渲染目标 (render target)的像素宽度和高度
float4 _ZBufferParams x=1-Far/near,yFar/Near, 最x/Far,wy/Far,该变量用于线性化Z缓存中的深度值
floart4 unity_OrhoParams x=width,y=height,z无意义,w=1.0(该相机是正交相机)或w=0.0(透视相机),其中width和height是正交投影相机的宽和高
float4x4 unity_CameraProjection 该摄像机的投影矩阵
floart4x4 unity_CameraInvProjection 该摄像机的投影矩阵的逆矩阵
float4 unity_CameraWorldClipPlanes 该摄像机的6个裁剪屏幕在世界空间下的等式,按左右上下近远的顺序裁剪平面

时间变量

命令 实例 说明
float4 _Time _Time.x;_Time.y; _Time.z; _Time.w; t是自该场景加载开始所经过的时间,4个分量分别是t/20,t,2t,3t
float4 _SinTime t是时间的正限制,4个分量分别是t/8,t/4,t/2,t
float4 _Costime t是时间的余弦值,t/8,t/4.t/2,t
float4 unity_DeltaTime dt是时间增量,4个值分别是dt,1/dt,smoothDt,1/smoothDt

UnityCG.cginc 库

UnityCG.cginc 该文件中包含了很多即成的参数方法。使用十分方便

引入文件

1
2
3
CGPROGRAM
#include "UnityCG.cginc"
ENDCG

unitycg.cginc 常用结构

命令 参数 实例 说明
appdata_base 顶点位置、顶点法线、第一组纹理坐标 float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord: TEXCOORD0; 可用于顶点着色器的输入
appdata_tan 顶点位置、顶点切线、顶点法线、第一组纹理坐标 float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; 可用于顶点着色器的输入
appdata_full 顶点位置、顶点切线、顶点法线、四组(或更多)纹理坐标i cfloat4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; #if defined(SHADER_API_XBOX360) half4 texcoord4 : TEXCOORD4; half4 texcoord5 : TEXCOORD5; #endif fixed4 color : COLOR; 可用于顶点着色器的输入
appdata_img 可用于顶点着色器的输入 float4 vertex : POSITION; half2 texcoord : TEXCOORD0; 可用于顶点着色器的输入
v2f_img 裁剪空间中的位置、纹理坐标 可用于顶点着色器的输出

unitycg.cginc 常用函数

命令 参数 实例 说明
float4 WorldSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
float4 UnityWorldSpaceViewDir(float4 v) 输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
float4 ObjSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回模型空间中从该店到摄像机的观察方向
float4 WorldSpace LightDir(flaot4 v) 仅用于向前渲染。 输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化
float4 ObjectSpaceLightDir(float4 v) 仅用于向前渲染中,输入一个模型空间中的顶点位置, 返回模型空间中从该点到光源的光照方向。没有被归一化
float4 UnityWorldSpaceLightDir(float4 v) 仅用于向前渲染中,输入一个世界空间中的顶点位置, 返回世界空间中从该点到光源的光照方向。没有被归一化
float3 UnityObjectToWorldNormal(float3 norm) 把法线方向从模型空间中转换到世界空间中
float3 UnityObjectToWorldDir(float3 dir) 把方向矢量从模型空间中变换到世界空间中
float3 Unity WorldToObjectDir(float3 dir) 把方向矢量从世界空间变换到模型空间中

shader数学函数:

函数 说明 实例
radians(degree) 角度变弧度(一般默认都用弧度)
degrees(radian) 弧度变角度
sin(angle), cos(angle), tan(angle) 三角函数
asin(x) arc sine, 返回弧度 [-PI/2, PI/2];
acos(x) arc cosine,返回弧度 [0, PI]
atan(y, x) arc tangent, 返回弧度 [-PI, PI];
atan(y/x) arc tangent, 返回弧度 [-PI/2, PI/2];
pow(x, y) x的y次方
exp(x) 指数, log(x)
exp2(x) 2的x次方, log2(x)
sqrt(x) x的根号;
inversesqrt(x) x根号的倒数
abs(x) 绝对值
sign(x) 取当前数值的正负符号,返回 1, 0 或 -1 (x>0;x=0;x<0)
floor(x) 底部取整
ceil(x) 顶部取整
fract(x) 取小数部分
mod(x, y) 取模, x - y*floor(x/y)
min(x, y) 取最小值
max(x, y) 取最大值
clamp(x, min, max) min(max(x, min), max);
mix(x, y, a) x, y的线性混叠, x(1-a) + y*a;
step(edge, x) 如 x smoothstep(edge0, edge1, x): threshod smooth transition时使用。 edge0<=edge0时为0.0, x>=edge1时为1.0
length(x) 向量长度
distance(p0, p1) 两点距离, length(p0-p1);
dot(x, y) 点积,各分量分别相乘 后 相加
cross(x, y) 差积 x[1]*y[2]-y[1]*x[2], x[2]*y[0] - y[2]*x[0], x[0]*y[1] - y[0]*x[1]
normalize(x) 归一化 length(x)=1;
faceforward(N, I, Nref) 如 dot(Nref, I)< 0则N, 否则 -N
reflect(I, N) I的反射方向 I -2*dot(N, I)*N, N必须先归一化
refract(I, N, eta) 折射 k=1.0-etaeta(1.0 - dot(N, I) * dot(N, I)); 如k<0.0 则0.0,否则 etaI - (etadot(N, I)+sqrt(k))*N
matrixCompMult(matX, matY) 矩阵相乘, 每个分量 自行相乘 r[j] = x[j]*y[j];
lessThan(vecX, vecY) 向量 每个分量比较 x < y
lessThanEqual(vecX, vecY) 向量 每个分量比较 x<=y
greaterThan(vecX, vecY) 向量 每个分量比较 x>y
greaterThanEqual(vecX, vecY) 向量 每个分量比较 x>=y
equal(vecX, vecY) 向量 每个分量比较 x==y
notEqual(vecX, vexY) 向量 每个分量比较 x!=y
any(bvecX) 只要有一个分量是true, 则true
all(bvecX) 所有分量是true, 则true
not(bvecX) 所有分量取反

三 着色器实例

1. 固定渲染管线

固定功能管线着色器的关键代码都在Pass的材质设置Material{}和纹理设置SetTexture{}部分。
目前固定着色器已经逐渐退出市场,只在为兼容一些老旧硬件设备而存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Shader "Custom/VertexList" {
Properties {
//设置与unity通信的变量,用来通过unity编辑器获取素材资源及参数
_Color("Main Color",Color) = (0,1,1,0.5)
_SpecColor("Spec Color",Color) = (1,1,1,1)
_Emission("Emissive Color",Color) = (0,0,0,0)
_Shininess("Shininess",Range(0.01,1)) = 0.7
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Pass{
Material{
Diffuse[_Color] //设置漫反射
Ambient[_Color] //环境光
Shininess[_Shininess] //光泽度
Specular[_SpecColor] //高光色
Emission[_Emission] //自发光
}
Lighting On
SeparateSpecular On //启用高光颜色
//设置纹理
SetTexture[_MainTex]{
constantColor[_Color] //设置颜色常量
//混合命令
combine texture * primary DOUBLE,
texture *constant
}
}
}
}

2 .顶点/片段渲染管线

功能强大,且用途最多的渲染器

顶点/片段渲染管线 卸载pass块中,用CGPROGRAM 标签包裹。

该shader 会实现根据观察方向而变色的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/SurfaceShader"{
SubShader {
Tags { "RenderType" = "Opaque" }
LOD 100
Pass {
//CG代码块
CGPROGRAM
//一个 CGPROGRAM 块中必须有且只能有一个vertex 和一个fragment
#pragma vertex vert
#pragma fragment frag
//引入unityCG处理库
#include "UnityCG.cginc"
//定义一个数据结构,该结构包含一个4维向量,一个三维向量 v2f的意思是顶点to片元
struct v2f{
//获取位置
float4 pos : SV_POSITION;
//获取颜色
float2 color : COLOR0;
};
//顶点程序代码,用于计算位置和颜色。 v2f是刚才新建的数据结构,vert是在cgprogram注册的用于顶点计算的程序名
//该方法输入值appdata_base v是与模型关联后自动输入的,他的输出值会自动传入到片段代码块。所以相关联的顶点和片段方法的输出和输入类型一定要一致
v2f vert(appdata_base v){
//创建v2f数据 o
v2f o;
//计算位置
o.pos = UnityObjectToClipPos(v.vertex);
//计算颜色
o.color = v.normal* 0.5 + 0.5;
//返回该v2f数据
return o;
}
//片段程序代码
//该代码块会自动接受顶点着色器的输出值,而他的输出值会交给像素着色器最终显示在屏幕上
half4 frag(v2f i) : COLOR{
//返回输入的颜色,并设置透明度为1
return half4(i.color,.5,1);
}
ENDCG
}
}
//如果以上代码块失败,则用默认vertexlit渲染器
Fallback "VertexLit"
}

总是按方向着色

3. 表面渲染管线

备受unity宠爱的渲染器

在Unity中,表面着色器的关键代码用Cg/HLSL语言编写,然后嵌在ShaderLab的结构代码中使用。使用表面着色器,用户仅需要编写最关键的表面函数,其余周边代码将由Unity自动生成,包括适配各种光源类型、渲染实时阴影以及集成到前向/延迟渲染管线中等。
光照模型可以是内置的Lambert和BlinnPhong,或者是自定义的光照模型。
表面函数的作用是接收输入的UV或者附加数据,然后进行处理,最后将结构填充到输出结构体SurfaceOutPut中。

表面着色器的输入参数表

数据类型 参数 说明
float3 viewDir 视角方向
float4 COLOR 每个顶点的插值颜色
float4 screenPos 屏幕坐标(使用.xy/.w来获得屏幕2D坐标)
float3 worldPos 世界坐标
float3 worldRefl 世界坐标系中的反射向量
float3 worldNormal 世界坐标系中的法线向量
INTERNAL_DATA 当输入结构包含worldRefl或worldNormal且表面函数会写入输出结构的Normal字段是需包含此声明

表面着色器的输出参数表

1
2
3
4
5
6
7
8
struct SurfaceOutput{
half3 Albedo;//反射光
half3 Normal;//法线
half3 Emission;//自发光
half Specular;//高光
half Gloss;//光泽度
half Alpha;//透明度
};

实例说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Shader "Custom/surfShader"
Properties{}
SubShader{
//当系统渲染不透明物体时 调用该shader
Tags{"RenderType" = "Opaque"}
//表面着色器代码块 不放在pass中,编译后会分放至各个pass中
CGPROGRAM
//定义着色器类型为surface(表面着色器),并使用光照模型Lambert
#progma surface surf Lambert
//定义输入数据的结构体
struct Input {
float color :COLOR;
}
//定义输出数据的结构体
struct SurfaceOutput{
half3 Albedo;//反射光
half3 Normal;//法线
half3 Emission;//自发光
half Specular;//高光
half Gloss;//光泽度
half Alpha;//透明度
};
//表面函数
void surf(Input IN, inoutSurfaceOutput o){
o.Albedo = 1;//输出颜色
}
ENDCG
}
Fallback "Diffuse"//备选着色器
}

四,着色器效果集

该连接中包含很多着色器代码实例,具体的代码和详细的注释,不定时更新中
https://blog.csdn.net/lengyoumo/article/details/99676462

五,三大测试与剔除、透明混合

三大测试:深度测试、透明测试、模版测试
重点:三大测试与剔除都是决定是否显示像素条件!混合是指有透明物体的情况下像素该如何叠加显示
深度测试依据物体在镜头前的空间位置排序。
透明测试依据颜色透明度,也就是alpha值。
模版测试依据自定义的值,当同样带有模版值的元素叠加时触发

剔除与三种测试渲染顺序按先后排列。

5.0 Cull剔除

Cull 是剔除的意思。

命令 说明 实例
Off 绘制所有的面 Cull Off
Front 不绘制面向相机部分的面 Cull Front
Back 不绘制背对相机的面 Cull Back

5.1 透明测试 AlphaTest

当透明度到达指定值,就输出像素,否则抛弃
语法:

指令 说明 实例
Greater 大于,只渲染大于该值的像素。 alphatest greater [_alphaValue] //类似于抠图
Less 小于,只渲染小于该值的像素。 类似于反向抠图
GEqual 大于等于
LEqual 小于等于
Equal 等于
NotEqual 不等于
Always 总是
Never 永不
Off 关闭 alphatest Off

例1 表面着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
实例,只要声明 alphatest greater [_alphaValue] 即可。
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_alphaValue("alphavalue",range(0,1))=0.3
}
SubShader {
Pass{
alphatest greater [_alphaValue]
CGPROGRAM
...............
ENDCG
}
}

例2 片段着色器

1
2
3
4
5
6
7
8
fixed4 frag(v2f i):SV_Target{
fixed4 texColor = tex2D(_MainTex, i.uv);
clip(texColor.a - _Cutoff);
//等同于
//if((texColor.a - _Cutoff)<0.0){
// discard;
//}
}

5.1.2 透明度混合 Blend

混合命令

指令 说明 实例
Blend Off 关闭混合
Blend SrcFactor DstFactor 开启混合,并设置混合因子,片元颜色胡i成因SrcFactor,而已经存在颜色缓存中的颜色会诚意DstFactor,然后把两者相加后再存入颜色缓冲中
Blend SrcFactor DstFactor, SrcFactorA DstFactorA 上同,使用不同因子来混合透明通道
BlendOp BlendOperation 并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOperation对他们进行其他操作

混合因子

指令 说明 实例
One 因子值为1
Zero 因子值为0
SrcColor 因子为源颜色值(当前片元),当用于混合rgb时,使用SrcColor的RGb分量作为混合因子,当用于混合a的混合等式时,使用SrcColor的A分量作为混合因子
SrcAlpha 因子为源颜色的透明度,A通道
DstColor 因子为目标颜色(已经存在颜色缓存中的颜色),当用于混合rgb时,使用DstColor的RGb分量作为混合因子,当用于混合a的混合等式时,使用DstColor的A分量作为混合因子
DstAlpha 因子为源颜色的透明度,A通道
OneMinusSrcColor 因子为 1 - 源颜色,其余与SrcColor相同
OneMinusSrcAlpha 因子为 1-源颜色的透明度值
OneMinusDstColor 因子为 1- 目标颜色,其余与DstColor相同
OneMinusDstAlpha 因子为 1- 目标颜色透明度

混合操作
BlendOp

指令 说明 实例
Add 将混合后的源颜色和目标颜色相加
Sub 将混合后的源颜色减去混合后的目标颜色
RevSub 用混合后的目标颜色减去混合后的源颜色
Min 使用源颜色和目标i颜色中较小的值
Max 使用源颜色和目标颜色中较大的值

片段
正常:Blend SrcAlpha OneMunusSrcAlph
柔和相加 Blend OneMinusDstColor One
正片叠底:Blend DstColor Zero
两倍相乘:Blend DstColor SrcColor
变暗:BlendOp Min
Blend One One
变量:BlendOp Max
Blend One One
滤色:Blend OneMinusDstColor One 等偶同于Blend One OneMinusSrcColor
线性减淡:Blend One One

5.2 模板测试 StencilTest

啥是模板测试,每个像素都有一个stencil值,在同一个像素上,所有shader的stencil都共享这一个值,当有其他带有遮罩像素与其重合时就能获取到该值,并根据自身的stencil值处理或。典型的应用就是遮罩显示。你可以选择每次重合都增加1,然后再指定某个物体,当值达到某个数量级再显示。这样的场景,比如,有个隐身的怪物,你只有使用圣水喷雾才能让他现行,但必须喷3次才行,这样,空中就存在了3次叠加的雾,透过这个3层雾就能看到怪物了。但你偏一下角度,透过两层wu就看不到。
Stencil完整语法:

1
2
3
4
5
6
7
8
9
stencil{
Ref referenceValue //每个像素都有一个stencil值,在同一个像素上,所有shader的stencil都共享这一个值,当有其他带有遮罩像素与其重合时就能获取到该值,并根据自身的stencil值处理触发小狗
ReadMask readMask //读遮罩
WriteMask writeMask //写遮罩
Comp comparisonFunction //条件判断 大于小于等触发
Pass stencilOperation //满足条件后,相应的处理办法 是替换值还是增长值等
Fail stencilOperation //没有通过模板测试怎么办
ZFail stencilOperation //通过了模板测试怎么办
}

模板语法

参数 说明 实例
Ref ref用来设定参考值(范围0-255)。这个值用来与stencilbuffer比较
ReadMask ReadMask 从字面意思的理解就是读遮罩,readMask将和referenceValue以及stencilBufferValue进行按位与(&)操作,readMask取值范围也是0-255的整数,默认值为255,二进制位11111111,即读取的时候不对referenceValue和stencilBufferValue产生效果,读取的还是原始值
WriteMask WriteMask是当写入模板缓冲时进行掩码操作(按位与【&】),writeMask取值范围是0-255的整数,默认值也是255,即当修改stencilBufferValue值时,写入的仍然是原始值。
Comp Comp是定义参考值(referenceValue)与缓冲值(stencilBufferValue)比较的操作函数,默认值:always
Pass Pass是定义当模板测试(和深度测试)通过时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep
Fail Fail是定义当模板测试(和深度测试)失败时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep
ZFail ZFail是定义当模板测试通过而深度测试失败时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep

模板对比

指令 说明 实例
Greater 大于,只渲染大于该值的像素。 alphatest greater [_alphaValue] //类似于抠图
Less 小于,只渲染小于该值的像素。 类似于反向抠图
GEqual 大于等于
LEqual 小于等于
Equal 等于
NotEqual 不等于
Always 总是
Never 永不
Off 关闭 alphatest Off

模板操作

指令 说明 实例
Keep 保留当前缓冲中的内容,即stencilBufferValue不变
Zero 将0写入缓冲,即stencilBufferValue值变为0。
Replace 将参考值写入缓冲,即将referenceValue赋值给stencilBufferValue。
IncrSat stencilBufferValue加1,如果stencilBufferValue超过255了,那么保留为255,即不大于255。
DecrSat stencilBufferValue减1,如果stencilBufferValue超过为0,那么保留为0,即不小于0。
Invert 将当前模板缓冲值(stencilBufferValue)按位取反
IncrWrap 当前缓冲的值加1,如果缓冲值超过255了,那么变成0,(然后继续自增)
DecrWrap 当前缓冲的值减1,如果缓冲值已经为0,那么变成255,(然后继续自减) 。

实例:遮罩
将此shader付给遮罩物体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Shader "Custom/st1" {
SubShader{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry-1"}
CGINCLUDE
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,1,0,1);
}
ENDCG
Pass {
ColorMask 0
ZWrite Off
Stencil
{
Ref 1
Comp Always
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}

将此shader付给被遮罩物体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'


Shader "Custom/st2" {
Properties{
_MainTex("Base (RGB)", 2D) = "white" {}
}
SubShader{
Tags { "Queue" = "Geometry" "RenderType" = "Opaque" }
LOD 100
Pass {
Stencil
{
Ref 2
Comp Equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord);
UNITY_APPLY_FOG(i.fogCoord, col);
UNITY_OPAQUE_ALPHA(col.a);
return col;
}
ENDCG
}
}
}

即可完成

5.3 深度测试 DepthTest

根据深度值选择通过测试。
Cull Back | Front | Off

ZWrite On | Off:用于控制是否将对象的像素写入深度缓冲(默认开启),如果需要绘制纯色物体,便将此项打开。如需绘制半透明效果,则关闭深度缓冲。
开启深度写入:当两个像素重合时,根据深度缓冲中的值对比,剔除掉离相机较远的那个,留下最近的那个显示。
关闭深度写入:不剔除任何像素,按顺序覆盖像素。(半透明物体需要这个)

ZTest Less | Greater | LEqual | GEqual | Equal | NotEqual | Always
用于控制深度测试如何执行, 缺省值是LEqual。如果要绘制的像素的Z值 小余等于深度缓冲区中的值,那么就用新的像素颜色值替换。

Offset Factor,Units
利用Factor和Units来定义深度偏移
Factor参数表示Z缩放的最大斜率的值
Units参数表示可分辨的最小深度缓冲区的值
利用该句法,我们就可以强制使位于同一位置上的两个集合体中的一个几何体绘制在另一个的上层
以上几个值可以行内写。

懒人片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//顶点着色器正文
v2f vert(a2v v) {
v2f o;
//获取顶点的裁剪坐标
o.pos = UnityObjectToClipPos(v.vertex);//将模型顶点坐标转换为裁剪坐标
//获取纹理坐标
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//贴图与顶点对应的uv,必须配合下面的TRANSFER_SHADOW(o)
//顶点法线 * 世界转模型法线 获取世界法线?
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//获取世界顶点
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
ColorMask 0 //屏蔽所有颜色
fiexed4 frag(v2f i):color{
//纹理寻址
fixed4 colo = tex2D(_MainTex, i.texcoord);
//给贴图上色
colo = _Color * col;
}

语法解释:

SetTexture[_MainTex]{
Combine Primary * Texture
}

SeparateSpecular On

Blend SrcAlpha OneMinusSrcAlpha

该文章参考了下列文章:
https://www.jianshu.com/p/5be2801e226c
https://docs.unity3d.com/Manual/SL-Pass.html
https://blog.csdn.net/weixin\_33973609/article/details/85751777
https://blog.csdn.net/pizi0475/article/details/6574700
https://blog.csdn.net/zx1091515459/article/details/79262053
https://blog.csdn.net/zyq20130118/article/details/52874639
https://blog.csdn.net/qq\_38572472/article/details/79020122
https://www.cnblogs.com/Jason-c/p/8385946.html
https://blog.csdn.net/qq826364410/article/details/81744032 关于测试
喜欢看书的同学,重点推荐冯乐乐的shader入门教程,虽然说是入门教程,但写的由浅入深阅读曲线十分不错。重点推荐。csdn还有她的博客请自搜。

新地址
https://github.com/QianMo/Awesome-Unity-Shader

https://blog.csdn.net/lyh916/category\_6208767.html

https://blog.csdn.net/ynnmnm/article/details/69791337


to be continued…