本文主要是介绍【Unity SurfaceShader】学习笔记(六)Cubemap,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Cubemap是一种类似天空盒的由六张贴图组成的贴图。它是用于一些需要反射效果的材质,用来反射周围的环境。如果要表现金属材质,通常会给它添加一张反射贴图,来模拟金属表面反射的环境的颜色。因为金属之类的材质它本身其实是没有颜色的,它的颜色就是它反射的周围物体的颜色。如果给金属赋予银白色,并不能得到金属的质感,要让它反射周围的颜色。立方图就是这样一种记录了周围环境颜色的贴图。
Unity提供了一个RenderToCubemap的方法在脚本中生成Cubemap,官网代码.
将代码拷贝下来,然后在场景中随意地搭个场景
随便搭个就可以,这就是我们要反射的场景,只要场景里有东西就可以。当然你可以弄个漂亮些的。
然后再建一个空物体,作为我们需要的Transform,这就是脚本中那个go相机的位置,也就是最后生成六张贴图中,front贴图中的图像,就是go相机看到的景象。
最后我们还需要一个空的Cubemap,在Project视图里右键,Create-Legacy-Cubemap,创建一个Cubemap。
点击我们自己创建菜单Render into Cubemap,会弹出一个对话框,将Transform、Cubemap赋给它就可以,然后render。需要注意的是,Cubemap要勾选Readable才可以。
渲染后的效果是这样的:
这样就生成了Cubemap。
解释
using UnityEngine;
using UnityEditor;
using System.Collections;public class RenderCubemapWizard : ScriptableWizard {public Transform renderFromPosition;public Cubemap cubemap;void OnWizardUpdate () {string helpString = "Select transform to render from and cubemap to render into";bool isValid = (renderFromPosition != null) && (cubemap != null);}void OnWizardCreate () {// create temporary camera for renderingGameObject go = new GameObject( "CubemapCamera");go.AddComponent<Camera>();// place it on the objectgo.transform.position = renderFromPosition.position;go.transform.rotation = Quaternion.identity;// render into cubemap go.GetComponent<Camera>().RenderToCubemap( cubemap );// destroy temporary cameraDestroyImmediate( go );}[MenuItem("GameObject/Render into Cubemap")]static void RenderCubemap () {ScriptableWizard.DisplayWizard<RenderCubemapWizard>("Render cubemap", "Render!");}
}
-
首先需要添加UnityEditor的命名空间。
-
然后要继承ScriptableWizard类,而不是MonoBehavior。
-
OnWizardUpdate()是Unity内置的函数。当向导(wizard)第一次弹出或者当GUI被用户改变时(如拖进去某些对象,输入某些字符等)它会被调用。我们可以使用它来判断用户是否真的输入了我们需要的资源。isValid也是Unity内置的一个变量。isValid用来控制向导下方的按钮是否可用。
-
MenuItem用来自定义自己的菜单栏,你会发现在GameObject菜单栏中多了要一个RenderintoCubemap菜单选项。点击菜单,就会执行下面的RenderCubemap函数,打开一个向导。
-
"Render cubemap", "Render!"定义了向导的标题和想到下方默认的create按钮的文字,点击Render!按钮就会执行OnWizardCreate ()函数,这也是Unity内置的函数,在create按钮按下时执行。
-
将我们设置的渲染位置赋给相机,相机执行RenderToCubemap方法生成Cubemap。
还有其他的工具可以生成立方图,大家可以搜搜看。
Simple Cubemap
先来看一下最简单的Cubemap。
Shader "Custom/SimpleReflection"
{Properties {_MainTint ("Diffuse Tint", Color) = (1,1,1,1)_MainTex ("Base (RGB)", 2D) = "white" {}_Cubemap ("CubeMap", CUBE) = ""{}_ReflAmount ("Reflection Amount", Range(0.01, 1)) = 0.5}SubShader {Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM#pragma surface surf Lambertsampler2D _MainTex;samplerCUBE _Cubemap;float4 _MainTint;float _ReflAmount;struct Input {float2 uv_MainTex;float3 worldRefl;};void surf (Input IN, inout SurfaceOutput o) {half4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;o.Emission = texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount;o.Albedo = c.rgb;o.Alpha = c.a;}ENDCG} FallBack "Diffuse"
}
其实就是用texCUBE()函数来对Cubemap采样。
第二个参数应该传入UV坐标,但实际传入的是世界反射向量。这是Unity替我们计算了UV坐标。
Cubemap是由六张贴图构成的,组成了一个类似天空盒的六面体,那这六张贴图是如何映射到小球上的呢,使小球看起来像倒映着周围的环境一样?
将Cubemap想象成一个立方体,包裹着小球,从小球的中心点发射一条射线,穿过小球表面和立方体表面,射线与小球和立方体会各有一个交点,小球上的这个点对应的就是立方体上的这个点的纹理。那要怎样计算各个点对应的UV坐标呢?从小球中心点发射的一条条射线其实就是法线,有个很明显的事实就是,法线朝向法线坐标分量最大的坐标轴指向的立方体的面。也就是坐标(1,1,3)的法线朝向的是Z轴指向的立方体的面。这样就由法线找到了点对应的面。那么UV坐标又该如何计算呢?法线另外两个较小的坐标值和UV坐标是有关系的。举个特殊点的例子,小球最顶点的法线的坐标应该是(0,0,1),对应的应该是立方体的顶面的中点,UV坐标应该是(0.5,0.5)。计算UV坐标的方法是(x/z×0.5+0.5,y/z×0.5+0.5),将特殊点代进去,答案是正确的。原理有点难说明,相当于把X、Y坐标投影到了对应的面上。乘0.5加0.5是为了让法线坐标的区间从[-1,1]变为[0,1]。
在Unity里,我们不必自己计算,可以直接使用内置变量worldRefl来检索Cubemap。
Normal Cubemap
Shader "Custom/NormalMappedReflection"
{Properties {_MainTint ("Diffuse Tint", Color) = (1,1,1,1)_MainTex ("Base (RGB)", 2D) = "white" {}_NormalMap ("Normal Map", 2D) = "bump" {}_Cubemap ("Cubemap", CUBE) = ""{}_ReflAmount ("Reflection Amount", Range(0,1)) = 0.5}SubShader{Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM#pragma surface surf LambertsamplerCUBE _Cubemap;sampler2D _MainTex;sampler2D _NormalMap;float4 _MainTint;float _ReflAmount;struct Input {float2 uv_MainTex;float2 uv_NormalMap;float3 worldRefl;INTERNAL_DATA};void surf (Input IN, inout SurfaceOutput o) {half4 c = tex2D (_MainTex, IN.uv_MainTex);float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb;o.Normal = normals;o.Emission = texCUBE (_Cubemap, WorldReflectionVector (IN, o.Normal)).rgb * _ReflAmount;o.Albedo = c.rgb * _MainTint;o.Alpha = c.a;}ENDCG} FallBack "Diffuse"
}
就只是增加了法线贴图而已。
因为法线信息的改变,所以要重新计算传入texCUBE()函数的世界反射向量。float3 worldRefl;INTERNAL_DATA变量用于原本的法线信息不使用时,比如使用了法线贴图,原来的法线信息就不使用了。要根据新的法线信息计算世界反射向量使用WorldReflectionVector()函数。
动态立方图系统
有时候游戏里的物体从一个环境走到另一个环境,需要更换Cubemap,这样显示的反射效果才真实。
有两种方法更换Cubemap,一种是实时更换Cubemap,这样的效果最真实,但是要牺牲性能;第二种是当物体走到另一个环境的时候更换Cubemap,这就是我要讲的方法。
方法很简单,就是在C#里用SetTexture的方法动态更换Cubemap。
[ExecuteInEditMode]
public class SwapCubemaps : MonoBehaviour
{public Cubemap cubeA;public Cubemap cubeB;public Transform posA;public Transform posB;private Material curMat;private Cubemap curCube;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {curMat = renderer.sharedMaterial;if(curMat){curCube = CheckProbeDistance();curMat.SetTexture("_Cubemap", curCube);}}private Cubemap CheckProbeDistance(){float distA = Vector3.Distance(transform.position, posA.position);float distB = Vector3.Distance(transform.position, posB.position);if(distA < distB){return cubeA;}else if(distB < distA){return cubeB;}else{return cubeA;}}void OnDrawGizmos(){Gizmos.color = Color.green;if(posA){Gizmos.DrawWireSphere(posA.position, 0.5f);}if(posB){Gizmos.DrawWireSphere(posB.position, 0.5f);}}
}
-
[ExecuteInEditMode]是为了让脚本在编辑器状态的时候也能执行,这样就不必点击Play调试,比较方便。
-
OnDrawGizmos()是在Scene里画一些可视化的东西方便调试。
-
CheckProbeDistance()里用Distance判断物体和A点、B点的距离,根据距离决定返回哪种Cubemap。
-
在Update()设置材质的Cubemap。
Cubemap效果
我有加个岩石的纹理。效果是这样的。
NormalCubemap
这是加了法线贴图的。
这篇关于【Unity SurfaceShader】学习笔记(六)Cubemap的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!