环境反射的做法有Matcap和Cubemap
菲涅尔现象

菲涅尔连连看部分
Fresnel = pow(1-ndotv,powVal)
下的法线
点乘
世界下的视线

现实中View Dir观察方向和light Dir光方向都是太阳照射到地面上的,但在引擎内View Dir观察方向和lDir光向量都是指向人眼方向的,也可以说是指向光源的
因此View Dir观察方向和lDir光向量在物理上和引擎内效果是相反的

Matcap效果

Matcap连连看部分
金属反射环境是没有中间与边缘的区分

Remap
相当于是一个映射,法线方向点乘光方向输出的值是-1到1之间,我们将-1到1之间的值映射到0到1之间
将点乘的值转换为Lambert输出的操作

菲涅尔 = 世界下的法线 dot 世界下的视向量
法线从切线空间(tangent space)变换到世界空间(world space)再变换到观察空间(view space)

一束光从屏幕的右边打过来,它的中间是黑色,右边的黑色不是0,而是负值,无论怎样旋转视角
为了验证它是一个负数,把它+1再*0.5,直接做一个Remap,它是一个(-1,1)的值,我们将其映射为(0,1)

法线朝左边,从左到右的一个渐变
红通道的含义就是它这个表面,法线方向,它在平行于屏幕的轴向上,朝左它就是0.朝右就是1,这是Remap之后的结果
没有Remap之前,朝左就是-1,正对着你是0,朝右是1
绿通道的含义,就是左右的关系,将红通道平行于屏幕的关系变成垂直于屏幕的关系,就变成了一个上下关系,同样去做一个Remap,它也是无论怎样旋转模型或者旋转视角也好,它始终都是在屏幕上的一个上下关系
我们拿到这个
绿通道,做了Remap之后,它就是一个以朝下,法线取0值,法
线朝你取0.5,法线朝你取1
红通道是法线朝左取0,法线朝你取0.5,法线朝右取1
把观察空间下View_Space的下的法线取出它的
红绿通道做Remap去采样纹理

红通道从左到右依次取值为0,0.5,1

绿通道从下到上依次取值为0,0.5,1

Cubemap效果
简单理解就是一张全景图

Cubemap效果连连看部分
视方向ViewDir不是眼睛射出的光线,而是一个光线射向眼睛的方向
Reflect反射不是要拿视方向ViewDir,而是要拿视方向ViewDir在法线上反弹之后的方向去采样
不是直接去看Cubemap的结果,我们是把自己的视线方向ViewDir经过一面镜子的反弹,然后用那个方向去采样cubemap的结果
将视方向ViewDir先取了一个反, 然后跟法线Normal去做了一次反射Reflect,这样就拿到了一个视方向ViewDir的反弹方向,就可以去查询Cubemap反弹到哪个像素了

HDR的图片很多手机不支持,所以想办法要把HDR的信息编码成LDR的图,就是不超过1的图,有一些方法可以做一下编码,然后在去手机上做一个还原,而且还能保留一个高动态范围的信息,但是它的载体是低动态范围的,是常用手段之一

将SD内导出的HDR贴图(EXR.格式)导入PS,然后勾选
作为Alpha通道,将HDR贴图由长方形改为正方形,之后还是一个高动态范围的状态,在图像>>模式>>改为8位/通道(A),然后贴图整体就变暗了,因为它将原先最亮的地方变成1,最暗的地方变为0,也就是重新做了一下Remap映射,然后保存为TGA格式导出

Cubemap的映射方式Mapping选择为Latitude-Longitue Layout(Cylindrical)
Convolution Type 选择 Specular(Glossy Reflection) 可以生成 Mipmap
远处采样低精度的图,近处采样高精度的图,这也是Mipmap一个常规的用法
但是我们做Cubemap,是利用Mipmap这样一个特性,将不用级别的Mipmap视作不用粗糙度表面对环境的反射
Fixup Edge Seams 修正接缝

Mapcap效果代码部分


