【Unity&Shader】基础光照原理及代码实践

ConvNeXt 改进 :ConvNeXt添加GnConv递归门控卷积,二次创新CNBlock结构 ,独家首发 视觉 Transformer 在多种任务中取得了显著的成功,这得益于基于点积自注意力的新空间建模机制。视觉 Transformer 中的关键因素——即输入自适应、长距离和高阶空间交互——也可以通过卷积框架高效实现。作者提出了递归门控卷积(Recursive Gated Convolution,简称 gnConv),通过门控卷积和递归设计执行高阶空间交互。该新操作具有高度的灵活性和可定制性,兼容各种卷积变体,并将自注意力中的二阶交互扩展到任意阶数,而不会引入显著的额外计算。 阅读详情

《Unity+Shader入门精要》学习 第六章
Unity版本:2019.4.23f1c1

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

  1. 首先,光线从光源种被发射出来。
  2. 然后,光线和场景种的一些物体相交:一些光线被物体吸收了,而另一些光线被散射到其他方向。
  3. 最后,摄像机吸收了一些光,产生了一张图像。

光源

在实时渲染中,我们通常把**光源(light source)**当成一个没有体积的点,用 l 来表示它的方向。

在光学里,我们使用**辐射度(irradiance)**来量化光(测量一个光源发射出多少光)。

对于平行光来说,它的辐射度可通过计算在垂直于l的单位面积上单位时间内穿过的能量来得到。在计算光照模型时,我们需要知道一个物体表面的辐射度,而物体表面往往是和l不垂直的。计算表面的辐射度时,可以使用光源方向l和表面法线n之间的夹角的余弦cos值来得到。需要注意的是,这里默认方向矢量的模都为1。
示例图
左图中,光是垂直照射到物体表面,因此光线之间的垂直距离不变;
右图中,光是斜着照射到物体表面,在物体光线之间的距离是d/cosθ,因此单位面积接收到光线数目要少于左图。

吸收和散射

光线由光源发射出来后,就会与一些物体相交。通常,相交的结果有两个:

  • 散射(scattering):只改变光线的方向,但不改变光线的密度和颜色。
  • 吸收(absorption):只改变光线的密度和颜色,但不改变光线的方向。

光线在物体表面经过散射后,有两种方向:

  • 折射(refraction)或透射(transmission):散射到物体内部。
  • 反射(reflection):散射到外部。
    在这里插入图片描述
    散射时,光线会发生折射和反射现象。对于不透明物体,折射进入物体内部的光线还会继续与内部的颗粒进行相交,其中一些光线最后会重新发射出物体表面,而另一些则被物体吸收。那些从物体表面重新发射出的光线将具有和入射光线不同的方向和颜色。

为了区分两种不同的散射方向,在光照模型中使用了不同的部分来计算:

  • 高光反射(specular):表示物体表面是如何反射光线
  • 漫反射(diffuse):表示有多少光线会被折射、吸收和散射出表面。出射度:根据入射光线的数量和方向,我们可以计算出射光线的数量和方向。辐照度和出射度之间是满足线性关系的,而它们之间的比值就是材质的漫反射和高光反射属性。

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

着色

着色(shading):根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿某个观察方向的出射度的过程。我们也把这个等式称为光照模型(Lighting Model)。

BRDF光照模型

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

	计算机图形学的第一定律:如果它看起来是对的,那么它就是对的。——《3D数学基础:图形与游戏开发》邓恩

标准光照模型

在BRDF理论被提出之前,标准光照模型已被广泛使用。

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

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

  1. 自发光(emissive):用于描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。需要注意的是,如果没有使用全局光照(global illumination)技术,这些自发光的表面并不会真的照亮周围的物体,二十它本身看起来更亮了而已。
  2. 高光反射(specular):用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
  3. 漫反射(diffuse):用于描述当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
  4. 环境光(ambient):用于描述其他多有的间接光照。

环境光

标准光照模型的重点在于描述直接光照,而现实中,物体也可以被间接光照(indirect light)所照亮。

间接光照:光线通常会在多个物体之间反射,最后进入摄像机,即在光线进入摄像机之前,经过了不止一次的物体反射。

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

