length(v)
使用length可以实现从中心向四周发散的效果
下面shader略去不必要内容
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// return i.uv.x;
// return i.uv.y;
// return length(i.uv);
// i.uv - 0.5 相当于把原点从(0, 0)移动到了(0.5, 0.5)
return length(i.uv - 0.5);
}
abs(x)
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//从 0 ~ 1 转换到 -1 ~ 1
// return i.uv.x * 2 - 1;
return abs(i.uv.x * 2 - 1);
}
step(a, b) //如果 a <= b,返回1,否则返回0,即 a <= b ? 1 : 0
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// i.uv - 0.5 相当于把原点从(0, 0)移动到了(0.5, 0.5)
// fixed4 color = length(i.uv);
fixed4 color = length(i.uv - 0.5);
color = step(color, 0.5);
return color;
}
step常用于代替if else,伪代码表示
if (uv < 0.5)
return 1; //白色
else
return 0; //黑色
float4 _Offset; //偏移值
float _LineWidth; //线的宽度
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float color1 = step(i.uv.x, _Offset.x);
float color2 = step(i.uv.x, _Offset.x - _LineWidth);
// return color1;
// return color2;
return color1 - color2;
}
float _Slope; //斜率
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return step(i.uv.x, _Slope * i.uv.y);
}
pow(a, b) //求a^b
float _Power; //指数
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float k = pow(i.uv.x, _Power);
return step(i.uv.x, k * i.uv.y);
}
min(a, b)
max(a, b)
min和max可以看作是不同部分取并集
min可以看作是对黑色部分取并集,max可以看作是对白色部分取并集
相乘的结果和min一样,因为只有左下角相乘结果不为0,类似可以推断出,多个颜色相乘,只会保留白色交集部分,其他全是黑色
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed color1 = step(i.uv.x, 0.3);
fixed color2 = step(i.uv.y, 0.3);
// return color1;
// return color2;
// return min(color1, color2);
return max(color1, color2);
}
fmod(x,y) //返回x%y的余数
f = fmod(x, 1) 的函数图像
f = fmod(x, 2) 的函数图像
fmod可以实现重复变化的效果
float _TilingX; //x方向循环次数
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//将x的范围从 0 ~ 1 转到 0 ~ _TilingX
i.uv.x = i.uv.x * _TilingX;
//每当x值到达一个整数,就会归0,在区间内反复从0变化到1
return fmod(i.uv.x, 1);
}
frac(x) //返回x的小数部分
y = frac(x) 的函数图像,和 f = fmod(x, 1) 的函数图像一样
和fmod类似也可以实现重复变化的效果
float _Tiling; //循环次数
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//将x,y分量的范围从 0 ~ 1 转到 0 ~ _Tiling
i.uv = i.uv * _Tiling;
return frac(i.uv.x) + frac(i.uv.y);
}
//若 x <= min,返回0,若 x >= max,返回1
//若x在两者之间,返回 3x^2 - 2x^3 ,其中x = (x0-min)/(max-min)
smoothstep(min, max, x)
维基百科上显示的公式
绘制曲线可以看出smoothstep曲线在0 ~ 1区间内的特点,中间变化快,两头变化慢
smoothstep常用于抗锯齿
从图中可以看出step实现的圆弧上有锯齿,而smoothstep对边缘进行了柔化处理(抗锯齿),如果smoothstep的第一个参数大于第二次参数,颜色会取反。
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float color = length(i.uv);
color = step(color, 0.5);
// color = smoothstep(0.5, 0.6, color);
// color = smoothstep(0.6, 0.5, color);
return color;
}
另一个常用的插值函数lerp
//计算 a + w*(b-a),在a和b之间插值,w表示权值,范围0~1,w = 0 结果 = a,w = 1 结果 = b
lerp(a, b, w)
lerp会将所有值都映射到区间内,而smoothstep是对区间内的值进行插值,区间外的返回0或1
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float col = frac(length(i.uv - 0.5) * 5);
// col = lerp(0.3, 0.5, col);
col = smoothstep(0.3, 0.5, col);
return col;
}
第三个常用的抗锯齿函数是fwidth
ddx(v) = 该像素点右边的值 - 该像素点的值
ddy(v) = 该像素点下面的值 - 该像素点的值
fwidth(v) = abs(ddx(v)) + abs(ddy(v)) //邻域像素之间的近似导数值
ddx,ddy反映了相邻像素在屏幕空间x和y方向上的变化率
用fwidth获取一个小范围,配合smoothstep函数在区间内做插值运算
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = length(i.uv);
// color = step(0.5, color);
//w是一个很小的值,这里乘以5是为了扩大范围,当color小于0.5 - w时,返回0,
//大于0.5 + w时,返回1,否则在0到1之间进行插值。这样我们可以在[−w, w]区间内,
//即高光区域的边界处,得到从0到1平滑变化的值
float w = fwidth(color) * 5;
color = lerp(0, 1, smoothstep(0.5 - w, 0.5 + w, color));
return color;
}
dot(a,b)
求向量的夹角或b向量在a向量上的投影
cross(a,b)
求与这两个向量组成的平面垂直的向量
saturate(x) //把x限制到[0,1]之间
clamp(x, a, b) //把x限制到[a,b]之间
sign(x) //x > 0,返回1;x < 0,返回-1;x = 0,返回0
degrees(x) //将弧度值转换为角度值
radians(x) //将角度值转换为弧度值
transpose(M) //返回矩阵的转置矩阵
normalize(v) //返回单位向量
reflect(l, n) //入射光线方向l和表面法向量n,计算反射向量
refract(l, n, ratio) //光线方向l,法向量n和两种介质折射率的比值ratio,计算折射向量
计算反射或折射光线后,一般会用来在CubeMap上采样,详情参考另一篇文章:反射,折射,菲涅尔反射总结
ceil(x) //向上取整
round(x) //四舍五入
floor(x) //向下取整
sin(x) //正弦
cos(x) //余弦
tan(x) //正切
#define PI 3.141592653
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//从 0 ~ 1 转换到 0 ~ 2 * PI,即sin函数的一个周期
float2 uv2 = i.uv * float2(2 * PI, 2 * PI);
//将sin函数的结果从 -1 ~ 1 转换到 0 ~ 1
float sinUV = sin(uv2) * 0.5 + 0.5;
float slope = uv2.y - sinUV;
slope = round(slope);
return slope;
}
反三角函数
asin(x) //反正弦
acos(x) //反余切
atan(x) //反正切
atan2(y,x) //求y/x的反正切,其返回值为[-π, π]之间
时间和三角函数组合形成变化的效果
这里用时间控制颜色在0到1之间循环
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float4 color = 1;
// _Time的4个分量的值分别是 (t/20, t, t*2, t*3),t是自该场景加载开始所经过的时间
color.xyz = sin(_Time.z) * 0.5 + 0.5;
return color;
}
Shader Graph中有个Simple Noise节点,右键选择Open Documentation就可以看到它实现的代码,这些代码可以复制到项目里直接使用
使用Unity_SimpleNoise_float这个函数,传入uv和缩放值,就会能得到一个随机值,上图是修改缩放值得到的噪声图
float _Seed;
inline float unity_noise_randomValue (float2 uv)
{
return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453);
}
inline float unity_noise_interpolate (float a, float b, float t)
{
return (1.0-t)*a + (t*b);
}
inline float unity_valueNoise (float2 uv)
{
float2 i = floor(uv);
float2 f = frac(uv);
f = f * f * (3.0 - 2.0 * f);
uv = abs(frac(uv) - 0.5);
float2 c0 = i + float2(0.0, 0.0);
float2 c1 = i + float2(1.0, 0.0);
float2 c2 = i + float2(0.0, 1.0);
float2 c3 = i + float2(1.0, 1.0);
float r0 = unity_noise_randomValue(c0);
float r1 = unity_noise_randomValue(c1);
float r2 = unity_noise_randomValue(c2);
float r3 = unity_noise_randomValue(c3);
float bottomOfGrid = unity_noise_interpolate(r0, r1, f.x);
float topOfGrid = unity_noise_interpolate(r2, r3, f.x);
float t = unity_noise_interpolate(bottomOfGrid, topOfGrid, f.y);
return t;
}
void Unity_SimpleNoise_float(float2 UV, float Scale, out float Out)
{
float t = 0.0;
float freq = pow(2.0, float(0));
float amp = pow(0.5, float(3-0));
t += unity_valueNoise(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;
freq = pow(2.0, float(1));
amp = pow(0.5, float(3-1));
t += unity_valueNoise(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;
freq = pow(2.0, float(2));
amp = pow(0.5, float(3-2));
t += unity_valueNoise(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;
Out = t;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float noise;
Unity_SimpleNoise_float(i.uv, _Seed, noise);
return noise;
}
方法1:片元着色器的输入中声明VPOS或WPOS语义
// VPOS是HLSL中对屏幕坐标的语义,而WPOS是Cg中对屏幕坐标的语义。两者在Unity Shader中是等价的
fixed4 frag (float4 sp : WPOS) : SV_Target
{
// sp.xy就是当前片元在屏幕空间的坐标
// 用屏幕坐标除以屏幕分辨率_ScreenParams.xy,得到视口空间中的坐标
return fixed4(sp.xy / _ScreenParams.xy, 0.0, 1.0);
}
方法2:使用ComputeScreenPos函数
实现效果和上图一样
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 scrPos : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
// 第一步:把ComputeScreenPos的结果保存到scrPos中
o.scrPos = ComputeScreenPos(o.pos);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 第二步:用scrPos.xy除以scrPos.w得到视口空间中的坐标
float2 wcoord = i.scrPos.xy / i.scrPos.w;
return fixed4(wcoord, 0.0, 1.0);
}
在Unity旧版本中,需要在相机上添加脚本修改相机的depthTextureMode,去生成深度纹理,传递给shader中的变量_CameraDepthTexture
新版本不需要了,默认会生成深度纹理,只需要在shader中申明_CameraDepthTexture即可使用
using UnityEngine;
[ExecuteInEditMode]
public class DepthCamera : MonoBehaviour
{
public Material mat;
public int width = 512;
public int height = 512;
private Camera cam;
void Start()
{
cam = GetComponent<Camera>();
// 设置模式后就可以在Shader中通过声明_CameraDepthTexture变量来访问它
cam.depthTextureMode = DepthTextureMode.Depth;
cam.targetTexture = new RenderTexture(width, height, 24);
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (mat != null)
Graphics.Blit(source, destination, mat);
}
}
创建材质,使用下面shader
Shader "MyCustom/Depth"
{
Properties {}
SubShader
{
Tags { "RenderType" = "Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 scrPos : TEXCOORD1;
};
//声明了深度纹理,Unity会把深度纹理传递给该值
sampler2D _CameraDepthTexture;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.scrPos = ComputeScreenPos(o.pos);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 当通过纹理采样得到深度值后,这些深度值往往是非线性的
float d1 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.scrPos.xy / i.scrPos.w);
float d2 = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.scrPos);
// LinearEyeDepth是视角空间下的深度值,范围是 [相机近裁剪面, 相机远裁剪面]
float eyeDepth = LinearEyeDepth(d1);
// 范围在[0,1]的线性深度值
return Linear01Depth(d1);
// return Linear01Depth(d2);
}
ENDCG
}
}
}
按住Ctrl,点击相机上的Target Texture就可以看到这张深度图,注意相机的远近裁剪面对深度图有很大影响
HLSL 中可用的内部函数
Simple Noise
《Shader 入门精要》