在Untiy Shader中实现高光反射光照模型(逐顶点光照、逐像素光照、Blinn—Phong光照模型)

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

在公式可以看出,要计算高光反射需要4个参数:入射光线的颜色和强度clight,材质的高光反射系数mspecular,视角方向v以及反射方向r。其中,反射方向r可以有表面法线n和光源方向l计算而得
在这里插入图片描述
Cg中为我们提供了计算反射方向的函数reflect

  • 函数:reflect(i,n)
  • 参数:i:入射角方向,n:法线方向。可以是float、float2、float3等类型。
  • 描述:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。下图给出了参数和返回值之间的关系。
    在这里插入图片描述

实践:逐顶点光照

我们先新建好一个场景,再新建有个材质,最后在新建有个Shader,把这个Shader赋给刚刚建好的材质
在这里插入图片描述
再在Lighting窗口中把天空盒去掉
打开Lighting窗口的方法在这:打开Lighting窗口的方法在此文章最底下
开始编写代码

Shader "Unity Shader Book/Chapter 6/Specular Verx-Level"
{
    
    
    Properties
    {
    
    
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular ("Specyler", 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 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 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 - mul(unity_ObjectToWorld, v.vertex).xyz);

                fixed3 specular = _LightColor0.rgb * _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
        }
    }
    FallBack "Specular"
}

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

实践:逐像素光照

我们可以使用逐像素光照来得到更加平滑的高光效果
我们先新建好一个场景,在新建有个材质,最后在新建有个Shader,把这个Shader赋给刚刚建好的材质
在这里插入图片描述

然后我们开始编写代码

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

Shader "Custom/Chapter6-SpecularPixelLevel"
{
    
    
    Properties
    {
    
    
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular ("Specyler", 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;
                float3 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.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"
}

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

可以看出,按逐像素的方式可以得到更加平滑的高光效果,至此我们就实现了一个完整的Phong模型

Blinn—Phong模型

Blinn模型没有使用反射方向,而是引入了一个新的矢量h,它是通过对视角方向v和光照方向l相加后再归一化得到的,即
在这里插入图片描述

而Blinn模型计算高光反射的公式如下:
在这里插入图片描述

Blinn—Phong模型的实现和上面的代码很相似
我们先新建好一个场景,在新建有个材质,最后在新建有个Shader,把这个Shader赋给刚刚建好的材质
在这里插入图片描述
下面我们开始编写Shader代码

Shader "Custom/Chapter6-BlinnPhong"
{
    
    
    Properties
    {
    
    
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular ("Specyler", 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;
                float3 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.xyz - i.worldPos.xyz);

                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"
}

效果图:逐顶点的高光反射光照、逐像素的高光反射光照(Phong光照模型)、Blinn—Phong高光反射光照模型
在这里插入图片描述
可以看出,Blinn—Phong光照模型的高光反射部分看起更大、更亮一些。在实际渲染中,绝大多数情况我们都会选择Blinn—Phong光照模型。

猜你喜欢

转载自blog.csdn.net/weixin_50617270/article/details/123882818
今日推荐