【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps

本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================



写在前面


为了在Shaders中创建反射的效果,我们将首先开始学习如何创建我们自己的Cubemaps。当然,你可以在网上找到许多已经做好的Cubemaps,但是你很快就会想,怎么制作自己的Cubemaps,因为网上那些是不能反射你自己游戏中的场景的。
制作你自己的Cubemaps是创建更真实的反射效果的关键。我们将会学习几个可以在Unity editor中直接使用的方法。另外,我们还将看看在单机游戏中的效果。这些知识将会帮助你理解下一章(光照模型)中的内容。

好啦,下面我们就正式学习如何为我们的Shaders创建Cubemaps吧!


开始工作


Unity为我们提供了JavaScript的代码来从我们创建的场景中生成Cubemap。所以,让我们来看一下它是怎么工作的。这个链接中的脚本是我们脚本的基础。接下来,我们将把它翻译成C#。在本章的最后一节(动态创建Cubemaps)中,我们将会学习如何创建一个简单的系统来从不同的位置创建Cubemaps,然后使用这些数据在reflection maps中转换,模拟游戏角色在场景内移动的效果,这将最终得到一个半实时的反射系统。

这里,我们仅仅学习如何创建一个单独的Cubemap。
  1. 我们需要创建一些元素来当做Cubemaps的光源。因此,我们需要在场景中放置一些几何平面。你可以使用一个建模软件,例如Maya或者Max,当然你也可以使用Unity自带的plane。哪一种方式都行,这无所谓。你的场景应该可以像这样:
    【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps_第1张图片
  2. 场景中的物体如下所示:
    【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps_第2张图片
    其中,Position是一个空对象,它将仅仅作为观察的位置将从该点观察到的环境信息渲染到我们的Cubemap上。


