第五章 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_AMBIENT就可以得到环境光的颜色和强度信息。
而大多数物体是没有发光特性的,因此在本文中的大部分Shader中都没有计算自发光部分。如果要就算自发光也很简单,我们只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色即可。

2. 在UnityShader中实现漫反射光照模型

在了解了上述理论后,我们现在来看一下如何在Unity中实现这些基本的光照模型。首先,我们来实现标准光照模型中的漫反射光照部分。
在以前我们给出了基本光照模型中漫反射部分的计算公式:

从公式可以看出,要计算漫反射需要知道4个参数:入射光线的颜色和强度Clight,材质的漫反射系数mdiffuse,表面法线n以及光源方向I。
为防止点积的结果出现负值,我们需要使用max操作,而Cg提供了这样的函数。在本例中使用Cg的另一个函数可以达到同样的目的,即saturate函数。
函数:saturate(x)
参数:x:为用于操作的标量或矢量,可以是float、float2、float3等类型。
描述:把x截取在[0,1]的范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作。

2.1 实践:逐顶点光照

我们首先来看如何实现一个逐顶点的漫反射光照效果。在学习完本节后,我们会得到类似于下图的效果。

(1)首先,为了得到并且控制材质的漫反射颜色,我们首先在Shader语的Properties语义块中声明了一个Color类型的属性,并把它的初始值设置为白色:

Properties{
_Diffuse("Diffuse",Color)=(1,1,1,1)
}

(2)然后,我们在SubShader语义块中定义了一个Pass语义块。这是因为顶点/片元着色器的代码需要写在Pass语义块,而非SubShader语义块中。而且我们在Pass的第一行指明了该Pass的光照模式:

SubShader{
        Pass{
                    Tags{"LightMode"="ForwardBase"}
}
}

LightMode标签是Pass标签中的一种,它用于定义该Pass在Unity的光照流水线中的角色,在后面我们会更加详细的解释它。在这里我们只需要知道,只有定义了正确的LightMode,我们才能得到一些Unity内置光照变量,例如下面讲到的_LightColor0。
(3)然后,我们使用CGPROGRAM和ENDCG来包围Cg代码片段,以定义最重要的顶点着色器和片元着色器代码。首先,我们使用#pragma指令来告诉Unity,我们定义的顶点着色器和片元着色器叫什么名字。在本例中,它们的名字分别是vert和frag:

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

(4)为了使用Unity内置的一些变量,如后面讲到的_LightColor0,还需要包含进Unity的内置文件Lighting.cginc:

#include "Lighting.cginc"

(5)为了在Shader中使用Properties语义块中声明的属性,我们需要定义一个和该属性类型相匹配的变量:

fixed _Diffuse;

通过这样的方式,我们就可以得到漫反射公式中需要的参数之一——材质的漫反射属性。由于颜色的属性范围在0到1之间,我们可以使用fixed精度的变量来存储它。
(6)然后我们定义了顶点着色器的输入和输出结构体(输出结构体同时也是片元着色器的输入结构体):

struct a2v{
        float4 vertex:POSITION;
        float3 normal:NORMAL;
};
struct v2f{
        float4 pos:SV_POSITION;
        fixed3 color:COLOR;
};

为了访问顶点的法线,我们需要在a2v中定义一个normal变量,并通过NORMAL语义来告诉Unity要把模型顶点的法线信息存储到normal变量中。为了把在顶点着色器计算得到的光照颜色传递给片元着色器,我们需要在v2f中定义一个color变量,且并不是必须使用COLOR语义,一些资料中会使用TEXCOORD0语义。
(7)接下来是关键的顶点着色器。由于本小节关注如何实现一个逐顶点的漫反射光照,因此漫反射部分的计算都将在顶点着色器中进行:

v2f vert(a2v v){
v2f o;
//Transform the vertex from object space to projection space
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//Transform the normal fram object space to worldspace
fixed3 worldNormal = normalize(mul(v.normal,(float3×3)_World2Object);
//Get the light direction in world space
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
o.color = ambient + diffuse;
return o;
}

在第一行我们首先定义了返回值o。我们已经重复过很多次,顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间。因此我们需要使用Unity内置的模型×世界×投影矩阵UNITY_MATRIX_MVP来完成这的坐标变换。接下来我们通过Unity的内置变量UNITY_LIGHTMODEL_AMBIENT得到了环境光部分。
然后,就是真正计算漫反射光照的部分。回忆一下,为了计算漫反射光照我们需要知道四个参数。在前面的步骤中,我们已经知道了材质的漫反射颜色_Diffuse以及顶点法线v.normal。我们还需要知道光源的颜色和强度信息以及光源方向。Unity为我们提供了一个内置变量_LightColor0来访问该Pass处理的光源的颜色和强度信息(注意,想要得到正确的值需要定义合适的LightModel标签),而光源方向可以由_WorldSpaceLightPos0来得到。需要注意的是,这里对光源方向的计算并不具有通用性。在本节中,我们假设场景只有一个光源且该光源的类型是平行光。但如果场景中有多个光源并且类型可能是点光源等其它类型,直接使用_WorldSpaceLightPos0就不能得到正确的结果,我们将在后面学习如何使用内置函数来处理更复杂的光源类型。
在计算法线和光源方向之间的点积时,我们需要选择它们所在的坐标系,只有两者处于同一坐标空间下,它们的点积才有意义。在这里,我们选择了世界坐标空间。而由a2v得到的顶点法线是位于模型空间下的,因此我们首先需要把法线转换到世界空间中。在以前,我们已经知道可以使用顶点变换矩阵的逆转置矩阵对法线进行相同的变换,因此我们首先得到模型空间到世界空间的变换矩阵的逆矩阵_World2Object,然后通过调换它在mul函数中的位置,得到和转置矩阵相同的矩阵乘法。由于法线是一个三维矢量,因此我们只需要截取_World2Object的前三行前三列即可。
在得到了世界空间中的法线和光源方向后,我们需要对它们进行归一化操作。在得到它们的点积结果后,我们要防止这个结果为负值。为此,我们使用了saturate函数。saturate函数是Cg提供的一种函数,它的作用是可以把函数截取到[0,1]的范围。最后再与光源的颜色和强度以及材质的漫反射颜色相乘即可得到最终的漫反射光照部分。
最后,我们对环境光和漫反射光部分相加,得到最终的光照结果。
(8)由于所有的计算在顶点着色器中都已经完成了,因此片元着色器的代码很简单,我们只需要把顶点颜色输出即可:

fixed frag(v2f i):SV_Target{
return fixed(i.color,1.0);
}

(9)最后,我们需要把这个Unity Shader的回调Shader设置为内置的Diffuse:
Fallback "Diffuse"
至此,我们已经详细解释了逐顶点的漫反射光照的实现。对于细分程度较高的模型,逐顶点光照已经可以得到比较好的光照效果了。但对于一些细分程度较低的模型,逐顶点光照就会出现一些细节问题,就如上面的图片我们看到胶囊体的背光面与向光面交界处有一些锯齿。为了解决这些问题,我们可以使用逐像素的漫反射光照。

2.2 实践:逐像素光照

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

对以前的代码修改如下:
(1)修改顶点着色器的输出结构体v2f:

struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
}

(2)顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器即可。

v2f vert(a2v v){
v2f o;
//Transform the vertex from object space  to projection space
o.pos = mul(UNITY_MATRIX_MVP,v.vertex)
//transform the normal fram object space to world space
o.worldNormal=mul(v.normal,(float3×3)_World2Object);
return o;
}

(3)片元着色器需要计算漫反射光照模型:

fixed4 frag(v2f i):SV_Target{
//Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
//Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//Compute diffuse term
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}

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

2.3 半兰伯特模型

在2.1小结中,我们使用的漫反射光照模型也被称为兰伯特光照模型,因为它符合兰伯特定律——在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。为了改变上小结中提出的问题,Valve公司在开发游戏《半条命》时提出了一种技术,由于该技术是在原兰伯特光照模型的基础上进行了一个简单的修改,因此被称为半兰伯特光照模型。
广义的半兰伯特光照模型的公式如下:

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

通过这样的方式,我们可以把n·l的结果范围从[-1,1]映射到[0,1]的范围内。也就是说,对于模型的背光面,在原版兰伯特光照模型中点积结果将映射到同一个值,即0值处;而在半兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。
需要注意的是,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术。
我们只需把代码进行一点修改就可以得到半兰伯特模型:

fixed frag(v2f  i):SV_Target{
......
//Compute diffuse term
fixed halfLambert = dot(worldNormal,worldLightDir)*0.5+0.5;
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*halfLambert;
fixed3 color = ambient+diffuse;
return fixed(color,1.0);
}

在上面代码中,我们使用了半兰伯特模型替代了原有的兰伯特模型。下图给出了逐顶点漫反射光照、逐像素漫反射光照和半兰伯特光照的对比效果。

原文地址:https://www.cnblogs.com/xiegaosen/p/10847663.html

时间: 2024-07-28 15:10:28

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

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

转自冯乐乐的<Unity Shader入门精要> 通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象. 首先,光线从光源中被发射出来. 然后,光线和场景中的一些物体相交:一些光线被物体吸收了,而另一些光线被散射到其他方向. 最后,摄像机吸收了一些光,产生了一张图像. 在光学中,我们使用辐照度来量化光.对于平行光来说,它的辐照度可通过计算在垂直于l的单位面积上单位时间内穿过的能量来得到.在计算光照模型时,我们需要知道一个物体表面的辐照度,而物体表面往往是和l不垂直的,我们可以

《软件工程 ——理论、方法与实践》知识概括第五章 软件工程中的形式化方法

