热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Unityshader入门精要笔记(六)

   1.1.1   Unity中的基础光照从宏观上说,渲染包含了两大部分:决定一个像素的可见性,决定这个像素上的光照计算。而光照模型就是用于决定着一个像素上进行怎样的光照计算。 

     


1.1.1   Unity中的基础光照

从宏观上说,渲染包含了两大部分:决定一个像素的可见性,决定这个像素上的光照计算。而光照模型就是用于决定着一个像素上进行怎样的光照计算。

    通过模拟真实的光照环境来生成一张图像,需要考虑3中物理现象:

    [1]光线从光源(light source)中被发射出来

    [2]光线和场景中的一些物体相交;一些光线被物体吸收了,而另一些光线被散射到其他方向

    [3]摄像机吸收了一些光,产生了一张图像

 

通常把光源当作一个没有体积的点,用l来表示它的方向,用辐射度来量化光。对于平行光来说,光源的辐射度可通过计算在垂直于l的单位面积上单位时间内穿过的能量来得到。而在计算光照模型时,物体表面往往和l不垂直,对此,可通过使用光源方向l和表面发现n之间的夹角的余弦值来得到。

 

图 1.35 右图单位面积上接收到的光线少于左图

吸收和散射:

光线由光源发射出来后,就会与一些物体相交。通常,相交的结果有两个:散射(scattering)和吸收(absorption)。

散射只改变光线的方向,但不改变光线的密度和颜色;吸收只改变光线的密度和强度,不改变光线的方向。光线在物体表面经过散射后,有两种方向:一中将会散射到物体内部,该现象称为折射(refraction)或透射(transmission);另一种则会散射到外部,该现象称为反射(reflection)。

 

 

 

 

图 1.36 光线照射不透明物体后的过程

为区分两种不同的散射方向,在光照模型中使用了不同的部分来计算它们:高光反射(specular)部分表示物体表面是如何反射光线的,而漫反射(diffuse)部分则表示有多少光线会被折射、吸收和散射出表面。[个人可以是那么想的,高光反射是不进入物体内部,只看有多少光是会被弹开;而漫反射是需要光进入物体内部后,再与物体内部碰撞过后出来有多少光,以及被物体内部吸收了多少光,也就是损耗了多少光能,该光的反射后的方向是多变的]

 

着色:

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

 

BRDF光照模型:

    当给定模型表面上的一个点时,BRDF(Bidirectional Reflectance Distribution Function)包含了对该点外观的完整描述。在图形学中,BRDF大多使用一个数学公式来表示,并且提供了一些参数来调整材质属性。通俗来讲,当给定入射光线的方向和辐照度后,BRDF可以给出在某个出射方向上的光照能量分布。


1.1.2   标准光照模型

1973年,裴祥风提出标准光照模型背后的理念,标准光照模型只关心光照,也就是直接从光源发射出来照射到物体表面后,经过物体表面的第一次反射直接进入摄像机的光线。其基本方法是,把进入到摄像机内的光线分为4部分,每个部分使用一种方法来计算它的贡献度,4个部分分别是:

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

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

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

    环境光(ambient)部分,该部分用于描述其他所有的间接光照。

 

