接上文:https://blog.csdn.net/Jaihk662/article/details/113248074
四、菲涅尔反射
菲涅尔反射(Fresnel Reflection)就是同时考虑反射和漫反射,或是说同时考虑反射和折射:当光照到物体上时,一部分发生反射,一部分进入物体内部发生折射和散射。当你站在湖边低头看脚边的水面时,会发现水几乎是透明的,可以很清晰的看到湖底,但是当你抬头看远处的水面时,就几乎看不到任何水下的场景。几乎任何物体都多多少少包含了菲涅尔反射的现象
现实中的菲涅尔反射计算非常的复杂,因此往往采用如下的近似方式:
其中 为反射系数, 代表视角方向, 代表表面法线
下面为叠加反射和漫反射的 Shader:
Shader "Jaihk662/Fresnel1"
{
Properties
{
_DiffuseColor ("DiffuseColor", Color) = (1, 1, 1, 1)
_FresnelScale ("FresnelScale", Range(0, 1)) = 0.5
_Cubemap ("ReflectionCubemap", Cube) = "_Skybox" {}
}
SubShader
{
LOD 200
PASS
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert //声明顶点着色器的函数
#pragma fragment frag //声明片段着色器的函数
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed _FresnelScale;
samplerCUBE _Cubemap;
struct _2vert
{
float4 vertex: POSITION;
float3 normal: NORMAL;
float4 texcoord: TEXCOORD0;
};
struct vert2frag
{
float4 pos: SV_POSITION;
float3 wPos: TEXCOORD0;
float3 wNormal: TEXCOORD1;
float3 wViewDir: TEXCOORD2;
float3 wReflect: TEXCOORD3;
};
vert2frag vert(_2vert v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.wViewDir = normalize(UnityWorldSpaceViewDir(o.wPos));
o.wReflect = reflect(-o.wViewDir, o.wNormal);
return o;
}
fixed4 frag(vert2frag i): SV_Target
{
fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * (0.5 * dot(i.wNormal, wLightDir) + 0.5);
fixed3 reflection = texCUBE(_Cubemap, i.wReflect).rgb;
fixed fresnel = saturate(_FresnelScale + (1 - _FresnelScale) * pow(1 - dot(i.wViewDir, i.wNormal), 5));
return fixed4(ambient + lerp(diffuse, reflection, fresnel), 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
可以看出,垂直视角下漫反射的效果明显,而在倾斜视角下,反射效果明显
类似的可以实现叠加反射和折射的 Shader:
Shader "Jaihk662/Fresnel1"
{
Properties
{
_DiffuseColor ("DiffuseColor", Color) = (1, 1, 1, 1)
_FresnelScale ("FresnelScale", Range(0, 1)) = 0.5
_RefractRatio ("RefractiRatio", Range(0.1, 1)) = 0.5
_CubeMap ("ReflectionCubeMap", Cube) = "_Skybox" {}
}
SubShader
{
LOD 200
PASS
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert //声明顶点着色器的函数
#pragma fragment frag //声明片段着色器的函数
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed _FresnelScale;
fixed _RefractRatio;
samplerCUBE _CubeMap;
struct _2vert
{
float4 vertex: POSITION;
float3 normal: NORMAL;
float4 texcoord: TEXCOORD0;
};
struct vert2frag
{
float4 pos: SV_POSITION;
float3 wPos: TEXCOORD0;
float3 wNormal: TEXCOORD1;
float3 wViewDir: TEXCOORD2;
float3 wReflect: TEXCOORD3;
float3 wRefract: TEXCOORD4;
};
vert2frag vert(_2vert v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.wViewDir = normalize(UnityWorldSpaceViewDir(o.wPos));
o.wReflect = reflect(-o.wViewDir, o.wNormal);
o.wRefract = refract(-o.wViewDir, o.wNormal, _RefractRatio);
return o;
}
fixed4 frag(vert2frag i): SV_Target
{
fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed4 reflection = texCUBE(_CubeMap, i.wReflect);
fixed4 refraction = texCUBE(_CubeMap, i.wRefract);
fixed fresnel = saturate(_FresnelScale + (1 - _FresnelScale) * pow(1 - dot(i.wViewDir, i.wNormal), 5));
return fixed4(ambient, 1) + fresnel * reflection + (1 - fresnel) * refraction; //此处应考虑能量守恒,而非使用插值
}
ENDCG
}
}
FallBack "Specular"
}
五、动态环境映射
在上一节中实现了假反射,也就是物体仅能反射天空盒,而无法反射周边的其它的物体
可以尝试在不考虑性能的情况下实现以下动态环境映射,也就是实现“真”反射
1):动态生成立方体贴图:
可以依靠 Unity 自带的函数 Camera.RenderToCubemap(cubemap) 来生成以当前物体为中心,周围场景的天空盒:
Unity 官网上给出了一个很经典的例子如下:
using UnityEngine;
using UnityEditor;
using System.Collections;
public class RenderCubemapWizard : ScriptableWizard
{
public Transform renderFromPosition;
public Cubemap cubemap;
void OnWizardUpdate()
{
string helpString = "Select transform to render from and cubemap to render into";
bool 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;
go.transform.rotation = Quaternion.identity;
// 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!");
}
}
这是一份工具代码,应用后,就可以在这里找到自己的“编辑器”:
点击 Render 就可以将图象渲染到上面设置的立方体纹理中,其中立方体纹理的创建如下:
不过需要注意的是,这个立方体需要设置可读,不然会出现报错“Unable to render into cubemap, make sure it's marked as 'Readable'”
2):渲染到 RT
根据 1),我们可以得到一个很暴力的实现方法,对于每一帧每一个物体都动态生成对应的立方体贴图,并将其作为前面的 Shader 中的 CubeMap
当然在实现过程中需要用到渲染纹理(RenderTexture),这部分就在后面介绍了
还是能得到一个很不错的效果的