在这一篇中会实现会介绍折射和反射,以及菲尼尔反射;并且实现镜子和玻璃效果;
这里和之前不同的地方在于取样的是一张CubeMap;
demo里的cubemap使用的一样,相机所在位置拍出来的周围环境图;
生成CubeMap的工具脚本:
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!");
}
}
1.反射
用反射方向在CubeMap上取样,_ReflectAmount控制反射程度,_ReflectColor反射颜色;
v2f vert (appdata v){
//计算反射向量
o.worldReflect = reflect(-o.worldViewDir,o.worldNormal);
...
}
fixed4 frag (v2f i) : SV_Target{
//根据反射向量从cubemap纹理上取样
fixed3 reflection = texCUBE(_Cubemap,i.worldReflect).rgb * _ReflectColor.rgb;
//混合反射和漫反射
return fixed4(ambient + lerp(diffuse,reflection,_ReflectAmount)*atten, 1.0);
}
2.折射
和反射几乎相同,将反射改成折射,计算公式改成折射计算公式;
v2f vert (appdata v){
//计算反射向量
o.worldRefract = refract(-normalize(o.worldViewDir),normalize(o.worldNormal),_RefractRatio);
...
}
fixed4 frag (v2f i) : SV_Target{
//根据反射向量从cubemap纹理上取样
fixed3 refraction = texCUBE(_Cubemap,i.worldRefract).rgb * _RefractColor.rgb;
//混合反射和漫反射
return fixed4(ambient + lerp(diffuse,refraction,_RefractAmount)*atten, 1.0);
}
成像是倒的;透过茶壶可以看到对面;
3.菲尼尔
反射光的强度与视线方向和法线方向的夹角有关,夹角越大反射光越强;最高90度,也就是边缘光最强;
Schlick菲尼尔公式:Fschlick(v,n) = F0 + (1-F0)(1- dot(v,n)) ^ 5;F0控制菲尼尔强度;
fixed4 frag (v2f i) : SV_Target{
...
//Schlick Fresnel——边缘光
fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb;
fixed3 fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir,worldNormal), 5);
//菲尼尔系数控制反射强度
return fixed4(ambient + lerp(diffuse,reflection,saturate(fresnel)) * atten, 1.0);
}
4.玻璃效果
通过GrabPass{"_RefractionTex"} 抓取当前屏幕内容渲染到_RefractionTex贴图上
RefractionTex贴图用来取样折射纹理;_Distortion参数模拟法线扰动的程度;
GrabPass{"_RefractionTex"}
...
//GrabPass纹理
sampler2D _RefractionTex;
//纹素大小
float4 _RefractionTex_TexelSize;
fixed4 frag (v2f i) : SV_Target
{
//法线偏移扰动-模拟折射
fixed3 bump = UnpackNormal(tex2D(_BumpMap,i.uv.zw));
float2 offset = bump.xy*_Distortion*_RefractionTex_TexelSize.xy;
//折射计算-屏幕坐标偏移后透视除法取样折射纹理
i.screenPos.xy = offset + i.screenPos.xy;
fixed3 refractColor = tex2D(_RefractionTex,i.screenPos.xy/i.screenPos.w).rgb;
//矩阵计算世界法线
bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
//反射计算
fixed3 reflectDir = reflect(-worldViewDir,bump);
fixed4 texColor = tex2D(_MainTex,i.uv.xy);
fixed3 reflectColor = texCUBE(_Cubemap,reflectDir).rgb * texColor.rgb;
//混合反射和折射_RefractAmount
return fixed4(reflectColor*(1-_RefractAmount)+refractColor*_RefractAmount, 1.0);
}
5.镜子
tex2Dproj(_ReflectionTex,UNITY_PROJ_COORD(i.refl));
UNITY_PROJ_COORD:given a 4-component vector, return a texture coordinate suitable for projected texture reads. On most platforms this returns the given value directly.
传入Vector4,返回一张用来投影取样的纹理,大部分平台直接返回给定值;
镜子直接传入屏幕顶点坐标获得投影纹理,再通过投影取样获得颜色,和最终结果混合;
但是上面效果和局限性都比较大,所以找了个大佬写的镜子效果;
使用相机和RenderTexture,底层原理差不多,效果要好了很多;
Unity镜子效果制作教程
Life is too short for so much sorrow.