本节笔记主要摘自《UnityShader入门精要》
一些概念:
在图形学中,立方体纹理是环境映射的一种实现方法。也算是反射和折射实现的第一步。环境映射的主要作用就是模拟周围环境,而使用了环境映射的物体就像是在表面镀上了一层金属。
不过需要注意的是,立方体纹理可以用来反射环境,但是不能反射使用了该立方体纹理的物体本身。因为立方体纹理不能模拟多次反射的结果。(需要使用全局光照系统来解决这个问题)。所以尽量对凸面体使用这个纹理而不要对凹面体使用。
在Unity5中,创建用于环境映射的立方体纹理的方法有三种:
第一种方法是直接使用由一些特殊布局的纹理创建;
第二种方法是手动创建一个Cubemap资源,再把六张图都赋给它;
第三种方法是由脚本生成(在指定位置创建一个临时相机,然后将观察到的图像渲染到指定的立方体之中)。
天空盒就是一个典型的立方体纹理应用的例子:
在Unity之中,天空盒是在所有不透明物体之后渲染的,而其背后使用的网格是一个立方体或者是一个细分之后的球体。
最经典的就是如下图这样使用六张图片来构成一个天空盒材质纹理
脚本使用的临时摄像机脚本如下:
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!");
}
}
然后在这里拽入脚本和位置就可以生成一个立方体纹理了。
创建的立方体纹理如下:
PS:我有点金属控,这个材质球呈现的反射效果我可太喜欢了。
反射效果的实现相对来说比较简单,只需要通过入射光线和表面法线来计算反射方向,再利用反射方向对立方体纹理采样即可。
Shader代码:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/Reflection"
{
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("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;
fixed4 _ReflectColor;
fixed _ReflectAmount;
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 wolrdLightDir = 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,wolrdLightDir));
fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb * _ReflectColor.rgb;
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
//之前提过,这个是计算光照衰减和阴影的宏
//第一个参数会接受计算完光照衰减和阴影值相乘的结果,第二个参数是v2f,第三个参数是该片元的世界坐标
fixed3 color = ambient + lerp(diffuse,reflection,_ReflectAmount) * atten;
return fixed4(color,1.0);
}
ENDCG
}
}
Fallback "Reflective/VertexLit"
}
折射使用的是斯涅尔定律,涉及到两个介质之间的透射比和折射角。
折射模型(来源于百度百科):
折射具体的Shader代码如下:
值得注意的是,CG有提供refract函数来计算折射方向。
它的第一个参数即为入射光线的方向,它必须是一个归一化之后的矢量。
它的第二个参数是表面法线,法线同样是需要归一化之后的矢量。
它的第三个参数是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值。
Shader代码:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/Refraction"
{
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
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
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
最后还有一个效果叫做菲涅尔反射,它是根据视角方向来决定反射程度的定律。不过原本的菲涅尔反射公式十分复杂,我们一般使用近似公式来表达。
比如Schlick菲涅尔近似等式
Shader代码:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/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"
}