Unity Shader :实现漫反射与高光反射

最近在研究Unity 的Shader编写,冯乐乐《UnityShader 入门精要》发现还是挺有意思的。

这里就来实现一下基础的Shader。笔者使用的Unity版本是2019.4.19f1。相比于《UnityShader 入门精要》中的某些写法和函数进行了更新。

标准光照模型

在游戏引擎中光照模型有很多种,但在早期的游戏引擎中往往只使用一个光照模型,这个模型被称为标准光照模型。

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

  • 自发光
  • 环境光
  • 满反射
  • 高光反射

环境光照

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

在Unity 中 环境光使用如下代码求得:

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

漫反射

漫反射的介绍见 漫反射_百度百科

在漫反射中,视角的位置是不重要的,因为反射是完全随机的,因此可以认为在任何反射方向上的分布都是一样的。但是,入射光线的角度很重要。

漫反射公式如下:
在这里插入图片描述
C diffuse 为 漫反射颜色
C light 为 光源颜色
M diffuse 为 材质漫反射系数
向量 n 为法线方向
向量 l 为光源方向
为了防止法线和光源方向点积结果为负值,所以使用取最大值的函数来将其截取到0。

这里的公式没看懂也没关系。熟能生巧。随着研究的深入,就会慢慢理解了。

上代码

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

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            fixed4 _Diffuse;

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

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
            };

            v2f vert(appdata v)
            {
    
    
                v2f o;
                //从模型空间变换到裁剪空间
                o.vertex = UnityObjectToClipPos(v.vertex);
                //从模型空间变换到世界空间
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
    
    
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLight));
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1);
            }
            ENDCG
        }
    }
    // 上述SubShader都失败后用于回调的Unity Shader
    FallBack "Diffuse"
}

新建一个 Image Effect Shader,打开将此代码覆盖。新建一个材质 选择 Custom/DiffuseShader ,将材质拖到物体上即可。

代码解析:
Shader “Custom/DiffuseShader” 指定了Shader的路径。
在这里插入图片描述
在Properties 中规定了可以在Inspector中编辑的属性。这里我们指定了材质的默认漫反射系数(公式中的M diffuse)。

在SubShader的Pass中,我们指定了Pass中LightMode为ForwardBase。该Pass会计算环境光、主要的平行光、顶点/SH光源和Lightmaps光照贴图。

同时在Pass中规定了顶点和片段着色器的输入。
在顶点着色器中,获取了模型空间下的顶点坐标和顶点法线。之后将顶点坐标变换到裁剪空间中。将法线变换到世界坐标下。法线即为公式中的向量 n

在片段着色器中 ambient 为 环境光
worldLight 为公式中的向量 l
_LightColor0.rgb 为公式中的 C light
最终得到了该片段颜色的结果。

最终指定了 Fallback),如果所有SubShader都无法在该硬件上运行,会使用默认的Diffuse。

得到的结果为在这里插入图片描述

高光反射

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

公式为
在这里插入图片描述
C specular为 高光反射颜色
C light 为 光源颜色
M specular为 材质高光反射系数
向量 v 为视角方向
向量 l 为反射方向

上代码

Shader "Custom/SpecularShader"
{
    
    
    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 "UnityCG.cginc"
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

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

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };

            v2f vert(a2v v)
            {
    
    
                v2f o;
                //从模型空间变换到裁剪空间
                o.vertex = UnityObjectToClipPos(v.vertex);
                //法线从模型空间变换到世界空间
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                //顶点从模型空间变换到世界空间
                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.xyz - i.worldPos.xyz);
                //计算高光反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1.0);
            }

        ENDCG
        }
    }
    FallBack "Specular"
}

在Pass的顶点着色器中计算得到了世界空间下的顶点坐标,在片段着色器中新增了对于高光反射的计算,最终将环境光 漫反射 以及高光反射的结果相加,得到最终结果。

同时指定了FallBack为默认的Specular

新建一个 Image Effect Shader,打开将此代码覆盖。新建一个材质 选择 Custom/SpecularShader ,将材质拖到物体上即可。
得到的结果为。
在这里插入图片描述
可以看到高光反射的Shader更带一些金属性,同时在材质的Inspector面板调整Gloss的值,Gloss值越大,高光反射的光点越小。

猜你喜欢

转载自blog.csdn.net/qq_35649669/article/details/117690914