美文网首页
UnityShader之漫反射

UnityShader之漫反射

作者: 欣羽馨予 | 来源:发表于2018-09-21 12:08 被阅读213次

    前言

    近些年,电子竞技行业发展迅速,游戏的品质也是逐年提升。从用户体验的角度来讲,画质渲染及画质特效方面非常重要。任何渲染特效都主要是光影的变幻,从而达到美轮美奂的效果。漫反射是最简单的光照效果,所有的光照特效都是以漫反射为基础的。本篇就来详细介绍,漫反射着色器如何实现。

    • 首先我们来看一下什么漫反射
      • 漫反射,是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,这种反射称之为“漫反射”或“漫射”。这种反射的光称为漫射光。很多物体,如植物、墙壁、衣服等,其表面粗看起来似乎是平滑,但用放大镜仔细观察,就会看到其表面是凹凸不平的,所以本来是平行的太阳光被这些表面反射后,弥漫地射向不同方向。


        漫反射
      • 以简单人物为例,在Unity中的漫反射是这样的


        人物漫反射
    • 关于漫反射的光照模型,常见的是兰伯特(Lambert)半兰伯特(Half-Lambert)
      • Lambert的光照公式是(C-light · M-diffuse)max(0,n·I)

      • 光照颜色 * 漫反射颜色 * max(0,法向量*光照方向)

      • 下面以球形2D图像解析一下原理

        • 第一步,我们确认公式中的两个方向向量,注意光照方向是指向光的方向,即入射光向量的逆向量,红色光照方向,蓝色顶点法向量。


          光照方向及法向量方向
        • 第二步,计算不同顶点两向量的夹角,我们可以得到面向光的部分角度在0-90度之间,背向光的角度在90-180度之间。


          计算不同顶点两向量的夹角
        • 第三步,Lambert漫反射需要的面向光的部分渲染,背向光的地方不渲染,且渲染部分需要做曲线性的递减。公式中两个向量做点乘计算,即n · I,点乘公式是|n|*|I|*Conθ,其中θ是两个向量的夹角,下面先看一下余弦图。
          余弦函数曲线
        • 第四步,我们的夹角范围是0-180度,因此我们裁掉后半部分


          0°-180°余弦图
        • 第五步,我们需要0度时颜色最显眼,到90度逐渐递减,余弦前半段得到的结果与我们的预期相符,但90度到180度区间内,漫反射不做任何渲染,即纯黑色代表阴暗面效果,因此得出下图。


          90°--180°渲染比例为0
        • 最终我们在两向量夹角为0°到90°范围内按比例渲染,就形成了最终的漫反射效果。


          最终比例
      • 使用Lambert进行漫反射渲染,在Shader中有两种写法,一种是逐顶点着色,另一种是逐像素着色。从代码中我们可以看出,逐顶点是在顶点着色器中进行漫反射计算,而逐像素则在片元着色器中进行漫反射计算,下面来看逐顶点代码。

        //兰伯特Lambert漫反射(逐顶点)
        Shader "AlbertShader/VertexDiffuse"
        {
            Properties
            {
                //漫反射颜色
                _DiffuseColor("Color",Color)=(1,1,1,1)
            }
            SubShader
            {
                Pass
                {
                    //正向渲染
                    Tags{ "LightMode"="ForwardBase" }
                    CGPROGRAM//------------------CG语言开始-------------------
                    //声明顶点函数
                    #pragma vertex vert
                    //声明片段函数
                    #pragma fragment frag
                    //引入光照函数库
                    #include "Lighting.cginc"
                    //定义外部属性-漫反射颜色
                    float4 _DiffuseColor;
                    //顶点输入结构体
                    struct appdata
                    {
                        //顶点坐标
                        float4 vertex : POSITION;
                        //顶点法线
                        float3 normal : NORMAL;
                    };
                    //顶点输出结构体
                    struct v2f
                    {
                        //像素坐标
                        float4 vertex : SV_POSITION;
                        //临时变量:颜色
                        fixed3 color : COLOR;
                    };
                    //顶点函数实现
                    v2f vert (appdata v)
                    {
                        //定义顶点输出结构体对象
                        v2f o;
                        //顶点坐标转换到屏幕像素坐标
                        o.vertex = UnityObjectToClipPos(v.vertex);
                        //获取环境光
                        float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                        //将顶点法线转换到世界空间下,并做归一化处理
                        float3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
                        //将光照方向转换到世界空间下,并做归一化处理
                        float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                        //带入公式运算
                        fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLight));
                        //结合漫反射和环境光
                        o.color = ambient + diffuse;
                        //返回结果
                        return o;
                    }
                    
        
                    fixed4 frag (v2f i) : SV_Target
                    {
                        //将结果返回
                        return fixed4(i.color,1.0);
                    }
                    ENDCG//------------------CG语言结束-------------------
                }
            }
        }
        
      • 下面来看逐像素代码

        //兰伯特Lambert漫反射(逐像素)
        Shader "AlbertShader/PixelDiffuse"
        {
            Properties
            {
                //漫反射颜色
                _DiffuseColor("Color",Color)=(1,1,1,1)
            }
            SubShader
            {
                Pass
                {
                    //正向渲染
                    Tags{ "LightMode"="ForwardBase" }
                    CGPROGRAM//------------------CG语言开始-------------------
                    #pragma vertex vert
                    #pragma fragment frag
                    
                    #include "Lighting.cginc"
        
                    float4 _DiffuseColor;
                    struct appdata
                    {
                        float4 vertex : POSITION;
                        //顶点法线
                        float3 normal : NORMAL;
                    };
        
                    struct v2f
                    {
                        float4 vertex : SV_POSITION;
                        //世界空间下的顶点法线
                        fixed3 worldNormal : TEXCOORD0;
                    };
        
                    v2f vert (appdata v)
                    {
                        v2f o;
                        o.vertex = UnityObjectToClipPos(v.vertex);
                        //通过矩阵运算,得到世界空间下的顶点法线
                        o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                        return o;
                    }
                    
                    fixed4 frag (v2f i) : SV_Target
                    {
                        float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                        fixed3 worldNormal = normalize(i.worldNormal);
                        fixed3  worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                        //使用兰伯特光照模型公式
                        fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb *
                            saturate(dot(worldNormal,worldLightDir));
                        fixed3 color = diffuse + ambient;
                        return fixed4(color,1.0);
                    }
                    ENDCG//------------------CG语言结束-------------------
                }
            }
        }
        
    • Lambert漫反射效果不错,但背光的一面全是黑色,若镜头在后面照射,则渲染出来的就是一个黑色平面,视觉效果很差,因此,有时我们也常常使用半兰伯特(Half-Lambert)的光照模型。

    • Half-Lambert的公式与Lambert相似,只是人为改动了公式中的渲染比例,半兰伯特没有太多的物理证明,可以理解为测试出来的一种实现方式。公式即(C-light · M-diffuse)(α(n·I)+β),通常情况下αβ的值为0.5。具体代码如下:

      //半兰伯特HalfLambert漫反射(逐像素)
      Shader "Hidden/HalfPixelDiffuse"
      {
          Properties
          {
              _DiffuseColor("Color",Color)=(1,1,1,1)
          }
          SubShader
          {
              Pass
              {
                  Tags{ "LightMode"="ForwardBase" }
                  CGPROGRAM
                  #pragma vertex vert
                  #pragma fragment frag
                  
                  #include "Lighting.cginc"
      
                  float4 _DiffuseColor;
                  struct appdata
                  {
                      float4 vertex : POSITION;
                      float3 normal : NORMAL;
                  };
      
                  struct v2f
                  {
                      float4 vertex : SV_POSITION;
                      //临时变量:世界法线
                      fixed3 worldNormal : TEXCOORD0;
                  };
      
                  v2f vert (appdata v)
                  {
                      v2f o;
                      o.vertex = UnityObjectToClipPos(v.vertex);
                      //世界法线
                      o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                      return o;
                  }
                  
                  fixed4 frag (v2f i) : SV_Target
                  {
                      //环境光
                      float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                      //世界法线
                      fixed3 worldNormal = normalize(i.worldNormal);
                      //世界入射光
                      fixed3  worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                      //半兰伯特漫反射公式
                      fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb *
                          (0.5 * (dot(worldNormal,worldLightDir)) + 0.5);
                      fixed3 color = diffuse + ambient;
                      return fixed4(color,1.0);
                  }
                  ENDCG
              }
          }
      }
      
    • Half-Lambert显的更透亮一点,背光面也不是全黑的,三种Shader的渲染效果对比如下:

      逐顶点Lambert、逐像素Lambert、逐像素Half-Lambert

    结束语

    漫反射是光影效果的基础,通过对漫反射底层实现原理,以及对漫反射公式的图像剖析,大家有没有更清晰的认识呢?Shader就是如此奇妙,这就是所谓的数学之美、编程之美。想成为逻辑开发和图形学开发的双料大师吗?一起加油吧!😋😋😋

    相关文章

      网友评论

          本文标题:UnityShader之漫反射

          本文链接:https://www.haomeiwen.com/subject/accanftx.html