实现


  1. 首先,我们需要创建一个新的脚本,但是由于我们想要一个弹出的编辑器窗口,因此我们必须把脚本放到一个叫做Editor的文件夹里。在你的Project面板中创建一个叫Editor的文件夹,然后再创建一个C#脚本,叫做GenerateStaticCubemap
    【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps_第3张图片
  2. 打开上述脚本,为了使用特定的函数,我们需要使用新的using指令:
    using UnityEngine;
    using UnityEditor;
    using System.Collections

  3. 为了让Unity认识到,这个脚本会是一个弹出的编辑器窗口,我们需要让GenerateStaticCubemap类继承ScriptableWizard类。这使得我们可以使用一些很好的底层函数。
    public class GenerateStaticCubemap : ScriptableWizard {

  4. 然后,我们需要添加一些新的变量来存储新的CubeMap以及它的位置(即上面提到的position空对象)。
    	public Transform renderPosition;
    	public Cubemap cubemap;
  5. 第一个函数是Unity的内置函数OnWizardUpdate()。它在向导(wizard)第一次弹出或者当GUI被用户改变时(如拖进去某些对象,输入某些字符等)时被调用。因此,我们可以在这里检查用户已经向向导中填入我们需要的所有的资源。在这里,如果Cubemap或者它的位置(一个transform)没有被填充,那么就设置内置变量isValid为false,直到拿到所有资源。
    	void OnWizardUpdate() {
    		helpString = "Select transform to render" +
    			" from and cubemap to render into";
    		if (renderPosition != null && cubemap != null) {
    			isValid = true;
    		}
    		else {
    			isValid = false;
    		}
    	}
  6. isValid变量为true时,向导将调用OnWizardCreate()函数。在这个函数里,我们将创建一个新的摄像机,然后把它放到之前设置的transform的位置上,再调用RenderToCubemap函数得到最终的Cubemap。
    	void OnWizardCreate() {
    		GameObject go = new GameObject("CubeCam", typeof(Camera));
    
    		go.transform.position = renderPosition.position;
    		go.transform.rotation = Quaternion.identity;
    
    		go.camera.RenderToCubemap(cubemap);
    
    		DestroyImmediate(go);
    	}
  7. 最后,我们需要从Unit编辑器的菜单栏打开这个向导。这需要MenuItem关键词。
    	[MenuItem("CookBook/Render Cubemap")]
    	static void RenderCubemap() {
    		ScriptableWizard.DisplayWizard("Render CubeMap", typeof(GenerateStaticCubemap), "Render!");
    	}

最后,整体代码如下:
using UnityEngine;
using UnityEditor;
using System.Collections;

public class GenerateStaticCubemap : ScriptableWizard {

	public Transform renderPosition;
	public Cubemap cubemap;

	void OnWizardUpdate() {
		helpString = "Select transform to render" +
			" from and cubemap to render into";
		if (renderPosition != null && cubemap != null) {
			isValid = true;
		}
		else {
			isValid = false;
		}
	}

	void OnWizardCreate() {
		GameObject go = new GameObject("CubeCam", typeof(Camera));

		go.transform.position = renderPosition.position;
		go.transform.rotation = Quaternion.identity;

		go.camera.RenderToCubemap(cubemap);

		DestroyImmediate(go);
	}

	[MenuItem("CookBook/Render Cubemap")]
	static void RenderCubemap() {
		ScriptableWizard.DisplayWizard("Render CubeMap", typeof(GenerateStaticCubemap), "Render!");
	}
}

此时,回到Unity编辑器页面,点击一下Unity菜单栏(有时需要点击才会刷新)就会看到出现一个新的选项:CookBook/Render Cubemap,如下所示:


点击它你将会打开一个向导界面,如下所示。它需要两个资源,一个是Render Position,这将决定观察位置(你可以理解成你将会在该位置放一面镜子,这里需要注意的是由于代码里设置的摄像机的rotation为初始值,这意味着摄像机将看向图中蓝色箭头所指方向),一个是Cubemap,可以理解成就是镜子反射的图像。我的例子中设置如下。
【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps_第4张图片


点击Render!按钮后,查看你的Cubemap,就会看到类似下面这样的情景。
【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps_第5张图片

可以发现,我们已经把图像渲染到了一个立方体里了。恭喜你,你已经完成了自己的一个Cubemap!你可以尝试在不同的场景中试验。



解释


还记得一开始我们继承了 ScriptableWizard类,这是为了告诉Unity3D我们想要制作一个新的弹出窗口类型的用户编辑器,这也是为什么我们需要把脚本放到Editor文件夹里的原因。如果我们不这么做,Unity将不会把它识别为一个用户编辑器类型的脚本。

接下来我们声明的参数是用于存储Cubemap的渲染位置,以及把新的渲染得到的Cubemap放到Project的哪里,例如上面的名为First的Cubemap就是我提前在工程文件下创建的一个Cubemap。有了这些我们就可以生成自己的Cubemap了。

然后我们使用了 OnWizardUpdate()函数,它是由 ScriptableWizard类提供给我们的。同样的, isValid变量也是一个内置变量。它让我们可以方便的打开或者关闭向导下方的Create按钮(这里指的是Render!按钮)。这样可以防止用户使用空的transform或者Cubemap进行下面的操作。

一旦我们确定用户提供了正确的数据,我们就可以进入到 OnWizardCreate()函数了。这是Cubemap真正被创建的地方。它首先创建了一个新的 GameObject构造器,并把它的类型创建为 Camera。然后把它放到用户提供的transform的位置上。

到了这里,我们剩下要做的就是把用户提供的Cubemap传递给 RenderToCubemap函数,生成六张图片。

最后,我们为向导创建了一个菜单选项,以便让用户可以从Unity顶部的菜单栏打开这个工具。除了需要 [ MenuItem ( " CookBook/Render Cubemap " )] 关键词以外,我们还需要将函数声明为static函数。

至此,我们就完成了一个简单的工具,可以用于在Unity编辑器中直接生成Cubemaps!


你可能感兴趣的:(游戏,unity,unity3d,图形,shader)