Unity 截图工具开发实战

本文介绍如何开发一个功能完整的截图工具,涵盖 RenderTexture 原理、分辨率概念、透明背景截图等内容。

一、屏幕与分辨率基础

1.1 屏幕尺寸定义

术语 说明
屏幕尺寸 对角线长度(英寸)
分辨率 像素数量(宽×高)
像素密度 (PPI) 每英寸像素数
DPI Dots Per Inch,同 PPI
1
2
3
4
5
6
7
8
9
屏幕尺寸计算(勾股定理):
┌─────────────────┐
│ │ ← 对角线 = 屏幕尺寸(英寸)
│ 1920 px │
│ │
│ │
│ │
└─────────────────┘
1080 px

💡 1 英寸 (inch) = 2.54 厘米 (cm)

1.2 常见分辨率标准

类型 分辨率 宽高比
HD 1280×720 16:9
Full HD 1920×1080 16:9
2K 2560×1440 16:9
4K 3840×2160 16:9
手机竖屏 1080×1920 9:16

二、RenderTexture 基础

RenderTexture 是 Unity 中用于渲染到纹理的核心组件。

2.1 RenderTexture 概念

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────────┐
│ 渲染流程 │
├─────────────────────────────────────────────┤
│ │
│ Camera ─────► RenderTexture ─────► Texture2D │
│ │ │ │
│ ▼ ▼ │
│ GPU FrameBuffer PNG 文件 │
│ │
└─────────────────────────────────────────────┘

2.2 关键参数

参数 说明
width 纹理宽度(像素)
height 纹理高度(像素)
depth 深度位数(0, 16, 24)
format 纹理格式(RGBA32, RGB24 等)
antiAliasing 抗锯齿级别

2.3 纹理格式

格式 说明 用途
RGB24 24 位 RGB,无透明通道 普通截图
RGBA32 32 位 RGBA,带透明通道 透明背景
ARGB32 32 位 ARGB,带透明通道 旧版兼容
RGB565 16 位 RGB 节省内存

三、截图工具窗口实现

3.1 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
using UnityEngine;
using UnityEditor;
using System.IO;

public class ScreenshotWindow : EditorWindow
{
// === 截图设置 ===
private int resolutionWidth = 1920;
private int resolutionHeight = 1080;
private int scale = 1;
private bool transparentBackground = false;

// === 输出设置 ===
private string savePath = "Assets/Screenshots/";
private Camera targetCamera;
private string lastScreenshotPath = "";

// === 预览设置 ===
private bool showPreview = true;
private RenderTexture previewRenderTexture;

[MenuItem("Tools/Screenshot Tool %#s")] // Ctrl+Shift+S
public static void ShowScreenshotWindow()
{
var window = GetWindow<ScreenshotWindow>("Screenshot Tool");
window.minSize = new Vector2(350, 450);
window.autoRepaintOnSceneChange = true;
window.Show();
}

private void OnGUI()
{
EditorGUILayout.LabelField("📸 截图工具", EditorStyles.boldLabel);
EditorGUILayout.Space();

// 分辨率设置
DrawResolutionSection();

// 输出路径设置
DrawPathSection();

// 摄像机设置
DrawCameraSection();

// 截图预览
if (showPreview)
{
DrawPreview();
}

// 操作按钮
DrawActionButtons();
}

// === 分辨率设置 ===
private void DrawResolutionSection()
{
EditorGUILayout.LabelField("分辨率设置", EditorStyles.boldLabel);

resolutionWidth = EditorGUILayout.IntField("宽度 (Width)", resolutionWidth);
resolutionHeight = EditorGUILayout.IntField("高度 (Height)", resolutionHeight);

// 缩放倍数
scale = EditorGUILayout.IntSlider("超分辨率缩放", scale, 1, 4);

EditorGUILayout.Space();

// 快捷分辨率按钮
EditorGUILayout.BeginHorizontal();
{
if (GUILayout.Button("当前屏幕"))
{
var size = Handles.GetMainGameViewSize();
resolutionWidth = (int)size.x;
resolutionHeight = (int)size.y;
}

if (GUILayout.Button("2K (2560×1440)"))
{
resolutionWidth = 2560;
resolutionHeight = 1440;
scale = 1;
}

if (GUILayout.Button("4K (3840×2160)"))
{
resolutionWidth = 3840;
resolutionHeight = 2160;
scale = 1;
}
}
EditorGUILayout.EndHorizontal();

// 输出尺寸预览
int finalWidth = resolutionWidth * scale;
int finalHeight = resolutionHeight * scale;
EditorGUILayout.HelpBox(
$"输出尺寸: {finalWidth} × {finalHeight} 像素",
MessageType.Info
);
}

// === 路径设置 ===
private void DrawPathSection()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("保存路径", EditorStyles.boldLabel);

EditorGUILayout.BeginHorizontal();
{
EditorGUILayout.TextField(savePath, GUILayout.ExpandWidth(false));
if (GUILayout.Button("浏览...", GUILayout.Width(80)))
{
savePath = EditorUtility.SaveFolderPanel(
"选择截图保存路径",
savePath,
Application.dataPath + "/Screenshots"
);

if (string.IsNullOrEmpty(savePath))
{
savePath = "Assets/Screenshots/";
}
}
}
EditorGUILayout.EndHorizontal();

// 打开文件夹按钮
EditorGUILayout.BeginHorizontal();
{
if (GUILayout.Button("打开保存文件夹"))
{
if (Directory.Exists(savePath))
{
EditorUtility.RevealInFinder(savePath);
}
}

if (GUILayout.Button("打开最后截图") && !string.IsNullOrEmpty(lastScreenshotPath))
{
EditorUtility.OpenWithDefaultApp(lastScreenshotPath);
}
}
EditorGUILayout.EndHorizontal();
}

