先上效果图
(拖到最下面直接看代码)
立方体纹理
立方体纹理(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);
}