Shader | 01基本光照模型浅析Diffuse
DiffuseShader技术渲染代码Thepoly 15325 1
实名

通过了实名认证的内容创造者

发布于 2022-3-26 14:42:58

您需要 登录 才可以下载或查看,没有账号?注册

x
           
174551k9r66ex7z719e99h.jpg


Hello . 大家好
今天带来的是基本光照模型浅析
我是麦田


a8e7f8990925571c6f19c9a600dbce26.png


前天在狼主技术群里,有一个朋友问了一个问题:


所以今天就来给大家详解一下标准光照模型。写在前面:很多技术点是借鉴了冯乐乐女神的Shader精要内的,我不过是拾人牙慧而已,这种圣书推荐大家人手一本。


兰伯特定律:反射光线的强度与表面法线和光源方向之间的夹角余弦值成正比。而漫反射是符合兰伯特定律的。

在没有BRDF(双向分布反射函数)之前,标准的光照模型早就被广泛的使用了。乍一看有点难理解,仔细想想。


看一下这张图,回忆一下初高中学的cos/sin,来看一下公式:

Cdiffuse =( Clight  * Mdiffuse )max(0,dot(N,L))

其中:Clight 是光源的颜色;Mdiffuse是材质的漫反射颜色;N是归一化后的法线(倒三角符号试了半天没打出来);L是光源单位矢量(即光源方向);MAX是为了不要取到负值,将它映射到一个范围内,通常这个范围是0-1,Shader内有一个saturate函数可以直接使用。

此时,我们还需要牵扯到一个概念,即环境光:ambient

什么是环境光呢?如果说,太阳是一个直接光源,照射到了一个红苹果,然后红苹果下面有个绿布,那么红苹果和绿布交接的阴影和投影会被互相影响到。这就是环境光,也被称为间接光照。


环境光照公式非常简单,因为他是一个全局变量,就是说,整个场景内都是使用的这个环境光,等式如下:

Cambient = Gambient

一个函数:saturate(x)x:可以是标量或者矢量,也可以是float1,2,3类型干嘛用的呢?上文提到过;即MAX,钳制范围在(0,1)

下面我就就开始上实战的代码,这里我直接贴了女神的代码,我在后面逐句写了注释。

逐像素光照与逐顶点光照

逐顶点光照

  1. Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
  2.   // 定义Shader的路径和名称,以斜杠分割
  3.   Properties {
  4.   // Properties语义块,用来声明属性
  5.     _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
  6.   // 这里声明了一个颜色属性,默认值为白色
  7.   }
  8.   SubShader {
  9.     Pass {
  10.     定义一个PASS语义
  11.       Tags { "LightMode"="ForwardBase" }
  12.     // 我们设置一个标签,设置他的渲染路径为前向渲染,lightMode会计算环境光,平行光等
  13.       CGPROGRAM
  14.       // CGPROGRAM...ENDCG,这个语义内是Cg/HLSL语言
  15.       #pragma vertex vert
  16.       #pragma fragment frag
  17.       // 告诉Unity,顶点和片元分别叫什么
  18.       #include "Lighting.cginc"
  19.       // 包含一个内置变量Lighting.cginc,可以通过去官网下载Unity内置Shader看看到底做了什么
  20.       fixed4 _Diffuse;
  21.       // 我们在Properties中声明了一个属性,现在我们要给这个属性赋值,给他一个fixed4的变量
  22.       // 因为color是RGBA,四个通道,平时用PS时是0-255,这里我们是映射0-1的

  23.       struct a2v {
  24.       // 输入结构体 a2v为输入结构体的名称
  25.         float4 vertex : POSITION;
  26.         float3 normal : NORMAL;
  27.         //访问顶点位置和法线,把模型顶点法线信息存到normal变量内
  28.       };

  29.       struct v2f {
  30.       // 输出结构体,同时也是片元着色器的输入结构体
  31.         float4 pos : SV_POSITION;
  32.         fixed3 color : COLOR;
  33.         // 定义一个颜色变量,把我们获得的信息传递给片元着色器
  34.       };
  35.       // 接下来就是算法部分了
  36.       v2f vert(a2v v) {
  37.         v2f o;
  38.         //  定义一个返回值o
  39.         o.pos = UnityObjectToClipPos(v.vertex);
  40.         // 把顶点位置从模型空间转换到其次裁剪空间,这里要注意的是
  41.         // 自2017之后,这里的坐标转换变量改成了上文

  42.         // 获得光源颜色,强度和方向。
  43.         fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  44.         // 下面就是点积过程,我们要将他们转化到统一坐标系,只有这样,点积才有意义
  45.         //将物体法线转换到世界空间下并进行归一化,首先我们要先得到模型到空间变化矩阵的逆矩阵
  46.         //这里要注意一下17往后的语义改了,原为_World2Object
  47.         fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
  48.         // 获取世界空间下的法线和光源方向后,们要进行一次归一化
  49.         fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
  50.         // 我们将世界空间光和世界空间法向量Dot(点积),然后使用saturate钳制输出范围0-1,这一步就对应了公式
  51.         fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
  52.         // 最后,我们将环境光和漫反射相加后输出
  53.         o.color = ambient + diffuse;

  54.         return o;
  55.       }
  56.       逐顶点光照基本都是在顶点着色器内完成的,片元着色器内只需要输出就好

  57.       fixed4 frag(v2f i) : SV_Target {
  58.         return fixed4(i.color, 1.0);
  59.         //(i。color可以理解为RGB。1.0即为A通道)
  60.       }

  61.       ENDCG
  62.       //CG语句结束
  63.     }
  64.   }
  65.   FallBack "Diffuse"
  66.   //最后我们留一个后门,防止在有些平台上变成小粉红,这里我们就指向默认
  67. }