// === 摄像机设置 ===
private void DrawCameraSection()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("摄像机设置", EditorStyles.boldLabel);

targetCamera = EditorGUILayout.ObjectField(
new GUIContent("目标摄像机", "用于截图的摄像机"),
targetCamera,
typeof(Camera),
true
) as Camera;

// 自动选择主摄像机
if (targetCamera == null)
{
targetCamera = Camera.main;
}

transparentBackground = EditorGUILayout.Toggle(
new GUIContent("透明背景", "生成带透明通道的 PNG"),
transparentBackground
);

// 背景颜色设置
if (!transparentBackground && targetCamera != null)
{
var oldColor = GUI.backgroundColor;
GUI.backgroundColor = targetCamera.backgroundColor;
EditorGUILayout.ColorField("背景颜色", targetCamera.backgroundColor);
GUI.backgroundColor = oldColor;
}
}

// === 预览区域 ===
private void DrawPreview()
{
EditorGUILayout.Space();
showPreview = EditorGUILayout.Foldout(showPreview, "实时预览", true);

if (showPreview)
{
EditorGUI.indentLevel++;

// 创建预览 RenderTexture
if (previewRenderTexture == null ||
previewRenderTexture.width != resolutionWidth ||
previewRenderTexture.height != resolutionHeight)
{
if (previewRenderTexture != null)
DestroyImmediate(previewRenderTexture);

previewRenderTexture = new RenderTexture(resolutionWidth, resolutionHeight, 24);
previewRenderTexture.hideFlags = HideFlags.HideAndDontSave;
}

// 实时预览
if (targetCamera != null)
{
RenderTexture previousRT = targetCamera.targetTexture;
targetCamera.targetTexture = previewRenderTexture;
targetCamera.Render();
targetCamera.targetTexture = previousRT;
}

// 绘制预览
var rect = GUILayoutUtility.GetAspectRect(
(float)resolutionWidth / resolutionHeight,
GUILayout.MaxWidth(400)
);
GUI.DrawTexture(rect, previewRenderTexture, ScaleMode.ScaleToFit);

EditorGUI.indentLevel--;
}
}

// === 操作按钮 ===
private void DrawActionButtons()
{
EditorGUILayout.Space(20);

// 截图按钮
if (GUILayout.Button("📷 截取屏幕", GUILayout.MinHeight(40)))
{
TakeScreenshot();
}

EditorGUILayout.Space(10);

// 其他操作按钮
EditorGUILayout.BeginHorizontal();
{
if (GUILayout.Button("设置分辨率"))
{
GameViewSize size = GameViewSize.Get(GameViewSizeType.FixedResolution, 1920, 1080);
}

if (GUILayout.Button("清空缓存"))
{
if (previewRenderTexture != null)
{
DestroyImmediate(previewRenderTexture);
previewRenderTexture = null;
}
}
}
EditorGUILayout.EndHorizontal();
}

