Unity Shader学习随笔

阴影:

光源看不到,但相机看得到的地方,就是阴影

变体:

一个普通的Shader可能会有很多种效果

例如一个火焰溶解效果,写在Shader里,但其实在未触发之前我们不需要去计算该效果

因此需要在未触发前,将火焰溶解的效果计算关闭

这就用到了变体,把火焰溶解的效果计算变成变体

无论如何都会被编译的变体multi_compile

#pragma multi_compile _ _NAME

变体名必须要全大写

Properties
{  //在属性面部设置一个开关,来表示是否要启用变体(需求全大小)
  [Toggle]_DISSLOVEEABLED("Disslove Eabled", int) = 0
}
SubShader
{
  pass
  {
    ...
    //在pass中的命名,前面的‘_’表示空变体,之所以有这个是为了不默认开启 _DISSLOVEEABLED 变体
    #pragma multi_compile _ _DISSLOVEEABLED_ON
    ...
    fixed4 func()
    {
      ...
      #if _DISSLOVEEABLED_ON
                       fixed4 dis = tex2D(_Dissolve, i.uv.zw);
                       fixed weight = dis.r - _Clip;
                       clip(weight);
                       if(weight < _Clip) {
                         tex = tex * lerp(_Color0, _Color1, weight/_Scope);
                      }
                  #endif
      ...
    }
  }
 
}

通过材质使用情况来决定是否进行编译的变体shader_feature

一般用于特效,即运行过程中不会去开启该变体

属性中就是要暴露一个开关:

[Toggle]_MaskEnabled("启用遮罩", int) = 0

pass中要启用变体:

#pragma shader_feature _MASKENABLED_ON

然后就是和上面的判断一样了

#if

  xxx;

#else

  xxx;

#endif

这就可以做到:

同一个Shader可以给不同的材质球使用;

是否开启某个变体由材质球决定,并且在打包时不会将该材质球未开启的变体打进包里。

然后信息存在于这里

常用函数:

abs(x) 绝对值
frac(x) 取小数
floor(x) 向下取整
ceil(x) 向上取整
max(x, y) 取大的
min(x, y) 取小的
pow(x, y) x的y次方
rcp(x) x的倒数
exp(x) e的x次方
exp2(x) 2的x次方
fmod(x, y) x%y
saturate(x) 将x限制在0~1
clamp(x, a, b) 将x限制在a~b
sqrt(x) 对x开方
rsqrt(x) 对x开方后取倒数
lerp(x, y, a) x+(y-x)*a
sin(x)
cos(x)
distance(x, y) 返回xy之间的距离(几维都可以)
length(x) 返回模长(必须是二维及以上)
step(x, y) 返回 x >=y ?1 : 0
smoothstep(a, b, x) 返回介于0和1之间的平滑 Hermite 内插

 

渲染队列Queue

2500以下会被认为是不透明物体,从前往后渲染,效率更高

2500以上就是透明物体,从后往前渲染,效率低,但这是为了保证显示效果正确

Queue=Background,1000,最先渲染

Queue=Geometry,2000,默认场景中的渲染对象

Queue=AlphaTest,2450,要么不透要么全透,常常用来实现美术部分的透贴

Queue=Transparent,3000,场景中的半透明对象

Queue=Overlay,4000,叠加效果,最后渲染的东西放这,如镜头光晕等

混合Blend

Blend后面一般跟随两至三个参数

Blend One Zero

Blend One One

第一个参数就是针对片段着色器pass计算出来的SrcFactor源颜色,要对该参数干什么

第二个参数就是针对存在于帧缓冲区的DstFactor目标颜色,要对该参数干什么

其实中间还有一个,不填就是默认加法

Blend SrcAlpha OneMinusSrcAlpha - 传统透明度

Blend One OneMinusSrcAlpha - 自左乘透明度

Blend One One

Blend OneMinusDstColor One - 软合并

Blend DstColor Zero - 乘法

Blend DstColor SrcColor - 双倍乘法

面剔除Cull

剔除不需要的面,优化渲染速度

 

Cull Front - 剔除正面

Cull Back - 剔除背面

 

正面背面是指模型的正面背面(顶点环绕方向,及法线),不是玩家看到的是正面