自发光

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

通常在实时渲染中,自发光的表面往往不会照亮周围表面,即这个物体并不会被当成一个光源。

漫反射

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

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

cdiffuse=(clight·mdiffuse)max(0,n·I)

漫反射=(光源颜色·材质的漫反射颜色)max(0,表面法线·光源的单位矢量)

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

高光反射

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

计算高光反射需要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。此处假设这些矢量都是单位矢量。
在这里插入图片描述
在这四个矢量中,我们实际只需要知道其中3个矢量即可,而第四个矢量——反射方向可以通过其他信息计算得到:
在这里插入图片描述
这样,我们就可以利用Phong模型来计算高光反射的部分:
在这里插入图片描述

其中,mgloss是材质的光泽度,也被称为反光度。用于控制高光区域的“亮点”有多宽,mgloss越大,亮点越小。mspscular是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。clight则是光源颜色和强度。同样,这里也需要防止v·r的结果为负数。

和上述的Phong模型相比,Blinn提出了一个简单的修改方法来得到类似的结果。它的基本思想是,避免计算反射方向r。为此,Blinn模型引入了一个新的矢量h,它是通过对v和I的取平均后再归一化得到的。即
在这里插入图片描述
然后,使用n和h之间的夹角进行计算,而非v和r之间的夹角,
总结,Blinn模型公式为
在这里插入图片描述
在硬件实现时,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,这是因为,此时可以认为v和I都是定值,因此h将是一个常量。但是,当v和L不是定值时,Phong模型可能反而更快一些。需要注意的是,这两种光照模型都是经验模型,即我们不应该认为Blinn模型是对“正确的”Phong模型的近似。实际上,在一些情况下,Blinn模型更符合实验结果。

逐像素还是逐顶点

通常在两个地方计算这些光照模型:

  • 逐像素光照,在片元着色器中计算。
  • 逐顶点光照,在顶点着色器中计算。

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

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

总结

虽然标准光照模型仅仅是一个经验模型,即它并不完全符合真实世界中的光照现象。但由于它的易用性、计算速度和得到的效果都比较好,因此仍然被广泛使用。而也是由于它的广泛使用性,这种标准光照模型有很多不同叫法。例如:Phong光照模型,因为裴祥风(Bui Tuong Phong)首先提出了使用漫反射和高光反射的和来对反射光照进行建模的基本思想,并且提出了基于经验的计算高光反射的方法(用于计算漫反射光照的兰伯特模型在那时已经被提出)。而后,由于Blinn的方法简化了计算而且在某些情况下计算更快,我们把这种模型称为Blinn-Phong光照模型。

但这种模型有很多局限性。首先,有很多重要的物理现象无法用Blinn-Phong模型表现出来,例如菲涅尔反射(Fresnel reflection)。其次,Blinn-Phong模型是各向同性(isotropic)的,也就是说,当我们固定视角和光源方向旋转这个表面时,反射不会发生任何变化。但有些表面是具有各向异性(anisotropic)反射性质的,例如拉丝金属、毛发等。

Unity中的环境光和自发光

照明设置

在Unity Shader中实现漫反射光照模型

基本光照模型中漫反射部分计算公式:
cdiffuse=(clight·mdiffuse)max(0,n·I)
可知,要计算漫反射需要知道4个参数:入射光线的颜色和强度clight,材质的漫反射系数mdiffuse,表面法线n以及光源方向I。
为了防止点积结果为负值,我们需要使用max操作,而CG提供了这样的函数(CG的saturate函数可以达到同样目的)

  • 函数:saturate(x)
  • 参数:x:为用于操作的标量或矢量,可以是float、float2、float3等类型。
  • 描述:把x截取在[0,1]范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作。

实践:逐顶点光照

新建场景,包含一个摄像机和一个平行光,去掉内置天空盒
去除环境
新建一个3d物体
新建一个材质,赋予物体
新建一个shader,赋予材质
编写shader:

