在Unity3D中创建反射纹理

距离上次技术相关的博文有半年了吧。。。这次回来了,会坚持下去。
说到反射纹理,本质上就是创建一个 CubeMap 立方体贴图, 把需要被反射的目标渲染的6个纹理上, 然后反射的物体根据自身的法线方向在对应的6个面上进行采样。
一步一步来

首先创建一个CubeMap纹理
在project面板上右键->Create->Legacy->CubeMap
然后在Inspector面板上将前后左右上下的纹理添加进去 like this..
cubemaptex
创建好后,不如把它作为天空盒吧,这样到时候反射物体可以直接反射周围的天空的说,说干就干。。
首选创建一个材质,设置材质的Shader为skybox->Cubemap,然后把刚才创建的纹理赋予它 like this
cubemapmat
打开菜单 Window->Lighting 将创建的材质放入 SkyBox槽位,我们就能看见天空啦
skyboxview

之后我们需要编写一个Shader来对天空盒的cubemap 进行采样,就是反射啦
[cc language=”cpp”]
Shader “Custom/CubeMap” {
Properties {
_Cube(“Reflection Map”, Cube) = “” {}
}
SubShader {
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include “UnityCG.cginc”

uniform samplerCUBE _Cube;

struct vertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct vertexOutput
{
float4 pos : SV_POSITION;
float3 normalDir : TEXCOORD0;
float3 viewDir : TEXCOORD1;
};

vertexOutput vert(vertexInput input)
{
vertexOutput output;

float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;

//计算视野方向,世界空间顶点与世界空间内相机位置相减
output.viewDir = mul(modelMatrix, input.vertex).xyz – _WorldSpaceCameraPos;
//将法线从模型空间也统一转换到世界空间
output.normalDir = normalize(mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}

float4 frag(vertexOutput input) :COLOR
{
//计算视线方向与法线法线方向的反射向量
float3 reflectDir = reflect(input.viewDir, input.normalDir);
//texCUBE是CubeMap采样,tex2D是2D纹理采样
return texCUBE(_Cube, reflectDir);
}

ENDCG
}
}
FallBack “Diffuse”
}
[/cc]

之后创建一个材质 叫CubeMapMat 使用上面的Shader
最后我们在场景上创建一个 Sphere 物件将 CubeMapMat 材质赋予它,运行看看效果
skyboxview1

=。= 是不是感觉很简单,因为它只能反射预设好的纹理,所以假设在游戏中,有个游戏人物路过这个反射体,游戏人物是无法被反射出来的。。所以很没意思,我们要更进一步。创建一个动态渲染的CubeMap.

话说,这里我做了个实验,但是失败了。。
我创建了6个相机,分别朝向 +X -X +Y -Y +Z -Z 然后再创建6个RenderTexture 将相机拍摄的6个方向的景物都渲染到对应的6个纹理上。。
然后问题来了。。我找不到方法将这6个纹理合并成CubeMap…无论是动态创建一个CubeMap Class 还是创建一个RenderTexture 然后设置 isCubeMap 都没有设置6个面的API…连设置UV都没有。 当年写Stage3D是可以这么干的。。看来Unity3D这部分封装掉了,连自己组装的机会都不给。。。

最后只好使用官方范例来实现了
大致流程创建一个隐藏的 DummyCamera, 然后使用它的API RenderToCubemap(CubeMap, FaceMask) 来动态创建CubeMap. 貌似调用了这个API, 这个DummyCamera 就会自动往6个方向拍。真是够智能,不过我不喜欢。。

准备工作是这样的
创建一个反射物件,就拿刚才的Sphere来用就行
创建一个CubeMap材质搭配上刚才的CubeMap Shader 但是这里不需要给材质选中特定的纹理,我们在脚本中会动态创建纹理赋予这个材质
具体C# Script如下
[cc language=”cshape”]
using UnityEngine;
using System.Collections;

public class CubeMap : MonoBehaviour
{
public int CubeMapSize = 128;
public bool onFacePerFrame = false;

private Camera cam;
private RenderTexture rtex;
private GameObject go;
// Use this for initialization
[ExecuteInEditMode]
void Start () {
UpdateCubeMap(63);
}

void LateUpdate()
{
if (onFacePerFrame)
{
int faceToRender = Time.frameCount % 6;
int faceMask = 1 << faceToRender; //Debug.Log(faceMask); UpdateCubeMap(faceMask); } else { UpdateCubeMap(63); } } void UpdateCubeMap(int faceMask) { if (!cam) { go = new GameObject("CubemapCamera"); go.AddComponent(typeof(Camera)); go.hideFlags = HideFlags.HideAndDontSave; go.transform.position = transform.position; go.transform.rotation = Quaternion.identity; cam = go.GetComponent();
cam.farClipPlane = 100;
cam.enabled = false;
}

if (!rtex)
{
rtex = new RenderTexture(CubeMapSize, CubeMapSize, 16);
rtex.isCubemap = true;
rtex.hideFlags = HideFlags.HideAndDontSave;
GetComponent().sharedMaterial.SetTexture(“_Cube”, rtex); //这里需要注意,在Shader中必须有个同名的属性 _Cube 才会有效果。
}

cam.transform.position = transform.position;
cam.RenderToCubemap(rtex, faceMask);
}

void OnDisable()
{
DestroyImmediate(cam);
DestroyImmediate(rtex);
}

// Update is called once per frame
void Update () {

}
}
[/cc]

很多人看到 Start() 函数中有 UpdateCubeMap(63);
会好奇为什么是63
实际上63表示渲染全部6个面
63实际上是个枚举值 6个面对应的facemask 是 1,2,4,8,16,32
它们相加的值就是63

在函数 LateUpdate()中有优化代码,即分帧渲染不同的面
int faceToRender = Time.frameCount % 6; //这里会输出 1 ~ 6
int faceMask = 1 << faceToRender; //这里会输出 1,2,4,8,16,32 好了,解惑完了,我们在场景上随便放2个Cube 看看效果。 skyboxend
see that? 这下可以动态反射周围的一切啦。。
but.. 根据我玩过的几款3A大作。。 比如<极品飞车>。。<看门狗>。。<美国末日> 他们的反射做做到文章一开始的那种,即预渲染好的场景CubeMap,就是说照镜子看不到自己。。所以如果出移动平台游戏的话,还是不要作死的好。。
finally.. 我的windows8.1 装了 jdk 7079 后,玩 魔兽争霸3 dota 居然跟我崩溃了。。我勒个去。

发表评论

电子邮件地址不会被公开。 必填项已用*标注