Unity内部截图功能
简单的仿照通用截图功能制作在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内部截图。

