Unity学习笔记(4) UnityShader学习笔记_3:标准光照模型的底层原理

笔记都是照着“Unity Shader入门精要”摘抄的,为以后想要复习实现方便。

标准光照模型

20220116140531
20220116140650
上面是考虑光照时抽象出的背景情况,光照模型Lighting Model就是用于根据材质属性光源信息等得出在某个观察方向出射度的过程。也就是着色(Shading)过程。

标准光照模型使用自发光环境光高光反射漫反射来叠加计算出摄像头看到的物体的光(颜色)

环境光和自发光

环境光是一个全局变量,在Unity中设置,
在Shader中可以通过UNITY_LIGHTMODEL_AMBIENT获取。
自发光暂时不考虑:
考虑直接自发光很简单,直接在片元着色器最后的颜色输出中添加即可,
考虑间接自发光较难,因为需要计算其它物体对自发光物体光源的反射。

漫反射光照

基本光照模型中,漫反射光照的计算公式:
20220116212835

入射光线的颜色和强度: c l i g h t c_{light} clight
材质的漫反射系数: m d i f f u s e m_{diffuse} mdiffuse
表面法线: n ^ \hat{n} n^
光源方向: I I I

逐顶点光照
Shader "Custom/DiffuseVertex"
{
    Properties{
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }
    SubShader{
        Pass{
            Tags{"LightMode" = "ForwardBase"}//定义了正确的LightMode,才能得到Unity内置的光照变量
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc" //使用内置库文件
            fixed4 _Diffuse; //得到材质的漫反射属性,需要使用Properties中声明的属性,要定义一个类型相通的同名变量
            //顶点、片元着色器的输入输出结构体
            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)_World2Object));//将模型法向量转化到世界坐标系下,使用右乘,避免逆
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//得到世界光源的光线向量(假设只有一个光源且使用平行光)
                //根据上述公式,计算漫反射。这里max函数用saturate函数代替,功能为将参数截取到[0,1]区间
                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
        }

    }
}

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

因为是逐顶点计算,可见有比较明显的锯齿状的颜色过渡:
在这里插入图片描述

逐片元光照

代码和上面很相近,但是片元着色器承担了计算的职责。
虽然顶点传给片元的各种法向量等值没有变化,但是 n ^ ∗ I \hat{n}*I n^I的会有细微变化。

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

Shader "Custom/DiffusePixel"
{
    Properties{
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }
    SubShader{
        Pass{
            Tags{"LightMode" = "ForwardBase"}//定义了正确的LightMode,才能得到Unity内置的光照变量
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc" //使用内置库文件
            fixed4 _Diffuse; //得到材质的漫反射属性,需要使用Properties中声明的属性,要定义一个类型相通的同名变量
            //顶点、片元着色器的输入输出结构体
            struct a2v{
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f{
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);//将模型法向量转化到世界坐标系下
                return o;
            }

            fixed4 frag(v2f i) : SV_Target{
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //获取环境光
                fixed3 worldNormal = normalize(i.worldNormal);//顶点着色器已经帮忙计算出来世界坐标系下的法向量了
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//得到世界光源的光线向量(假设只有一个光源且使用平行光)

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1.0);
            }
            ENDCG
        }

    }
}

可以看到非常明显的进步
20220116221051

半兰伯特模型

但是可以看到,在背光面,由于saturate函数,让点积为负的顶点/片元都取0值,
于是看起来完全就是一个同色的平面了:
20220116221853

一种解决方法就是使用半兰伯特模型,本质上就是改进一下saturate函数,让点积为负的也能映射到[0, 1]区间上——实际就是做了个缩放平移:
在这里插入图片描述

当然这里的系数0.5可以换成别的。

只需要将片元着色器代码中计算diffuse的那一行改一下:

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

改到

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal, worldLight) * 0.5 + 0.5);

效果是这样的:
20220116222656

高光反射

基本光照模型中,高光反射公式:
20220116222739

入射光线的颜色和强度: c l i g h t c_{light} clight
材质的高光反射系数——颜色: m s p e c u l a r m_{specular} mspecular
材质的高光反射系数——反射区域大小: m g l o s s m_{gloss} mgloss
视角方向: v ^ \hat{v} v^
反射方向: r r r

其中反射方向的计算公式为: r = 2 ∗ ( n ^ ⋅ I ^ ) ∗ n ^ − I ^ r = 2 * (\hat{n}·\hat{I}) * \hat{n} - \hat{I} r=2(n^I^)n^I^
而且可以直接用CG中的reflect(i, n)计算(i为入射方向,n为法线方向)。

逐片元光照

内含漫反射


Shader "Custom/DiffuseLambert"
{
    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"}//定义了正确的LightMode,才能得到Unity内置的光照变量
            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;
                float3 worldPos;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);//将模型法向量转化到世界坐标系下
                o.worldPos = mul(v.vertex, _World2Object).xyz;//将顶点坐标转化到世界坐标系下,为了得到视角方向
                return o;
            }

            fixed4 frag(v2f i) : SV_Target{
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //获取环境光
                fixed3 worldNormal = normalize(i.worldNormal);//顶点着色器已经帮忙计算出来世界坐标系下的法向量了
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//得到世界光源的光线向量(假设只有一个光源且使用平行光)
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal, worldLight) * 0.5 + 0.5);
                
                //上面计算了漫反射,下面计算高光反射
                fixed3 reflectDir = normalize(reflect(worldLight, worldNormal));//反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);//向量的差,得到模型到摄像机的方向向量
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);


                fixed3 color = ambient + diffuse + specular;
                return fixed4(color, 1.0);
            }
            ENDCG
        }

    }
}

效果,可以看到一个漂亮的光圈:
在这里插入图片描述

上面的代码整体就实现了一个完整地Phong光照模型。

Bling-Phong模型

主要是计算高光反射使用Bling的算法:
20220116231021
其中 h ^ \hat{h} h^计算方式是:
20220116231101
其它基本一致。

使用Unity内置的函数

上面基本上就是光照模型的底层原理。但是实在是理想的情况下得到的——只考虑单光源且平行光。
实际为了正确性,在计算光源和向量变换中,会使用Unity内置的函数。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值