UnityShader笔记 Cubemap
反射效果
使用CubeMap可以实现金属表面的反射效果
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Custom/ReflectionMat"
{
Properties{
//TintColor:色调
_Color("TintColor", Color) = (1.0, 1.0, 1.0, 1.0)
_CubeMap("CubeMap", Cube) = "_Skybox"{}
_Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
_Gloss("Gloss", Range(8.0, 256)) = 20
_ReflectScale("ReflectScale", float) = 1
}
SubShader{
Pass{
Tags{
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
half4 _Color;
samplerCUBE _CubeMap;
half4 _Specular;
half _Gloss;
float _ReflectScale;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct v2f{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD3;
};
v2f vert(a2v i){
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.vertex = UnityObjectToClipPos(i.vertex);
o.worldNormal = normalize(UnityObjectToWorldNormal(i.normal));
o.worldPos = mul(unity_ObjectToWorld, i.vertex);
//o.uv = i.texcoord;
return o;
}
half4 frag(v2f i) : SV_TARGET{
half3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
half3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
half3 worldReflectDir = reflect(-viewDir, i.worldNormal);
half3 reflectDir = normalize(reflect(-lightDir, i.worldNormal));
half3 albedo = texCUBE(_CubeMap, worldReflectDir).rgb * _Color;
half3 diffuse = _LightColor0.rgb * albedo * (0.5 * saturate(dot(i.worldNormal, lightDir)) + 0.5);
half3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color;
//half3 specular = _LightColor0.rgb * _Color * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return half4(diffuse, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
原理:利用CubeMap映射模型表面的纹理,对CubeMap纹理进行采样需要一个三维向量(不需要标准化),采样以CubeMap中心店为原点,以该向量为方向的射线与CubeMap相交的坐标的纹理。环境经过模型反射到摄像机的过程不好分析,但由于光路可逆,我们可以用视角方向和模型各顶点的法线方向求得环境到模型光线的方向向量,利用该向量在对应的CubeMap进行采样。这里的CubeMap用ScriptableWizard接口利用脚本自动生成。
反射的结果和纹理的清晰度有关,如上图就是用FaceSize为64的CubeMap绘制的纹理,而下图为用FaceSize为128重新采样的结果。
折射效果
Shader "Custom/RefractionMat"
{
Properties{
//TintColor:色调
_Color("TintColor", Color) = (1.0, 1.0, 1.0, 1.0)
_CubeMap("CubeMap", Cube) = "_Skybox"{}
_Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
_Gloss("Gloss", Range(8.0, 256)) = 20
_ReflectScale("RefractScale", Range(0, 1.0)) = 1
_AmbientScale("AmbientScale", Range(0, 1.0)) = 0.5
}
SubShader{
Pass{
Tags{
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
half4 _Color;
samplerCUBE _CubeMap;
half4 _Specular;
half _Gloss;
float _ReflectScale;
half _AmbientScale;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct v2f{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD3;
};
v2f vert(a2v i){
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.vertex = UnityObjectToClipPos(i.vertex);
o.worldNormal = normalize(UnityObjectToWorldNormal(i.normal));
o.worldPos = mul(unity_ObjectToWorld, i.vertex);
//o.uv = i.texcoord;
return o;
}
half4 frag(v2f i) : SV_TARGET{
half3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
half3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
half3 worldRefractDir = refract(-viewDir, i.worldNormal, _ReflectScale);
half3 reflectDir = normalize(reflect(-lightDir, i.worldNormal));
half3 albedo = texCUBE(_CubeMap, worldRefractDir).rgb * _Color;
half3 diffuse = _LightColor0.rgb * albedo * (0.5 * saturate(dot(i.worldNormal, lightDir)) + 0.5);
half3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color;
//half3 specular = _LightColor0.rgb * _Color * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return half4(diffuse + ambient * _AmbientScale, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
只需要将模型映射纹理的方式修改成折射即可,计算折射使用refract函数。 如果折射率为1时,则呈现透明效果。
Fresnel反射
Blinn-Phong经常并不能很好的表现模型的真实材质,如真实世界的水面,在近距离下看到的是清澈见底的水面,但远距离的水面却是波光粼粼的。换个说法就是视角方向与水面法线夹角小的时候,水面折射光线更多;反之,视角方向与水面法线夹角大的时候,主要是反射光线。
Shader "Custom/FresnelMat"
{
Properties{
//TintColor:色调
_Color("TintColor", Color) = (1.0, 1.0, 1.0, 1.0)
_CubeMap("CubeMap", Cube) = "_Skybox"{}
_Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
_Gloss("Gloss", Range(8.0, 256)) = 20
_FresnelScale("FresnelScale", Range(0, 1.0)) = 1
_AmbientScale("AmbientScale", Range(0, 1.0)) = 0.5
}
SubShader{
Pass{
Tags{
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
half4 _Color;
samplerCUBE _CubeMap;
half4 _Specular;
half _Gloss;
float _FresnelScale;
half _AmbientScale;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct v2f{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD3;
};
v2f vert(a2v i){
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.vertex = UnityObjectToClipPos(i.vertex);
o.worldNormal = normalize(UnityObjectToWorldNormal(i.normal));
o.worldPos = mul(unity_ObjectToWorld, i.vertex);
//o.uv = i.texcoord;
return o;
}
half4 frag(v2f i) : SV_TARGET{
half3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
half3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
half3 worldReflectDir = reflect(-viewDir, i.worldNormal);
half3 reflectDir = normalize(reflect(-lightDir, i.worldNormal));
half3 albedo = texCUBE(_CubeMap, worldReflectDir).rgb * _Color;
half fresnel = _FresnelScale + (1 - _FresnelScale) * pow((1 - dot(viewDir, i.worldNormal)), 5);
half3 diffuse = _LightColor0.rgb * _Color * (0.5 * saturate(dot(i.worldNormal, lightDir)) + 0.5);
half3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color;
//half3 specular = _LightColor0.rgb * _Color * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return half4(lerp(diffuse, albedo, saturate(fresnel)) + ambient * _AmbientScale, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
这里使用的近似等式为Schlick菲涅尔近似等式。
代码中使用的lerp函数作用是根据第三个参数的权值,来取得前两个参数之间的某个值。