(分析一张图片中共有多少种光: https://m.weibo.cn/5596473314/4653168161722212,挺有意思的,可以看一下不同光的效果)

 

环境光:

在真实世界中,物体可以被间接光照(indirect light)所照亮,间接光照指的是,光线通常会在多个物体之间反射,最后进入摄像机,即在光线进入摄像机之前,经过了不止一次的物体反射,如在红地毯上放上一个浅色沙发,沙发底部将会有红色,该红色是由红地毯反射了一部分光线再反弹到沙发上。

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

  

 

自发光:

光线直接由光源发射进入摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光来计算这个部分的贡献度,其计算是直接使用了该材质的自发光颜色。

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

 

漫反射:

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

    漫反射光照符合兰伯特定律(Lambert’s law):反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比。其公式如下:

 

 n为表面法线;  l为光源的单位矢量; m 为材质的漫反射颜色; c光源颜色

 

高光反射:

    计算高光反射需要信息较多,如表面法线、视觉方向、光源方向、反射方向等。(高中或大学物理大多数的光照计算题皆是高光反射)

反射方向计算公式:

  

图 1.37 Phong模型计算高光

就算高光反射部分的公式:

 

 m (gloss)为材质的光泽度(gloss),也被称为反光度(shininess),m (gloss)越大,亮点越小(即材质表面越光滑)    m(specular) 为材质的高光反射颜色,其用于控制该材质对于高光反射的强度和颜色     c(light)为光源的颜色和颜色

 

与Phong模型相比,还有Blinn模型

  

图 1.38 Blinn模型

计算公式:

 

在片元着色器中计算,也被称为逐像素光照(pre-pixel lighting);在顶点着色器中计算,也被称为逐顶点光照(pre-vertex lighting)

在逐像素光照中,将以每个像素为基础,得到它的法线,从而进行光照模型的计算,该面片之间对顶点发现进行插值就散称为phong着色。

而逐顶点光照也被称为高洛德着色,在其中将会在每个顶点上计算光照,然后在渲染图元内部进行线性插值,最后输出像素颜色。


1.1.3   Unity中的环境光和自发光

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

在Unity中,场景中的环境光可以在Uindow->Lighting->Ambient Source/Ambient Color/Ambient Intensity中控制。如下图所示:

 

图 1.39 Unity组件控制环境光

在Shader中通过Unity的内置函数UNITY_LIGHTMODEL_AMBIENT可以得到环境光的颜色和强度信息。

    而如果需要添加自发光,只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色即可。


1.1.4   Unity中实现漫反射光照模型

逐顶点光照:

Cg中的saturate函数可以起到防止点积结果为负值,起到max操作的作用[注:saturate译:饱和; 使饱和; 使湿透; 浸透;]

    函数:saturate(x)

    参数:x:用于草做的标量或矢量,可以是float、float2、float3等

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

 

实践:逐顶点光照

[1]构建场景

[2]新建材质与shader文件,并将shader文件赋给该材质

[3]打开shader代码,并清空,并把下代码赋值上去

Shader "Unity Shaders Book/Chapter6/DiffuseVertexLevel" { //给该shader起名
Properties{ //得到并控制材质的漫反射颜色,在Properties语义块中生命一个color类型的属性,并初始值设为白色
_Diffuse("Diffuse", Color) = (1,1,1,1)
}
Subshader{
Pass{ //顶点/片元着色器的代码需要写在pass中,所以需要定义一个pass语义块而非subshader中
Tags{
"LightMode"="ForwardBase" //明确该pass的光照模式,LightMode标签是Pass标签中的一种,用于定义该Pass在Unity的光照流水线的角色,只有定义了正确的LightMode才可以得到一些Unity的内置光照变量
}
CGPROGRAM //CGPROGRAM和ENDCG来包裹cg代码片,以定义顶点着色器和片元着色器
#pragma vertex vert //顶点着色器名字叫vert
#pragma fragment frag //片元着色器名字叫frag
#include "Lighting.cgnic" //使用内置的一些函数,从而需要包含对应内置文件
fixed4 _Diffuse; /因为在Properties中生命了实行,所以要定义一个与之匹配的变量。同时这里将得到漫反射公式所需要的材质的漫反射属性,颜色范围在0-1之间,从而使用fixed精度定义来存储
struct a2v{ //顶点着色器的输入结构体
float4 vertex : POSITION;
float3 normal : NORMAL; //为访问顶点的法线从而需定义normal变量,并通过使用NORMAL来告诉Unity要把模型顶点的法线信息存储到normal变量中
}
struct v2f{ //顶点着色器的输出结构体同时也是片元着色器的输入结构体
float4 pos : SV_POSITION;
fixed3 color : COLOR; //为访问顶点着色器中急速三得到的光照颜色传递给片元着色器从而需要定义color变量
}
v2f vert(a2v v){ //本例子关注如何实现逐顶点的漫反射光照,从而漫反射部分的计算都将在顶点着色器中进行
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex); //顶点坐标变化从模型空间转到投裁剪空间
//该部分就是开始按照前文所给的公式开始进行计算
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //通过该内置变量得到环境光部分
//法线和光源方向需要在同一个坐标轴下点积才有意义,因此皆转换为世界坐标系中
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//_WorldSpaceLightPos()提供光源方向
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight)); //_LightColor是unity提供的内置变量,可访问该Pass处理的光源颜色和强度,同时防止为负数,从而使用saturate函数
o.color = ambient + diffuse;
return o;
}
fixed4 frag(v2f i):SV_Target{ //计算过程皆在顶点着色器中完成,从而片元着色器只需要把顶点颜色输出即可
return fixed(i.color, 2.0); }
ENDCG
}
}
Fallback"Diffuse" //把该Unity shader的回调shader设置为内置的Diffuse
}

