前言

今天闲来无事,实现了一个简单的放大镜特效。

效果图如下:

unity image局部放大 轴心 unity放大镜效果_游戏开发

思路

思路其实很简单,大致分为两个步骤:

  1. 先实现整体放大效果;
  2. 最后在一定范围内放大(这里是圆)

既然是放大镜,那也就是对图像的处理, 这里我们就需要用到后期处理了, 在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;
}

效果如下:

unity image局部放大 轴心 unity放大镜效果_shader_02

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;
}

效果如下:

unity image局部放大 轴心 unity放大镜效果_unity3d_03

我们发现这个圆不太一样,是一个椭圆, 这是由于 屏幕宽高不一样, 然而在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;
}

如图:

unity image局部放大 轴心 unity放大镜效果_游戏开发_04

以上,基本的功能已经实现了, 其实我们还可以优化一下, 我们仔细看这个圆的边缘锯齿感比较严重, 我们可以使之更加平滑!

可以用 smoothstep 函数代替 step 达到平滑的效果。

// fixed atZoomArea = 1-step(_Size,dis);
float atZoomArea = smoothstep(_Size + _EdgeFactor,_Size,dis );

最终效果如下:

unity image局部放大 轴心 unity放大镜效果_unity image局部放大 轴心_05

最终完整代码如下:

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
        }
    }
}