Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照

转自冯乐乐的《Unity Shader入门精要》

通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象。

首先,光线从光源中被发射出来。

然后,光线和场景中的一些物体相交:一些光线被物体吸收了,而另一些光线被散射到其他方向。

最后,摄像机吸收了一些光,产生了一张图像。

在光学中,我们使用辐照度来量化光。对于平行光来说,它的辐照度可通过计算在垂直于l的单位面积上单位时间内穿过的能量来得到。在计算光照模型时,我们需要知道一个物体表面的辐照度,而物体表面往往是和l不垂直的,我们可以使用光源方向l和表面法线n之间的夹角的余弦值来得到。需要注意的是,这里默认方向矢量的模都为1。下图显示了使用余弦值来计算的原因。

因为辐照度是和照射到物体表面时光线之间的距离d/cosθ 成反比的,因此辐照度就和cosθ 成正比。cosθ 可以使用光源方向l和表面法线n的点积来得到。这就是使用点积来计算辐照度的由来。

光照由光源发射出来后,就会与一些物体相交。通常的结果又两个:散射和吸收。

散射只
改变光线的方向,但不改变光线的密度和颜色。而吸收只改变光线的密度和颜色,但不改变光线的方向。光线在物体表面经过散射后,有两种方向:一种将会散射到物体内部,这种现象被称为折射或投射;另一种将会散射到外部,这种现象被称为反射。对于不透明物体,折射进入物体内部的光线还会继续与内部的颗粒进行相交,其中一些光线最后会重新发射出物体表面,而另一些则被物体吸收。那么从物体表面重新发射出的光线将具有和入射光线不同的方向分布和颜色。下图给出了这样的例子:

为了区分这两种不同的散射方向,我们再光照模型中使用了不同的部分来计算它们:高光反射部分表示物体表面是如何发射光线的,而漫反射部分则表示有多少光线会被折射、吸收和散射出表面。根据入射光线的数量和方向,我们可以计算出射光线的数量和反向,我们通常使用出射度来描述它。辐射度和出射度之间是满足线性关系的,而它们之间的比值就是材质的漫反射和高光反射属性。

在本章中,我们假设漫反射部分是没有方向性的,也就是说,光线在所有方向上是平均分布的,同时,我们也只考虑某一个特性方向上的高光反射。

着色指的是,根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿某个观察方向的出射度的过程。我们也把这个等式称为光照模型。不同的光照模型有不同的目的。例如,一些用于描述粗糙的物体表面,一些用于描述金属表面等。

我们已经了解了光线在和物体表面相交时会发生哪些现象。当已知光源位置和方向、视角方向时,我们就需要知道一个表面是和光照进行交互的。例如,当光线从某个方向照射到一个表面时,有多少光线被反射?反射的方向有哪些?而BRDF 就是用来回答这些问题的。当给定模型表面上的一个点时,BRDF 包含了对该点外观的完整的描述。在图形学中,BRDF 大多使用一个数学公式来表示,并且提供了一些参数来调整材质属性。通俗来讲,当给定入射光线的方向和辐照度后,BRDF可以给出在某个出射方向上的光照能量分布。后面说的BRDF都是对真实场景进行理想化和简化后的模型,也就是说,它们并不能真实地反应物体和光线之间的交互,这些光照模型被称为经验模型。尽管如此,这些经验模型仍然在实时渲染领域被应用了多年。

标准光照模型只关心直接光照,也就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。

它的基本方法是,把进入到摄像机内的光线分为4个部分,每个部分使用一种方法来计算它的贡献度。这4个部分是:

1)自发光部分。这个部分用于描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。需要注意的是,如果没有使用全局光照技术,这些自发光的表面并不会真的照亮周围的物体,而是它本身看来更亮了而已。

2)高光反射部分。这个部分用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。

3)漫反射部分。这个部分用于描述,当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。

4)环境光部分。它用于描述其他所有的间接光照。

虽然标准光照模型的重点在于描述直接光照,但在真实的世界中,物体也可以被间接光照所照亮。间接光照指的是,光线通常会在多个物体之间进行反射,最后进入摄像机,也就是说,在光线进入摄像机之间,经过了不止一次的物体反射。