Cull Off - 关闭剔除

屏幕后处理

实现热扭曲的效果

1.做好场景

2.由代码抓取当前一帧的内容

3.获取受扭曲影响的部分屏幕坐标

4.利用屏幕坐标对抓取的图片采样

5.再采样扰动贴图做扭曲

使用unity参数组成屏幕坐标

查看代码
 Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
};
sampler2D _GrabTex;
sampler2D _MainTex;float4 _MainTex_ST;
fixed _SpeedX, _SpeedY, _Distort;
v2f vert (float4 vertex : POSITION, float2 uv : TEXCOORD0, out float4 pos : SV_POSITION)
{
pos = UnityObjectToClipPos(vertex);
v2f o;
o.uv = TRANSFORM_TEX(uv, _MainTex) + float2(_SpeedX, _SpeedY) * _Time.x;
return o;
}
//由于VPOS和SV_POSITION冲突,因此需要在vert里out
fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos:VPOS) : SV_Target
{
//最初的版本,通过unity参数去计算,_ScreenParams.xy是屏幕的宽高
fixed2 uv = lerp(screenPos.xy / _ScreenParams.xy, tex2D(_MainTex, i.uv).xy, _Distort);
fixed4 grabTex = tex2D(_GrabTex, uv);
return grabTex;
}
ENDCG
}

使用正交空间下的pos计算屏幕坐标

查看代码
 Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct a2f
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float2 screenUV : TEXCOORD1;
};
sampler2D _GrabTex;
sampler2D _MainTex;float4 _MainTex_ST;
fixed _SpeedX, _SpeedY, _Distort;
v2f vert (a2f v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex) + float2(_SpeedX, _SpeedY) * _Time.x;
o.pos = UnityObjectToClipPos(v.vertex);
//1.此时pos是放在正交矩形中的顶点
//2.因此可以直接拿该值当做屏幕坐标
//3.但要注意pos的范围是-1到1
//4.因此需要把它规范到0到1,但OpenGL和DX11的屏幕坐标原点不同
//5.OpenGL的在左下角,仅需将xy * 0.5 + 0.5即可
//6.但DX11的在左上角,需要将y轴反转,即y = 1 - y
// o.screenUV = o.pos.xy / o.pos.w * 0.5 + 0.5;
// o.screenUV.y = 1 - o.screenUV.y;
//7.上面就是根据正交中的顶点坐标转换成屏幕坐标的操作
//8.可以注意到稍微有点扭曲,原因是顶点不是片元,顶点和顶点之间的参数都是靠插值算出来的
// o.screenUV = ComputeScreenPos(o.pos) / o.pos.w;
//9.当然,unitycg提供了方法,上面
//10.这里除以一个w是因为:正交矩形中是正交的,而相机是透视的,要将其还原
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed2 uv = lerp(i.screenUV, tex2D(_MainTex, i.uv).xy, _Distort);
fixed4 grabTex = tex2D(_GrabTex, uv);
return grabTex;
}
ENDCG
}

使用片元计算后的pos计算屏幕坐标

查看代码
 Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct a2f
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float2 screenUV : TEXCOORD1;
};
sampler2D _GrabTex;
sampler2D _MainTex;float4 _MainTex_ST;
fixed _SpeedX, _SpeedY, _Distort;
v2f vert (a2f v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex) + float2(_SpeedX, _SpeedY) * _Time.x;
o.pos = UnityObjectToClipPos(v.vertex);
o.screenUV = o.pos;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// return i.screenUV.x;
// return i.pos.x;
//可以看到,我明明对screenUV赋了pos的值,但上面两个居然不一样
//那是因为SV_POSITION在中间经过了改变
//在经过计算后,pos就是屏幕像素大小了,不再是-1到1了,那这样就更方便了;
i.screenUV = i.pos.xy / _ScreenParams.xy;
//OK,就上面这一行就解决战斗了
fixed2 uv = lerp(i.screenUV, tex2D(_MainTex, i.uv).xy, _Distort);
fixed4 grabTex = tex2D(_GrabTex, uv);
return grabTex;
}
ENDCG
}

[PerRendererData]

属性的特性

Properties
{
_MainTex ("Texture", 2D) = "white" {}
[PerRendererData]_Color ("Color", Color) = (0,0,0,0)
}