点击此处复制文本


逐像素光照
  1. Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
  2.   Properties {
  3.     _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
  4.   }
  5.   SubShader {
  6.     Pass {
  7.       Tags { "LightMode"="ForwardBase" }

  8.       CGPROGRAM

  9.       #pragma vertex vert
  10.       #pragma fragment frag

  11.       #include "Lighting.cginc"

  12.       fixed4 _Diffuse;

  13.       struct a2v {
  14.         float4 vertex : POSITION;
  15.         float3 normal : NORMAL;
  16.       };

  17.       struct v2f {
  18.         float4 pos : SV_POSITION;
  19.         float3 worldNormal : TEXCOORD0;
  20.       };

  21.       v2f vert(a2v v) {
  22.         v2f o;
  23.         // Transform the vertex from object space to projection space
  24.         o.pos = UnityObjectToClipPos(v.vertex);

  25.         // Transform the normal from object space to world space
  26.         o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

  27.         return o;
  28.       }

  29.       fixed4 frag(v2f i) : SV_Target {
  30.         // Get ambient term
  31.         fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

  32.         // Get the normal in world space
  33.         fixed3 worldNormal = normalize(i.worldNormal);
  34.         // Get the light direction in world space
  35.         fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

  36.         // Compute diffuse term
  37.         fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

  38.         fixed3 color = ambient + diffuse;

  39.         return fixed4(color, 1.0);
  40.       }

  41.       ENDCG
  42.     }
  43.   }
  44.   FallBack "Diffuse"
  45. }
点击此处复制文本


逐像素光照和逐顶点基本一样,只有将计算那一步挪到了片元着色器中,大家可以尝试一下,观察两种效果那个更好。




左边为逐像素,右边为逐顶点,很明显,右边包含了明显的锯齿状,这个锯齿状是根据模型顶点来的,模型细分越高,锯齿越少(得不偿失)。


- End -

喜欢Thepoly的可以通过三种方式与我们建立联系。分别是公众号、微信群以及QQ群。公众号是我们最为官方的窗口,更多内容都必须关注公众号后才能获取。另外现已开通微博:手机版http://weibo.cn/thepoly网页版http://weibo.com/thepoly
         

本帖被以下画板推荐:

还没有设置签名!您可以在此展示你的链接,或者个人主页!
使用道具 <
Teny  发表于 2022-3-26 18:43:33  
2#
谢谢楼主分享!!!
回复 收起回复
使用道具
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表