在标准光照模型中,我们使用了一种被称为环境光的部分来近似模拟间接光照。环境光的计算非常简单,它通常是一个全局变量,即场景中的所有物体都使用这个环境光。下面的等式给出了计算环境光的部分:

光线也可以直接由光源发射进入摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光来计算这个部分的共享度。它的计算也很简单,就是直接使用了该材质的自发光颜色:

通常在实时渲染中,自发光的表面往往并不会照亮周围的表面,也就是说,这个物体并不会被当成一个光源。Unity 5 引入的全新全局光照系统则可以模拟这类自发光物体对周围物体的影响。

漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的,因为反射是完全随机的,因此可以认为在任何反射反向上的分布都是一样的。但是,入射光线的角度很重要。

漫反射光照符合兰伯特定律:反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比。因此,漫反射部分的计算如下:

其中,n是表面法线,I是指向光源的单位矢量,m(diffuse)是材质的漫反射颜色,c(light)是光源颜色。需要注意的是,我们需要防止返现和光源方向点乘的结果为负值,为此,我们使用取最大值的函数来将其截取到0,这可以防止物体被从后面来的光源照亮。

这里的高光反射是一种经验模型,也就是说,它并不完全符合真实世界中的高光反射现象。它可以用于计算那些沿着完全镜面反射反向被反射的光线,这可以让物体看起来是由光泽的,例如金属材质。

计算高光反射需要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。我们假设这些矢量都是单位矢量,下图给出了这些方向矢量。

在这四个矢量中,我们实际上只需要知道其中3个矢量即可,而第4个矢量——反射方向可以通过其他信息计算得到:

这样,我们就可以利用Phong模型来计算高光反射的部分:

其中m(gloss)是材质的光泽度,也被反称为反光度。它用于控制高光区域的“亮点”有多宽,m(gloss)越大,亮点就越小。m(spscular)是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。c(light)则是光源的颜色和强度。

和上述的Phong模型相比,Blinn提出了一个简单的修改方法来得到类似的效果。它的基本思想是,避免计算反射方向。为此,Blinn模型引入了一个新的矢量,如下:

然后,使用n和h之间的夹角进行计算,而非v和r之间的夹角,如下图所示:

总结一下,Blinn模型的公式如下:

在硬件实现时,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,这是因为,此时可以认为V和I都是定值,因此h将是一个常量。但是,当V或者I不是定值时,Phong模型可能反而更快一些。需要注意的是,这两种光照模型都是经验模型,也就是说,我们不应该认为Blinn模型是对“正确的”Phong的近似。实际上,在一些情况下,Blinn模型更符合实验结果。

上面,我们给出了基本光照模型使用的数学公式,那么我们再哪里计算这些光照模型呢?通常来讲,我们有两种选择:在片元着色器中计算,也被称为逐像素光照;在顶点着色器中计算,也被称为逐顶点光照。

在逐像素光照中,我们会以每个像素为基础,得到它的法线,然后进行光照模型的计算。这种在面片之间对顶点法线进行插值的技术被称为Phong着色,也被称为Phong插值或者法线插值着色技术。这不同于我们之前讲到的Phong光照模型。

与之相对的是逐顶点光照,也被称为高洛德着色。在逐顶点光照中,我们在每个顶点上计算光照,然后会在渲染图元内部进行线性插值,最后输出成像素颜色。由于顶点数目往往小于像素数目,因此逐顶点光照的计算量往往要小于逐像素光照。但是,由于逐顶点光照依赖于线性插值来得到像素光照,因此,当光照模型中有非线性的计算(例如计算高光反射时)时,逐顶点光照就会出问题。而且,由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的菱角现象。

在标准光照模型中,环境光和自发光的计算是最简单的。

在Unity 5中,场景中的环境光可以在Window -> Lighting -> Ambient Source / Ambient Color / Ambient Intensity 中控制,如下图所示。在Shader 中个,我们只需要通过Unity 内置变量 UNITY_LIGHTMODEL_AMBIENT 就可以得到环境光的颜色和强度信息。而大多数物体是没有自发光特性的,因此在本书绝大部分的Shader 中都没有计算自发光部分。如果要计算自发光也非常简单,我们只需要再片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上即可。

