先上效果圖
(拖到最下面直接看代碼)
立方體紋理
立方體紋理(Cubemap)由6張圖像組成,對這個紋理進行採樣時,需要提供一個向量,它會從立方體中間出發,當它向外延伸時就會與立方體的6個紋理之一相交,而採樣結果就是由該交點(藍點)計算而來【candycat】
讓着色器使用立方體紋理
(當然,unity自帶的着色器中,也有一些能夠直接使用立方體紋理,例如Legacy Shaders - Reflective裏面的那幾個)
- 首先新建一個Unity Shader,把原有代碼全部刪除,然後給shader起個名字。爲Shader添加一個Properties 語義塊,聲明我們需要的屬性。
Shader "RefShader" {
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" {} //立方體紋理
//對於2D,3D,Cube這3鍾紋理類型,
//它們的默認值是通過一個字符串後跟一個花括號來指定的。
//其中,字符串要麼是空的,要麼是內置的紋理名稱。
}
}
這個 _Cubemap就是稍後我們要進行採樣的立方體紋理。
- 爲Shader添加SubShader和Pass,並設置標籤“光照類型”(LightMode)爲“向前渲染”(ForwardBase)
Shader "RefShader" {
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 {
Pass {
//設置正確的標籤
Tags { "LightMode"="ForwardBase" }
}
}
}
- 使用CG/HLSL語言來編寫頂點/片元着色器
Shader "RefShader" {
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 {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
//SV_POSITION語義告訴Unity,pos裏包含了頂點在裁剪空間中的位置信息
//這也是頂點着色器最重要的一個工作:將頂點座標從模型空間轉換到裁剪空間
float4 pos : SV_POSITION;
//TEXCOORD0語義表示worldNormal變量佔用了TEXCOORD0插值寄存器
//每個插值寄存器可以存儲4個浮點值(float)
float3 worldNormal : TEXCOORD0; //世界空間下的頂點法線向量
float3 worldPos : TEXCOORD1; //世界空間下的頂點座標
float3 worldViewDir : TEXCOORD2; //世界空間下的觀察方向,也就是從頂點到攝像機的方向
float3 worldRef1 : TEXCOORD3; //鏡面反射光的方向
SHADOW_COORDS(4) //用於處理陰影的宏
};
v2f vert(a2v v) {
}
fixed4 frag(v2f i) : SV_Target {
}
ENDCG
}
}
}
現在,我們可以在頂點着色器中計算我們需要的信息了
v2f vert(a2v v)
{
//申明返回值v2f
v2f o;
//這是頂點着色器最重要的一個任務,將頂點座標從模型空間轉換到裁剪空間
//UnityObjectToClipPos函數接受一個模型空間的座標,返回該座標在裁剪空間的座標
o.pos = UnityObjectToClipPos(v.vertex);
//UnityObjectToWorldNormal函數接受一個模型空間的法線向量,將其轉換到世界空間中並返回
o.worldNormal = UnityObjectToWorldNormal(v.normal);
//unity_ObjectToWorld是unity模型空間到世界空間的變換矩陣
//對v.vertex進行左乘,使其變換到世界空間下
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//UnityWorldSpaceViewDir函數接受一個世界空間中的頂點位置
//返回世界空間中從該點到攝像機的觀察方向
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
//反射光
o.worldRef1 = reflect(-o.worldViewDir, o.worldNormal);
//用於處理陰影的宏
TRANSFER_SHADOW(o);
return o;
}
其中,反射光的計算是這樣子來的:
reflect函數需要輸入兩個值,第一個參數是入射向量l,第二個是法向量n,經過計算 [r^ = l^ - 2 ( n^ · l^ ) n^ ] ( v^表示單位向量)reflect函數返回反射方向r
shader:
Shader "RefShader" {
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 {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _ReflectColor;
float _ReflectAmount;
samplerCUBE _Cubemap;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float3 worldViewDir : TEXCOORD2;
float3 worldRef1 : 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.worldRef1 = 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;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 reflection = texCUBE(_Cubemap, i.worldRef1).rgb * _ReflectColor.rgb;
//texCUBE(samplerCUBE, float3|half3|min10float3|min16float3)
//texCUBE(samplerCUBE, float3|half3|min10float3|min16float3, float3|half3|min10float3|min16float3, float3|half3|min10float3|min16float3)
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
C#腳本:
RenderTexture cubemap;
Camera refCamera;
// Use this for initialization
void Start () {
gameObject.AddComponent<Camera>();
cubemap = new RenderTexture(128, 128, 16)
{
dimension = UnityEngine.Rendering.TextureDimension.Cube
};
GetComponent<Renderer>().material.SetTexture("_Cubemap", cubemap);
refCamera = GetComponent<Camera>();
}
// Update is called once per frame
void Update () {
refCamera.RenderToCubemap(cubemap);
}