i.posWS = mul(unity_objectToWorld,vertex); //世界坐标
//世界空间下的视线方向 = 摄像机的位置 - 世界坐标的位置
float3 nDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
Shader "AP01/L09/Matcap"
{
Properties
{
_NormalMap ("法线贴图", 2D) = "bump" {}
_Matcap ("Matcap", 2D) = "gray" {}
_FresnelPow ("菲涅尔次幂", Range(0, 10)) = 1
_EnvSpecInt ("环境镜面反射强度", Range(0, 5)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Name "FORWARD"
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
// 输入参数
uniform sampler2D _NormalMap;
uniform sampler2D _Matcap;
uniform float _FresnelPow;
uniform float _EnvSpecInt;
// 输入结构
struct VertexInput
{
float4 vertex : POSITION; // 顶点信息
float2 uv0 : TEXCOORD0; // uv信息
float3 normal : NORMAL; // 法线信息
float4 tangent : TANGENT; // 切线信息
};
// 输出结构
struct VertexOutput
{
float4 pos : SV_POSITION; // 屏幕顶点位置
float2 uv0 : TEXCOORD0; // uv信息
float4 posWS : TEXCOORD1; // 世界顶点位置
float3 nDirWS : TEXCOORD2; // 世界法线方向
float3 tDirWS : TEXCOORD3; // 世界切线方向
float3 bDirWS : TEXCOORD4; // 世界副切线方向
};
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v)
{
VertexOutput o = (VertexOutput)0; // 新建一个输出结构
o.pos = UnityObjectToClipPos( v.vertex );
o.uv0 = v.uv0; // 传递uv信息
o.posWS = mul(unity_ObjectToWorld, v.vertex); // 顶点位置 OS>WS
o.nDirWS = UnityObjectToWorldNormal(v.normal); // 法线方向 OS>WS
o.tDirWS = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz); // 切线方向 OS>WS
o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS) * v.tangent.w); // 根据nDir tDir求bDir
return o; // 将输出结构 输出
}
// 输出结构>>>像素
float4 frag(VertexOutput i) : COLOR
{
// 准备向量
float3 nDirTS = UnpackNormal(tex2D(_NormalMap, i.uv0)).rgb;
float3x3 TBN = float3x3(i.tDirWS, i.bDirWS, i.nDirWS);
float3 nDirWS = normalize(mul(nDirTS, TBN)); // 计算nDirVS 计算Fresnel
float3 nDirVS = mul(UNITY_MATRIX_V, nDirWS); // 计算MatcapUV
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz); // 计算Fresnel
// 准备中间变量
float vdotn = dot(vDirWS, nDirWS);
float2 matcapUV = nDirVS.rg * 0.5 + 0.5;
// 光照模型
float3 matcap = tex2D(_Matcap, matcapUV);
float fresnel = pow(max(0.0, 1.0 - vdotn), _FresnelPow);
float3 envSpecLighting = matcap * fresnel * _EnvSpecInt;
// 返回值
return float4(envSpecLighting, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
Cubemap代码部分


采样Cubemap要用texCUBElod
采样的UV坐标必须是四维的,float4(vrDirWS,_CubemapMip)
Shader "AP01/L09/Cubemap"
{
Properties
{
_Cubemap ("环境球", Cube) = "_Skybox" {}
_NormalMap ("法线贴图", 2D) = "bump" {}
_CubemapMip ("环境球Mip", Range(0, 7)) = 0
_FresnelPow ("菲涅尔次幂", Range(0, 5)) = 1
_EnvSpecInt ("环境镜面反射强度", Range(0, 5)) = 0.2
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Name "FORWARD"
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
// 输入参数
uniform samplerCUBE _Cubemap;
uniform sampler2D _NormalMap;
uniform float _CubemapMip;
uniform float _FresnelPow;
uniform float _EnvSpecInt;
// 输入结构
struct VertexInput
{
float4 vertex : POSITION; // 顶点信息
float2 uv0 : TEXCOORD0; // uv信息
float3 normal : NORMAL; // 法线信息
float4 tangent : TANGENT; // 切线信息
};
// 输出结构
struct VertexOutput
{
float4 pos : SV_POSITION; // 屏幕顶点位置
float2 uv0 : TEXCOORD0; // uv信息
float4 posWS : TEXCOORD1; // 世界顶点位置
float3 nDirWS : TEXCOORD2; // 世界法线方向
float3 tDirWS : TEXCOORD3; // 世界切线方向
float3 bDirWS : TEXCOORD4; // 世界副切线方向
};
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v)
{
VertexOutput o = (VertexOutput)0; // 新建一个输出结构
o.pos = UnityObjectToClipPos( v.vertex );
o.uv0 = v.uv0; // 传递uv信息
o.posWS = mul(unity_ObjectToWorld, v.vertex); // 顶点位置 OS>WS
o.nDirWS = UnityObjectToWorldNormal(v.normal); // 法线方向 OS>WS
o.tDirWS = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz); // 切线方向 OS>WS
o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS) * v.tangent.w); // 根据nDir tDir求bDir
return o; // 将输出结构 输出
}
// 输出结构>>>像素
float4 frag(VertexOutput i) : COLOR
{
// 准备向量
float3 nDirTS = UnpackNormal(tex2D(_NormalMap, i.uv0)).rgb;
float3x3 TBN = float3x3(i.tDirWS, i.bDirWS, i.nDirWS);
float3 nDirWS = normalize(mul(nDirTS, TBN)); // 计算Fresnel 计算vrDirWS
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz); // 计算Fresnel
float3 vrDirWS = reflect(-vDirWS, nDirWS);// 采样Cubemap
// 准备中间变量
float vdotn = dot(vDirWS, nDirWS);
// 光照模型
float3 var_Cubemap = texCUBElod(_Cubemap, float4(vrDirWS, _CubemapMip)).rgb;
float fresnel = pow(max(0.0, 1.0 - vdotn), _FresnelPow);
float3 envSpecLighting = var_Cubemap * fresnel * _EnvSpecInt;
// 返回值
return float4(envSpecLighting, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
World Reflection
