shader_实现放大镜功能

发布时间 2023-04-10 14:49:03作者: 数学天才琪露诺

需求

放大镜镜头内的区域需要有放大的效果;成品Gif如下:

分析

shader编写分析:

  1. 先实现整体放大效果
  2. 最后在一定范围内放大(这里是圆)
  3. 需要实时获得放大镜的中心点
  4. 需要知道放大的强度,和放大镜的大小,边缘需要有一个边缘强化

因为要实时从获取屏幕中获取放大镜的中心点,所以这一块需要用到屏幕后处理。
(屏幕后处理,顾名思义,通常指的是在渲染完整个场景得到屏幕图像后,在对这个图像进行一系列的操作,实现各种屏幕特效;Unity也提供了一个接口——OnRenderImage)

C#脚本的编写

首先是重写Unity提供的接口:

     // 放大强度
    [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 = Vector2.zero;
   // 渲染屏幕
    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函数使用特定的UnityShader来对当前图像进行处理,再把返回的渲染纹理显示到屏幕上
            Graphics.Blit (source, destination, material);
        } else {
            Graphics.Blit (source, destination);
        }
    }

因为放大镜在屏幕中的位置在不断的变换,所以需要在Update函数中不断的获取放大镜的位置:

    public void Update ()
    {
        if (!isStartEnlarge) return;
        if (tool == null) return;
        Vector2 mousePos =  GameCamera.Ins.gameCamera.WorldToScreenPoint(tool.position);
        //将mousePos转化为(0,1)区间
        pos = new Vector2 (mousePos.x / Screen.width, mousePos.y / Screen.height);
    }

C#全部代码如下:

MagnifyingGlass类:

using Logic.Global;
using UnityEngine;

public class MagnifyingGlass : PostEffectsBase
{
    //放大的工具
    public Transform tool;
    //是否开始放大
    public bool isStartEnlarge = false;
    
    // shader
    private Shader myShader;
    //材质 
    private Material mat = null;
    public Material material {
        get {
            // 检查着色器并创建材质
            mat = CheckShaderAndCreateMaterial(myShader, ref 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 = Vector2.zero;

    void Start () {
        //找到对应的Shader文件  
        myShader = shader;
        if(myShader == null)
            myShader = Shader.Find ("Custom/MagnifyingGlass");
    }

    // 渲染屏幕
    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函数使用特定的UnityShader来对当前图像进行处理,再把返回的渲染纹理显示到屏幕上
            Graphics.Blit (source, destination, material);
        } else {
            Graphics.Blit (source, destination);
        }
    }

    public void Update ()
    {
        if (!isStartEnlarge) return;
        if (tool == null) return;
        Vector2 mousePos =  GameCamera.Ins.gameCamera.WorldToScreenPoint(tool.position);
        //将mousePos转化为(0,1)区间
        pos = new Vector2 (mousePos.x / Screen.width, mousePos.y / Screen.height);
    }
}

PostEffectsBase类:

using UnityEngine;

//提供一个后处理的基类,主要功能在于直接通过Inspector面板拖入shader,生成shader对应的材质
public class PostEffectsBase : MonoBehaviour
{

    //Inspector面板上直接拖入
    public Shader shader = null;
    private Material _material = null;
    
    /// <summary>
    /// 检测需要渲染的Shader可用性,然后返回使用了该shader的material
    /// </summary>
    /// <param name="shader">指定shader</param>
    /// <param name="material">创建的材质</param>
    /// <returns>得到指定shader的材质</returns>
    protected Material CheckShaderAndCreateMaterial(Shader shader, ref Material material)
    {
        if (shader == null || !shader.isSupported)
            return null;

        if (material && material.shader == shader)
            return material;

        material = new Material(shader);
        material.hideFlags = HideFlags.DontSave;
        return material;
    }
}

放大镜的shader


// ---------------------------【放大镜特效】---------------------------

Shader "Custom/MagnifyingGlass"
{
    // ---------------------------【属性】---------------------------
    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) // 接受类型为VertexInput的参数,返回类型为VertexOutput
            {
                VertexOutput o; // 定义一个VertexOutput类型的变量o
                o.vertex = UnityObjectToClipPos(v.vertex); // 将顶点坐标从对象空间转换到剪辑空间
                o.uv = v.uv; // 将纹理坐标复制到输出结构
                return o; // 返回VertexOutput类型的结构体
            }

            // ---------------------------【片元着色器】---------------------------
            fixed4 frag(VertexOutput i) : SV_Target // 接受VertexOutput类型的参数,指定输出颜色类型为fixed4
            {
                float2 scale = float2(_ScreenParams.x / _ScreenParams.y, 1); // 计算屏幕长宽比的缩放因子
                float2 center = _Pos; // 放大区域的中心点
                float2 dir = center - i.uv; // 当前像素到中心点的距离向量
                float dis = length(dir * scale); // 当前像素到中心点的距离
                float atZoomArea = smoothstep(_Size + _EdgeFactor, _Size, dis); // 根据距离判断是否在放大区域内
                fixed4 col = tex2D(_MainTex, i.uv + dir * _ZoomFactor * atZoomArea); // 根据放大程度计算新的坐标位置,并获取对应的颜色值
                return col; // 返回颜色值
            }
            ENDCG
        }
    }
}

引用:

Unity Shader - 放大镜
LearnUnityShader