Shader "MyShader/DiffuseVertexLevel"
{
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)//初始值设为白色
    }
        SubShader
    {
        Pass//顶点/片元着色器的代码需要写在Pass语义块
        {
             Tags{
                "LightMode" = "ForwardBase"//指明该Pass的光照模式
            }

            CGPROGRAM//CGPROGRAM和ENDCG来包围CG代码片,以定义顶点着色器和片元着色器代码

            #pragma vertex vert//顶点着色器名字
            #pragma fragment frag//片元着色器名字

            #include "Lighting.cginc"//unity内置文件

            fixed4 _Diffuse;//为了使用properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
            //得到材质的漫反射属性,由于颜色属性的范围在0到1之间,因此可以使用fixed精度的变量来存储它

            struct a2v//定义顶点着色器的输入结构体
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f//定义顶点着色器的输出结构体  同时也是片元着色器的输入结构体
            {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            //顶点着色器
            v2f vert(a2v v)
            {
                v2f o;//定义返回值o
                //顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁剪空间。使用内置UnityObjectToClipPos来完成坐标转换,得到环境光
                o.pos = UnityObjectToClipPos(v.vertex);

                //计算漫反射(已知才知道漫反射颜色_Diffuse以及顶点法线v.normal,还需要知道光源的颜色和强度信息以及光源方向
                //内置变量_LightColor0可访问该Pass处理的光源的颜色和强度信息(需要定义合适的LightMode标签)
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                //选择世界坐标空间(计算发现和光源方向之间的点积,两者需处于同一坐标空间
                //a2v得到的顶点法线是位于模型空间下,此处需要把法线转换到世界空间
                //首先得到模型空间到世界空间的变换举证的逆矩阵_World2Object,然后通过调换它在mul函数中的位置,得到和转置矩阵相同的矩阵惩罚。
                fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
                //光源方向可以由_WorldSpaceLightPos0来得到(只适用一个光源且为平行光
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                //得到世界空间中的法线和光源方向后,对它们进行归一化。在得到点积结果,防止这个结果为负值,使用saturate函数。
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
                
                o.color = ambient + diffuse;

                return o;
            }

            //片元着色器:直接把顶点颜色输出即可
            fixed4 frag(v2f i) :SV_Target
            {
                return fixed4(i.color,1.0);
            }

            ENDCG
        }
    }
        //把这个UnityShader的回调shader设置为内置的Diffuse
        Fallback "Diffuse"
}

在这里插入图片描述

实践:逐像素光照

只需要对Shader进行一些更改就可以实现逐像素的漫反射效果。

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "MyShader/DiffusePixelLevel"
{
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)
    }
        SubShader
    {
        Pass
        {
             Tags{
                "LightMode" = "ForwardBase"
            }

           CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Diffuse;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

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

            //顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器即可
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                return o;
            }

            //片元着色器需要计算漫反射光照模型
            fixed4 frag(v2f i) :SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                fixed3 color = ambient + diffuse;

                return fixed4(color,1.0);
            }

            ENDCG
        }
    }
                Fallback "Diffuse"
}

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

半兰伯特模型

兰伯特定律——在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。(上面使用的漫反射光照模型也被称为兰伯特光照模型.

为了改善上述提出的问题,Valve公司在开发游戏《半条命》时提出了一种技术,由于该技术是在原兰伯特光照模型的基础上进行一个简单的修改,因此被称为半兰伯特光照模型。

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

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

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

Shader "MyShader/HalfLambert"
{
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)//初始值设为白色
    }
        SubShader
    {
        Pass//顶点/片元着色器的代码需要写在Pass语义块
        {
             Tags{
                "LightMode" = "ForwardBase"//指明该Pass的光照模式
            }

           CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Diffuse;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

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

            //顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器即可
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                return o;
            }

            //片元着色器需要计算漫反射光照模型
            fixed4 frag(v2f i) :SV_Target
            {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//获取环境光部分的变量
                fixed3 worldNormal = normalize(i.worldNormal);//归一化操作
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//获取光源的方向,
                fixed3 halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;//半兰伯特
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;//_LightColor0获取光源的颜色和强度
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }
        Fallback "Diffuse"
}

在这里插入图片描述
背光:
在这里插入图片描述

