Unity Shader学习日志二:基础光照技术-Phone光照模型与法线


一、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)及其原理

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值