[UnityShaderCookbook 读书笔记] [02] 光照模型


自定义漫反射(Lambert)光照模型


Unity 为我们创建了一些内置的光照模型函数,但我们也可以自定义光照模型
该书作者表示,在自己参与过的项目中,想要有一个比较不错的效果,几乎都用到了自定义的光照模型
通过自定义光照模型,我们可以实现轮廓光,基于 Cubemap 的光照,或让 Shader 与游戏玩法发生交互~

在上一章代码中,有这么一行

#pragma surface surf Lambert

#pragma 是编译器指令,用来设置编译器状态或指示编译器完成特定动作
这里用于指示 Shader 使用 Lambert 光照模型 (定义于 Lighting.cginc )
将它修改为

#pragma surface surf BasicDiffuse

这意味着我们将不再使用 Unity 提供给我们的 Lambert 光照模型
而是使用一个自定义的名为 BasicDiffuse 的光照模型

下面即为 BasicDiffuse 光照模型的定义
在 SubShader中添加以下代码

inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3
lightDir, fixed atten)
{
float difLight = max(0, dot (s.Normal, lightDir));
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * (difLight * atten * 2);
col.a = s.Alpha;
return col;
}

通过创建一个新的光照模型函数,即可创建新的光照模型
该函数名为 LightingName,其中 Name 部分替换为自定义函数名
因此我们的 BasicDiffuse 即为 LightingBasicDiffuse
这是一个基本的 Lambert 漫反射模型的实现,关于 Lambert 介绍参看下一节

在 Unity 中共有三种光照模型函数可选

half4 LightingName (SurfaceOutput s, half3 lightDir, half atten){}
在正向渲染中,不需要视角方向时使用

half4 LightingName (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten){}
在正向渲染中,需要视角方向时使用

half4 LightingName_PrePass (SurfaceOutput s, half4 light){}
在延迟渲染中使用

dot( arg1, arg2 ) 也是 Cg 语言的内置函数,提供了向量点乘
http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter05.html


朗伯特漫反射(Lambert)模型


这是最常用的一个简单漫反射模型,我们对其进行简单介绍

Lambert 漫反射模型,认为漫反射与视角无关,在各个方向上观察的亮度都相等,它接收光源方向和表面法线方向两个向量,计算出表面亮度
这里写图片描述

最终,自定义光照模型的 Lambert Shader 代码为

Shader "HineNotes/CookbookCh_01/CustomLambertian" {
     Properties {
     }
     SubShader {
          Tags { "RenderType"="Opaque" }
          LOD 200

          CGPROGRAM
          #pragma surface surf BasicDiffuse

          inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
          {
               float difLight = max(0, dot (s.Normal, lightDir));

               float4 col;
               col.rgb = s.Albedo * _LightColor0.rgb * (difLight * atten * 2);
               col.a = s.Alpha;
               return col;
          }

          struct Input {
               float2 uv_MainTex;
          };

          void surf (Input IN, inout SurfaceOutput o) {
                half4 c = (1,1,1,1);
               o.Albedo = c.rgb;
               o.Alpha = c.a;
          }
          ENDCG
     }
     FallBack "Diffuse"
}

半漫反射(Half Lambert)光照模型


这是最早在 Original Half-Life 游戏中开发出的一种技术,它是一种与实际物理完全无关的, 纯粹为了获得更好的显示效果而使用的技术,它可以让模型背光的暗部不再是死黑一片
Valve 公司的开发页面:
https://developer.valvesoftware.com/wiki/Half_Lambert


半漫反射光照模型的原理是,先将原先点乘得到的光照系数乘以0.5,即将原先 -1~1 的值域空间线性映射到 -0.5~0.5 的值域空间,再加上 0.5的偏移回到 0~1 值域空间显示。

辅之以颜色贴图,就能得到比纯 Lambert 更好、更通透的暗部效果,同时计算复杂度并不高,不会对系统造成很大负担。
这里写图片描述
Half-Lambert 的数值分布图解

关于半漫反射模型的实现方法
修改光照模型的代码如下

inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3
lightDir, fixed atten)
{
float difLight = dot (s.Normal, lightDir);
float hLambert = difLight * 0.5 + 0.5;
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * (hLambert * atten * 2);
col.a = s.Alpha;
return col;
}

最终得到的效果
这里写图片描述


使用渐变纹理(Ramp Texture)控制漫反射着色


另外一种很好的漫反射着色方式,是用渐变纹理来控制漫反射着色,通过渐变纹理,我们可以加强表面的颜色,来伪造出更多暗面的反射光,以及更高级的灯光设置结果,这种技术常常用在美术风格较为卡通的游戏中。
有一篇在TeamFortress2中使用该技术的论文,很值得读一读
http://www.valvesoftware.com/publications/2007/NPAR07_IllustrativeRenderingInTeamFortress2.pdf

这里写图片描述
渐变纹理的作用

使用这样一张渐变纹理
这里写图片描述