[4]可查看效果

逐像素光照:

[1]构建场景

[2]构建材质与shader,并把该shader赋给该材质

[3],和上面的差不多但是顶点着色器不需要计算光照模型,只需要在世界空间下的法线传递给片元着色器即可;片元着色器需要计算漫反射光照模型

逐像素光照可以得到更加平滑的光照效果,而逐顶点光照对于戏份度程度较高的模型会得到比较好的光照效果,但对于细分成度较低的模型则会出现锯齿问题。但逐像素光照在光照无法到达的区域,模型的外观通常是全黑的,而对此可通过添加环境光来得到非全黑效果,但无法解决背光面明暗一样的缺点,从而半兰伯特光照模型被提出。

 

半兰伯特光照模型:

    漫反射光照模型也被称为兰伯特光照模型。半兰伯特光照模型规律为:在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。而半兰伯特光照模型是在其基础上进行简单的修改。

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

 

在绝大多情况下,和的值取0.5,即:

 

通过该公式,可以将的结果范围从[-1,1]映射到[0,1]

半兰伯特没有任何物理依据,仅仅只是一个视觉加强技术。

fixed halfLambert = dot( worldNormal,worldLightDir ) *0.5 *0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert.rgb;
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);

  

图 1.40 效果对比图(逐顶点、逐像素、半兰伯特)

 


1.1.5   Unity中实现高光反射光照模型

函数:reflect(i,n)——[计算反射方向函数]

参数:i,入射方向;n,法线方向

当给定入射方向i和法线方向n时,reflect函数可以返回反射方向

 

图 1.41 reflect函数

逐顶点光照代码:

Shader "Unity Shaders Book/Chapter6/Specular Vertex-Level" { //材质面板中为方便控制高光反射属性,从而在Porperties语义块中声明3个属性
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1) //材质漫反射颜色
_Specular("Specular", Color) = (1,1,1,1) //高光反射颜色
_Gloss("Gloss", Range(8.0,256)) = 20 //高光区域大小
}
Subshader{
Pass{
Tags{
"LightMode"="ForwardBase" //标签,定义该pass在unity的光照流水线的角色
}
CGPROGRAM //CG代码块开始
#pragma vertex vert //定义顶点着色器
#pragma fragment frag //定义片元着色器
#include "Lighting.cgnic" //调用内置文件
fixed4 _Diffuse; //定义与Properties对应的属性,color范围为[0,1],从而可用fixed来存储
fixed4 _Specular
float _Gloss //Gloss范围较大,从而用float存储
struct a2v{ //顶点着色器输入结构体
float4 vertex : POSITION;
float3 normal : NORMAL;
}
struct v2f{ //片元着色器输入结构体
float4 pos : SV_POSITION;
fixed3 color : COLOR;
}
v2f vert(a2v v){ //计算包含高光反射的光照模型
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex); //顶点从模型空间转换到投影空间
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_World2Object)); //将法线坐标从模型空间转换到世界空间
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); //得到世界空间的光线方向
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir)); //计算扩散项
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)); //得到世界空间下反射后的方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_Object2World, v.vertex).xyz); //得到世界空间下的视线方向
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(reflectDir, viewDir))._Gloss); //计算反射项
o.color = ambient + diffuse;
return o;
}
fixed4 frag(v2f i):SV_Target{
return fixed(i.color, 1.0);
}
ENDCG //CG代码块结束
}
}
Fallback"Specular"
}

 

 

