US3S8L3——动态生成立方体纹理
US3S8L3——动态生成立方体纹理
本章代码关键字
1 | camera.RenderToCubemap() // 摄像机根据所在位置动态生成立方体纹理 |
为什么要动态生成立方体纹理?
立方体纹理 中提到过,立方体纹理的其中一个最大的作用就是环境映射,
在实现反射、折射等等效果时,需要用到立方体纹理来制作对应效果,
而立方体纹理中最重要的就是组成它的 6 张 2D 纹理图片
对于之前学过的天空盒来说,6 张 2D 纹理图片可以根据想要的美术表现效果来进行自定义制作,
提前把纹理制作好,直接使用即可,这种立方体纹理往往是被提前做好的,是场景中物体们共用的,
但如果制作反射、折射等效果还是使用这样的立方体纹理,效果肯定不够理想,因为物体在场景上的位置不同,产生的对应效果也会是不同的
举例说明:以下图为例,假设我们希望立方体可以反射出周围光照,
如果只使用提前做好的立方体纹理(例如天空盒)做反射,那么周围球体等其他物体是不会被反射出来的
因此,为了更好更真实的表现效果,对于场景中不同位置的物体,我们应该为它们在各自的位置上生成不同的立方体纹理(6 张 2D 纹理贴图)
如何动态生成立方体纹理?
我们将结合
-
Unity编辑器拓展
具体知识详见:UED——Unity编辑器拓展
-
Camera 中的
RenderToCubemap()
方法
这两个知识点,在对应位置生成对应的立方体纹理贴图,对于一些场景展示类项目,我们不需要实时生成,只需要在编辑器模式下生成一次即可
主要要完成的功能为:
- 自定义编辑器窗口,关联对象(通过对象来指定位置)和
Cubemap
变量 - 自定义窗口中有一个生成按钮,点击后使用
Camera
中的RenderToCubemap()
自动生成对应的6张2D纹理贴图
其中编辑器窗口相关功能使用 Unity 编辑器拓展相关知识
自动生成立方体纹理贴图功能使用 Camera
中的 RenderToCubemap()
方法
该方法可以将任意位置观察到的场景图像存储到 6 张图像中
动态生成立方体纹理
camera.RenderToCubemap()
会基于 Camera
所依附的 GameObject
所在的位置生成立方体纹理(立方体纹理呈现的就是此位置周围的环境)
-
参数一:需要保存立方体纹理的
Cubemap
(也可以使用 RenderTexture) -
参数二:指示渲染到立方体贴图的哪个面,默认是同时渲染六个面,传入
CubemapFace
枚举即可(需要转换为int
类型)
CubemapFace
定义如下:1
2
3
4
5
6
7
8
9
10public enum CubemapFace
{
Unknown = -1,
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ
} -
返回值:是否渲染成功的
bool
值
按照上文提到的制作思路来实现逻辑:
-
新建一个脚本
RenderToCubemap
放在 Editor 文件夹中
-
让该类继承 EditorWindow 将其作为一个编辑器窗口类
-
实现打开该窗口的静态函数
创建编辑器窗口知识详见:UEDL2——自定义窗口拓展
1
2
3
4
5
6
7
8
9public class RenderToCubeMap : EditorWindow
{
[ ]
static void OpenWindow()
{
RenderToCubeMap window = EditorWindow.GetWindow<RenderToCubeMap>("立方体纹理生成窗口");
window.Show();
}
} -
实现
OnGUI()
中的窗口布局,添加以下控件- 关联位置对象的控件
- 关联立方体纹理贴图的空间
- 生成按钮 GUILayout.Button()
其中,要关联一个对象,需要使用 EditorGUILayout.ObjectField() 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class RenderToCubeMap : EditorWindow
{
private GameObject renderObj;
private Cubemap cubemap;
private void OnGUI()
{
GUILayout.Label("立方体纹理生成所在位置的对象");
renderObj = (EditorGUILayout.ObjectField(renderObj, typeof(GameObject), true)) as GameObject;
GUILayout.Label("保存立方体纹理数据的Cubemap文件");
cubemap = (EditorGUILayout.ObjectField(cubemap, typeof(Cubemap), true)) as Cubemap;
if (GUILayout.Button("生成立方体纹理"))
{
if (renderObj == null || cubemap == null)
{
EditorUtility.DisplayDialog("提醒", "在生成立方体纹理前,需要先关联需要生成立方体纹理的对象和保存纹理的立方体贴图", "确认");
return;
}
// 动态生成立方体纹理
// ...
}
}
} -
实现具体逻辑
注意点:
-
我们需要在指定位置(关联的对象上)动态创建一个空物体,并为它添加摄像机
Camera
,再通过此Camera
生成立方体纹理贴图创建该对象为临时对象,使用完毕后直接删除即可
-
cubemap
上需要勾选Readable
-
Face size 分辨率决定了清晰度,但也决定了
Cubemap
的大小,因此需要根据项目的实际情况去选择
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25private GameObject renderObj;
private Cubemap cubemap;
private void OnGUI()
{
GUILayout.Label("立方体纹理生成所在位置的对象");
renderObj = (EditorGUILayout.ObjectField(renderObj, typeof(GameObject), true)) as GameObject;
GUILayout.Label("保存立方体纹理数据的Cubemap文件");
cubemap = (EditorGUILayout.ObjectField(cubemap, typeof(Cubemap), true)) as Cubemap;
if (GUILayout.Button("生成立方体纹理"))
{
if (renderObj == null || cubemap == null)
{
EditorUtility.DisplayDialog("提醒", "在生成立方体纹理前,需要先关联需要生成立方体纹理的对象和保存纹理的立方体贴图", "确认");
return;
}
// 动态的生成立方体纹理
GameObject tempObj = new GameObject("临时对象");
tempObj.transform.position = renderObj.transform.position;
Camera camera = tempObj.AddComponent<Camera>();
// 使用 RenderToCubemap 方法生成6张2D纹理贴图,用于立方体纹理
camera.RenderToCubemap(cubemap);
DestroyImmediate(tempObj);
}
} -
完整代码如下:
1 | using System.Collections; |
现在就可以生成立方体纹理,假设要为场景内如下对象生成立方体纹理:
首先创建两个用于存储立方体纹理数据的 Cubemap
文件(Project 窗口右键 — Create — Legacy — Cubemap)
然后分别将 要立方体纹理生成所在位置的 GameObject
和 Cubemap
文件 关联到实现的编辑器窗口上,点击 “生成立方体纹理”
此时,关联的 Cubemap
文件就保存了立方体纹理数据,可以在预览窗口上看到:
将立方体纹理生成到 Render Texture 上
camera.RenderToCubemap()
除了将立方体纹理数据生成到 Cubemap 文件内,还可以生成到 Render Texture 文件上
其中 Render Texture 的 Dimension 属性需要设置为 Cube ,才能将数据生成到 Render Texture 内
而 Render Texture 的 size 属性决定了立方体纹理的清晰度和大小
注意!
虽然
RenderTexture
可以当作立方体纹理使用,但是RenderTexture
的数据在默认情况下不会被保存中,
这意味着,在保存场景时,只生成一次的RenderTexture
的立方体纹理数据会被直接丢弃,导致反射贴图变黑,
因此 RenderTexture
不适用于持久化存储立方体纹理,而适用于运行时动态生成立方体纹理的场景下文的例子,虽然还是在编辑器环境下为
RenderTexture
一次生成立方体纹理数据,
但是一旦在保存场景,贴图会直接变黑,因此,下文的编辑器生成例子仅供参考,不要用在实际运行环境内
它的生成方法和上文中使用的 Cubemap 差不多,这里不再阐述:
1 | using System.Collections; |
显示效果:
值得一提的是,生成到 Render Texture 上的运行时间要比生成到 Cubemap 上的时间要短得多,
以下为测试代码(使用 C# Stopwatch
测试,生成位置相同的 512*512 质量的 Cube 贴图):
1 | private void OnGUI() |
运行时间对比:
在运行时动态生成立方体纹理
Camera
中的 RenderToCubemap
也可以在运行时实时动态生成立方体纹理,但是要注意对性能的影响,
如果每帧都需要渲染立方体贴图的所有六个面,生成操作开销将会很大
- 在 LateUpdate 中使用
- 降低立方体纹理贴图的分辨率
- 分帧渲染,
RenderToCubemap()
有重载(使用第二个参数),可以一个面一个面的渲染 - 降低更新频率,不要每帧执行
- 改用
RenderTexture
来接收立方体纹理数据
等等
以下面的代码为例:
1 | using UnityEngine; |
将该脚本依附某个 GameObject
上,再关联一个 RenderTexture
,这个对象就可以实时的在 GameObject
位置上生成立方体纹理
假设,GameObject
使用了基于此 RenderTexture
的立方体纹理实现反射的 Shader,
那么这个 GameObject
就可以实时的反射周围的对象了,而不需要再手动重新生成立方体纹理
反射效果相关内容详见:US3S8L4——反射效果