该材质cs被调用时,会被打包进cs 的MaterialPropertyBlock

每帧修改一万个对象的颜色,

方法1,使用材质的方法去修改颜色,开销是16ms每帧

方法2,使用GetPropertyBlock(MaterialPropertyBlock prop)的方法修改颜色,开销是7ms每帧

Color

模板测试

模板缓冲区

自己的话

是屏幕大小的区域,例如1920*1080的大小

每个像素是8位

当某一个物体被渲染到屏幕上时,它看到带有材质,也就是肯定带有shader

而shader中可以定义,当前我被渲染出来的区域的模板值

因此,当另一个物体被渲染时,另一个shader上面的也会有模板值

两个模板值比较,新的模板值大会怎么样,小会怎么样,可以自己定义

因此就可以做出类似PS遮罩的效果

官方的话

模板缓冲区可以为屏幕上的每个像素点保存一个无符号的整数值

这个值的意义视程序的具体操作而定

在渲染过程中

可以用这个值与一个预先设定的参考值相比较

根据比较的结果来决定是否更新相应的像素点的颜色值,这个比较的过程被称为模板测

代码部分

公式

将StencilBuffer的值与ReadMask进行与运算,然后与Ref值进行Comp比较,结果为True时进行Pass操作,否则进行Fail操作

操作值写入StencilBuffer前先与WriteMask进行与运算

(Ref & ReadMask)Comp(StencilBuffer & ReadMask)? Pass : Fail

Comp

Less    <

Greater  >

Lequal  <=

Gequal  >=

Equal   =

NotEqual  !=

Always  总为true

Never  总为false

实践

UI组件加了Mask之后

会有这个,这是由UGUI自己去写的

Id就是公式中的StencilBuffer ,已经被写到深度缓冲区中了

我们写的新shader就是要和这个Id:1去比

Properties
{
[PerRendererData]_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (0,0,0,0)
_Ref("Stencil Ref", int) = 0
[Enum(UnityEngine.Rendering.CompareFunction)]_Comp("Stencil Comp", int) = 0
[Enum(UnityEngine.Rendering.StencilOp)]_OP("Stencil OP", int) = 0
}
Stencil//深度测试
{
Ref [_Ref]
//ReadMask [0~255]
//WriteMask [0~255]
Comp [_Comp]
Pass [_OP]
}

这就是Enum

此时_Ref = 1

这是Mask

这是效果

渲染方式

ForwardBase

可以实现一个逐像素的平行光

以及剩下所有的逐顶点和球谐SH光

ForwardAdd

用于剩下所有的逐像素光

Lambert光照模型

Diffuse = Ambient + Kd * LightColor * dot(N, L);

Diffuse:最终物体上的漫反射强度
Ambient:环境光强度,为简化计算,用常数表示
Kd:物体材质对光的反射系数
LightColor:光源颜色
N:normal
L:顶点指向光源的单位向量

dot = cos(N,L)

 

Phong光照模型

Specular = SpecularColor * Ks * pow(  max(0, dot(R, V)),   Shininess  );

Specular:最终物体上的高光反射
SpecularColor:高光的颜色
Ks:反射系数
R:反射单位向量
V:顶点到观察点的单位距离
Shininess:高光指数,用于模拟高光范围

dot(R,V):

R就是反射光的方向

V就是眼睛观察改点的方向

因此两者方向越靠近,dot越大,就越亮

R的推演:

深度写入:

ZWrite On|Off
深度写入,默认为On
On:向深度缓冲中写入深度值

深度测试:

ZTest
深度测试,拿当前像素的深度值与深度缓冲中的深度值进行比较,默认为LEqual
可通过在属性中添加枚举:UnityEngine.Rendering.CompareFunction

Less:小于,表示如果当前像素的深度值小于深度缓冲中的深度值,则通过

Greater:大于

LEqual:小于等于

GEqual:大于等于

Equal:等于

NotEqual:不等于

Nerver:永不通过

Always:永远通过

当两个面重叠时,总会出现两者材质的交错现象

解决办法之一,就是移动其中某个面
当如果必须保持两个面重叠,但还需要某个面在上面时

可以用Offset