在Unity Shader中实现高光反射光照模型

基本光照模型中高光反射部分的计算公式:
在这里插入图片描述
可知,要计算高光反射需要知道4个参数:入射光线的颜色和强度clight,材质的高光反射系数mspecualr,视角方向v以及反射方向r。其中,反射方向r可以由表面法线n和光源方向I计算而得:
在这里插入图片描述
CG提供了计算反射方向的函数reflect(i,n)
函数:reflect(i,n)
参数:i,入射方向;n,法线方向。可以是float、float2、float3等类型。
描述:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。
参数和返回值之间的关系:在这里插入图片描述

实践:逐顶点光照

Shader "MyShader/SpecularVertexLevel"
{
    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"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

        //由于颜色属性范围在0到1之间,因此_Diffuse和_Specular属性可以使用fixed精度变量来存储
        //_Gloss的范围很大,因此使用float精度来存储
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

        //顶点着色器输入结构体
            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 = UnityObjectToClipPos(v.vertex);

                fixed ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
            
            //高光反射
                //计算入射光线方向关于表面法线的反射方向reflectDir。CG的reflect函数的入射方向要求是由光源指向交点处的,因此需要对worldLightDir取反后再传给reflect函数
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                //通过_WorldSpaceCameraPos得到世界空间中的摄像机位置,再把顶点位置从模型空间变换到世界空间下,再通过和_WorldSpaceCameraPos相减即可得到世界空间下的视角方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

                //已知4个参数,代入公式即可得到高光反射的光照部分。
                fixed3 specular = _LightColor0.rbg * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                //最后再和环境光、漫反射光相加存储到最后的颜色中
                o.color = ambient + diffuse + specular;
                return o;
            }
        //片元着色器:直接返回顶点颜色即可
            fixed4 frag(v2f i) :SV_Target{
                return fixed4(i.color,1.0);
            }


            ENDCG
        }
    }
    //把这个Unity Shader的回调Shader设置为内置的Specular
    FallBack "Specular"
}

在这里插入图片描述
使用逐顶点的方法得到的高光效果有比较大的问题,可以看出高光部分明显不平滑。这主要是因为,高光反射部分的计算是非现行的,而在顶点着色器中计算光照再进行插值的过程是线性的,破坏了原计算的非线性关系,就会出现较大的视觉问题。因此我们需要使用逐像素方法来计算高光反射。

实践:逐像素光照

Shader "MyShader/SpecularPixelLevel"
{
    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"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };
            struct v2f {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };
        //顶点着色器只需要计算世界空间下的法线方向和顶点坐标,并把他们传递给片元着色器
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
  
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;

            }
            fixed4 frag(v2f i) :SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.zyx - i.worldPos.zyx);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                
                return fixed4(ambient + diffuse + specular,1.0);
            }
            ENDCG            
        }
    }
        FallBack "Specular"
}

在这里插入图片描述

Blinn-Phong光照模型

Blinn模型没有使用反射方向,而是引入一个新的矢量h,它是通过对视角方向v和光照方向I相加后再归一化得到的。即
在这里插入图片描述
而Blinn模型计算高光反射的公式:
在这里插入图片描述
代码中只需要修改片元着色器中对高光反射部分的计算:

Shader "MyShader/BlinnPhong"
{
    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"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };
            struct v2f {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };
            //顶点着色器只需要计算世界空间下的法线方向和顶点坐标,并把他们传递给片元着色器
                v2f vert(a2v v) {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);

                    o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                    return o;

                }
                fixed4 frag(v2f i) :SV_Target
                {
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                    fixed3 worldNormal = normalize(i.worldNormal);
                    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                    fixed3 viewDir = normalize(_WorldSpaceCameraPos.zyx - i.worldPos.zyx);
                    fixed3 halfDir = normalize(worldLightDir + viewDir);
                    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)), _Gloss);

                    return fixed4(ambient + diffuse + specular,1.0);
                }
                ENDCG
            }
    }
        FallBack "Specular"
}

在这里插入图片描述

使用Unity内置函数

UnityCG.cginc中一些常用的帮助函数

