第五天 开始Unity Shader的学习之旅之Unity中的基础光照之漫反射光照模型

发布于:2025-03-27 ⋅ 阅读:(36) ⋅ 点赞:(0)

Unity Shader的学习笔记

第五天 开始Unity Shader的学习之旅之Unity中的基础光照之漫反射光照模型



前言

今天来介绍一下Unity Shader中的漫反射光照模型中的逐顶点光照,同时大致说一下环境光和自发光部分的计算.


一、Unity中的环境光和自发光

在标准光照模型中,环境光和自发光的计算是最简单的.
在Shader中,我们只需要通过Unity的内置变量UNITY_LIGHTMODEL_AMBIENT,得到环境光的颜色和强度信息.
计算自发光也很简单,我们只需要在片元着色器输出最后的颜色之前,将材质的自发光颜色添加到输出颜色上即可.

二、漫反射光照模型

逐顶点光照

逐顶点的漫反射光照效果
上图为逐顶点光照效果,代码如下:

Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level"{
    Properties
    {
        _Diffuse ("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0)}

    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;
                fixed3 color : COLOR;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                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
        }
    }
    Fallback "Diffuse"												11
}

这是我们第一段正经的Unity Shader的代码,我们从头开始分析:

① Shader名称

首先为了方便找到我们编写的Shader,我们需要定义Shader的名称,以及所在的位置,例如我们在给Material找Shader的话,我们首先选择Unity Shaders Book,然后选择Chapter6,找到Diffuse Vertex_Level,选择即可.

② 定义属性

在昨天我们讲述了漫反射的兰伯特模型,它需要材质的漫反射颜色,所以我们需要在Properties语义块中声明了一个Color类型的属性,并将其颜色设置为白色(白色的rbg均为1).

③ Tags

LightMode也是Pass标签的一种,在这里,我们只需要知道,只有定义了正确的LightMode,我们才能得到一些Unity的内置光照变量,例如下面使用的_LightColor0.

④ CGPROGRAM和ENDCG

我们使用CGPROGRAM和ENDCG来包含cg代码片.

⑤ 定义vert和frag

我们使用#pragma来定义顶点着色器和片元着色器的名称,在这段代码中,顶点着色器和片元着色器的名称分别为vert和frag.

⑥ #include “Lighting.cginc”

为了使用Unity的一些内置变量,例如上面所说的_LightColor0,我们必须包含Unity的内置头文件:#include “Lighting.cginc”.

⑦ fixed4 _Diffuse

为了在Shader中使用Properties中声明的属性,我们需要定义一个和该属性类型相匹配的变量,(怎么相互匹配我们已经在之前的帖子里面说过了,可以去翻一下).由于该属性是材质的漫反射属性,由于颜色属性的范围是0~1,所以我们使用fixed来存储.

⑧ a2v和v2f

我们定义了顶点着色器的输入输出结构体,因为我们计算漫反射时需要模型的法线信息,所以a2v中定义了normal变量,存储需要的法线信息;
同时为了将顶点着色器中计算的颜色传递给片元着色器,需要在v2f中定义color变量,此处的Color语义不是必须的,也可以使用TEXCOORD0.

⑨ 关键的顶点着色器vert

因为我们采用逐顶点光照,所以计算过程中存在于顶点着色器中.
首先我们定义返回值o,顶点着色器的任务就是将顶点位置从模型空间转换到裁剪空间中,因此我们使用UNITY_MATRIX_MVP来完成这样的坐标变换(现在直接使用UnityObjectToClipPos来完成),接着我们通过UNITY_LIGHTMODEL_AMBIENT来得到环境光的部分.
接着就是计算漫反射光照的部分了:
计算法线和光源方向的点积必须两个向量在同一个空间内才有意义,所以我们选择在世界空间内计算,由于通过v.normal得到的法线时位于模型空间下的,(我们已知可以使用顶点变换矩阵的逆矩阵来对法线进行相同的变换,因此我们首先得到模型空间到世界空间的变换矩阵的逆矩阵unity_World2Object,再通过调换他们在mul中的位置,由于法线是一个三维矢量,所以我们只需要unity_World2Object的前三行前三列就够了),这样我们就可以得到世界空间下的法线方向了,然后我们通过_WorldSpaceLightPos0来得到(因为环境中只有一个光源且为平行光,但是如果环境中存在多个光源时,这种方法可能无法得到正确的结果),光源的颜色和强度信息可以通过_WorldSpaceLightPos0来得到,接着按照公式对这些变量计算得到漫反射的颜色,在与环境光相加,对o.color进行赋值.

⑩ 片元着色器frag

在片元着色器中我们只需要将顶点颜色输出即可.

11 Fallback “Diffuse”

我们需要将这个Unity Shader的回调Shader设置为内置的Diffuse.

这样我们的逐顶点的漫反射光照模型就已经实现了,你可以将它所在的材质拖到物体上看看效果.但是根据我给出的图可以看出胶囊体的背光面和向光面交界处有一些锯齿,在原书上时这么说的:逐顶点光照对于一些细分度较高的模型,可以得到比较好的光照效果,但是对于细分度较低的模型,逐顶点光照就会出现一些视觉问题,为了解决这些问题,我们使用逐像素的漫反射光照


总结

今天我们只是对逐顶点光照进行一个总结,因为是我们的第一段Unity Shader代码,需要花一段时间去理解,所以明天我们再去解释逐像素光照.