欢迎光临散文网 会员登陆 & 注册

Unity内部截图功能

2021-04-16 19:32 作者:哀哥哥ob  | 我要投稿

简单的仿照通用截图功能制作在unity内部截图导出。

  • 截图框UI

        截图框分为四部分,遮罩、外边框、拖拽节点和一个确认按钮。遮罩采用UIshader做一个选中区域的透明;外边框只在开启截图时启用,作提醒功能;拖拽节点负责在拖拽时重置选中区域。

  • shader部分的代码

Shader "UI/Default_Mask_Rect"
{
   Properties
   {
       [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
       _Color ("Tint", Color) = (1,1,1,1)

       _StencilComp ("Stencil Comparison", Float) = 8
       _Stencil ("Stencil ID", Float) = 0
       _StencilOp ("Stencil Operation", Float) = 0
       _StencilWriteMask ("Stencil Write Mask", Float) = 255
       _StencilReadMask ("Stencil Read Mask", Float) = 255

       _ColorMask ("Color Mask", Float) = 15

       [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
     //-------------------add----------------------
       _OriginPos("OriginPos", vector) = (0,0,0,0)
       _EndPos("EndPos", vector) = (0,0,0,0)
     _Width("Width", Float) = 1
     _Height("Height", Float) = 1
     //-------------------add----------------------
   }

   SubShader
   {
       Tags
       {
           "Queue"="Transparent"
           "IgnoreProjector"="True"
           "RenderType"="Transparent"
           "PreviewType"="Plane"
           "CanUseSpriteAtlas"="True"
       }

       Stencil
       {
           Ref [_Stencil]
           Comp [_StencilComp]
           Pass [_StencilOp]
           ReadMask [_StencilReadMask]
           WriteMask [_StencilWriteMask]
       }

       Cull Off
       Lighting Off
       ZWrite Off
       ZTest [unity_GUIZTestMode]
       Blend SrcAlpha OneMinusSrcAlpha
       ColorMask [_ColorMask]

       Pass
       {
           Name "Default"
       CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #pragma target 2.0

           #include "UnityCG.cginc"
           #include "UnityUI.cginc"

           #pragma multi_compile __ UNITY_UI_CLIP_RECT
           #pragma multi_compile __ UNITY_UI_ALPHACLIP
        #pragma multi_compile _ROUNDMODE_RECT _ROUNDMODE_DYNAMIC_RECT

           struct appdata_t
           {
               float4 vertex   : POSITION;
               float4 color    : COLOR;
               float2 texcoord : TEXCOORD0;
               UNITY_VERTEX_INPUT_INSTANCE_ID
           };

           struct v2f
           {
               float4 vertex   : SV_POSITION;
               fixed4 color    : COLOR;
               float2 texcoord  : TEXCOORD0;
               float4 worldPosition : TEXCOORD1;
               UNITY_VERTEX_OUTPUT_STEREO
           };

           fixed4 _Color;
           fixed4 _TextureSampleAdd;
           float4 _ClipRect;
        //-------------------add----------------------
        float2 _OriginPos;
        float2 _EndPos;
        half _Width;
        half _Height;

        //-------------------add----------------------

           v2f vert(appdata_t v)
           {
               v2f OUT;
               UNITY_SETUP_INSTANCE_ID(v);
               UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
               OUT.worldPosition = v.vertex;
               OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

               OUT.texcoord = v.texcoord;

               OUT.color = v.color * _Color;
               return OUT;
           }

           sampler2D _MainTex;

           fixed4 frag(v2f IN) : SV_Target
           {
               half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

               #ifdef UNITY_UI_CLIP_RECT
               color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
               #endif

               #ifdef UNITY_UI_ALPHACLIP
               clip (color.a - 0.001);
               #endif

               float2 _CenterT = (_OriginPos + _EndPos)/2;
               half _SliderXT = abs(_EndPos.x - _OriginPos.x)/2;
               half _SliderYT = abs(_EndPos.y - _OriginPos.y)/2;
           //-------------------add----------------------
           //计算X轴方向距离
           float disX = distance(IN.worldPosition.x, _CenterT.x);
           //计算Y轴方向距离
           float disY = distance(IN.worldPosition.y, _CenterT.y);
           //计算过渡范围内的alpha值
           color.a *= (abs(disX) > _SliderXT) || (abs(disY) > _SliderYT);
           //-------------------add----------------------
               return color;
           }
       ENDCG
       }
   }
}

shader主要根据输入的起点终点两个坐标确定一个需要剔除的矩形区域。


  • UI部分的代码

using App.System;
using UnityEngine;
using UnityEngine.UI;

namespace App.UI.NewUI
{
   public class UIExperimentScreenShotMask : MonoBehaviour
   {
       public static UIExperimentScreenShotMask instance;
       public Image rectMask;
       private Vector2 opdata;
       private Vector2 epdata;
       private Vector2 originpoint;
       private Vector2 endpoint;
       public Image leftup;
       public Image middleup;
       public Image rightup;
       public Image leftcenter;
       public Image rightcenter;
       public Image leftdown;
       public Image middledown;
       public Image rightdown;
       public Button surebtn;
       public GameObject fullscreen;
       private bool candrag;
       private bool isdrag;
       public Texture2D lurd;
       public Texture2D ud;
       public Texture2D lr;
       public Texture2D ldru;

       private void Awake()
       {
           instance = this;
           opdata = new Vector2(-Screen.width / 2, Screen.height / 2);
           epdata = new Vector2(Screen.width / 2, -Screen.height / 2);
           originpoint = opdata;
           endpoint = epdata;
           surebtn.onClick.AddListener(SureCut);
           var leftup_evt = leftup.gameObject.AddComponent<UIMouseEvent>();
           leftup_evt.onEnter += BorderOnEnter;
           leftup_evt.onExit += BorderOnExit;
           leftup_evt.onDrag += BorderOnDrag;
           var middleup_evt = middleup.gameObject.AddComponent<UIMouseEvent>();
           middleup_evt.onEnter += BorderOnEnter;
           middleup_evt.onExit += BorderOnExit;
           middleup_evt.onDrag += BorderOnDrag;
           var rightup_evt = rightup.gameObject.AddComponent<UIMouseEvent>();
           rightup_evt.onEnter += BorderOnEnter;
           rightup_evt.onExit += BorderOnExit;
           rightup_evt.onDrag += BorderOnDrag;
           var leftcenter_evt = leftcenter.gameObject.AddComponent<UIMouseEvent>();
           leftcenter_evt.onEnter += BorderOnEnter;
           leftcenter_evt.onExit += BorderOnExit;
           leftcenter_evt.onDrag += BorderOnDrag;
           var rightcenter_evt = rightcenter.gameObject.AddComponent<UIMouseEvent>();
           rightcenter_evt.onEnter += BorderOnEnter;
           rightcenter_evt.onExit += BorderOnExit;
           rightcenter_evt.onDrag += BorderOnDrag;
           var leftdown_evt = leftdown.gameObject.AddComponent<UIMouseEvent>();
           leftdown_evt.onEnter += BorderOnEnter;
           leftdown_evt.onExit += BorderOnExit;
           leftdown_evt.onDrag += BorderOnDrag;
           var middledown_evt = middledown.gameObject.AddComponent<UIMouseEvent>();
           middledown_evt.onEnter += BorderOnEnter;
           middledown_evt.onExit += BorderOnExit;
           middledown_evt.onDrag += BorderOnDrag;
           var rightdown_evt = rightdown.gameObject.AddComponent<UIMouseEvent>();
           rightdown_evt.onEnter += BorderOnEnter;
           rightdown_evt.onExit += BorderOnExit;
           rightdown_evt.onDrag += BorderOnDrag;
           gameObject.SetActive(false);
       }

       public void Show()
       {
           gameObject.SetActive(true);
           originpoint = opdata;
           endpoint = epdata;
           RefreshMask();
           fullscreen.SetActive(true);
           candrag = true;
       }

       public void Hide()
       {
           gameObject.SetActive(false);
       }
       
       /// <summary>
       /// 检测开始和结束框选,排除开始直接点击完成按钮的情况
       /// </summary>
       private void Update()
       {
           if (Input.GetMouseButtonDown(1) || Input.GetKeyDown(KeyCode.Escape))
           {
               Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
               originpoint = opdata;
               endpoint = epdata;
               RefreshMask();
               Hide();
               return;
           }

           if (candrag)
           {
               if (Input.GetMouseButtonDown(0) && Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out RaycastHit hitInfo) && hitInfo.transform != null && hitInfo.transform.name != "surebtn")
               {
                   OnBeginDrag();
                   isdrag = true;
               }

               if (isdrag)
               {
                   OnDrag();
               }

               if (Input.GetMouseButtonUp(0))
               {
                   OnEndDrag();
                   isdrag = false;
                   candrag = false;
               }
           }
       }

       /// <summary>
       /// 开始框选
       /// </summary>
       public void OnBeginDrag()
       {
           originpoint = new Vector2(Input.mousePosition.x - Screen.width / 2f,
               Input.mousePosition.y - Screen.height / 2f);
           endpoint = originpoint;
           fullscreen.SetActive(false);
           RefreshMask();
       }

       /// <summary>
       /// 框选中
       /// </summary>
       public void OnDrag()
       {
           endpoint = new Vector2(Input.mousePosition.x - Screen.width / 2f,
               Input.mousePosition.y - Screen.height / 2f);
           RefreshMask();
       }

       /// <summary>
       /// 结束框选
       /// </summary>
       public void OnEndDrag()
       {
           endpoint = new Vector2(Input.mousePosition.x - Screen.width / 2f,
               Input.mousePosition.y - Screen.height / 2f);
           Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
           RefreshMask();
       }

       /// <summary>
       /// 刷新框选区域
       /// </summary>
       private void RefreshMask()
       {
           var op = new Vector4(originpoint.x, originpoint.y, 0, 0);
           var ep = new Vector4(endpoint.x, endpoint.y, 0, 0);
           rectMask.material.SetVector("_OriginPos", op);
           rectMask.material.SetVector("_EndPos", ep);
           leftup.GetComponent<RectTransform>().anchoredPosition = originpoint;
           middleup.GetComponent<RectTransform>().anchoredPosition =
               new Vector2((originpoint.x + endpoint.x) / 2, originpoint.y);
           rightup.GetComponent<RectTransform>().anchoredPosition = new Vector2(endpoint.x, originpoint.y);
           leftcenter.GetComponent<RectTransform>().anchoredPosition =
               new Vector2(originpoint.x, (originpoint.y + endpoint.y) / 2);
           rightcenter.GetComponent<RectTransform>().anchoredPosition =
               new Vector2(endpoint.x, (originpoint.y + endpoint.y) / 2);
           leftdown.GetComponent<RectTransform>().anchoredPosition = new Vector2(originpoint.x, endpoint.y);
           middledown.GetComponent<RectTransform>().anchoredPosition =
               new Vector2((originpoint.x + endpoint.x) / 2, endpoint.y);
           rightdown.GetComponent<RectTransform>().anchoredPosition = new Vector2(endpoint.x, endpoint.y);
           surebtn.GetComponent<RectTransform>().anchoredPosition = new Vector2(
               originpoint.x > endpoint.x
                   ? originpoint.x
                   : endpoint.x - surebtn.GetComponent<RectTransform>().rect.width / 2,
               originpoint.y < endpoint.y
                   ? originpoint.y
                   : endpoint.y + surebtn.GetComponent<RectTransform>().rect.height / 2);
       }

       /// <summary>
       /// 鼠标进入节点
       /// </summary>
       /// <param name="border"></param>
       private void BorderOnEnter(UIMouseEvent border)
       {
           if (candrag)
               return;
           switch (border.name)
           {
               case "leftup":
                   Cursor.SetCursor(lurd, new Vector2(500, 500), CursorMode.Auto);
                   break;
               case "middleup":
                   Cursor.SetCursor(ud, new Vector2(500, 500), CursorMode.Auto);
                   break;
               case "rightup":
                   Cursor.SetCursor(ldru, new Vector2(500, 500), CursorMode.Auto);
                   break;
               case "leftcenter":
                   Cursor.SetCursor(lr, new Vector2(500, 500), CursorMode.Auto);
                   break;
               case "rightcenter":
                   Cursor.SetCursor(lr, new Vector2(500, 500), CursorMode.Auto);
                   break;
               case "leftdown":
                   Cursor.SetCursor(ldru, new Vector2(500, 500), CursorMode.Auto);
                   break;
               case "middledown":
                   Cursor.SetCursor(ud, new Vector2(500, 500), CursorMode.Auto);
                   break;
               case "rightdown":
                   Cursor.SetCursor(lurd, new Vector2(500, 500), CursorMode.Auto);
                   break;
           }

           RefreshMask();
       }

       /// <summary>
       /// 拖拽节点
       /// </summary>
       /// <param name="border"></param>
       private void BorderOnDrag(UIMouseEvent border)
       {
           if (candrag)
               return;
           var newpos = border.GetComponent<RectTransform>().anchoredPosition;
           switch (border.name)
           {
               case "leftup":
                   originpoint = newpos;
                   break;
               case "middleup":
                   originpoint = new Vector2(originpoint.x, newpos.y);
                   break;
               case "rightup":
                   originpoint = new Vector2(originpoint.x, newpos.y);
                   endpoint = new Vector2(newpos.x, endpoint.y);
                   break;
               case "leftcenter":
                   originpoint = new Vector2(newpos.x, originpoint.y);
                   break;
               case "rightcenter":
                   endpoint = new Vector2(newpos.x, endpoint.y);
                   break;
               case "leftdown":
                   originpoint = new Vector2(newpos.x, originpoint.y);
                   endpoint = new Vector2(endpoint.x, newpos.y);
                   break;
               case "middledown":
                   endpoint = new Vector2(endpoint.x, newpos.y);
                   break;
               case "rightdown":
                   endpoint = newpos;
                   break;
           }

           RefreshMask();
       }

       /// <summary>
       /// 鼠标离开节点
       /// </summary>
       /// <param name="border"></param>
       private void BorderOnExit(UIMouseEvent border)
       {
           Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
           RefreshMask();
       }

       /// <summary>
       /// 确认截图
       /// </summary>
       private void SureCut()
       {
           SExperimentScreenShot.instance.ScreenShot_ReadPixelsWithCamera(Camera.main, originpoint, endpoint);
           Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
           originpoint = opdata;
           endpoint = epdata;
           RefreshMask();
           Hide();
       }
   }
}

这部分的功能大体上就是框选区域的逻辑和在拖拽节点时cursor的修改。

  • 截图系统

    截图系统负责开启截图模式和截图之后的图片保存。

  • 截图系统代码

    using UnityEngine;
    using System.IO;
    using App.UI.NewUI;
    using SFB;

    namespace App.System
    {
       public class SExperimentScreenShot : MonoBehaviour
       {
           public static SExperimentScreenShot instance;
           
           private void Awake()
           {
               instance = this;
           }

           private void Update()
           {
               if (Input.GetKeyUp(KeyCode.F1))
               {
                   ScreenShot_ScreenCapture();
               }
               else if (Input.GetKeyUp(KeyCode.F2))
               {
                   UIExperimentScreenShotMask.instance.Show();
               }
           }
           
           /// <summary>
           /// 全屏截图
           /// </summary>
           private void ScreenShot_ScreenCapture()
           {
               ScreenCapture.CaptureScreenshot($"{Application.dataPath}/ss.png");
           }
           
           /// <summary>
           /// 根据相机截图
           /// </summary>
           /// <param name="_camera"></param>
           /// <param name="cuto"></param>
           /// <param name="cute"></param>
           public void ScreenShot_ReadPixelsWithCamera(Camera _camera, Vector2 cuto, Vector2 cute)
           {
               RenderTexture rt = new RenderTexture(Screen.width, Screen.height, 16);
               _camera.targetTexture = rt;
               _camera.Render();
               RenderTexture.active = rt;
               
               Texture2D t = new Texture2D((int)Mathf.Abs(cute.x - cuto.x), (int)Mathf.Abs(cute.y - cuto.y));
               t.ReadPixels(new Rect((cuto.x < cute.x ? cuto.x : cute.x) + Screen.width/2, Screen.height/2 - (cuto.y > cute.y ? cuto.y : cute.y), t.width, t.height), 0, 0);
               if (t.width > 540)
               {
                   t = ScaleTexture(t, 540, (int)(t.height / (float)t.width * 540));
               }
               t.Apply();

               _camera.targetTexture = null;
               RenderTexture.active = null;
               GameObject.Destroy(rt);
               
               //输出图片
               SavePNG(t);
           }

           /// <summary>
           /// 截图缩放
           /// </summary>
           /// <param name="source">原图</param>
           /// <param name="targetWidth">目标宽度</param>
           /// <param name="targetHeight">目标高度</param>
           /// <returns></returns>
           private Texture2D ScaleTexture(Texture2D source, int targetWidth, int targetHeight)
           {
               Texture2D result = new Texture2D(targetWidth, targetHeight, source.format, true);
               Color[] rpixels = result.GetPixels(0);
               float incX = ((float) 1 / source.width) * ((float) source.width / targetWidth);
               float incY = ((float) 1 / source.height) * ((float) source.height / targetHeight);
               for (int px = 0; px < rpixels.Length; px++)
               {
                   rpixels[px] = source.GetPixelBilinear(incX * ((float) px % targetWidth),
                       incY * ((float) Mathf.Floor(px / targetWidth)));
               }
               result.SetPixels(rpixels, 0);
               result.Apply();
               return result;
           }

           /// <summary>
           /// 保存PDF
           /// </summary>
           private void SavePNG(Texture2D t)
           {
               var path = StandaloneFileBrowser.SaveFilePanel("保存截图", "", $"", "png");
               if (string.IsNullOrEmpty(path))
               {
                   
               }
               else
               {
                   File.WriteAllBytes(path, t.EncodeToPNG());
               }
           }
       }
    }

    通过快捷键触发截图UI开启,获取到截图区域后,从camera的渲染上截取所需范围的texture2D,然后进行保存。通过以上代码就可以实现简单的unity内部截图。

软件截图界面
输出的图片


Unity内部截图功能的评论 (共 条)

分享到微博请遵守国家法律