函数名描述
float3 WorldSpaceViewDir (float4 v)输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。内部实现使用了UnityWorldSpaceViewDir函数
float3 UnityWorldSpaceViewDir (float4 v)输入一个世界空间的顶点位置,返回世界空间中从该点到摄像机的观察方向
float3 ObjSpaceViewDir (float4 v)输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向
float3 WorldSpaceLightDir (float4 v)仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir函数。没有被归一化
float3 UnityWorldSpaceLightDir (float4 v)仅可用于前向渲染中。输入一个世界空间中的顶点位置,返回世界空间中从该点到光源方向。没有被归一化
float3 ObjSpaceLightDir (float4 v)仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化
float3 UnityObjectToWorldNormal (float3 norm)把法线方向从模型空间转换到世界空间中
float3 UnityObjectToWorldDir (in float3 dir)把方向矢量从模型空间变换到世界空间中
float3 UnityWorldToObjectDir (float3 dir)把方向矢量从世界空间变换到模型空间中

类似UnityXXX的几个函数是Unity5中新添加的内置函数,使得我们不需要根各种变换矩阵、内置变量打交道,也不需要考虑各种不同的情况(例如使用了那些光源),而仅仅调用一个函数就可以得到需要的信息。

注意:这些函数都没有保证得到的方向矢量是单位矢量,因此,我们需要在使用前把它们归一化。

计算光源方向的3个函数:WorldSpaceLightDir、UnityWorldSpaceLightDir和ObjSpaceLightDir,稍微复杂一些,这是因为,Unity帮我们处理了不同种类光源的情况。注意:这3个函数仅可用于前向渲染。这是因为只有在前向渲染时,这3个函数里使用的内置变量——WorldSpaceLightPos0等才会被正确赋值。

内置函数改写上面Blinn-Phong光照模型代码

Shader "MyShader/BlinnPhong"
{
    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"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };
            struct v2f {
                float4 pos:SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };
                v2f vert(a2v v) {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);

                    o.worldNormal = UnityObjectToWorldNormal(v.normal);//改写部分1
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                    return o;

                }
                fixed4 frag(v2f i) :SV_Target
                {
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                    fixed3 worldNormal = normalize(i.worldNormal);
                    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));//改写部分2

                    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));//改写部分3
                    fixed3 halfDir = normalize(worldLightDir + viewDir);
                    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)), _Gloss);

                    return fixed4(ambient + diffuse + specular,1.0);
                }
                ENDCG
            }
    }
        FallBack "Specular"
}
11、改进的加权轮询调度算法 本文详细介绍了对传统加权轮询(WRR)调度算法的改进方法,包括动态权重调整、优先级调度和预测机制,旨在提升系统的公平性、响应速度和适应性。通过设计与实现改进算法,并结合实验验证其在响应时间、资源利用率和公平性方面的显著提升,同时列举了该算法在数据中心、云服务平台、CDN及电商平台等实际场景中的应用案例。 阅读详情

相关推荐

abap中使用Screen painter为普通屏幕创建单选按钮组radiobutton组的过程

选择屏幕中,创建radiobutton组直接使用代码实现,如下所示 SELECTION-SCREEN BEGIN OF BLOCK BK1 WITH FRAME TITLE TEXT-001. PARAMETERS R1 RADIOBUTTON GROUP GR1. PARAMETERS R2 RADIOBUTTON GROUP GR1. PARAMETERS R3 RADIOBUTTON GR

charliefromkansas的博客 8298

Unity Shader入门精要 第六章——Unity中的基础光照

逐像素光照,以每个像素为基础,得到它的法线(可以是对顶点法线插值得到的,也可以是从法线纹理中采样得到的),然后进行光照模型计算,这种在面片之间对顶点法线进行插值的技术称为Phong着色,也被称为Phong插值或法线插值技术,这不同于Phong光照模型。上面两个模型(也叫兰伯特光照模型)有个问题就是,光照没有照射到的地方是全黑的,没有明暗变化,导致背面就像是有个平面一样。与原来的兰伯特光照模型相比,半兰伯特光照模型没有使用max操作来防止n*I的点积为负值,而是进行了一个α倍的缩放和β大小的偏移。

