_Time float4 t是自游戏开始所经过的时间,(t/20,t,2t,3t)
_SinTime float4 t是时间的正弦值 (t/8,t/4,t/2,t)
_CosTime float4 t是时间的余弦值 (t/8,t/4,t/2,t)
unity_DeltaTime float4 dt是时间增量 (dt,1/dt,smoothDt,1/smoothDt)
需要一张关键帧的图像
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 11/Image Sequence Animation" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Image Sequence", 2D) = "white" {}
_HorizontalAmount ("Horizontal Amount", Float) = 4
_VerticalAmount ("Vertical Amount", Float) = 4
_Speed ("Speed", Range(1, 100)) = 30
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha//透明度混合
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _HorizontalAmount;
float _VerticalAmount;
float _Speed;
struct a2v {
float4 vertex : POSITION;
float2 texcoord : 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.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target {
float time = floor(_Time.y * _Speed); //时间与速度相乘后在通过下面计算得到行和列
float row = floor(time / _HorizontalAmount);
float column = time - row * _HorizontalAmount;
// half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount);
// uv.x += column / _HorizontalAmount;
// uv.y -= row / _VerticalAmount;
half2 uv = i.uv + half2(column, -row);
uv.x /= _HorizontalAmount;
uv.y /= _VerticalAmount;
fixed4 c = tex2D(_MainTex, uv);
c.rgb *= _Color;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 11/Water" {
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_Magnitude ("Distortion Magnitude", Float) = 1
_Frequency ("Distortion Frequency", Float) = 1
_InvWaveLength ("Distortion Inverse Wave Length", Float) = 10
_Speed ("Speed", Float) = 0.5
}
SubShader {
// Need to disable batching because of the vertex animation
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
//批处理操作会合并所有相关的模型,这些模型各自的模型空间就会丢失,因此顶点动画关闭批处理
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;
struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v) {
v2f o;
float4 offset;
offset.yzw = float3(0.0, 0.0, 0.0);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
o.pos = UnityObjectToClipPos(v.vertex + offset);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv += float2(0.0, _Time.y * _Speed);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
广告牌技术会根据视角方向来旋转一个被纹理着色的多边形,使得多边形看起来总是面向摄像机。广告牌技术使用的基向量:表面法线,指向上的方向,指向右的方向。还需要一个锚点,这个锚点在旋转过程中固定不变,确定多边形的中心位置。
right = up * normal
广告牌技术也是一种顶点动画,因此也需要关闭批处理。
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 11/Billboard" {
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1
}
SubShader {
// Need to disable batching because of the vertex animation
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _VerticalBillboarding;
struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (a2v v) {
v2f o;
// Suppose the center in object space is fixed
float3 center = float3(0, 0, 0);
float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1));
float3 normalDir = viewer - center;
// If _VerticalBillboarding equals 1, we use the desired view dir as the normal dir
// Which means the normal dir is fixed
// Or if _VerticalBillboarding equals 0, the y of normal is 0
// Which means the up dir is fixed
normalDir.y =normalDir.y * _VerticalBillboarding;
normalDir = normalize(normalDir);
// Get the approximate up dir
// If normal dir is already towards up, then the up dir is towards front
float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
float3 rightDir = normalize(cross(upDir, normalDir));
upDir = normalize(cross(normalDir, rightDir));
// Use the three vectors to rotate the quad
float3 centerOffs = v.vertex.xyz - center;
float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;
o.pos = UnityObjectToClipPos(float4(localPos, 1));
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target {
fixed4 c = tex2D (_MainTex, i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
(三)屏幕后处理效果
屏幕后处理通常指在渲染完整个场景得到的屏幕图像后,在对这个图像进行一系列操作,实现各种屏幕特效。
Unity抓取屏幕提供了一个接口:
MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest)
在OnRenderImage中通常利用Graphics.Blit函数来完成对渲染纹理的处理。OnRenderImage会在所有的透明的不透明的Pass执行完毕后被调用,而有时我们希望在不透明的Pass(渲染队列小于等于2500的Pass,内置的background,geometry,alphatest)后就执行,可以在OnRenderImage前调用ImageEffectOpaque属性。使用时需要在摄像机上增加一个屏幕后处理的脚本。
using UnityEngine;
using System.Collections;
public class BrightnessSaturationAndContrast : PostEffectsBase {
public Shader briSatConShader;
private Material briSatConMaterial;
public Material material {
get {
briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
return briSatConMaterial;
}
}
[Range(0.0f, 3.0f)]
public float brightness = 1.0f;
[Range(0.0f, 3.0f)]
public float saturation = 1.0f;
[Range(0.0f, 3.0f)]
public float contrast = 1.0f;
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_Brightness", brightness);
material.SetFloat("_Saturation", saturation);
material.SetFloat("_Contrast", contrast);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
}
Shader "Unity Shaders Book/Chapter 12/Brightness Saturation And Contrast" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Brightness ("Brightness", Float) = 1
_Saturation("Saturation", Float) = 1
_Contrast("Contrast", Float) = 1
}
SubShader {
Pass {
ZTest Always Cull Off ZWrite Off//屏幕后处理实际就是在场景中绘制一个与屏幕同宽同高的四边形面片,为了防治他对其他物体产生影响,关闭深度写入,防治挡住后面的物体渲染。这一般是屏幕后处理shader的标配
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
half _Brightness;
half _Saturation;
half _Contrast;
struct v2f {
float4 pos : SV_POSITION;
half2 uv: TEXCOORD0;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed4 renderTex = tex2D(_MainTex, i.uv);
// Apply brightness
fixed3 finalColor = renderTex.rgb * _Brightness;//_Brightness调整亮度
// Apply saturation
fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;//计算该像素对应的亮度值 每个颜色乘以一个特定的系数再相加得到的
fixed3 luminanceColor = fixed3(luminance, luminance, luminance);//用亮度值创建了一个饱和度为0的颜色值
finalColor = lerp(luminanceColor, finalColor, _Saturation);//利用_Saturation 插值得到希望的饱和度颜色
// Apply contrast 对比度处理
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
Fallback Off
}
(四)边缘检测
卷积:在图像处理中,卷积就是使用一个卷积核对一张图像中的每一个像素进行一系列的操作。卷积核通常是一个四方形网状结构(2*2 3*3)该区域内每个格都有一个权重值,当对图像中每个像素进行卷积时,会把卷积核的中心放置于该像素上,翻转核之后再依次计算核中的每个元素和其覆盖的图像像素值的乘积并求和,得到的结果就是该位置的新的像素值。卷积一般用于图像模糊和边缘检测等。
边缘检测的卷积核也称为边缘检测算子。
Roberts
Gx Gy
-1 0 0 -1
0 1 1 0
Prewitt
Gx Gy
-1 0 1 -1 -1 -1
-1 0 1 0 0 0
-1 0 1 1 1 1
Sobel
Gx Gy
-1 0 1 -1 -2 -1
-2 0 2 0 0 0
-1 0 1 1 2 1
一般计算整体的梯度是需要 Gx Gy平方相加开根号,处于性能考虑一般是
G = |Gx| + |Gy|
G梯度值越大,越可能是边缘点
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 12/Edge Detection" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_EdgeOnly ("Edge Only", Float) = 1.0
_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment fragSobel
sampler2D _MainTex;
uniform half4 _MainTex_TexelSize;//_MainTex_TexelSize是Unity用来访问XXX纹理对应的每个纹素值的大小,例如一张512 * 512大小的纹理,该值一般为1/512 = 0.001953
fixed _EdgeOnly;
fixed4 _EdgeColor;
fixed4 _BackgroundColor;
struct v2f {
float4 pos : SV_POSITION;
half2 uv[9] : TEXCOORD0;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
return o;
}
fixed luminance(fixed4 color) {
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
half Sobel(v2f i) {
const half Gx[9] = {-1, 0, 1,
-2, 0, 2,
-1, 0, 1};
const half Gy[9] = {-1, -2, -1,
0, 0, 0,
1, 2, 1};
half texColor;
half edgeX = 0;
half edgeY = 0;
for (int it = 0; it < 9; it++) {
texColor = luminance(tex2D(_MainTex, i.uv[it]));//对9个像素进行采样,计算他们的亮度值,
edgeX += texColor * Gx[it];//利用亮度值与卷积核对应的权重相乘,叠加到各自的梯度值上
edgeY += texColor * Gy[it];
}
half edge = 1 - abs(edgeX) - abs(edgeY);//1减去X方向和Y方向上的绝对值,edge越小,说明越可能是一个边缘点。
return edge;
}
fixed4 fragSobel(v2f i) : SV_Target {
half edge = Sobel(i);
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);//利用梯度值计算背景为原图下的颜色值
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);//利用梯度值计算背景为纯色时的颜色值
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);//插值得到最终的像素值
}
ENDCG
}
}
FallBack Off
}
(五)高斯模糊
高斯滤波:高斯模糊利用了卷积计算,使用的卷积核名为高斯核。高斯核是一个正方形的滤波核,每个元素的计算都给予高斯方程。高斯方程很好的模拟了领域每个像素对当前处理像素的影响程度,距离越近,影响越大。高斯核的维数越高,模糊程度越大。