在漫反射公式中可以看出,要计算漫反射需要知道4个参数:入射光线的颜色和强度,材质的漫反射系数,表面法线以及光源方向。

为了防止点积结果为负值,我们需要使用max操作,而CG提供了这样的函数。在本例中,使用CG的另一个函数可以达到同样的目的,即saturate函数。

函数:saturate(x)

参数:x 为用于操作的标量或矢量

描述:把 x 截取在[0, 1]范围内,如果 x 是一个矢量,那么会对它的每一个分量进行这样的操作。

实践:逐顶点光照

效果如下图:

1)在Unity 中新建一个场景。在Unity 5.2 中,默认情况下场景将包含一个摄像机和一个平行光,并且使用了内置的天空盒子。在Window -> Lighting -> Skybox 中去掉场景中的天空盒子。

2)新建一个材质

3)新建一个Unity Shader。把新的Shader赋给第2步中创建的材质

4)在场景中创建一个胶囊体,并把第2步中的材质赋给该胶囊体

5)保存场景

接下来我们编写自己的Shader来实现一个逐顶点的漫反射效果

  1. Shader "Unity Shader Book/Chapter 6/Diffuse Vertex-Level"
  2. {
  3. Properties
  4. {
  5. //用来控制材质的漫反射颜色
  6. _Diffuse ("Diffuse", Color) = (1,1,1,1)
  7. }
  8. SubShader
  9. {
  10. Pass
  11. {
  12. //LightMode 标签是Pass标签的一种,它用于定义该Pass在Unity的光照流水线中的角色。
  13. Tags {"LightMode" = "ForwardBase"}
  14. CGPROGRAM
  15. #pragma vertex vert
  16. #pragma fragmentfrag
  17. //为了使用Unity内置的一些变量而包含内置文件
  18. #include "Lighting,cgnic"
  19. //为了在Shader中使用Properties语义块中声明的属性,我们需要定义一个和该属性类型相匹配的变量
  20. //通过这样的方式,我们就可以得到漫反射公式中需要的参数之一——材质的漫反射属性。
  21. //由于颜色属性的范围在0到1之间,因此我们可以使用fixed精度的变量来存储它。
  22. fixed4 _Diffuse;
  23. //顶点着色器的输入结构体
  24. //为了访问顶点的法线,我们需要再a2v中定义一个normal变量,并通过使用NORMAL语义来告诉Unity
  25. //要把模型顶点的法线信息存储到normal变量中。
  26. struct a2v
  27. {
  28. float4 vertex : POSITION;
  29. float3 normal : NORMAL;
  30. };
  31. //顶点着色器的输出结构体(同时也是片元着色器的输入结构体)
  32. //为了把在顶点着色器中计算得到的光照颜色传递给片元,我们需要再v2f中定义一个color变量,且并不是必须使用COLOR语义
  33. struct v2f
  34. {
  35. float4 pos : SV_POSITION;
  36. fixed3 color : COLOR;
  37. };
  38. v2f vert(a2v v)
  39. {
  40. //定义返回值o
  41. v2f o;
  42. //顶点着色器最基本任务就是把顶点位置从模型空间转换到裁剪空间中,因此需要用矩阵来进行变换
  43. o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  44. //我们通过Unity的内置变量UNITY_LIGHTMODEL_AMBIENT 得到了环境光部分
  45. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  46. //然后开始真正计算漫反射光照部分,首先我们已经知道了材质的漫反射颜色_Diffuse以及顶点法线v.normal。
  47. //我们还需要知道光源的颜色和强度信息以及光源方向。Unity提供了我么一个内置变量_LightColor0来访问该Pass处理的光源的颜色和
  48. //强度信息(注意,想要得到正确的值需要定义合适的LightMode标签),
  49. //而光源方向可以由_WorldSpaceLightPos0 来得到。需要注意的是,这里对光源方向的计算并不具有通用性
  50. //在计算法线和光源方向之间的点积时,我们需要选择它们所在的坐标系,只有两者处于同一坐标空间下,它们的点积才有意义。
  51. //在这里,我们选择了世界坐标空间。而由a2v得到的顶点法线是处于模型空间下的,因此我们首先需要把法线转换到世界空间中。
  52. //在第4章中,我们已经知道可以使用顶点变换矩阵的逆转置对法线进行相同的变换,因此我们首先得到模型空间到世界空间的
  53. //变换矩阵的逆矩阵_World2Object,然后通过调换它在mul函数中的位置,得到和转置矩阵相同的矩阵乘法。
  54. //由于法线是一个三维矢量,因此我们只需要截取_World2Object的前三行前三列即可。
  55. fixed3 worldNormal = normalize(mul(v.normal, (float3×3)_World2Object));
  56. fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
  57. //在得到了世界空间中的法线和光源方向后,我们需要对它们进行归一化操作。在得到它们点击的结果后,我们使用saturate函数
  58. //把参数截取到[0, 1]范围内。最后,再与光源颜色和强度以及材质的漫反射颜色相乘可得到最终的漫反射光照部分
  59. fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
  60. //最后我们对环境光和漫反射部分相加,得到最终的光照结果
  61. o.color = ambient + diffuse;
  62. return o;
  63. }
  64. //由于所有的计算在顶点着色器中都已完成了,因此片元着色器的代码很简单,我们只需要直接把顶点颜色输出即可
  65. fixed4 frag(v2f i) : SV_Target
  66. {
  67. return fixed4(i.color,1.0);
  68. }
  69. ENDCG
  70. }
  71. }
  72. Fallback "Diffuse"
  73. }