buzhengli的博客 2518

气象干旱触发水文(农业)干旱的概率及其触发阈值的动态变化-贝叶斯copula模型

本文通过建立不同类型干旱传播过程的触发阈值模型,解析了在不同等级气象干旱胁迫下诱发次一级干旱类型的触发阈值。通过贝叶斯概率模型,我们推导了气象干旱与水文干旱、农业干旱、地下水干旱之间的条件概率关系,进一步明确了气象干旱引发水文干旱、农业干旱和地下水干旱的概率及其触发阈值。该模型有效揭示了气象干旱对各级干旱类型的传播机制及其动态变化,为干旱预警系统的建立和水资源管理提供了理论依据和技术支持。

frankgis的博客 2482

屏幕空间反射和光线追踪_用unity写光线追踪3:反射与折射

本系列文章仅为我的学习经验交流,错误很多,请结合闫令琪大佬的图形学课程一起食用~本文的叙述部分涉及的向量皆为单位向量。0.光与物体表面的交互前面我们能够判断光是否打到了物体表面(为什么加个“表面”?因为有的物体的表面很难得到,比如云雾。光说光是否打到物体上有些不严谨)上,而渲染的目标就是计算物体表面某点的颜色:计算能射到此点的所有光线(什么叫“全局”?这就叫“全局”),以得到视角方向的颜色。而考察...

weixin_39830323的博客 1525

第二节_PySide6项目创建流程介绍、手把手教学打包EXE可执行文件

本文详细介绍了如何使用PySide6创建一个带UI界面的Python程序,并将其打包成exe可执行文件。首先,通过Pycharm创建项目,并使用PySide6-Designer工具设计UI界面,将生成的ui文件转换为Python代码。接着,编写主程序,加载UI界面并实现功能,如加载图片、设置图标等。最后,通过资源文件管理图片资源,并确保UI界面的更新能同步到主程序中。整个过程涵盖了项目创建、UI设计、代码编写及资源管理,适合初学者学习如何将Python程序打包为可执行文件。

su桥木的博客 2538

ESP32-C3入门教程 环境篇②——Windows开发环境准备

文章目录一、注意事项二、下载ESP-IDF工具安装器三、安装内容四、详细安装步骤报错处理 一、注意事项 请注意 ESP-IDF 和 ESP-IDF 工具的安装路径不能超过 90 个字符,安装路径过长可能会导致构建失败。 Python 或 ESP-IDF 的安装路径中一定不能包含空格或括号。 除非操作系统配置为支持 Unicode UTF-8,否则 Python 或 ESP-IDF 的安装路径中也不能包括特殊字符(非 ASCII 码字符、中文字符) 二、下载ESP-IDF工具安装器 ESP-IDF Win

5239

Unity Shader 常规光照模型代码整理

本次整理在Unity中几种常见的光照模型,包含 1.BlinnPhong 2.ForwardRender 3.AttenuationAndShadow 4.Stander 5.CustonStander

weixin_39289457的博客 1754

遇到问题--OpenClaw--API rate limit reached. Please try again later429 您的账户已达到速率限制,请您控制请求频率

OpenClaw调用glm-4.7-flash模型时触发智谱AI平台429速率限制错误。分析发现当前重试策略采用固定间隔(7-11秒)导致连续失败,且未记录关键响应头信息。解决方案分三步:1)开启响应头日志获取配额详情;2)将重试策略改为带抖动的指数退避(初始1秒,最大60秒);3)在网关层实现令牌桶限流主动控制请求频率。需特别注意令牌配额(TPM)可能比请求次数(RPM)更早触达限制。

直到世界的尽头 2592

0基础尝试实现UnityURP渲染管线的AdditionalLit(点光源,聚光灯)非平行光的光照模型代码

目录 0基础其实最好 代码的本质 代码的入口 代码分析 代码逻辑 代码实现 自行练习 0基础其实最好 其实不可能是0基础,但是要是你是0基础,就好像张三丰说的,忘了你爸吧 假如你现在开始看现在的Unity URP或Standard Shader 1-如果熟读乐乐姐的《入门精要》,那是2015年出版的,2013年开始写,写的那是一个优雅,而你现在再看Unity内部Shader肯定内伤。 2-如果你有多年程序经验,你会觉得看不明白的代码,肯定是好代码,但细心想想,这年头看不明白的代.

