一、Phone光照模型

该模型能够简单地描述物体表面对光的吸收和反射,使物体表面呈现出不同的明暗程度,但其不是最真实的一种反射光模型。它能够用尽量简单的数学原理尽可能解释物体表面呈现出不同颜色及明暗程序的原因。
1、漫反射
漫反射求的是光在经过物体表面反射后还剩多少能量让摄像机接收,当法线n与光方向l夹角越小,反射后的能量就越多。反之越小


其中Ld为漫反射光照,kd为物体表面的反射系数(reflection coefficient),I/r2是当前点接受的光照强度,max(0,L•n)是当前点接受到的能量。
代码示例:
//漫反射
float3 light_dir=normalize(_WorldSpaceLightPos0.xyz);//光的方向
float diff_term=max(dot(normal_dir,light_dir),0.0);//N dot L
float3 diff_color=diff_term*_LightColor0.xyz*base_color.xyz;//混合了光的颜色和贴图的最终颜色
base_color为漫反射纹理,_LightColor0为光源,最终效果为

2、高光

物体表面反射后看到的高光
phong模型中认为,高光反射的强度与反射光线R和观察角度v之间夹角的余弦值成正比。由于反射角的计算量非常大,因此使用blinn-phone模型。
blinn-phone模型提出:通过对光源方向l和观察方向v取平均然后归一化得到一个新的向量h,其中h被称为半程向量(bisector),使用h与法线n点乘来计算高光,这样就可以避免计算反射方向了。

由于在物理世界中高光存在于物体表面很小的一部分,而cos的衰减速度太慢,通过携带指数p,可以促进衰减速度,使得高光只能在与法线向量非常接近的情况下才能被视角看见。

公式如下

代码如下:
float3 view_dir=normalize(_WorldSpaceCameraPos.xyz-i.pos_world);//求视角方向
float3 half_dir=normalize(light_dir+view_dir);//半角向量
float spec_term=pow(max(0.0,dot(normal_dir,half_dir)),_Shininess);
float3 spec_color=spec_term*_LightColor0.xyz*_SpecIns*specTex_color;//混合了光色,贴图强度,高光贴图的高光
输出spec_color可看到贴图白色部分为高亮

SpecIns为贴图亮度,specTex_color为贴图颜色值,_Shininess为衰减值,衰减值减小,光圈增大,反之光圈减小

混合漫反射后得到最终颜色
float3 final_color=spec_color+diff_color;//最终颜色

3、环境光
环境光是照射在其它物体上的光先反射到观察物体上的光。在Window -> Rendering -> Lighting Settings -> Scene中设置

代码获取unity自带环境光,环境光应用反射贴图的颜色,最终混合色再混合AO贴图
half3 ambient_color=UNITY_LIGHTMODEL_AMBIENT.rgb*base_color;
float3 final_color=(diffuse_color+spec_color+ambient_color)*ao_color;
未应用环境光之前,物体背部完全黑暗

应用环境光后,可以看到背部纹理

二、法线贴图
1、切线空间
Tangent Space,其实一个坐标系,以法线normal为z轴,以切线tangent为x轴,以binormal为y轴。
2、法线贴图为何为蓝色
如果一个顶点的法线方向不变,那么在它的Tangent Space中,新的normal值就是z轴方向,也就是说值为(0, 0, 1)。但这并不是法线纹理中存储的最终值,因为一个向量每个维度的取值范围在(-1, 1),而纹理每个通道的值范围在(0, 1),因此我们需要做一个映射,即pixel = (normal + 1) / 2。这样,之前的法线值(0, 0, 1)实际上对应了法线纹理中RGB的值为(0.5, 0.5, 1),而这个颜色也就是法线纹理中那大片的蓝色。


3、求法线贴图中的法线
初始化设置
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal:NORMAL;
float4 tangent:TANGENT;//切线
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 normal_dir:TEXCOORD1;
float4 pos : SV_POSITION;
float3 pos_world:TEXCOORD2;
float3 tangent_dir:TEXCOORD3;//切线
float3 binormal_dir:TEXCOORD4;//副切线
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.pos_world=mul(unity_ObjectToWorld,v.vertex).xyz;//模型顶点坐标转世界空间坐标
o.normal_dir=normalize(mul(float4(v.normal,0.0),unity_WorldToObject).xyz);//世界空间下的法线方向
o.tangent_dir=normalize(mul(unity_ObjectToWorld,float4(v.tangent.xyz,0.0)).xyz);//世界空间下的切线方向
o.binormal_dir=normalize(cross(o.normal_dir,o.tangent_dir))*v.tangent.w;//法线与切线差乘求副切线,w用于翻转次法线方向
return o;
}
求法线
//方向定义
float3 normal_dir=normalize(i.normal_dir);
half3 tangent_dir=normalize(i.tangent_dir);
half3 binormal_dir=normalize(i.binormal_dir);
//法线
float3x3 TBN=float3x3(tangent_dir,binormal_dir,normal_dir);//切线空间
float3 normal_data=UnpackNormal(normalMap_color);//法线贴图0~1转-1~1//等同于 normal_data = Normaltex*2-1;
normal_data.xy=normal_data.xy*_NormalIns; //应用法线强度
normal_data.z=sqrt(1.0-saturate(dot(normal_data.xy,normal_data.xy)));//x^2+y^2+z^2=1;saturate确保x^2+y^2<=1
normal_dir=normalize(mul(normal_data.xyz,TBN));//求世界空间下的新法线
UnpackNormal的作用正如上面所说,将像素的取值范围在(0, 1),转变成方向向量的取值范围(-1,1),然后得到该贴图的法线。
应用法线贴图后的总图形为

