表面着色器
Shader 的三种形势(语言)
HLSL:主要用于 Direct3D 平台:Windows
GLSL:主要用于OpenGL 平台:移动平台
CG:与DX9以上版本以及OpenGL完全兼容的语言形式
Shader 三种写法
FixedFunction Shader 固定功能着色器 在硬件受限的情况下使用 只能通过固定管线实现一些基本内容
SurfaceShader 表面着色器 Unity 自带的 Shader 模式 抽象层次更高 复杂效果简单实现
VertexShader & FragmentShader 顶点与片元着色器 代码量庞大 最灵活的 Shader 模式
标准着色器系列
漫反射 Diffuse
一张漫反射纹理 一组颜色
慢反射属性: 表面光照强度 和 与光的夹角成反比
凹凸漫反射 Bumped Diffuse
一张漫反射纹理 一组颜色 一张法线纹理
法线属性: 不改变对象形状 使用纹理来秒速表面细节 取代使用多边形来雕刻的方式
凹凸高光 Bumped Specular
加了一个 Shininess属 性 用于设置发光强度
加了一个specularColor 用于设置发光颜色
用于制作一些特殊材质:比如金属
透明着色器系列
原理和类型和标准着色器类似
只是具备了额外的半透明或者全透明的功能
祝文里贴图接受RGBA4个通道
R 0 – 1
G 0 – 1
B 0 – 1
A 0 – 1
Shader 属性定义
Shader "Custom/MySurface"
{
// 定义Shader代码中需要使用到的变量(颜色,贴图,数值等)
Properties
{
// _Name("displayName", type) = defaultValue
// float 32 位高精度浮点数 half 16 位 中精度点数(-6万, 6万) fixed 11 位低精度点数(-2, 2)
_Flot("MyFloat", float) = 1
// _Fixed("MyFixed", fixed) = 1
_Range("MyRange", range(0,1))= 0
// 颜色
_Color("MyColor", color) = (1,1,1,1)
// 浮点四元组 float4
_Vector("MyVector", vector) = (1,2,3,4)
// 2的阶次方大小的纹理贴图
_Tex("MYTexture", 2D) = "white"{}
// 非2的阶次方大小的纹理贴图
_Rect("MyRect", rect) = "white"{}
// Cube
_Cube("MyCube", cube) = "white"{}
}
}
Shader "Custom/MySurface"
{
// 定义Shader代码中需要使用到的变量(颜色,贴图,数值等)
Properties {}
// 运行时 Unity自上而下提取第一个运行于当前显卡Shader来执行
SubShader {}
SubShader {}
FallBack "Diffuse"
}
Shader "Custom/MySurface"
{
// 定义Shader代码中需要使用到的变量(颜色,贴图,数值等)
Properties
{
_Color("MainColor", color) = (1,1,1,1)
_MainTex("MainTex", 2D) = "white"{}
_DetailTex("DetailTex", 2D) = "white"{}
_NormalMap("法线", 2D) = "bump"{}
}
// 运行时 Unity自上而下提取第一个运行于当前显卡Shader来执行
SubShader
{
// Tags 硬件根据标签设置决定什么时候调用该着色器
// Queue 渲染层级 渲染队列 渲染队列值越高 渲染层级越高
// Background 1000 Geometry2000 (非透明) AlphaTest 2450 Transparent 3000 (透明) Overlay 4000
// RenderType 渲染类型 Opaque 非透明物体 Transparent 含有透明物体
// IgnoreProjector 是否接受阴影 True False
// ForceNoShadowCasting True 不产生阴影 False 产生阴影
Tags
{
"Queue" = "Geometry"
"RenderType" = "Opaque"
}
// ZWrite Off
// Alphatest greater
// blend SRCBLENDALPHA
// Level of Detail 多层次细节
LOD 200
// CG 程序开始标记
CGPROGRAM
// surface 表面着色器 surf 函数名(可以自定义) Lambert兰伯特光照模型
#pragma surface surf Lambert
fixed4 _Color;
sampler2D _MainTex;
sampler2D _DetailTex;
sampler2D _NormalMap;
struct Input
{
float2 uv_MainTex;
float2 uv_DetailTex;
float2 uv_NormalMap;
};
// Input 结构体变量IN inout 即是参数又是返回值 SurfaceOutput 表面着色器输出结构体
void surf(Input IN, inout SurfaceOutput o)
{
// 纹理采样
fixed4 texCol = tex2D(_MainTex,IN.uv_MainTex);
fixed4 detailCol = tex2D(_DetailTex,IN.uv_DetailTex);
o.Normal = UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));
o.Albedo = texCol.rgb * _Color.rgb * detailCol.rgb;
}
// CG 程序结束标签
ENDCG
}
FallBack "Diffuse"
}
struct SurfaceOutput
{
half3 Albedo; //反应像素的基色
half3 Normal; // 该像素的法线方向
half Alpha; // 像素的不透明度
half Gloss; // 像素的高光光滑度
half3 Emission; // 像素的自发光颜色
}
人物身上的雪
Shader "Custom/Snow"
{
Properties
{
_MainTex("MainTex", 2D) = "white"{}
_Color("Color", color) = (1,1,1,1)
_Bump("Bump", 2D) = "bump"{}
_SnowColor("SnowColor", color) = (1,1,1,1)
// 雪落下的方向
_SnowDirection("SnowDirection", vector) = (0,1,0)
_SnowLevel("SnowLevel", range(0, 1)) = 1
}
SubShader
{
Tags
{
"Queue" = "Geometry"
"RenderType" = "Opaque"
}
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
sampler2D _Bump;
fixed4 _Color;
fixed4 _SnowColor;
float4 _SnowDirection;
half _SnowLevel;
struct Input
{
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal;
INTERNAL_DATA
};
void surf(Input In, inout SurfaceOutput o)
{
fixed3 baseColor = tex2D(_MainTex, In.uv_MainTex).rgb * _Color.rgb;
o.Normal = UnpackNormal(tex2D(_Bump, In.uv_Bump));
if (dot(WorldNormalVector(In, o.Normal), normalize(_SnowDirection.xyz)) > lerp(1,-1, _SnowLevel))
{
o.Albedo = _SnowColor.rbg;
}
else
{
o.Albedo = baseColor;
}
}
ENDCG
}
FallBack "Diffuse"
}
边缘光
Shader "Custom/Light2"
{
Properties
{
_MainTex("纹理", 2D) = "white"{}
_Bump("法线", 2D) = "bump"{}
_Color("颜色", Color) = (1,1,1,1)
_RimColor("边缘光颜色", Color) = (1,0,0,1)
_RimPower("边缘光强度", range(0, 10)) = 1
_RimSize("边缘光大小", range(0, .9)) = .1
}
SubShader
{
Tags
{
"Queue" = "Geometry"
"RenderType" = "Opaque"
}
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
sampler2D _Bump;
fixed4 _Color;
fixed4 _RimColor;
half _RimPower;
fixed _RimSize;
struct Input
{
float2 uv_MainTex;
float2 uv_Bump;
float3 viewDir;
};
void surf(Input IN, inout SurfaceOutput o)
{
fixed4 main = tex2D(_MainTex, IN.uv_MainTex);
half3 bump = UnpackNormal(tex2D(_Bump, IN.uv_Bump));
half rim = 1 - saturate(dot(normalize(IN.viewDir), o.Normal));
o.Albedo = main * _Color;
if (rim > _RimSize)
o.Emission = rim * _RimColor * _RimPower;
o.Normal = bump;
}
ENDCG
}
FallBack "Diffuse"
}
水波
Shader "Custom/默写"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex:vert
fixed4 _Color;
struct Input
{
float3 viewDir;
};
void vert(inout appdata_full v)
{
// 旗帜波
// v.vertex.y +=.2 + sin(v.vertex.x + _Time.y);
// 水波
// v.vertex.y += .5 * sin((v.vertex.x + v.vertex.z) + _Time.y * 3);
// 涟漪
v.vertex.y += 5 * sin(length(v.vertex.xz) + _Time.y);
}
void surf(Input IN, inout SurfaceOutput o)
{
half rim = 1 - saturate(dot(normalize(IN.viewDir), o.Normal));
o.Albedo = _Color;
}
ENDCG
}
Fallback "Diffuse"
}
顶点/片元着色器
三次转换
模型空间-世界空间-观察空间-裁剪空间
模型自带的坐标均为模型空间的表示(顶点位置 顶点法线 顶点切线 UV)
当模型被放到世界坐标系下 表达模型的位置使用的规则应该是世界坐标下的坐标
从模型空间到世界空间的变换–>模型变换
strict a2v
{
float vertex : POSITION; // 表示模型空间下的顶点位置
}
我们用户能看到的图像是通过摄像机得到,为了后续更方便的进行裁剪和投影操作
在得到世界空间坐标后还需要将其转换到观察空间
观察空间:以相机为原点,相机的局部坐标轴作为坐标轴的坐标系
从世界空间到观察空间的变换–>观察变换(视图变换)
坐标转换观察空间后由于直接使用相机平截投进行裁剪比较复杂 需要转换到裁剪空间
变换思路:将平截头体进行缩放,使近裁剪平面和远裁剪平面变成正方形 w分量表示裁剪范围
从观察空间到裁剪空间的变换–> 投影变换
UnityCG.cginc
appdata_base(顶点着色器的输入):顶点位置 顶点法线 纹理坐标
appdata_tan(顶点着色器的输入):顶点位置 顶点法线 顶点切线 纹理坐标
appdata_full(顶点着色器的输入):顶点位置 顶点法线 顶点切线 四组纹理坐标 顶点颜色
appdata_img(顶点着色器的输入):顶点位置 纹理坐标
v2f_img(顶点着色器的输出):裁剪空间顶点位置 纹理坐标
常用函数
UnityObjectToClipPos 将模型空间顶点转到裁剪空间
float3 WorldSpaceViewDir 输入模型空间的顶点位置 返回世界空间下该点到相机的观察向量
float3 ObjSpaceViewDir 输入模型空间的顶点位置 返回模型空间下该点到相机的观察向量
float3 WorldSpaceLightDir 输入模型空间的顶点位置 返回世界空间下该点到光源的光照方向
float3 ObjSpaceLightDir 输入模型空间的顶点位置 返回模型空间下该点到光源的光照方向
float3 UnityObjectToWorldNormal 把法线向量从模型空间转换到世界空间
float3 UnityObjectToWorldDir 把向量从模型空间转换到世界空间
float3 UnityWorldToObjectDir 把向量从世界空间转换到模型空间
在Unity的Shader编程中,worldSpaceViewDir和objSpaceViewDir是两种表示观察者(相机)到顶点或片元的方向的方式。
- worldSpaceViewDir(世界空间观察方向): worldSpaceViewDir表示从顶点或片元位置指向相机位置的方向向量,它是基于世界空间坐标系的。它使用世界坐标而不是局部模型空间坐标来计算方向。通常在渲染场景中的效果时使用,例如计算镜面反射、折射等。
- objSpaceViewDir(物体空间观察方向): objSpaceViewDir表示从顶点或片元位置指向相机位置的方向向量,它是基于物体自身的局部模型空间坐标系的。它使用物体的坐标系来计算方向。当我们需要与物体自身的属性相关联时,如漫反射、法线贴图等,通常会使用该方向。
区别:
- 坐标系:worldSpaceViewDir是基于世界坐标系计算的,而objSpaceViewDir是基于物体的局部模型空间坐标系计算的。
- 使用范围:worldSpaceViewDir通常用于计算与场景相关的效果,而objSpaceViewDir通常用于计算与物体本身相关的效果。
- 处理方式:worldSpaceViewDir需要将顶点或片元的位置从局部坐标系转换为世界坐标系,然后计算与相机位置的方向。objSpaceViewDir直接使用物体的局部模型空间坐标系进行计算。
在编写Shader时,选择合适的观察方向用于特定的效果是非常重要的。根据具体的需求和效果要求,选择适当的方向来获得期望的渲染效果。
一个渲染流程分为3个阶段
应用阶段
CPU负责,开发者准备好摄像机,模型,光源等数据,做好粗粒度剔除工作
设置好材质,纹理,Shader等渲染状态,最后输出渲染需要的图元
几何阶段
GPU负责,把上个阶段CPU传递来的图元进行逐顶点,逐多边形的操作
把顶点坐标经过一系列转换最终到屏幕空间。输出屏幕空间的顶点坐标,顶点深度,顶点着色等数据 交给光栅器处理
光栅化阶段
GPU负责,对上个阶段的逐顶点数据进行插值 再进行逐像素处理根据Shader代码逻辑 渲染出每一个像素不同的颜色根据一些列测试 决定哪些输出保留 哪些像素剔除
CPU 和 GPU 的通信
CPU在每一帧渲染中 把数据加载到显存 设置渲染状态 调用DrawCall
数据加载到显存:所有的数据都要从硬盘加载到系统内存 然后 网格,纹理,顶点坐标 颜色 法线等数据要加载到显存
设置渲染状态:使用哪一个顶点着色器和片元着色器 光源属性 材质等等
调用DrawCall: 绘制命令 由 CPU 发出 指向一个需要别渲染的图元列表 通过 GPU 按照以上的状态来渲染图元
GPU流水线
简称:顶曲几裁屏 三三片逐屏
顶点着色器是必选,片元着色器可选
顶点着色器
处理顶点数据 处理单个顶点 无法访问其他顶点的信息
必须在逐个阶段完成对顶点的坐标变换 从模型空间到裁剪空间的变换 得到NDC(归一化设备坐标 立方体空间坐标)
需要在这个阶段准备好后续需要使用的信息 比如:纹理坐标 法线 顶点颜色等等 如果需要则完成对顶点的光照运算(逐顶点光照)
曲面细分着色
细分图元
几何着色器
执行逐图元的着色操作 可以产生新图元
裁剪
之前所有阶段的操作都是在立方体空间进行
Openl(-1,-1,-1 —- 1,1,1)
DirectX(0,0,0 —- 1,1,1)
如果是完全在空间内的保留 完全在外部被抛弃 一半在内一半在外的则需要裁剪
最终保留事业所能看到的部分
屏幕映射
到目前为止所有坐标都是三维的而屏幕是二位的
所谓屏幕映射就是将图元x, y 坐标转换到屏幕坐标系下
映射到屏幕上经历一个缩放的过程 得到屏幕坐标系
同时配合Z轴 可以构建出窗口坐标系 交给光栅器处理
三角形设置
之前所有的阶段的输出都是三角形顶点 也会生成新顶点 修改顶点
利用生成的顶点 和 计算出来的边界 构建出三角形网格数据
三角形遍历
检查每个像素被那个三角形覆盖从而生成片元
对覆盖的每个像素进行插值运算:颜色,深度,坐标等
片元着色器
上面阶段准备好了片元数据 此阶段可以根据需要输出想要输出的颜色
和顶点着色器一样 此处只能处理自身的片元 不能访问其他片元
逐片元操作
合并 1 经过三大测试(透明度测试 模板测试 深度测试)决定片元是否可见 2 混合或者覆盖
屏幕图像
从颜色缓冲区中拿到对应的颜色值
GPU采用多重缓冲机制 渲染发生在后置缓存 渲染结束后GPU切回前置缓存来显示
深度
某一个像素点在3D世界中到相机的距离,里相机越近 则深度越小 离相机越远 深度越大
深度缓存
深度缓存存储着准备绘制在屏幕上的像素的深度值
如果弃用了深度缓存 在绘制像素之前 Opengl 会把该像素的深度值和深度缓存区的深度值进行比较
新像素深度值 < 深度缓存深度值 则新像素取代原先的
反之 新像素被遮挡 其颜色和深度都将被丢弃
如果不适用深度测试
假设先绘制了一个距离较远的物体 在绘制距离较近的物体 近物体后绘制 会覆盖之前的远物体 看起来效果正常
假设先绘制了一个距离较近的物体 在绘制距离较远的物体 远物体后绘制 会覆盖之前的近物体 看起来效果出错
ZWrite 是否写入深度缓存 On/Off 默认On表示要将深度写入到深度缓存区
ZTest 是否进行深度测试 Greater/GEqual/Less/Lequal/Equal/NotEqual/Always/Nerver/Off
参数 | 值 | 功能 |
---|---|---|
operation | Less | 绘制位于现有几何体前面的几何体。不绘制位于现有几何体相同距离或后面的几何体。 |
LEqual | 绘制位于现有几何体前面或相同距离的几何体。不绘制位于现有几何体后面的几何体。 这是默认值。 | |
Equal | 绘制位于现有几何体相同距离的几何体。不绘制位于现有几何体前面的或后面的几何体。 | |
GEqual | 绘制位于现有几何体后面或相同距离的几何体。不绘制位于现有几何体前面的几何体。 | |
Greater | 绘制位于现有几何体后面的几何体。不绘制位于现有几何体相同距离或前面的几何体。 | |
NotEqual | 绘制不位于现有几何体相同距离的几何体。不绘制位于现有几何体相同距离的几何体。 | |
Always | 不进行深度测试。绘制所有几何体,无论距离如何。 |
默认值:LEqual 当新像素Z值小于等于深度缓存的Z值则保留新像素颜色
Always 直接将颜色保留写入到颜色缓冲区 Off同义
Never 一定不讲颜色写入到颜色缓冲区 消失
1 当ZWrite为On ZTest通过
像素的深度会成功写入到深度缓存 同时因为测试通过 颜色会写入到颜色缓存
2 当ZWrite为On ZTest不通过
像素的深度不会成功写入到深度缓存 同时因为测试不通过 颜色也不会写入到颜色缓存
3 当ZWrite为Off ZTest通过
像素的深度不会成功写入到深度缓存 同时因为测试通过 颜色会写入到颜色缓存
4 当ZWrite为Off ZTest不通过
像素的深度不会成功写入到深度缓存 同时因为测试不通过 颜色也不会写入到颜色缓存
所以 像素的深度是否能成功写入到深度缓存 条件是ZWrite为On 且 ZTest通过
模板测试 StencilTest
处理重合像素如何显示 更加自由的根据需要自定义配置
屏幕上每个像素都可以设置一个Stencil值
在同一个像素上 当有其他带有Stencil的像素与之重合就可以获取该值并根据Stencil值做处理
应用:遮罩
模板测试和透明度 深度测试的关系和区别
不同点:
- 深度测试用于处理后遮挡关系
- 透明度测试用于透明度阈值
- 模板测试用于处理重合关系
签名 | 示例语法 | 功能 |
---|---|---|
Stencil { Ref <ref> ReadMask <readMask> WriteMask <writeMask> Comp <comparisonOperation> Pass <passOperation> Fail <failOperation> ZFail <zFailOperation> CompBack <comparisonOperationBack> PassBack <passOperationBack> FailBack <failOperationBack> ZFailBack <zFailOperationBack> CompFront <comparisonOperationFront> PassFront <passOperationFront> FailFront <failOperationFront> ZFailFront <zFailOperationFront> } 请注意,所有参数都是可选的。 | Stencil { Ref 2 Comp equal Pass keep ZFail decrWrap } | 根据给定参数配置模板缓冲区。 |
有效参数值
参数 | 值 | 功能 |
---|---|---|
ref | 整数。范围为 0 到 255。默认值为 0。 | 参考值。 GPU 使用在 compareOperation 中定义的操作将模板缓冲区的当前内容与此值进行比较。 此值使用 readMask 或 writeMask 进行遮罩,具体取决于进行的是读取操作还是写入操作。 如果 Pass、Fail 或 ZFail 的值为 Replace,则 GPU 也可以将此值写入模板缓冲区。 |
readMask | 整数。范围为 0 到 255。默认值为 255。 | GPU 在执行模板测试时使用此值作为遮罩。 有关模板测试方程,请参阅上文。 |
writeMask | 整数。范围为 0 到 255。默认值为 255。 | GPU 在写入模板缓冲区时使用此值作为遮罩。 请注意,与其他遮罩一样,它指定操作中包含的位。例如,值为 0 表示写入操作中不包含任何位,而不是模板缓冲区接收值 0。 |
comparisonOperation | 比较操作。请参阅比较操作值以了解有效值。默认值为 Always。 | GPU 为所有像素的模板测试执行的操作。 这会定义适用于所有像素的操作,而与朝向无关。如果定义了此值以及 comparationOperationBack 和 comparationOperationFront,则此值会覆盖它们。 |
passOperation | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素通过模板测试和深度测试时,GPU 对模板缓冲区执行的操作。 这会定义适用于所有像素的操作,而与朝向无关。如果定义了此值以及 passOperationBack 和 passOperationFront,则此值会覆盖它们。 |
failOperation | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素未能通过模板测试时,GPU 对模板缓冲区执行的操作。 这会定义适用于所有像素的操作,而与朝向无关。如果定义了此值以及 failOperationBack 和 failOperationFront,则此值会覆盖它们。 |
zFailOperation | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素通过模板测试,但是未能通过深度测试时,GPU 对模板缓冲区执行的操作。 这会定义适用于所有像素的操作,而与朝向无关。如果定义了此值以及 zFailOperation 和 zFailOperation,则此值会覆盖它们。 |
comparisonOperationBack | 比较操作。请参阅比较操作值以了解有效值。默认值为 Always。 | GPU 为模板测试执行的操作。 这会定义仅适用于背面像素的操作。如果定义了 comparisonOperation,则该值会覆盖此值。 |
passOperationBack | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素通过模板测试和深度测试时,GPU 对模板缓冲区执行的操作。 这会定义仅适用于背面像素的操作。如果定义了 passOperation,则该值会覆盖此值。 |
failOperationBack | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素未能通过模板测试时,GPU 对模板缓冲区执行的操作。 这会定义仅适用于背面像素的操作。如果定义了 failOperation,则该值会覆盖此值。 |
zFailOperationBack | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素通过模板测试,但未能通过深度测试时,GPU 对模板缓冲区执行的操作。 这会定义仅适用于背面像素的操作。如果定义了 zFailOperation,则该值会覆盖此值。 |
comparisonOperationFront | 比较操作。请参阅比较操作值以了解有效值。默认值为 Always。 | GPU 为模板测试执行的操作。 这会定义仅适用于正面像素的操作。如果定义了 comparisonOperation,则该值会覆盖此值。 |
passOperationFront | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素通过模板测试和深度测试时,GPU 对模板缓冲区执行的操作。 这会定义仅适用于正面像素的操作。如果定义了 passOperation,则该值会覆盖此值。 |
failOperationFront | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素未能通过模板测试时,GPU 对模板缓冲区执行的操作。 这会定义仅适用于正面像素的操作。如果定义了 failOperation,则该值会覆盖此值。 |
zFailOperationFront | 模板操作。请参阅模板操作值以了解有效值。默认值为 Keep。 | 当像素通过模板测试,但未能通过深度测试时,GPU 对模板缓冲区执行的操作。 这会定义仅适用于正面像素的操作。如果定义了 zFailOperation,则该值会覆盖此值。 |
比较操作值
在 C# 中,这些值通过 Rendering.CompareFunction 枚举进行表示。
值 | Rendering.CompareFunction 枚举中的对应整数值 | 功能 |
---|---|---|
Never | 1 | 从不渲染像素。 |
Less | 2 | 在参考值小于模板缓冲区中的当前值时渲染像素。 |
Equal | 3 | 在参考值等于模板缓冲区中的当前值时渲染像素。 |
LEqual | 4 | 在参考值小于或等于模板缓冲区中的当前值时渲染像素。 |
Greater | 5 | 在参考值大于模板缓冲区中的当前值时渲染像素。 |
NotEqual | 6 | 在参考值与模板缓冲区中的当前值不同时渲染像素。 |
GEqual | 7 | 在参考值大于或等于模板缓冲区中的当前值时渲染像素。 |
Always | 8 | 始终渲染像素。 |
模板操作值
In C#, these values are represented by the Rendering.Rendering.StencilOp enum.
值 | Rendering.StencilOp 枚举中的对应整数值 | 功能 |
---|---|---|
Keep | 0 | 保持模板缓冲区的当前内容。 |
Zero | 1 | 将 0 写入模板缓冲区。 |
Replace | 2 | 将参考值写入缓冲区。 |
IncrSat | 3 | 递增缓冲区中的当前值。如果该值已经是 255,则保持为 255。 |
DecrSat | 4 | 递减缓冲区中的当前值。如果该值已经是 0,则保持为 0。 |
Invert | 5 | 将缓冲区中当前值的所有位求反。 |
IncrWrap | 6 | 递增缓冲区中的当前值。如果该值已经是 255,则变为 0。 |
DecrWrap | 7 | 递减缓冲区中的当前值。如果该值已经是 0,则变为 255。 |
示例
Shader "Examples/CommandExample"
{
SubShader
{
// 此处是定义子着色器的代码的其余部分。
Pass
{
// 此通道中的所有像素都会通过模板测试并将值 2 写入模板缓冲区
// 如果要防止后续着色器绘制到渲染目标的此区域或将它们限制为仅渲染到此区域,则通常会执行此操作
Stencil
{
Ref 2
Comp Always
Pass Replace
}
// 此处是定义通道的代码的其余部分。
}
}
}
此示例代码演示在 SubShader 代码块中使用此命令的语法。
Shader "Examples/CommandExample"
{
SubShader
{
// 仅当模板缓冲区的当前值小于 2 时,此子着色器中的所有像素才通过模板测试
// 如果希望仅绘制到渲染目标中未"遮罩"的区域,则通常会执行此操作
Stencil
{
Ref 2
Comp Less
}
// 此处是定义子着色器的代码的其余部分。
Pass
{
// 此处是定义通道的代码的其余部分。
}
}
}
照明渲染
顶点照明渲染路径细节(Vertex Lit Rendering path Details)
顶点照明在一个通道中渲染物体 所有光源的照明都是物体的顶点上的计算
顶点照明是渲染速度最快的渲染路径 并且有最为广泛支持
所有需要逐像素的效果 顶点照明无法呈现:阴影 法线贴图 定光遮罩 高精度的高光等
正向渲染路径细节(Forward Rendering Path Details)
在正向渲染的过程中 影响物体最亮的几个光源逐像素光照模式
接下来最多有4个点光源会使用逐顶点渲染模式
其他光源以球面调和的方式进行计算
延迟光照渲染路径细节(Defeered Light Rendering Details)
实现光线和细节高保真的渲染路径
对影响物体的光源的个数限制
完全采用逐像素的方式来评估光线
所有光线的信息都可以缓存且所有光线都可以产生阴影
Pass内部LightMode标签
设置不同渲染路径下选项
Always 不管使用哪种渲染路径 该Pass都会被渲染 不会计算任何光照
ForwardBase 用于向前渲染 该Pass计算环境光 平行光 自发光等
ForwardAdd 用于向前渲染 除了全局光源以外的其他逐像素光源的计算 每一个Pass对
应一个光源
Deferred 用于延迟渲染 该Pass渲染G缓冲
ShadowCaster 把物体的深度信息渲染到一张阴影映射纹理或者一张深度纹理中
PrepassBase PrepassFinal 用于遗留的延迟渲染
Vertex VertexLM 用于遗留的顶点照明
向前渲染的原理
一次完整的向前渲染 需要渲染对象的图元 并计算两个缓冲区的信息:颜色缓冲区和深度
缓冲区
如果一个物体被多个逐像素光源影响 那么该物体会执行多个Pass
一个Pass的执行 计算一个逐像素光源的光照结果 帧缓冲会把每一个光照结果混合起来得
到最终的颜色值
场景中有N个物体需要渲染 同时有M个光源 那么渲染整个场景需要N*M个pass
ForwardBase 只执行一次
FowardAdd 执行多次 取决于场景中逐像素光源的数量
BasePass 渲染的平行光默认支持阴影
AdditionalPass 渲染的光源默认不支持阴影 #pragma multi_complie_fwadd
环境光和自发光是在BasePass中计算 因为环境光和自发光都只计算一次 如果在AddPass里计算会计算多次
AdditionalPass 中默认开始混合模式 希望每个AddPass与上一次光照的结果在帧缓存中进行叠加最终混合出多个光照的效果 Blend One One
BasePass 只会执行一个 除非定义多个
Additional Pass 会执行多次 每个逐像素光源会执行一次
_LightColor0 该Pass处理的逐像素光源颜色
_WorldSpaceLightPos0 float4 如果是平行光 w 分量为 0 否者为1
WorldSpaceLightDir(float4) 输入模型空间位置 得到世界空间光照方向
UnityWorldSpaceLightDir(float4) 输入世界空间位置 得到世界空间光照方向
ObjSpaceLightDir(float4) 输入模型空间位置 得到模型空间光照方向
延迟照明怎么来的呢?
当光源较多时,向前渲染的性能下降,延迟渲染可以解决这个问题(仅进行一次光照)
除了向前渲染要使用到的颜色缓存和深度缓存以外 延迟渲染还需要额外的缓冲区
G缓存区(Geometry)
G缓存区存储了物体的表面信息:法线,位置,光照计算用的材质属性等等
两个Pass
第一个Pass 不进行任何光照计算 仅计算片元是否可见 如果可见 就存入到GBuffer
物体的漫反射颜色 光照反射颜色 平滑度 法线 自发光和深度等跟渲染到屏幕空间的 GBuffer 仅执行一次
第二个Pass利用G缓冲区的各个片元信息 进行真正的光照计算 与光源数无关
延迟渲染因为大部分模型在渲染时不需要光照计算 而是在场景接近渲染完成时 才会使用光照信息来统一计算
把光照运算变成了渲染一个二维图像。在屏幕空间对原本的二维图像做修改,所以延迟空间的光照发生在屏幕空间
延迟渲染:
不支持真正的抗锯齿功能,不能处理半透明物体 对显卡都一定的要求 显卡必须支持MRT shadermode3.0 以上
G缓冲区
RT0: ARGB32 RGB用户存储漫反射颜色 A 通道不用
RT1: ARGB32 RGB 用于存储高光反射颜色 A 通道用于存储高光反射指数
RT2: ARGB2101010 RGB 通道存储法线 A 通道不用
RT3: ARGB32 HDR 用于存储自发光 + lightmap + 反射探针
位置,方向,颜色,强度,衰减和阴影
阴影
投射阴影的物体
接受阴影的物体
完全被阴影忽略的物体
ShadowMap
首先把相机放到和光源位置重合的地方
深度图的计算是基于光源视角的(点光源一般使用透明的阴影 平行光使用正交投射阴影)
场景中该光源的阴影区域是哪些相机看不到的地方
简化成了判断一个片元是否为相机可见 如果可见就不渲染阴影否者就渲染阴影
场景中每个开始了阴影的光源都会有一张ShadowMap
比如平行光是默认开始阴影的 所以Unity会自动为其计算阴影映射纹理–ShadowMap
本质是一张深度图 记录了从光源位置出发 能看到的场景中举例它最近的表面的深度值
Shader中使用LightModel = ShadowCaster的pass来生成ShadowMap,如果没有就会从Fallback中层层查找
在片元着色器中 比较该像素和ShadowMap中对应的深度若大于则处于阴影区域否者不在阴影区域
把在 Shadowmap 中的采样结果和最后的光照结果混合相乘来产生阴影
Red*Tangent+Green*Bitangent+Blue*Normal
Vert函数定义
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// UNITY_MATRIX_M 模型空间到世界空间
// UNITY_MATRIX_v 世界空间到观察空间
// UNITY_MATRIX_P 观察空间到投影空间
o.pos = mul(UNITY_MATRIX_MVP ,v.vertex);
return o;
}
float4 vert(float4 v : POSITION) : SV_POSITION {
return UnityObjectToClipPos(v);
}
POSITION它是该顶点在裁剪空间中的未知,是不可以省略的,POSITION讲告诉Unity, 把模型的顶点坐标填充到输入参数 v 中,SV_POSITION 告诉 Unity, 顶点着色器的输出是裁剪空间中的顶点坐标,如果没有这些语义来限定输入输出参数的话,渲染器就完全不知道用户的输入输出是什么。UnityObjectToClipPos() 这一步就是把顶点坐标从模型空间转换到裁剪空间中。
Frag函数定义
fixed4 frag() : SV_Target
{
return fixed(1,1,1,1);
}
SV_Target也是HLSL中的一个系统语义,它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标中,这里将输出到默认的帧缓存中
效果实现
透明混合
Shader "Unlit/半透明"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Transparent"
}
LOD 100
// Blend srcAlpha OneMinusSrcAlpha // 正常透明混合
// BlendOp Min Blend One One // 变暗
// BlendOp Max Blend One One // 变亮
// Blend DstColor zero // 正片叠底
// Blend OneMinusDstColor zero // 滤色
// Blend DstColor srcColor // 两倍相乘
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct a2v
{
float4 vertex : POSITION;
float2 main : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.main, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
Fallback"Diffuse"
}
模糊效果
Shader "Unlit/模糊效果"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Size("模糊大小", range(0, 4)) = 0
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
// 纹理的像素大小 width height
float4 _MainTex_TexelSize;
float _Size;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 color : COLOR;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
float2 uv3 : TEXCOORD3;
float2 uv4 : TEXCOORD4;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
// 计算UV上下左右4个点
o.uv1 = v.texcoord.xy + _MainTex_TexelSize * float2(1, 1) * _Size;
o.uv2 = v.texcoord.xy + _MainTex_TexelSize * float2(-1, 1) * _Size;
o.uv3 = v.texcoord.xy + _MainTex_TexelSize * float2(-1, -1) * _Size;
o.uv4 = v.texcoord.xy + _MainTex_TexelSize * float2(1, -1) * _Size;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 color = fixed4(0,0,0,0);
color += tex2D(_MainTex, i.uv);
color += tex2D(_MainTex, i.uv1);
color += tex2D(_MainTex, i.uv2);
color += tex2D(_MainTex, i.uv3);
color += tex2D(_MainTex, i.uv4);
return color / 5;
}
ENDCG
}
}
}
描边
Shader "Unlit/半透明"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_OutLine("OutLine", range(0,1)) = 0
_OutColor("OutColor", Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"RenderType"="Transparent"
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct a2v
{
float4 vertex : POSITION;
float2 main : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.main, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
Pass
{
Cull front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed _OutLine;
fixed4 _OutColor;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex + normalize(v.vertex) * _OutLine);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutColor;
}
ENDCG
}
}
Fallback"Diffuse"
}
双面渲染
Shader "Unlit/双面渲染"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_MainTex2 ("Texture2", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct a2v
{
float4 vertex : POSITION;
float4 texcoord0 : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord0;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
Pass
{
Cull front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex2;
float4 _MainTex2_ST;
struct a2v
{
float4 vertex : POSITION;
float4 texcoord0 : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord0;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex2, i.uv);
}
ENDCG
}
}
}
无贴图流光
Shader "Unlit/无贴图流光"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Speed("Speed", range(0, 30)) = 0
_Width("Width", range(0, 1)) = 0
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float _Speed;
float _Width;
struct a2v
{
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed3 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float x = i.uv.x + i.uv.y;
float v = sin(x - _Time.y * _Speed);
v = smoothstep(1 - _Width, 1.0, v);
fixed3 target = fixed3(v,v,v) + col;
return target;
}
ENDCG
}
}
}
物体在遮挡物后,显示物体轮廓
Shader "Custom/shader2"
{
Properties
{
_Color("颜色", Color) = (1,1,1,1)
_Color2("颜色", Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"Queue" = "Geometry+1"
"RenderType" = "Opaque"
}
Pass
{
ZWrite on
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _Color2;
struct v2f
{
float4 pos : POSITION;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _Color2;
}
ENDCG
}
Pass
{
ZTest Greater
ZWrite off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _Color;
struct v2f
{
float4 pos : SV_POSITION;
float3 normal : NORMAL;
float3 viewDir : TEXCOORD0;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float dov = 1 - saturate(dot(i.normal, i.viewDir));
return dov * _Color;
}
ENDCG
}
}
Fallback "Diffuse"
}
Unity 内置函数
Unity Shader 中的内置变量(时间)
CG函数
函数:saturate(x)
参数:x:为用于操作的标量或矢量,可以是float、float2、float3等类型
描述:把x截取在[0, 1]范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作
函数:reflect(i, n)
参数:i,入射方向;n,法线方向。可以是float、float2、float3等类型
描述:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向