修改光照模型的代码如下
1. 在 Properties 代码块中增加一个 2D 贴图属性
_RampTex (“Ramp Texture”, 2D) = “”{}
2. 在 Cg 程序中重新声明一个同名属性
sampler2D _RampTex;
3. 修改光照模型部分的代码,在其中使用渐变纹理

inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
     float difLight = dot (s.Normal, lightDir);
     float hLambert = difLight * 0.5 + 0.5;
     float3 ramp = tex2D(_RampTex, float2(hLambert, hLambert)).rgb;

     float4 col;
     col.rgb = s.Albedo * _LightColor0.rgb * ramp;
     col.a = s.Alpha;
     return col;
}

tex2D(arg1, arg2) 函数,也是 Cg 语言中的一个函数,arg1 是我们想要操作的 texture, arg2 是一个 float2 坐标,用于指示函数去 texture 的哪一处取值。

此处,我们没有使用顶点的 UV 坐标,而是将光照信息作为索引值,去贴图中取值,由此我们可以将光源方向映射到贴图上,最终得到基于光源方向的贴图效果。
这里写图片描述
使用渐变纹理的方式,可以让艺术家们更自由的去控制一些个性化的表面颜色,达到一些特殊的美术风格

最终得到的效果
这里写图片描述

渐变纹理 Shader 的全部代码为

Shader "HineNotes/CookbookCh_01/RampDiffuse" {
     Properties {
          _RampTex ("Ramp Texture", 2D) = "" {}
     }
     SubShader {
          Tags { "RenderType"="Opaque" }
          LOD 200

          CGPROGRAM
          #pragma surface surf BasicDiffuse

          sampler2D _RampTex;

          inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
          {
               float difLight = dot (s.Normal, lightDir);
               float hLambert = difLight * 0.5 + 0.5;
               float3 ramp = tex2D(_RampTex, float2(hLambert,hLambert)).rgb;
               float4 col;
               col.rgb = s.Albedo * _LightColor0.rgb * ramp;
               col.a = s.Alpha;
               return col;
          }

          struct Input {
               float2 uv_MainTex;
          };

          void surf (Input IN, inout SurfaceOutput o) {
               o.Albedo = (1,1,1);
               o.Alpha = 1.0;
          }
          ENDCG
     }
     FallBack "Diffuse"
}

使用二维渐变纹理(2D Ramp Texture)伪造BRDF效果


BRDF ( Bidirectional Reflectance Distribution Function ) ,即双向反射分布函数,指的是光线从表面反射后观察到的结果,与光源方向和视角方向这两个方向相关。

我们可以通过二维纹理的方式,伪造出一个这样的效果
以下是实现它准备使用的二维渐变纹理
这里写图片描述

修改光照模型的代码如下

inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir,half3 viewDir, fixed atten)
{
     float difLight = dot (s.Normal, lightDir);
     float rimLight = dot (s.Normal, viewDir);
     float hLambert = difLight * 0.5 + 0.5;
     float3 ramp = tex2D(_RampTex, float2(hLambert,rimLight)).rgb;
     float4 col;
     col.rgb = s.Albedo * _LightColor0.rgb * ramp;
     col.a = s.Alpha;
     return col;
}

这里写图片描述
在实际的场景中,将光源方向与视角方向综合,即得到了最终的画面结果

最终得到的效果(后面的 Lambert 模型用于做光源方向参考)
这里写图片描述
可以观察出,从背离光源到朝向光源方向,是从纹理左到右的白-黑渐变,而从背离视角到朝向视角方向,是从纹理的下向上的蓝-红渐变

这里写图片描述
float rimLight = dot (s.Normal, viewDir);
可以看做是基于视角方向与表面法线向量的点乘结果
图中所示为,将表面方向与视角方向点乘的结果作为亮度系数显示在表面上的效果

最终的 BRDF Shader 代码为

Shader "HineNotes/CookbookCh_01/RampBRDF" {
     Properties {
          _RampTex ("Ramp Texture", 2D) = "" {}
     }
     SubShader {
          Tags { "RenderType"="Opaque" }
          LOD 200

          CGPROGRAM
          #pragma surface surf BasicDiffuse

          sampler2D _RampTex;

          inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir,half3 viewDir, fixed atten)
          {
               float difLight = dot (s.Normal, lightDir);
               float rimLight = dot (s.Normal, viewDir);
               float hLambert = difLight * 0.5 + 0.5;
               float3 ramp = tex2D(_RampTex, float2(hLambert,rimLight)).rgb;
               float4 col;
               col.rgb = s.Albedo * _LightColor0.rgb * ramp;
               col.a = s.Alpha;
               return col;
          }

          struct Input {
               float2 uv_MainTex;
          };

          void surf (Input IN, inout SurfaceOutput o) {
               o.Albedo = (1,1,1);
               o.Alpha = 1.0;
          }
          ENDCG
     }
     FallBack "Diffuse"
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值