我们只需要对shader进行一些更改就可以实现逐像素的漫反射效果,如下图

为此,我们进行如下准备工作

1)使用逐顶点中的场景

2)新建一个材质

3)新建一个Unity Shader。把新建的Shader 赋给第2步中创建的材质。

4)把第2步中创建的材质赋给胶囊体。

Shader代码如下:

  1. Shader "Unity Shader Book/Chapter 6/Diffuse PixelLevel"
  2. {
  3. Properties
  4. {
  5. _Diffuse ("Diffuse", Color) = (1,1,1,1)
  6. }
  7. SubShader
  8. {
  9. Pass
  10. {
  11. Tags {"LightMode" = "ForwardBase"}
  12. CGPROGRAM
  13. #pragma vertex vert
  14. #pragma fragmentfrag
  15. #include "Lighting,cgnic"
  16. fixed4 _Diffuse;
  17. struct a2v
  18. {
  19. float4 vertex : POSITION;
  20. float3 normal : NORMAL;
  21. };
  22. //顶点着色器的输出结构体有作修改
  23. struct v2f
  24. {
  25. float4 pos : SV_POSITION;
  26. fixed3 worldNormal : TEXCOORD0;
  27. };
  28. //顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器即可
  29. v2f vert(a2v v)
  30. {
  31. v2f o;
  32. o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  33. o.worldNormal = mul(v.normal, (float3×3)_World2Object));
  34. return o;
  35. }
  36. //片元着色器需要计算漫反射光照模型
  37. fixed4 frag(v2f i) : SV_Target
  38. {
  39. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  40. fixed3 worldNormal = normalize(i.worldNormal);
  41. fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  42. fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
  43. fixed3 color = ambient + diffuse;
  44. return fixed4(color,1.0);
  45. }
  46. ENDCG
  47. }
  48. }
  49. Fallback "Diffuse"
  50. }

逐像素光照可以得到更加平滑的光照效果。但是,几遍使用了逐像素漫反射光照,有一个问题仍然存在,在光照无法达到的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面,失去了模型细节表现。实际上我们可以通过添加环境光来得到非全黑的效果,但即便这样仍然无法解决背光面明暗一样的缺点。为此,有一种改善技术被提出来,这就是半兰伯特光照模型。

广义的半兰伯特光照模型的公式如下:

可以看出,与原兰伯特模型相比,半兰伯特光照模型没有使用max操作来防止n和I的点积为负值,而是对其结果进行了一个α 倍的缩放再加上一个β 大小的偏移。绝大多数情况下,α 和β的值均为0.5,即公式为:

通过这样的方式,我们可以把(n·I)的结果范围从[-1,1]映射到[0,1]范围内,也就是说,对于模型的背光面,在原兰伯特光照模型中点积结果将映射到同一个值,即0值处;而在半兰伯特模型中,背光面也可以由明暗变化,不同的点积结果会映射到不同的值上。

需要注意的是,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术。

对逐像素光照的代码做一些修改就可以实现半兰伯特漫反射光照效果。

  1. fixed4 frag(v2f i) : SV_Target
  2. {
  3. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  4. fixed3 worldNormal = normalize(i.worldNormal);
  5. fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  6. fixed3 halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
  7. fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
  8. fixed3 color = ambient + diffuse;
  9. return fixed4(color,1.0);
  10. }

下图给出了逐顶点漫反射光照、逐像素漫反射光照和半兰伯特光照的对比效果。

高光反射

之前我们给出了基本光照模型中高光发射部分的计算公式:

函数reflect(i,n)可以计算反射方向:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。

实践:逐顶点光照的高光反射效果

代码如下:


  1. Shader "Unity Shaders Book/Chapter6/Specular Vertex-Level"
  2. {
  3. Properties
  4. {
  5. _Diffuse("Diffuse", Color) = (1,1,1,1)
  6. //用于控制材质的高光反射颜色
  7. _Specular("Specular",Color) = (1,1,1,1)
  8. //用于控制高光区域的大小
  9. _Gloss("Gloss", Range(8.0,256)) = 20
  10. }
  11. SubShader
  12. {
  13. Pass
  14. {
  15. //设置光照模式
  16. Tags { "LightMode" = "Forward"}
  17. CGPROGRAM
  18. #pragma vertex vert
  19. #pragma fragment frag
  20. #include "Lighting.cgnic"
  21. //存储属性中的变量
  22. fixed4 _Diffuse;
  23. fixed4 _Specular;
  24. float _Gloss;
  25. //输入结构体
  26. struct a2v
  27. {
  28. float4 vertex :     POSITION;
  29. float3 normal : NORMAL;
  30. }
  31. //输出结构体
  32. struct v2f
  33. {
  34. float4 pos : SV_POSITION;
  35. fixed3 color : COLOR;
  36. }
  37. //顶点着色器中,计算包含高光反射的光照模型
  38. v2f vert(a2v v)
  39. {
  40. v2f o;
  41. o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  42. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  43. fixed3 worldNormal = normalize(mul(v.normal, (float3×3)_World2Object));
  44. fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  45. fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
  46. //入射光线关于表面法线的反射方向。由于CG的reflect函数的入射方向要求
  47. //是由光源指向交点处的,因此需要对worldLightDir取反后再传给reflect函数。
  48. fixed3 reflectDir = normalize(reflect(-worldLightDir),worldNormal);
  49. //_WorldSpaceCameraPos得到世界空间中的摄像机的位置
  50. //再把顶点位置从模型空间变换到世界空间下,再通过和_WorldSpaceCameraPos相减
  51. //即可得到世界空间下的视角方向
  52. fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(_Object2World, v.vertex).xyz);
  53. //代入公式得到高光反射的光照部分
  54. fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)),_Gloss);
  55. o.color = ambient + diffuse + specular;
  56. }
  57. fixed4 frag(v2f i) : SV_Target
  58. {
  59. return fixed4(i.color, 1.0);
  60. }
  61. ENDCG
  62. }
  63. }
  64. Fallback "Specular"
  65. }

得到的效果图如下:

实践:高光逐像素光照