// === 执行截图 ===
private void TakeScreenshot()
{
if (targetCamera == null)
{
EditorUtility.DisplayDialog("错误", "请先选择目标摄像机!", "确定");
return;
}

// 确保目录存在
if (!Directory.Exists(savePath))
{
Directory.CreateDirectory(savePath);
}

// 计算最终分辨率
int finalWidth = resolutionWidth * scale;
int finalHeight = resolutionHeight * scale;

// 创建临时 RenderTexture
RenderTexture renderTexture = new RenderTexture(finalWidth, finalHeight, 24);

// 设置纹理格式
TextureFormat textureFormat = transparentBackground ? TextureFormat.RGBA32 : TextureFormat.RGB24;

// 创建截图 Texture2D
Texture2D screenshot = new Texture2D(finalWidth, finalHeight, textureFormat, false);

try
{
// 保存原目标纹理
RenderTexture previousTarget = targetCamera.targetTexture;
targetCamera.targetTexture = renderTexture;

// 渲染
targetCamera.Render();

// 激活 RenderTexture 以便读取
RenderTexture.active = renderTexture;

// 读取像素
screenshot.ReadPixels(new Rect(0, 0, finalWidth, finalHeight), 0, 0);
screenshot.Apply();

// 恢复
targetCamera.targetTexture = previousTarget;
RenderTexture.active = null;

// 编码为 PNG
byte[] pngBytes = screenshot.EncodeToPNG();

// 生成文件名
string timestamp = System.DateTime.Now.ToString("yyyyMMdd_HHmmss");
string fileName = $"screenshot_{resolutionWidth}x{resolutionHeight}";
if (scale > 1)
fileName += $"_{scale}x";

string filePath = Path.Combine(savePath, $"{fileName}_{timestamp}.png");
lastScreenshotPath = Path.GetFullPath(filePath);

// 保存文件
File.WriteAllBytes(filePath, pngBytes);

// 刷新资源
AssetDatabase.Refresh();

// 打开截图
EditorUtility.OpenWithDefaultApp(lastScreenshotPath);

Debug.Log($"截图已保存: {lastScreenshotPath}");
}
finally
{
// 清理资源
if (renderTexture != null)
DestroyImmediate(renderTexture);

if (screenshot != null)
DestroyImmediate(screenshot);
}
}

private void OnDestroy()
{
// 清理预览纹理
if (previewRenderTexture != null)
{
DestroyImmediate(previewRenderTexture);
}
}
}

3.2 窗口效果预览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
┌─────────────────────────────────────────────────────┐
│ 📸 截图工具 │
├─────────────────────────────────────────────────────┤
│ 分辨率设置 │
│ 宽度 (Width) [1920____________] │
│ 高度 (Height) [1080____________] │
│ 超分辨率缩放 ━━━━●━━━━━━━━━━━━━━━━ 1-4 │
│ [当前屏幕] [2K (2560×1440)] [4K (3840×2160)] │
│ ℹ 输出尺寸: 1920 × 1080 像素 │
│ │
│ 保存路径 │
│ [Assets/Screenshots/] [浏览...] │
│ [打开保存文件夹] [打开最后截图] │
│ │
│ 摄像机设置 │
│ 目标摄像机 [Main Camera (Camera)________] │
│ ✓ 透明背景 [□] │
│ │
│ ▼ 实时预览 │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ │ (摄像机预览画面) │ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 📷 截取屏幕 │ │
│ └─────────────────────────────────────────────────┘ │
│ [设置分辨率] [清空缓存] │
└─────────────────────────────────────────────────────┘

四、截图原理详解

4.1 截图流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────┐
│ 截图完整流程 │
└─────────────────────────────────────────────────────────┘

用户点击截图按钮

创建 RenderTexture (指定宽高)

设置 Camera.targetTexture = RenderTexture

调用 Camera.Render() (渲染到纹理)

设置 RenderTexture.active = RenderTexture

创建 Texture2D (读取像素)

Texture2D.ReadPixels() (从 GPU 读取到 CPU)

Texture2D.EncodeToPNG() (编码为 PNG)

File.WriteAllBytes() (保存到文件)

清理临时资源

4.2 关键代码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. 创建 RenderTexture(GPU 渲染目标)
RenderTexture rt = new RenderTexture(width, height, 24);