第5章 软件工程中的形式化方法    从广义上讲,形式化方法(Formal Method)是指将离散数学的方法用于解决软件工程领域的问题,主要包括建立精确的数学模型以及对模型的分析活动.狭义的讲,形式化方法是运用形式化语言,进行形式化的规格描述.模型推理和验证的方法.将形式化方法运用于软件工程实践当中的只要目的是保证软件的正确性. 软件生命周期中的形式化转化策略:常用转化策略.直接转化策略和运用半形式化表示的中间转化策略. 进行模型化的过程中涉及到三种系统模型:现实世界.模型表示和计算机系统.

【Unity】第6章 Unity脚本开发基础

分类:Unity.C#.VS2015 创建日期:2016-04-16 一.简介 游戏吸引人的地方在于它的可交互性.如果游戏没有交互,场景做得再美观和精致,也难以称其为游戏. 在Unity中,游戏交互通过脚本编程来实现.脚本可以理解为附加在游戏对象上的用于定义游戏对象行为的指令代码.通过脚本,开发者可以控制每一个游戏对象的创建.销毁以及对象在各种情况下的行为,进而实现预期的交互效果. 在Unity中进行脚本开发十分简易和高效,这是因为Unity的编辑器整合了很多脚本编辑的功能,比如脚本与游戏对象的

Unity Shader 之 基础光照

摄像机是如何看这个世界的 游戏中摄像机所看到的世界与我们现实中所看到的几乎是一样的. 首先,光线从光源中发射出来. 然后,光线和场景中的一些物体相交(散射,吸收). 最后,摄像机吸收了一些光,产生一张图像. 光线与物体相交的结果有两个:散射(scattering)和吸收(absorption) 散射:只改变光线的方向,但不改变光线的密度和颜色,有两种方向:内部与外部,对应折射与反射. 折射(refraction):散射到物体内部,用漫反射(diffuse)模型来计算. 反射(reflection

软件工程概论第五章--软件工程中的形式化方法

形式化方法指的是将离散数学的方法用于解决软件工程领域的问题,主要是建立精确的数学模型以及对模型的分析活动.在软件开发过程中运用数学模型有很多优点,例如能够解决规格说明的二义性,提高精确性,还能使软件相关问题的本质可以在不同抽象层次被展示出来.本章介绍形式化方法主要从形式化方法基本概念.时态逻辑.模型检验.Z语言.Petri网几个方面讲述. 形式化方法基本概念主要讲了形式规范.形式证明与验证.程序求精,形式规范说明是对软件系统对象,对象的操作方法,以及对象行为的描述.形式证明与验证主要包括模型检测

第五章 python中正则表达式的使用

第一节    正则表达式的一些介绍 1)掌握正则表达式的案例 2)写一个小爬虫 3)正则表达式(或RE)是一个小型的.高度专业化的编程语言,(在python中)它内嵌在python中,并通过re模块实现. - 可以为想要匹配的相应字符串集指定规则 - 该字符串集可能包含英文语句.e-mail地址.命令或任何你想搞定的东西 - 可以问诸如“这个字符串匹配该模式吗?” - “在这个字符串中是否有部分匹配该模式呢?” - 你也可以使用RE以各种方式来修改或分割字符串. 4)正则表达式模式被编译成一系列

第五章 CSS页面布局基础

1.标准文档流 在正常流中,在没有使用浮动或者定位的情况下,文本元素按照从上到下.从左到右的格式布局.这是浏览器的默认行为.在正常流中,块级元素从上到下依次排列,而行级元素从左到右依次排列.正常流中的元素影响其相邻元素的位置. 2.块级元素 前后换行显示,默认状态下独占一行: 块级元素可以作为容器,包含其他行级元素.块级元素: 块级元素有一定高度和宽度,可以通过width和height来设置. div,table,p,h1-h6,ul,form等等 3.行级元素(内嵌元素.内联标签) 类似于文本

第五章 python中的异常处理

每种编程语言都会有自己的异常处理机制,虽然各有特色,但基本上都差不多,那么python中强大异常处理机制是什么样的呢? 一.异常: python用异常对象来表示异常情况,遇到错误后,会引发异常.如果异常对象并未被处理或捕获,程序会用回溯终止执行: 1 >>> 1/0 2 Traceback (most recent call last): 3 File "<stdin>", line 1, in <module> 4 ZeroDivisionE

第五章 jQuery中的动画

http://www.cnblogs.com/shuibing/p/4080543.html 通过jQuery中的动画方法,能轻松地为网页添加精彩的视觉效果,给用户一种全新体验. 1.show()方法和hide()方法 该方法的功能与css()方法设置display属性效果相同. 给show()方法和hide()方法设置参数能有动画效果.例如 show(600);来设置时长600毫秒,同时改变元素的高度,宽度和不透明度. <!DOCTYPE html PUBLIC "-//W3C//DTD