更改逐顶点光照部分Shader代码

  1. Shader "Unity Shaders Book/Chapter6/Specular Pixel-Level"
  2. {
  3. Properties
  4. {
  5. _Diffuse("Diffuse", Color) = (1,1,1,1)
  6. _Specular("Specular",Color) = (1,1,1,1)
  7. _Gloss("Gloss", Range(8.0,256)) = 20
  8. }
  9. SubShader
  10. {
  11. Pass
  12. {
  13. Tags { "LightMode" = "Forward"}
  14. CGPROGRAM
  15. #pragma vertex vert
  16. #pragma fragment frag
  17. #include "Lighting.cgnic"
  18. fixed4 _Diffuse;
  19. fixed4 _Specular;
  20. float _Gloss;
  21. struct a2v
  22. {
  23. float4 vertex :     POSITION;
  24. float3 normal : NORMAL;
  25. }
  26. //更改了输出结构体
  27. struct v2f
  28. {
  29. float4 pos : SV_POSITION;
  30. float3 worldNormal : TEXCOORD0;
  31. float3 worldPos : TEXCOORD1;
  32. }
  33. //顶点着色器只需要计算世界空间下的法线方向和顶点坐标,
  34. //并把它们传递给片元着色器即可
  35. v2f vert(a2v v)
  36. {
  37. v2f o;
  38. o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  39. o.worldNormal = mul(v.normal, (float3×3)_World2Object);
  40. o.worldPos = mul(_Object2World, v.vertex).xyz;
  41. o.color = ambient + diffuse + specular;
  42. }
  43. //片元着色器需要计算关键的光照模型:
  44. fixed4 frag(v2f i) : SV_Target
  45. {
  46. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  47. fixed3 worldNormal = normalize(i.worldNormal);
  48. fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  49. fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
  50. fixed3 reflectDir = normalize(reflect(-worldLightDir),worldNormal);
  51. fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
  52. fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)),_Gloss);
  53. return fixed4(ambient + diffuse + specular, 1.0);
  54. }
  55. ENDCG
  56. }
  57. }
  58. Fallback "Specular"
  59. }

结果如下:

Blinn-Phong 光照模型

公式如下

修改逐像素光照中的部分代码

  1. fixed4 frag(v2f i) : SV_Target
  2. {
  3. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  4. fixed3 worldNormal = normalize(i.worldNormal);
  5. fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  6. fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
  7. fixed3 reflectDir = normalize(reflect(-worldLightDir),worldNormal);
  8. fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
  9. fixed3 halfDir = normalize(worldLightDir + viewDir);
  10. fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(reflectDir, halfDir)),_Gloss);
  11. return fixed4(ambient + diffuse + specular, 1.0);
  12. }

下图给出了三种高光反射光照的对比结果

可以看出,Blinn-Phong 光照模型的高光反射部分看起来更大、更亮一些。在实际渲染中,绝大多数情况我们都会选择Blinn-Phong光照模型。需要再次提醒的是,这两种模型都是经验模型,也就是说,我们不应该认为Blinn-Phong模型是对“正确的”Phong模型的近似。实际上,在一些情况下,Blinn-Phong模型更符合实验结果。

下表给出了UnityCG.cgnic 中一些常用的帮助函数

注意,类似 UnityXXX 的几个函数是Unity 5 中新添加的内置函数。

时间: 2024-10-13 00:46:32

Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照的相关文章

Unity Shader入门精要学习笔记 - 第4章 学习 Shader 所需的数学基础

摘录自 冯乐乐的<Unity Shader入门精要> 笛卡尔坐标系 1)二维笛卡尔坐标系 在游戏制作中,我们使用的数学绝大部分都是计算位置.距离.角度等变量.而这些计算大部分都是在笛卡尔坐标系下进行的. 一个二维的笛卡尔坐标系包含了两个部分的信息: 一个特殊的位置,即原点,它是整个坐标系的中心. 两条过原点的互相垂直的矢量,即X轴和Y轴.这些坐标轴也被称为是该坐标的矢量. OpenGL 和 DirectX 使用了不同的二维笛卡尔坐标系.如下图所示: 2)三维笛卡尔坐标系 在三维笛卡尔坐标系中,

Unity Shader入门精要学习笔记 - 第3章 Unity Shader 基础

来源作者:candycat   http://blog.csdn.net/candycat1992/article/ 概述 总体来说,在Unity中我们需要配合使用材质和Unity Shader才能达到需要的效果.一个最常见的流程是. 1)创建一个材质 2)创建一个Unity Shader,并把它赋给上一步创建的材质 3)把材质赋给要渲染的对象 4)在材质面板中调整Unity Shader的属性,以得到满意的效果 下图显示了Unity Shader和材质是如何一起工作来控制物体的渲染的. Uni