// 2. 设置摄像机目标纹理
camera.targetTexture = rt;

// 3. 渲染场景
camera.Render();

// 4. 激活 RenderTexture(用于读取)
RenderTexture.active = rt;

// 5. 创建 Texture2D(存储像素数据)
Texture2D tex = new Texture2D(width, height, TextureFormat.RGBA32, false);

// 6. 从 GPU 读取像素到 CPU
tex.ReadPixels(new Rect(0, 0, width, height), 0, 0);
tex.Apply();

// 7. 编码并保存
byte[] bytes = tex.EncodeToPNG();
File.WriteAllBytes(path, bytes);

// 8. 清理
RenderTexture.active = null;
camera.targetTexture = null;

五、高级功能

5.1 多摄像机截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void CaptureMultipleCameras()
{
Camera[] cameras = FindObjectsOfType<Camera>();

foreach (Camera cam in cameras)
{
// 为每个摄像机单独截图
CaptureCamera(cam, $"{cam.name}_screenshot.png");
}
}

private void CaptureCamera(Camera camera, string filename)
{
int width = 1920;
int height = 1080;

RenderTexture rt = new RenderTexture(width, height, 24);
camera.targetTexture = rt;
camera.Render();

Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false);
RenderTexture.active = rt;
tex.ReadPixels(new Rect(0, 0, width, height), 0, 0);
tex.Apply();

byte[] bytes = tex.EncodeToPNG();
File.WriteAllBytes(Path.Combine(savePath, filename), bytes);

camera.targetTexture = null;
RenderTexture.active = null;

DestroyImmediate(rt);
DestroyImmediate(tex);
}

5.2 定时截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private float captureInterval = 1f;  // 截图间隔
private float timer = 0f;
private bool isCapturing = false;
private int maxCaptures = 10;
private int captureCount = 0;

private void Update()
{
if (!isCapturing)
return;

timer += Time.deltaTime;

if (timer >= captureInterval)
{
timer = 0f;
TakeScreenshot();
captureCount++;

if (captureCount >= maxCaptures)
{
isCapturing = false;
}
}
}

private void StartCapture()
{
isCapturing = true;
captureCount = 0;
}

5.3 纹理格式选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private enum ScreenshotFormat
{
PNG_RGB24, // PNG 无透明
PNG_RGBA32, // PNG 有透明
JPG, // JPEG 压缩
EXR // HDR (高动态范围)
}

private ScreenshotFormat format = ScreenshotFormat.PNG_RGBA32;

private byte[] EncodeTexture(Texture2D tex, ScreenshotFormat fmt)
{
switch (fmt)
{
case ScreenshotFormat.PNG_RGB24:
return tex.EncodeToPNG();
case ScreenshotFormat.PNG_RGBA32:
return tex.EncodeToPNG();
case ScreenshotFormat.JPG:
return tex.EncodeToJPG();
default:
return tex.EncodeToPNG();
}
}

六、常见问题与解决方案

6.1 问题汇总

问题 原因 解决方案
截图为黑色 摄像机未渲染 检查 Camera.enabled
截图分辨率不对 缩放设置错误 检查 scale 参数
内存泄漏 资源未释放 确保 DestroyImmediate
保存路径错误 目录不存在 使用 Directory.CreateDirectory

6.2 资源清理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 正确的资源清理方式
private void CleanupResources()
{
if (renderTexture != null)
{
if (Application.isPlaying)
Destroy(renderTexture);
else
DestroyImmediate(renderTexture);
}

if (texture2D != null)
{
if (Application.isPlaying)
Destroy(texture2D);
else
DestroyImmediate(texture2D);
}
}

private void OnDestroy()
{
CleanupResources();
}

七、总结

本文介绍了 Unity 截图工具的完整实现:

主题 核心要点
屏幕概念 尺寸、分辨率、像素密度
RenderTexture GPU 渲染到纹理的核心组件
截图流程 Camera → RenderTexture → Texture2D → PNG
透明背景 使用 RGBA32 格式
资源管理 及时清理临时资源

💡 开发建议

  • 使用 RenderTexture 进行高质量截图
  • 注意及时释放临时资源避免内存泄漏
  • 支持透明背景便于后期处理
  • 提供多种分辨率预设方便使用
  • 实现预览功能提升用户体验

下一篇将详细介绍 Handles 场景可视化工具


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com