在Unity Shader中实现漫反射光照模型(逐顶点漫反射光照、逐像素漫反射光照、半兰伯特光照)

我们来看一下如何在Unity中实现这些基本光照模型。首先我们来实现标准光照模型中的漫反射部分

实现漫反射

我们已经知道了基本光照模型中漫反射的计算公式:
在这里插入图片描述

从公式可以看出,要计算漫反射部分需要知道4个参数:入射光的颜色和强度clight,材质的漫反射系数mdiffise,表面法线n以及光源方向l。未来房子点积结果为负值,我们需要使用max操作,而Cg提供了这样的函数。在本例中使用C搞定另一个函数可以达到同样目的,即saturate函数。

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

实践:逐顶点光照

我们先新建好一个场景,在新建有个材质,最后在新建有个Shader,把这个Shader赋给刚刚建好的材质
在这里插入图片描述

再在Lighting窗口中把天空盒去掉
打开Lighting窗口的方法在这:打开Lighting窗口的方法在此文章最底下

在这里插入图片描述
随后开始编写Shader代码

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

Shader "Unity Shaders Book/Chapter 6/6-4"
{
    
    
    Properties
    {
    
    
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1 )
    }
    SubShader
    {
    
    
        Pass
        {
    
       
        //指明光照模式
         Tags {
    
    "LightMode" = "ForwardBase"}

         CGPROGRAM

         #pragma vertex vert
         #pragma fragment frag

         //为了使用Unity内置的一些变量,需要包含进Unity的内置文件“Lighting.cginc”
         #include "Lighting.cginc"
         
         //为了在Shader中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
         //通过这样的方式我们就可以得到漫反射公式中最重要的参数之一——材质的漫反射属性
         fixed4 _Diffuse;

         //为了访问顶点的法线,我们在a2v中定义了一个normal变量,并通过使用NORMAL语义来告诉Unity要把模型顶点的法线信息储存到nornal变量中
         //为了把在顶点着色器中计算得到的光照颜色传递给片元着色器,我们需要在v2f中定义一个color变量,且并不是必须使用COLOR语义,一些资料中也会用TEXCOORD0语义
         struct a2v 
         {
    
    
             float4 vertex : POSITION;
             float4 normal : NORMAL;
         };

         struct v2f
         {
    
    
             float4 pos : SV_POSITION;
             float3 color : COLOR;
         };

         //以下是顶点着色器,我们需要关注如何实现一个逐顶点的漫反射光照,因此漫反射部分在顶点着色器中进行
         v2f vert (a2v v)
         {
    
    
             v2f o;

             //Transform the vertex from object space to projection space
             o.pos = UnityObjectToClipPos(v.vertex);

             //Get ambient term
             fixed3  ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

             //Transform the normal framn object space to world space
             fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

             //Get the ligth 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;
         }

         //所有的计算都在顶点着色器中完成了,因此片元着色器代码比较简单,我把颜色输出即可
         fixed4 frag(v2f i) : SV_Target
         {
    
    
             return fixed4(i.color, 1.0);
         }
         ENDCG
     }
  }
    FallBack "Diffuse"
}

效果图
在这里插入图片描述

对于细分程度比较高的模型,逐顶点光照已经可以得到比较好的光照效果了。但对于一些细分程度较低的模型,逐顶点关照会出现一些视觉问题,例如在背光没雨向光面交界处会有一些锯齿。为了解决这些问题,我们可以使用漫反射光照。

实践逐像素光照

我们只需要对Shade进行一些更改就可以实现逐像素的漫反射效果
和上面一样,我们新建有个材质和Shader,把材质赋给胶囊体,再把Shader赋给材质,我们的代码和上面的代码非常相似,我们直接吧代码复制到新建的Shader文件中

  1. 我们先修改顶点着色器的输出结构体v2f:
		 struct v2f
         {
    
    
             float4 pos : SV_POSITION;
             float3 worldNormal : TEXCOORD0;
         };
  1. 顶点着色器不需要计算光照模型,只需要把世界空间下的法线传递给片元着色器即可:
		 v2f vert (a2v v)
         {
    
    
              v2f o;
             //Transform the vertex from object space to projection space
             o.pos = UnityObjectToClipPos(v.vertex);

             //Transform the normal fram object space to world space
             o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

             return o;
         }
  1. 片元着色器需要计算漫反射光照模型:
//所有的计算都在顶点着色器中完成了,因此片元着色器代码比较简单,我把颜色输出即可
         fixed4 frag(v2f i) : SV_Target
         {
    
    
            //Get ambient term
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

            fixed3 worldNormal = normalize(i.worldNormal);
            //Get the light direnction in world space
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

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

            fixed3 color = ambient + diffuse;

            return fixed4(color, 1.0);
         }

效果图:
在这里插入图片描述

其它部分和上面的逐顶点光照代码完全相同
逐像素光照可以得到更加平滑的光照效果。但是计时是用了逐像素漫反射光照,有一个问题任然存在。在关照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这回使模型的背光区域看起来像一个平面一样,失去了模型的细节表现,我们可以挺过正价环境光来得到非全黑的效果,但任然无法解决背光面明暗一样的缺点,为此,有一种改善技术被提了出来,这就是半兰伯特光照模型

半兰伯特模型

之前我们所使用的的模型也称为兰伯特光照模型,为了改善上面的问题,Valve公司在开发游戏《半条命》时剔除了一种技术,由于该技术是在原来兰伯特关照模型的基础上进行了一个简单的修改,因此被称为半兰伯特光照模型
广义的班兰伯特光照模型公式如下:
在这里插入图片描述

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

和上面一样,我们新建有个材质和Shader,把材质赋给胶囊体,再把Shader赋给材质,我们的代码和上面的代码非常相似,我们直接吧代码复制到新建的Shader文件中
我们只需要修改片元着色器中计算漫反射光照部分:

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

            fixed3 worldNormal = normalize(i.worldNormal);
            //Get the light direnction in world space
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

            //Compute diffuse term
            fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;

            fixed3 color = ambient + diffuse;

            return fixed4(color, 1.0);
         }

一下是三种光照的对比图,依次是:逐顶点漫反射光照、逐像素漫反射光照和半兰伯特光照
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_50617270/article/details/123844404