总代码
Shader "Unlit/PhoneTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_SpecTex("SpecMask",2D)="white"{}
_SpecIns("SpcIns" ,Range(0.01,5))=1.0
_Shininess("Shininess",Range(0.01,100))=1.0
_AOTex("AOTex",2D)="white"{}
_NormalTex("NormalTex",2D)="white"{}
_NormalIns("NormalTexIns",float)=2
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode"="ForwardBase"}//光照标签
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//光照标签
#pragma multi_compile_fwdbase
//光照头文件
#include "AutoLight.cginc"
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal:NORMAL;
float4 tangent:TANGENT;//切线
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 normal_dir:TEXCOORD1;
float4 pos : SV_POSITION;
float3 pos_world:TEXCOORD2;
float3 tangent_dir:TEXCOORD3;//切线
float3 binormal_dir:TEXCOORD4;//副切线
};
sampler2D _MainTex;//反射纹理
float4 _MainTex_ST;
float4 _LightColor0;//光源
sampler2D _SpecTex;//高光纹理
float _SpecIns;//高光强度
float _Shininess;//衰减强度
sampler2D _NormalTex;//法线贴图
float _NormalIns;//法线强度
sampler2D _AOTex;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.pos_world=mul(unity_ObjectToWorld,v.vertex).xyz;//模型顶点坐标转世界空间坐标
o.normal_dir=normalize(mul(float4(v.normal,0.0),unity_WorldToObject).xyz);//世界空间下的法线方向
o.tangent_dir=normalize(mul(unity_ObjectToWorld,float4(v.tangent.xyz,0.0)).xyz);//世界空间下的切线方向
o.binormal_dir=normalize(cross(o.normal_dir,o.tangent_dir))*v.tangent.w;//法线与切线差乘求副切线,w用于翻转次法线方向
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//贴图
fixed4 base_color = tex2D(_MainTex, i.uv);
fixed4 specTex_color=tex2D(_SpecTex,i.uv);
fixed4 ao_color=tex2D(_AOTex,i.uv);
fixed4 normalMap_color=tex2D(_NormalTex,i.uv);
//方向定义
float3 normal_dir=normalize(i.normal_dir);
half3 tangent_dir=normalize(i.tangent_dir);
half3 binormal_dir=normalize(i.binormal_dir);
//法线
float3x3 TBN=float3x3(tangent_dir,binormal_dir,normal_dir);//切线空间
float3 normal_data=UnpackNormal(normalMap_color);//法线贴图0~1转-1~1//等同于 normal_data = Normaltex*2-1;
normal_data.xy=normal_data.xy*_NormalIns; //应用法线强度
normal_data.z=sqrt(1.0-saturate(dot(normal_data.xy,normal_data.xy)));//x^2+y^2+z^2=1;saturate确保x^2+y^2<=1
normal_dir=normalize(mul(normal_data.xyz,TBN));//求世界空间下的新法线
//漫反射
float3 light_dir=normalize(_WorldSpaceLightPos0.xyz);//光的方向
float diff_term=max(dot(normal_dir,light_dir),0.0);
float3 diff_color=diff_term*_LightColor0.xyz*base_color.xyz;//混合了光的颜色和贴图的最终颜色
//高光
float3 view_dir=normalize(_WorldSpaceCameraPos.xyz-i.pos_world);//求视角方向
float3 half_dir=normalize(light_dir+view_dir);//半角向量
float spec_diff=pow(max(0.0,dot(normal_dir,half_dir)),_Shininess);
float3 spec_color=spec_diff*_LightColor0.xyz*_SpecIns*specTex_color;//混合了光色,贴图强度,高光贴图的高光
//环境光
half3 ambient_color=UNITY_LIGHTMODEL_AMBIENT.rgb*base_color;
float3 final_color=(spec_color+diff_color+ambient_color)*ao_color;//最终颜色
return float4( final_color,1.0);
}
ENDCG
}
}
}
文章参考
Blinn-Phong光照模型从定义到实现,一文就够了(1.5w字)
入门Shading,详解Blinn-Phong和Phong光照模型
为什么要有切线空间(Tangent Space),它的作用是什么?
Unity Shader-法线贴图(Normal)及其原理
3382

被折叠的 条评论
为什么被折叠?



