在图形学中,立方体纹理是环境映射的一种实现方法。它一共提供六张图。即在一个立方体中心向外投射直给获取纹理。使用立方体纹理实现简单,效果也比较不错,但不能模拟多次反射。最常见的例子即是天空盒子Skybox。
在Unity中,创建映射纹理有三种方法,一种是由一些特殊纹理创建,第二种是手动创建一个Chuemap资源,再把六张图给他,第三种即用脚本生成。前面两种都需要我们提前准备好纹理,这里就直接跳过了,直接讲第三种通过脚本生成。
脚本生成立方体纹理:
这是通过利用 Unity提供的Camera.RenderToCubemap函数来实现的,它可以在在任意位置观察到的场景存成六张图。
step1:
生成RenderCubemapWizard.cs文件,拷贝以下代码,并装其放到Editor文件夹下。
using UnityEngine;
using UnityEditor;
using System.Collections;
public class RenderCubemapWizard : ScriptableWizard {
public Transform renderFromPosition;
public Cubemap cubemap;
void OnWizardUpdate () {
helpString = "Select transform to render from and cubemap to render into";
isValid = (renderFromPosition != null) && (cubemap != null);
}
void OnWizardCreate () {
// create temporary camera for rendering
GameObject go = new GameObject( "CubemapCamera");
go.AddComponent<Camera>();
// place it on the object
go.transform.position = renderFromPosition.position;
// render into cubemap
go.GetComponent<Camera>().RenderToCubemap(cubemap);
// destroy temporary camera
DestroyImmediate( go );
}
[MenuItem("GameObject/Render into Cubemap")]
static void RenderCubemap () {
ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
"Render cubemap", "Render!");
}
}
step2:
在场景中准备一个空物体用来给于位置信息。
step3:
创建立方体纹理,即在Project下右键,选择Create>>Legacy>>Cubemap来创建,记得勾选Readable选项。
step4:
在Unity菜单中选择GameObject>>Render into Cubemap并把前两步创建的对象赋予它。
step5:
就可以把该位置观察到的世界记录到六张图中了。
Face size选项可以选择分辨率,越大越耗费内存。
反射
关键代码:
在属性中添加,用于模拟环境反射的纹理
_Cubemap("ReflectionCubemap",Cube)="_Skybox"{}
对应属性
samplerCUBE _Cubemap;
v2f中添加
fixed3 worldRefl : TEXCOORD3;
顶点着色器中添加
o.worldRefl=reflect(-o.worldViewDir,o.worldNormal);
片元着色器中添加
fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb*_ReflectColor.rgb;
fixed3 color = ambient+lerp(diffuse,reflection,_ReflectAmount);
return fixed4(color, 1.0);
完整代码:
Shader "Custom/TestShader19" {
Properties {
_Color("Color Tint",Color)=(1,1,1,1)
//控制反射颜色
_ReflectColor("Reflection Color",Color)=(1,1,1,1)
//控制反射程度
_ReflectAmount("Reflect Amount",Range(0,1))=1
//用于模拟环境反射的纹理
_Cubemap("ReflectionCubemap",Cube)="_Skybox"{}
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
samplerCUBE _Cubemap;
float3 _ReflectColor;
float _ReflectAmount;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
//取顶点反射方向
o.worldRefl=reflect(-o.worldViewDir,o.worldNormal);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
//立方体纹理采样,因worldRefl只是进行传递,因此没有必要做归一化
fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb*_ReflectColor.rgb;
//和漫反射 反射颜色 环境光等相加后返回
fixed3 color = ambient+lerp(diffuse,reflection,_ReflectAmount);
return fixed4(color, 1.0);
}
ENDCG
}
}
}
折射
折射使用斯涅尔定律来计算。我们用折射率来形容折射的大小,例如真空的折射率为1,而玻璃的拆射率为1.5。一般游戏中的光线折射只执行一次。
核心代码:
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
完整代码:
Shader "Custom/TestShader20" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_RefractColor ("Refraction Color", Color) = (1, 1, 1, 1)
_RefractAmount ("Refraction Amount", Range(0, 1)) = 1
_RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5
_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _RefractColor;
float _RefractAmount;
fixed _RefractRatio;
samplerCUBE _Cubemap;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefr : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
// Compute the refract dir in world space
//利用refract来计算折射方法。
//参数一为入射方向,必须归一化
//参数二为表面法线,必须归一化
//参数三为拆射比率
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
// Use the refract dir in world space to access the cubemap
//矩形纹理采样
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
//衰减
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
// Mix the diffuse color with the refract color
//利用lerp合并颜色
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
菲涅耳反射
菲涅耳反身是一种光学更象,即光进去物体内部后发生的折射散射现象。会把漫反射光叠加到边缘中,模拟边缘光效果。
Shader "Unity Shaders Book/Chapter 10/Fresnel" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_FresnelScale ("Fresnel Scale", Range(0, 1)) = 0.5
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed _FresnelScale;
samplerCUBE _Cubemap;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}