可以简单理解为,两个负数就是往相机移,正数就是远离相机

Unity雾效

就是Fog,pass的重点是#pragma multi_compile_fog、#if defined(FOG_LINEAR) || defined(FOG_LINEAR) || defined(FOG_LINEAR)、和雾效计算

查看代码
 Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float fogFactor : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
float z = length(_WorldSpaceCameraPos - mul(unity_ObjectToWorld, v.vertex));
#if defined(FOG_LINEAR)
o.fogFactor = z * unity_FogParams.z + unity_FogParams.w;
#elif defined(FOG_EXP)
o.fogFactor = exp2(-unity_FogParams.y * z);
#elif defined(FOG_EXP2)
float value = unity_FogParams.x * z;
o.fogFactor = exp2(-value * value);
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 c = 1;
#if defined(FOG_LINEAR) || defined(FOG_LINEAR) || defined(FOG_LINEAR)
c = lerp(unity_FogColor,c,i.fogFactor);
#endif
return c;
}
ENDCG
}

然后还有两种内置的:

平移、缩放、旋转矩阵

查看代码
 //平移矩阵
float4x4 T = float4x4(
1, 0, 0, _Translate.x,
0, 1, 0, _Translate.y,
0, 0, 1, _Translate.z,
0, 0, 0, _Translate.w);
v.positionOS = mul(T, v.positionOS);
//缩放矩阵
float4x4 S = float4x4(
_Scale.x*_Scale.w, 0, 0, 0,
0, _Scale.y*_Scale.w, 0, 0,
0, 0, _Scale.z*_Scale.w, 0,
0, 0, 0, 1);
v.positionOS = mul(S, v.positionOS);
//旋转矩阵
_Rotate.xyz = _Rotate.xyz * 3.1415926 / 180;
float4x4 R1 = float4x4(
1,0,0,0,
0,cos(_Rotate.x),sin(_Rotate.x),0,
0,-sin(_Rotate.x),cos(_Rotate.x),0,
0,0,0,1
);
float4x4 R2 = float4x4(
cos(_Rotate.y), 0, sin(_Rotate.y), 0,
0, 1, 0, 0,
-sin(_Rotate.y), 0, cos(_Rotate.y), 0,
0, 0, 0, 1
);
float4x4 R3 = float4x4(
cos(_Rotate.z), sin(_Rotate.z), 0, 0,
-sin(_Rotate.z), cos(_Rotate.z), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
v.positionOS = mul(R1, v.positionOS);
v.positionOS = mul(R3, v.positionOS);
v.positionOS = mul(R2, v.positionOS);

 世界空间->观察空间

我们需要先由于无法读取到摄像机和物体的全部坐标和旋转,故使用参数模拟

//观察空间矩阵
//P_view = [W_view] * P_world
//P_view = [V_world]^-1 * P_world
//P_view = [V_world]^T * P_world

1.我们构建WorldSpace下的View坐标V_world的三个轴

float3 ViewZ = normalize(_ViewPosition.xyz - _ViewTarget.xyz);
float3 ViewY = float3(0, 1, 0);//参数模拟,水平的相机
float3 ViewX = normalize(cross(ViewY, ViewZ));
ViewY = normalize(cross(ViewZ, ViewX));
 
需要注意,World为左手坐标系,而View为右手坐标系,因此在corss时,需要注意二者的顺序
如ViewY = normalize(cross(ViewZ, ViewX));中的二者颠倒,就会导致模型上下颠倒。
 
//旋转矩阵:
float4x4 M_viewRot = float4x4(
    ViewX.x, ViewY.x, ViewZ.x, 0,
    ViewX.y, ViewY.y, ViewZ.y, 0,
    ViewX.z, ViewY.z, ViewZ.z, 0,
    0, 0, 0, 1
);
//位移矩阵
float4x4 M_viewTran = float4x4(
    1, 0, 0, -_ViewPosition.x,
    0, 1, 0, -_ViewPosition.y,
    0, 0, 1, -_ViewPosition.z,
    0, 0, 0, 1
);

float4x4 M_view = mul(M_viewRot, M_viewTran);

最后的计算

 

 

 

 

 

 

 

 

posted @   被迫吃冰淇淋的小学生  阅读(73)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示