Unity将预制体渲染到RenderTexture上

前言

        本篇文章会介绍一个简单想法,所以会有很多限制。比如无法设置光源、对预制体中的物体位置有一定的要求和在使用Unity默认的URP材质有一些奇怪的bug。

环境

    Windows11
    Unity2022.3.56f1c1

正文

        本篇文章的想法非常简单简单到我都觉得水的程度。我们只要使用Unity提供的CommandBuffer就可以实现了。首先我们先设定好摄像机。不过我们不是真的要创建一个Camera组件,而是我们设定一个虚拟的摄像机。这个摄像机的屏幕比就是我们要渲染的RenderTexture的长宽比。然后我们再定好摄像机是否要进行透视投影,如果是透视投影的话,fov(Field of view)是多少。如果是 正交投影的话,它窗口的大小又要是多少等等。最终我们通过这些属性建立我们投影矩阵和视图矩阵。听到这,如果你不了解整体的渲染流程或许你会很懵逼。所以我这边提供给你一个简单方法。我们只需要拿一个空场景设定好这个空场景的中的摄像机参数,千万别忘记将Unity编辑器的Game视图大小设定为和RendertTexture一样。这时候我们只需要输出这个摄像机的投影矩阵和视图矩阵就好了,即Camera.main.projectionMatrix;Camera.main.worldToCameraMatrix;。接下来我们只要遍历预制体中的所有的Render组件并通过CommandBuffer.DrawRender进行渲染就好了。完整代码如下所示:

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
using UnityEngine;
using UnityEngine.Rendering;

namespace JustFun
{
[ExecuteAlways]
public class RenderPrefabToRT : MonoBehaviour
{
public GameObject prefab;
public RenderTexture renderTexture;
[Min(0.1f)]
public float cameraFov;
// 是否要透视投影
public bool isPerspective;

public bool render;

public CommandBuffer cmd;

private void Update()
{
if (render)
{
RenderPerfab();
render = false;
}
}

public void RenderPerfab()
{
if(prefab != null && renderTexture != null)
{
if(cmd == null)
cmd = new CommandBuffer();
cmd.Clear();
cmd.SetRenderTarget(renderTexture);
cmd.ClearRenderTarget(true, true, Color.clear);
Graphics.ExecuteCommandBuffer(cmd);

// 我这里是自建矩阵
// 这里的摄像机被放置在原点的位置并且看向z轴正方向

var worldToCameraMatrix = new Matrix4x4(
new Vector4(1, 0, 0, 0),
new Vector4(0, 1, 0, 0),
new Vector4(0, 0, -1, 0),
new Vector4(0, 0, 0, 1));
Matrix4x4 projectMatrix;
float rate = renderTexture.height * 1.0f / renderTexture.width;
if (isPerspective)
{
var tan = Mathf.Tan(cameraFov * Mathf.PI / 180);
projectMatrix = new Matrix4x4(
new Vector4(tan / rate, 0, 0, 0),
new Vector4(0, tan, 0, 0),
new Vector4(0, 0, -1.00006f, -1),
new Vector4(0, 0, -0.06000f, 0));
}
else
{
projectMatrix = new Matrix4x4(
new Vector4(rate / cameraFov, 0, 0, 0),
new Vector4(0, 1 / cameraFov, 0, 0),
new Vector4(0, 0, -0.002f, 0),
new Vector4(0, 0, -1.0006f, 1));

}
cmd.SetViewProjectionMatrices(worldToCameraMatrix, projectMatrix);
var renders = prefab.GetComponentsInChildren<Renderer>();
foreach (var i in renders)
{
cmd.DrawRenderer(i, i.sharedMaterial);
}
Graphics.ExecuteCommandBuffer(cmd);
Debug.Log("执行完毕");
}
}
}
}

如果大家对矩阵如何创建感兴趣的话,大家可以自行搜索一下。这里我偷懒,并不想多讲。如果实在不理解,不如按照我的方法来做一下。不过正如我前文所说,这里我们无法设定光的信息。而且在我实践下,在渲染Unity的URP默认材质尤其是非透明材质的时候经常会出现图像丢失的问题。我认为是深度检测没通过,因为透明材质还是可以正常渲染的,但是我没想明白为什么没通过。简单一点的做法是不要使用这个默认材质,毕竟我们无法受光,那就用自己的写的Shader来代替。

闲言碎语

        类似的需求,我24年就和朋友讨论的时候实现过一次。没想到今年,他又来问了我一次。我实在觉得有趣便在这次记录下来了。下次他再来找我,我就直接把这篇文章甩到他脸上(๑¯㉨¯๑)。