前言
今天闲来无事,实现了一个简单的放大镜特效。
效果图如下:
思路
思路其实很简单,大致分为两个步骤:
- 先实现整体放大效果;
- 最后在一定范围内放大(这里是圆)
既然是放大镜,那也就是对图像的处理, 这里我们就需要用到后期处理了, 在Camera上面挂一个c#脚本来捕获需要渲染的图像,然后通过shader处理后渲染。
实现
1.首先我们来实现一下整体放大的效果:
思路非常简单:沿着 中心点到当前像素点的方向 采样像素点即可, 采样的距离越大, 缩放率就越大。
这里就贴一下关键代码,完整代码在文章最后会贴出来。
关键代码如下:
// ---------------------------【片元着色器】---------------------------
fixed4 frag (VertexOutput i) : SV_Target
{
// 中心点(鼠标点击位置,由c#传过来)
float2 center = _Pos;
// 方向
float2 dir = center-i.uv;
// 沿着缩放方向 缩放
fixed4 col = tex2D(_MainTex, i.uv + dir * _ZoomFactor);
return col;
}
效果如下:
2.然后限制它在一定范围内缩放:
我们这里就实现最简单的,让它在一个圆的范围内缩放。
其实我们仔细思考一下,会发现这个也比较简单,
决定缩放的关键代码无非就是这行:
fixed4 col = tex2D(_MainTex, i.uv + dir * _ZoomFactor);
只要我们给缩放因子乘以一个控制变量(atZoomArea), 当 atZoomArea = 0 时, 不缩放; 当 atZoomArea = 1 时 缩放。
代码修改如下:
fixed4 col = tex2D(_MainTex, i.uv + dir * _ZoomFactor * atZoomArea );
现在最关键的点来了, 我们怎么计算出 控制变量(atZoomArea)呢?
这里就和之前的文章 Unity Shader - 遮罩效果 类似了。
可以通过判断当前像素点到中心点的距离是否小于等于 圆的半径来决定是否在圆内。 小于说明在圆内,反之则不在,代码如下:
// ---------------------------【片元着色器】---------------------------
fixed4 frag (VertexOutput i) : SV_Target
{
// 中心点(鼠标点击位置,由c#传过来)
float2 center = _Pos;
// 方向
float2 dir = center-i.uv;
//当前像素到中心点的距离
float dis = length(dir);
// 是否在放大镜区域
fixed atZoomArea = 1-step(_Size,dis);
// 沿着缩放方向 缩放
fixed4 col = tex2D(_MainTex, i.uv + dir * _ZoomFactor * atZoomArea);
return col;
}
效果如下:
我们发现这个圆不太一样,是一个椭圆, 这是由于 屏幕宽高不一样, 然而在uv中, x轴和y轴始终是在[0,1]区间内, 所以造成圆被拉伸成了椭圆。
解决方案如下:
fixed4 frag (VertexOutput i) : SV_Target
{
//屏幕长宽比 缩放因子
float2 scale = float2(_ScreenParams.x / _ScreenParams.y, 1);
// 中心点(鼠标点击位置,由c#传过来)
float2 center = _Pos;
// 方向
float2 dir = center-i.uv;
//当前像素到中心点的距离
float dis = length(dir*scale);
// 是否在放大镜区域
fixed atZoomArea = 1-step(_Size,dis);
// 沿着缩放方向 缩放
fixed4 col = tex2D(_MainTex, i.uv + dir * _ZoomFactor * atZoomArea);
return col;
}
如图:
以上,基本的功能已经实现了, 其实我们还可以优化一下, 我们仔细看这个圆的边缘锯齿感比较严重, 我们可以使之更加平滑!
可以用 smoothstep 函数代替 step 达到平滑的效果。
// fixed atZoomArea = 1-step(_Size,dis);
float atZoomArea = smoothstep(_Size + _EdgeFactor,_Size,dis );
最终效果如下:
最终完整代码如下:
C#:
PostEffectsBase 基类可以在这里获取(来自《Unity Shader入门精要》)
// create by 长生但酒狂
// create time 2020.4.8
// ---------------------------【放大镜特效】---------------------------
using UnityEngine;
public class Zoom : PostEffectsBase {
// shader
public Shader myShader;
//材质
private Material mat = null;
public Material material {
get {
// 检查着色器并创建材质
mat = CheckShaderAndCreateMaterial (myShader, mat);
return mat;
}
}
// 放大强度
[Range (-2.0f, 2.0f), Tooltip ("放大强度")]
public float zoomFactor = 0.4f;
// 放大镜大小
[Range (0.0f, 0.2f), Tooltip ("放大镜大小")]
public float size = 0.15f;
// 凸镜边缘强度
[Range (0.0001f, 0.1f), Tooltip ("凸镜边缘强度")]
public float edgeFactor = 0.05f;
// 遮罩中心位置
private Vector2 pos = new Vector4 (0.5f, 0.5f);
void Start () {
//找到对应的Shader文件
myShader = Shader.Find ("lcl/screenEffect/Zoom");
}
// 渲染屏幕
void OnRenderImage (RenderTexture source, RenderTexture destination) {
if (material) {
// 把鼠标坐标传递给Shader
material.SetVector ("_Pos", pos);
material.SetFloat ("_ZoomFactor", zoomFactor);
material.SetFloat ("_EdgeFactor", edgeFactor);
material.SetFloat ("_Size", size);
// 渲染
Graphics.Blit (source, destination, material);
} else {
Graphics.Blit (source, destination);
}
}
void Update () {
if (Input.GetMouseButton (0)) {
Vector2 mousePos = Input.mousePosition;
//将mousePos转化为(0,1)区间
pos = new Vector2 (mousePos.x / Screen.width, mousePos.y / Screen.height);
}
}
}
Shader:
// create by 长生但酒狂
// create time 2020.4.8
// ---------------------------【放大镜特效】---------------------------
Shader "lcl/screenEffect/Zoom"
{
// ---------------------------【属性】---------------------------
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
// ---------------------------【子着色器】---------------------------
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
// ---------------------------【渲染通道】---------------------------
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//顶点输入结构体
struct VertexInput
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
// 顶点输出结构体
struct VertexOutput
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
// 变量申明
sampler2D _MainTex;
float2 _Pos;
float _ZoomFactor;
float _EdgeFactor;
float _Size;
// ---------------------------【顶点着色器】---------------------------
VertexOutput vert (VertexInput v)
{
VertexOutput o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
// ---------------------------【片元着色器】---------------------------
fixed4 frag (VertexOutput i) : SV_Target
{
//屏幕长宽比 缩放因子
float2 scale = float2(_ScreenParams.x / _ScreenParams.y, 1);
// 放大区域中心
float2 center = _Pos;
float2 dir = center-i.uv;
//当前像素到中心点的距离
float dis = length(dir * scale);
// 是否在放大镜区域
// fixed atZoomArea = 1-step(_Size,dis);
float atZoomArea = smoothstep(_Size + _EdgeFactor,_Size,dis );
fixed4 col = tex2D(_MainTex, i.uv + dir * _ZoomFactor * atZoomArea );
return col;
}
ENDCG
}
}
}