逐像素光照则是片元着色器中计算关键的光照模型,而顶点着色器只需要计算世界空间下的法线方向和顶点坐标,并把它们传递给片元着色器。按像素方式可以得到更光滑的高光效果。

 

Blinn光照模型没有使用反射方向,而是插入新的矢量,并通过视角方向和光照方向相加后再归一化得到/

 

其高光反射公式如下:

修改代码:

v2f vert(a2v v){ //计算包含高光反射的光照模型
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex); //顶点从模型空间转换到投影空间
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_World2Object)); //将法线坐标从模型空间转换到世界空间
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); //得到世界空间的光线方向
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir)); //计算扩散项
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)); //得到世界空间下反射后的方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); //得到世界空间下的视线方向
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return fixed4(ambient + specular + diffuse, 1.0);
}

  

图 1.42 高光反射(逐顶点、逐像素、blinn)


1.1.6   Unity内置函数

在计算光照模型的时候,开发者通常需要得到光源方向、视角方向两个基本信息。在上述例子中,开发者可使用 normalize(_WorldSpaceLightPos0.xyz)来得到光源方向(该方法通常只适用于平行光);normalize(_WorldSpaceCameraPos.xyz-i.worldPosition.xyz)来得到视角方向。但如果想要处理更复杂的光照类型,如点光源和聚光灯,该计算光源方向是错误的。从而开发者需要在代码中先判断光源类型。计算它的光源信息。

手动计算光源信息的过程十分麻烦,从而Unity提供了一些内置函数来帮助开发者计算该信息。在UnityCG.cginc中有一些有用的帮助函数,如下图所示:

 

图 1.43 UnityCG.cginc中有用的函数

 

以上函数没有保证得到的方向矢量是单位矢量,从而在使用前需要进行归一化。

计算光源方向的3个函数:WorldSpaceLightDir、UnityWorldSpaceLightDir、ObjSpaceLightDir仅可用于前向渲染。只有在前向渲染时,以上3函数里使用的内置变量_WorldSpaceLightPos0等才会



推荐阅读
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文研究了使用条件对抗网络进行图片到图片翻译的方法,并提出了一种通用的解决方案。通过学习输入图像到输出图像的映射和训练相应的损失函数,我们可以解决需要不同损失函数公式的问题。实验证明该方法在合成图片、重构目标和给图片着色等多个问题上都很有效。这项工作的重要发现是不再需要人为构建映射函数和损失函数,同时能够得出合理的结果。本文的研究对于图片处理、计算机图片合成和计算机视觉等领域具有重要意义。 ... [详细]
  • C# WPF自定义按钮的方法
    本文介绍了在C# WPF中实现自定义按钮的方法,包括使用图片作为按钮背景、自定义鼠标进入效果、自定义按压效果和自定义禁用效果。通过创建CustomButton.cs类和ButtonStyles.xaml资源文件,设计按钮的Style并添加所需的依赖属性,可以实现自定义按钮的效果。示例代码在ButtonStyles.xaml中给出。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • EzPP 0.2发布,新增YAML布局渲染功能
    EzPP发布了0.2.1版本,新增了YAML布局渲染功能,可以将YAML文件渲染为图片,并且可以复用YAML作为模版,通过传递不同参数生成不同的图片。这个功能可以用于绘制Logo、封面或其他图片,让用户不需要安装或卸载Photoshop。文章还提供了一个入门例子,介绍了使用ezpp的基本渲染方法,以及如何使用canvas、text类元素、自定义字体等。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
author-avatar
黑铁1988
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有