大话程序员 2483

ER图从入门到精通:数据库建模全攻略

本文为数据库建模领域的 ER 图全面指南,系统解析从基础概念到实战应用的核心要点。首先介绍 ER 图的定义、三大组成元素(实体、属性、关系)及三种基础关系类型(一对一、一对多、多对多);其次详解绘制流程,涵盖工具选择(如 Visio、PowerDesigner)、需求分析方法及实体 - 属性 - 关系的分步建模技巧;进一步探讨弱实体、复合属性等高级概念,结合范式理论讲解模型优化策略;针对实体属性混淆、关系类型误判等常见问题提供解决方案;最后通过电商系统、图书馆管理系统等实际案例展示 ER 图设计全流程。

邓邓子的博客 4200

planning模块(6)-参考线的平滑(二次规划)

上一篇已经介绍了采样点生成锚点数据的过程,并且将所有采样点生成的锚点数据设置到了平滑器中.接下来继续介绍使用二次规划平滑参考线的算法过程.从锚点数据中提取坐标存入raw_point2d,提取横向边界存入anchorpoints_lateralbound,将起点和终点的横向边界设置为0,防止车在起点和终点位置偏离车道中心线.

让复杂的问题变得更容易理解 784

【学习笔记】Unity2D使用shader使指定部分发光

在Unity游戏开发中,使游戏中的特定物体或场景部分发出指定的光芒,能够增强视觉效果,提升整体的游戏体验。该方法参照了B站大佬的视频,本文只会简单的讲解制作过程,不会对shader进行更深入的探讨。(使用的Unity版本为2023.2.20)想要更详细了解请移步【Unity教程】使用ShaderGraph让贴图中指定部分发光, 做出炫酷的效果_哔哩哔哩_bilibili。

weixin_67674686的博客 2814

RK3588,V4l2 读取Gmsl相机, Rga yuv422转换rgb (dma), 实现零拷贝

RK3588, 使用V4l2 读取 gmsl 相机,获得yuv422格式图像, 使用 rga 转换 rgb 图像。减少cpu占用率. 内存管理方式采用 dma,实现零拷贝.

J。 1257

Unity&Shader基础篇-概述

最近在学习Shader,将学习的一些心得和案例以及基础知识罗列了一下。这一篇是unity之Shader的基础篇之基础,更多的是一些概念。该篇将分以下几个部分: 1、什么是Shader? 2、GPU和Cg语言 3、Unity中使用Cg语言如何编写Shader? 1、什么是Shader? 1.1、Shder的概念:Shader即着色器,是一款运行在GPU上的程序,用以对三维物体进行着

Just do IT 4553

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Unity&Shader案例篇—声纳光波效果

原文:http://www.manew.com/thread-98405-1-1.html一、前言睡前再来一篇吧,明天周一了,加油吧!废话不多说了,先上效果图如图所示,前面两个的效果图是两种模式下的: 不同效果,最后一章图片是在其中一种模式下,场景进行了精心布置后的效果图。看效果图会不自觉的认为实现这个效果是不是要在场景中的物体中进行绘制,如果真是要这样做的话那是在是太耗性能了(毕竟都工作了,本人

阿童木的博客 2824

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Unity&Shader案例篇—膨胀效果

原文:http://www.manew.com/thread-99195-1-1.html一、前言首先,来看一下效果图,如图所示: 这个效果要使得摄像机的Clear Flags为Solid Color模式,如果为其他模式可能会看不到外部那一圈光环。二、实现原理1、Shader部分:将需要使用两个Pass块,两个Pass块里输出的颜色不同,并最终使用透明度混合得到最后输出的像素颜色。●第一个Pass

阿童木的博客 2446

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Unity&Shader高级案例篇—图形命令缓冲

