【Aladdin Unity3D Shader编程】之二 光照模型(一)

光照模型

光照模型就是一个公式,使用这个公式来计算在某个点的光照效果。
在标准光照模型里面,我们把进入摄像机的光分为下面四个部分:

  • 自发光

    类似生活中的萤火虫等自己能够发光

  • 高光反射

    类似生活中的镜子,近似认为百分百反射出去

  • 漫反射

    类似生活中的光照射到墙壁上、桌子上的反光不会百分百反射出去,各个方向都会反射。

  • 环境光

    类似生活中的光照照射在某个物体上,物体漫反射然后反射到其他物体上这些过程的光是环境光。

以上都是模拟生活中的光照效果,并不等同于实际生活中的光照计算,实际生活中的光照会更加复杂。

光照模型中关于漫反射的计算

漫反射计算放在顶点函数里面就是逐顶点光照

Disffuse = 直射光颜色*max(0,cosθ) (θ是法线跟光照的夹角) 如果夹角小于0的话就是背光面就直接取黑色

理论知识:

  • 向量点乘(结果值)

    a*b=|a||b|cosθ

  • 向量叉乘(结果是向量)

    a*b=c c的一个向量,值是|a||b|sinθ

编写漫反射Shader

在Pass块里添加Tags{"LightMode"="ForwardBase"}和#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息

只有定义了正确的LightMode才能得到一些Unity的内置光照变量。

  • normalize() 将一个向量单位化
  • max()用来取得函数中最大的一个
  • dot()用来取得两个向量的点积
  • _WorldSpaceLightPos0取得平行光的位置
  • _LightColor0取得平行光的颜色
    UNITY_MATRIX_MVP 这个矩阵是将模型空间转换到剪裁空间
  • _World2Object这个矩阵用来将一个方向从时间空间转换到模型空间
  • UNITY_LIGHTMODEL_AMBIENT 用来获取环境光
Shader "AladdinShader/04 Diffuse Tertex Shader"
{
    Properties
    {
        
    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 color:COLOR;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * max(0, dot(normalDir, lightDir));//取得第一个直射光的颜色
            f.color = diffuse;
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            return fixed4(f.color,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

效果:
没有写反射光照之前的效果,红色是均匀的
这里写图片描述
写了光照效果之后,会看到正对光照地方很亮,背对光照的地方就很暗甚至黑色。
这里写图片描述
现在是逐顶点光照,顶点的数量是有限的,如果将计算放在片元函数里面计算效果会更好,但计算量也会更大,那就叫逐片元光照。

给物体自身添加颜色和环境光共同控制

上面物体的颜色只是受环境光的颜色影响,但实际生活中我们看到的物体的颜色是环境光和自身颜色混合的效果。我们在上面的例子的基础上,添加上物体本身的颜色:
给物体添加颜色属性然后*环境光

Shader "AladdinShader/04 Diffuse Tertex Shader"
{
    Properties
    {
        _Diffuse("Diffuse",Color)=(1,1,1,1) //添加自身的颜色
    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 
    
        fixed4 _Diffuse;

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 color:COLOR;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);

            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//获得系统内置的环境光

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * max(0, dot(normalDir, lightDir)) * _Diffuse.rgb;//取得第一个直射光的颜色 颜色融合
            f.color = diffuse + ambient;  //颜色增加 增强
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            return fixed4(f.color,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

效果:
这里写图片描述
这样直射光设置成白色,颜色本身设置成绿色,并且在面板中调整颜色,物体就显示成这种颜色。颜色融合一般是颜色的改变,颜色相加是强度增强。

环境光设置:Window->Lighting->Ambient Source->Color 一般不建议很强的环境光

逐像素光照

Shader "AladdinShader/05 Diffuse fragment Shader"
{
    Properties
    {
        _Diffuse("Diffuse",Color)=(1,1,1,1) //添加自身的颜色
    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 
    
        fixed4 _Diffuse;

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 worldNormalDir:COLOR0;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);
            f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//获得系统内置的环境光

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(f.worldNormalDir); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * max(0, dot(normalDir, lightDir)) * _Diffuse.rgb;//取得第一个直射光的颜色 颜色融合
            fixed3 tempColor = diffuse + ambient;  //颜色增加 增强
            return fixed4(tempColor,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

效果图:
这里写图片描述
一个是逐顶点光照,一个是逐像素光照,自己看还是能看出区别的。逐像素光照会更消耗性能一些,如果不需要很高的精度的话就可以用逐顶点光照代替。

半兰伯特光照模型

模型表面的明亮度直接取决于光线向量(light vector)和表面法线(normal)两个向量将夹角的余弦值。

如果漫反射光强设置为Diffuse,入射光光强为I,光方向和法线夹角为θ,那么兰伯特光照模型可以用下面的公式表示:Diffuse = I * cosθ。
兰伯特光照模型 模型背面全都是黑色都看不清楚
这里写图片描述

为了解决这个问题,改造一些兰伯特光照模型的公式,让背光部分也随着光照强度改变而改变。
半兰伯特光照模型:
为了解决兰伯特模型背光面全都是黑的看不清楚的问题。
Diffuse=直射光颜色 * (cosθ * 0.5 + 0.5)
这里写图片描述

Shader修改

Shader "AladdinShader/06 Diffuse fragment Half Lanbote Shader"
{
    Properties
    {
        _Diffuse("Diffuse",Color)=(1,1,1,1) //添加自身的颜色
    }
    SubShader {
        Pass 
        {
            Tags{"LightMode"="ForwardBase"}

        CGPROGRAM
#include "Lighting.cginc" //引用一些写好的程序块 会包含一些获取光照的信息  
//_LightColor0 取得第一个直射光的颜色
//_WorldSpaceLightPos0 

#pragma vertex vert
#pragma fragment frag 
    
        fixed4 _Diffuse;

        struct a2v 
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL; //模型空间下法线
        };

        struct v2f
        {
            float4 position:SV_POSITION;
            fixed3 worldNormalDir:COLOR0;
        };
        v2f vert(a2v v)
        {
            v2f f;
            f.position = mul(UNITY_MATRIX_MVP, v.vertex);
            f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);
            return f;
        }

        fixed4 frag(v2f f):SV_Target
        {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//获得系统内置的环境光

            //获得世界空间的单位法线向量
            fixed3 normalDir = normalize(f.worldNormalDir); //从模型空间转到世界空间 float3x3 将一个4*4的矩阵强转成3*3的矩阵

            //世界空间下的光照位置
            fixed3  lightDir = _WorldSpaceLightPos0.xyz; //对于每一个点来说每一个光的位置就是光的方向(针对平行光)

            float halfLambert = dot(normalDir, lightDir) * 0.5 + 0.5; //半兰伯特光照模型
            //取得漫反射的颜色
            fixed3 diffuse = _LightColor0.rgb * halfLambert * _Diffuse.rgb;//取得第一个直射光的颜色 颜色融合
            fixed3 tempColor = diffuse + ambient;  //颜色增加 增强
            return fixed4(tempColor,1);
        }

        ENDCG
        }
    }
    FallBack  "VertexLit"
}

这里写图片描述
会明显的感觉模型亮度变亮了,而且背面也不会纯黑了也会渐变,而且正面颜色也会鲜明一些,这样整体效果会更好一些。

Shader学习交流群:
316977780

posted @ 2017-11-06 08:23  蓬莱仙羽  阅读(295)  评论(0编辑  收藏  举报