Unity Shader入门精要读书笔记(一)序章

本系列的博文是笔者读<Unity Shader入门精要>的读书笔记,这本书的章节框架是: 第一章:着手准备. 第二章:GPU流水线. 第三章:Shader基本语法. 第四章:Shader数学基础. 第五章:利用简单的顶点/片元着色器来实现辅助技巧. 第六章:基本光照模型. 第七章:法线纹理.遮罩纹理等基础纹理. 第八章:透明度测试和透明度混合. 第九章:复杂光照实现. 第十章:高级纹理(立方体纹理等). 第十一章:纹理动画.顶点动画. 第十二章:屏幕特效. 第十三章:深度纹理. 第十四章:非真

《Unity Shader入门精要》读书笔记(1)

主要是对第二章的整理 渲染流水线:由一个三维场景出发,生成(渲染)一张二维图像. 渲染流程:应用阶段.几何阶段.光栅化阶段. 应用阶段: 1. 把数据加载到显存中 渲染所需数据从硬盘,到内存,再到显存 2. 设置渲染状态 渲染状态:使用哪个顶点着色器.片元着色器.光源属性.材质等 3. 调用Draw Call 发起方CPU,接收方GPU GPU流水线 以下为几何阶段主要步骤 顶点着色器: CPU输入的每一个顶点都会调用一次顶点着色器 不创建或销毁任何顶点,且顶点之间相互独立 坐标转换:把顶点坐标

Unity Shader入门教程(一)

参考文献:http://www.360doc.com/content/13/0923/15/12282510_316492286.shtml Unity Shader是着色器,将纹理.网格信息输入,得到材质的一段程序,具体是个什么东西,还需要亲自实践才知道.一个Unity大神推荐我:如果要学计算机图形编程(游戏编程的基础),可以先学习UnityShader,往后再学习OpenGL和DX.不说废话,依我的风格,都是直接看实例,笔者的教程偏向于傻瓜式的,应该适合入门. 前提:安装了Unity和VS,

Unity Shader入门

这篇文章是我在学习蛮牛的一套关于Shader教程(http://www.unitytrain.cn/course/96)后的简单总结,个人感觉这套教程并不是以高级Shader编程为目的的,更像是授人以渔的宗旨.下面我会分为三个部分:Shader简述.图形学基础,Cg简介为大家介绍Shader的相关内容,也算是做一个总结. 一:Shader简述     a.先说一下GPU与CPU的区别,简单说:GPU主要负责跟显示相关的数据处理,而CPU主要负责操作系统和应用程序.为什么不把显示相关的数据直接交给

Unity Shader入门总结(一)

最近断断续续学习了一些Unity Shader的内容,总结一下,主要学习资料是siki学院的课程. Unity Shader基本结构 Shader "Unlit/002" //Shader路径 { Properties //Shader属性 可在面板修改 { _Int("Int",Int) = 2 _Float("Float",float) = 1.5 _Range("Range",range(0.0,2.0))= 1.0 _

第五章 Unity中的基础光照(2)

目录 1. Unity中的环境光和自发光 2. 在UnityShader中实现漫反射光照模型 2.1 实践:逐顶点光照 2.2 实践:逐像素光照 2.3 半兰伯特模型 1. Unity中的环境光和自发光 在标准光照模型中,环境光和自发光的计算是最简单的. 在Unity中,场景中的环境光可以在Window->Lighting->Ambient Source/Ambient Intensity中控制,如下图所示.在Shader中,我们只需要通过Unity的内置变量UNITY_LIGHTMODEL_

Unity Shader入门教程(二)最基本的Diffuse和Normal样例

本教程参考了<猫都能学会的Unity3dShaderLab教程.CHM>, 1.请上网搜索并下载此文件. 2.随后再下载里面提到的素材: http://vdisk.weibo.com/s/y-NNpUsxhYhZI 第一组实验(复习课,实现最简单的漫反射 [该组实验参考了官网示例中的Normal-Diffuse.shader例子]): 第1.1步:创建一个名为"NormalDiffuse"的shader 第1.2步:看到其中有一些已有的内容,不妨全部删掉(除了第一行),这样