原文http://www.manew.com/thread-97942-1-1.html一、前言上一章讲的到底有什么用呢?带着此疑问你可以耐心的看看这篇的介绍。继上一篇文Unity&Shader高级篇-渲染路径(Rendering Paths),本篇其实算是中渲染管线的一个拓展。当然也要在理解了上一章的内容之后看这篇的例子就会有一个形象的概念。在中已经有了很多开发者可见的图形渲染功能,诸如标准着色器

阿童木的博客 1527

Unity&Shader案例篇-镜子2

一、前言上一篇介绍了有关镜子的制作,有关理论部分的内容我会在后续相关的文章中陆续介绍,莫急,我先趁着自己脑子还是对此技术比较热,趁热打铁尽早把实现部分先写出来。上一章的介绍制作的镜子其实只是一个取巧的方法,并不能做到实时的反射出实物,但是思路还是比较有意思的。 还是废话不多说先上图,我用的Unity奔波5.3.3 二、Unity中制作原理 1、简单说明:其实这个原理就是用一个摄像机去拍镜

Just do IT 7842

Unity&Shader基础篇-可编程GPU图形绘制管线

Cg是最早的为可编程图形硬件设计的高级编程语言。它是英伟达和微软公司一起合作开发出来了语言,如果你非常熟悉C语言或者其他的编程语言,如C++、C#或者Java等,那对于Cg语言你将会非常容易掌握。Cg程序通常都比较的小,结构也非常的简单,尽管如此,不了解基本的Cg语言的语法和它的运行机制也是很难理解一段Cg程序到底做了什么以及要怎么控制它到达预期的效果 1.1、可编程GPU图形绘制管线   所

Just do IT 2300

Unity&Shader高级篇-渲染路径(Rendering Paths)

一、前言 在Unity的Camera中常常会看到一个Rendering Paths的选项,里面有5个选项,其中两个比较重要的选项分别是“Deferred Shading”和“Forward Rendering”,在我还是初学Unity的时候,并没有在意太多。如今随着学习Shader的深入慢慢领会到了这两个渲染路径的重要性。下面就这两个渲染路径的区别和优缺点做一个学习记录。主要参考文献 维基百科

Just do IT 6648

Unity 粒子特效、材质发光 HDR ShaderGraph图文教程[完成lit发光设置]

Shader ShaderGraph 自发光 粒子特效

zhuangjialo的博客 7976

Unity中的基础光照 -- Shader入门精要学习(5)

Unity中的基础光照 -- Shader入门精要学习(5)

m0_52941834的博客 694

Unity开发惯性动作捕捉或光学动作捕捉

在之前的基础上,我们将更深入地研究如何在Unity中实现更高级的动作捕捉功能,包括实时多角色互动、高精度动作重定向(Retargeting)、复杂骨骼系统支持,以及定制化的数据处理和优化。通过整合动作捕捉技术、AI优化、云计算和分布式技术,Unity已成为实现复杂动作捕捉和虚拟角色驱动的强大平台。在复杂项目中,可能需要整合多个捕捉设备(如身体捕捉、手部捕捉、表情捕捉),并将这些数据同步应用到角色上。动作捕捉不仅限于身体动作,表情捕捉同样是虚拟角色动画的重要组成部分,尤其在影视动画和虚拟主播领域。

小宝哥Code的专栏 1895

UnityShader基础(一)——基础光照

在入门系列最后一节中可以看到输出结果,尽管模型是一个立方体,但我们看到的就是一坨,为了丰富细节,会模拟光照使模型表面更加明显,就如同在现实生活中我们看到物体时是借助光线,在Shader也是同样。

SliverAsh_的博客 1575

UnityShader基础案例(三)——外发光(泛光)和内发光

基于菲涅尔反射来判断边缘所在。

SliverAsh_的博客 4808

unityshader光源效果

PropertiesSubShaderpassZWrite OffCGPROGRAMstruct v2fv2f f;return f;ENDCG。

qq_51416423的博客 334
上一篇: 【深度学习】深度学习之PyTorch初学
下一篇: 【Mimics】基于心脏ct影像重建3d模型 孔洞修复平滑处理 及 合并导出
Zoeyeo
博客等级 码龄7年 212粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值