栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > 其他

UnityShaderLab读书笔记 第6章(光照模型)

其他 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

UnityShaderLab读书笔记 第6章(光照模型)


  • 前5章主要是基础3D数学和Shader基本语法后面抽空再补上
第6章 光照模型
  • 本章主要内容光照模型:Lambert、Half-Lambert、Phong、Blinn-Phong等
Lambert光照模型
  • 物体的颜色是由射进眼睛里的光决定的,有反射光和透视光
  • 漫反射满足Lambert定律:反射光的强度与表面法线和光源方向夹角成正比,公式如下
    C d i f f u s e = ( C l i g h t ⋅ M d i f f u s e ) s a t u r a t e ( n ⋅ l ) C_{diffuse}=(C_{light}·M_{diffuse})saturate(n·l) Cdiffuse​=(Clight​⋅Mdiffuse​)saturate(n⋅l)
    C l i g h t C_{light} Clight​为入射光线颜色, M d i f f u s e M_{diffuse} Mdiffuse​为材质漫反射颜色, l l l为从物体指向光的方向,根据点乘可以知道当 n n n和 l l l夹角越小漫反射强度就越大,但是当Cos夹角大于90为负,所以使用saturate函数限定至(0,1)
v2f vert (appdata_base v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    float3 n = UnityObjectToWorldNormal(v.normal);
    n = normalize(n);
    // _WorldSpaceLightPos0和_LightColor0包含在UnityLightingCommon.cginc文件中
    // l处在世界空间中,所以法向量需要转换
    fixed3 l = normalize(_WorldSpaceLightPos0.xyz);

    fixed ndotl = dot(n,l);
    // Lambert公式
    o.diff = _LightColor0 * _MainColor * saturate(ndotl);

    return o;
}

Half-Lambert光照模型
  • 可以法线背对着光源,直接就是黑色的,所以提出了Half-Lambert,直接看下面公式
    C d i f f u s e = ( C l i g h t ⋅ M d i f f u s e ) [ 0.5 ( n ⋅ l ) + 0.5 ] C_{diffuse}=(C_{light}·M_{diffuse})[0.5(n·l)+0.5] Cdiffuse​=(Clight​⋅Mdiffuse​)[0.5(n⋅l)+0.5]
    这么看 ( n ⋅ l ) ∈ [ − 1 , 1 ] (n·l)∈[-1,1] (n⋅l)∈[−1,1], [ 0.5 ( n ⋅ l ) + 0.5 ] ∈ [ 0 , 1 ] [0.5(n⋅l)+0.5]∈[0,1] [0.5(n⋅l)+0.5]∈[0,1]结果好像没啥区别,因为saturate也是[0,1]区间,但是仔细看一下saturate直接把 ( n ⋅ l ) (n·l) (n⋅l)小于0的部分全部给抹掉了,这意味小于90度漫反射强度在[0,1],大于90度的全黑,而Half-Lambert公式并不像Lambert这么暴力具体看下面两张图

Phong光照模型
  • Lambert可以很好的模拟粗糙表面,但是现实生活中还有很多光滑的表面如金属、陶瓷等
    C s u r f a c e C o l o r = C A m b i e n t + C D i f f u s e + C S p e c u l a r C_{surfaceColor}=C_{Ambient}+C_{Diffuse}+C_{Specular} CsurfaceColor​=CAmbient​+CDiffuse​+CSpecular​
    C A m b i e n t C_{Ambient} CAmbient​为环境光、 C S p e c u l a r C_{Specular} CSpecular​为镜面反射,因为光照射到光滑表面还会反射一部分光
    C S p e c u l a r = ( C l i g h t ⋅ M s p e c u l a r ) s a t u r a t e ( v ⋅ r ) M s h i n i n e s s C_{Specular}=(C_{light}·M_{specular})saturate(v·r)^{M_{shininess}} CSpecular​=(Clight​⋅Mspecular​)saturate(v⋅r)Mshininess​
    M s p e c u l a r M_{specular} Mspecular​物体材质的镜面反射颜色、 v v v是视角方向(顶点指向摄像机)、 r r r是光线反射的方向、 M s h i n i n e s s M_{shininess} Mshininess​材质的光泽度
  • 可以从UnityShaderVariables.cginc得到环境光变量,具体代码如下
Shader "Unlit/Phong"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)
        // 材质镜面反射颜色
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)
        // 材质光泽度
        _Shininess("Shininess", Range(1,100)) = 1
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                fixed4 color:COLOR0;
            };

            fixed4 _MainColor;
            fixed4 _SpecularColor;
            half _Shininess;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                float3 n = UnityObjectToWorldNormal(v.normal);
                n = normalize(n);
                fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
                fixed ndotl = saturate(dot(n,l));
                // 漫反射
                fixed4 diff = _LightColor0 * _MainColor * ndotl;
                // 计算光的反射方向,l计算从顶点到光源的方向
                // 计算反射reflect需要的入射光线所以为-l
                float3 r = reflect(-l, n);
                r = normalize(r);
                // 顶点指向摄像机
                fixed3 view = WorldSpaceViewDir(v.vertex);
                view = normalize(view);

                fixed vdotr = saturate(dot(view, r));
                // 镜面反射公式
                fixed4 spec = _LightColor0 * _SpecularColor * pow(vdotr, _Shininess);
                // phong公式
                o.color = unity_AmbientSky + diff + spec;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}

逐像素光照
  • 在上面Phong的材质转动视角会发现一个问题,镜面反射光并不光滑会出现棱角,如下图

    这是因为上面代码是逐顶点光照而不是逐像素光照,逐顶点光照就是对每个顶点计算颜色,然后根据顶点在多边形占据的面积进行线性插值,这在三角面多的模型中没什么大问题,在三角面不是很多的模型上就会出现棱角,就像上面Unity默认球体模型
  • 那什么又是逐像素呢?就是在片元着色器中计算,颜色的计算不再基于顶点,而是基于像素,最终屏幕越大计算量越大
    具体可以看下图,上面是逐片元,下面是逐顶点可以很明显看出不同,代码和逐顶点其实没有什么太大不同
下面是逐像素光照
Shader "Unlit/FragPhong"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)
        // 材质镜面反射颜色
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)
        // 材质光泽度
        _Shininess("Shininess", Range(1,100)) = 1
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work

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

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 normal:TEXCOORD0;
                // 用来计算view所以需要将模型空间顶点坐标传给片元着色器
                float4 vertex:TEXCOORD1;
            };

            fixed4 _MainColor;
            fixed4 _SpecularColor;
            half _Shininess;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = v.normal;
                o.vertex = v.vertex;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 n = UnityObjectToWorldNormal(i.normal);
                n = normalize(n);
                fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
                fixed ndotl = saturate(dot(n,l));
                // 漫反射
                fixed4 diff = _LightColor0 * _MainColor * ndotl;
                // 计算光的反射方向,l计算从顶点到光源的方向
                // 计算反射reflect需要的入射光线所以为-l
                float3 r = reflect(-l, n);
                r = normalize(r);
                // 顶点指向摄像机
                fixed3 view = WorldSpaceViewDir(i.vertex);
                view = normalize(view);

                fixed vdotr = saturate(dot(view, r));
                // 镜面反射公式
                fixed4 spec = _LightColor0 * _SpecularColor * pow(vdotr, _Shininess);
                // phong公式
                return unity_AmbientSky + diff + spec;
            }
            ENDCG
        }
    }
}

Blinn-Phong光照模式

C S p e c u l a r = ( C l i g h t ⋅ M s p e c u l a r ) s a t u r a t e ( n ⋅ h ) M s h i n i n e s s C_{Specular}=(C_{light}·M_{specular})saturate(n·h)^{M_{shininess}} CSpecular​=(Clight​⋅Mspecular​)saturate(n⋅h)Mshininess​
Blinn-Phong不再依靠光反射向量 r r r,而是依靠视角方向和灯光方向的角平分向量 h h h计算,然后与法向量点乘,性能方面当观察者和灯光离物体很远时,Blinn可将h视为常量,而普通Phong需要根据表面曲率逐一去计算反射向量,Blinn计算效率远高于普通Phong代码片段如下

fixed4 frag (v2f i) : SV_Target
{
    float3 n = UnityObjectToWorldNormal(i.normal);
    n = normalize(n);
    fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
    fixed ndotl = saturate(dot(n,l));
    // 漫反射
    fixed4 diff = _LightColor0 * _MainColor * ndotl;

    // 顶点指向摄像机
    fixed3 view = WorldSpaceViewDir(i.vertex);
    view = normalize(view);

    // Blinn通过计算视角方向和光方向的角平分线向量
    float3 h = normalize(l + view);

    fixed ndoth = saturate(dot(n, h));
    // 镜面反射公式
    fixed4 spec = _LightColor0 * _SpecularColor * pow(ndoth, _Shininess);
    // phong公式
    return unity_AmbientSky + diff + spec;
}
渲染路径
  • 当显卡无法使用选定的渲染路径时,Unity将会选择较低精度的渲染路径,例如选择延迟渲染无法执行,将会选择前向渲染

  • 延迟渲染路径:
    – 灯光Pass基于G-Buffer(屏幕空间缓存)和深度计算光照,与场景复杂度无关,可以避免计算因未通过深度测试而被丢弃的片段,所有灯光都可以使用阴影和Cookie
    – 延迟渲染不支持真正的抗锯齿、处理半透明物体,使用延迟渲染的模型不支持接收阴影选项、Culling Mask只能再限定条件下使用,并且只能有4个
    – 延迟渲染只能再MRT(多重渲染目标)、Shader Model3或以上并支持深度渲染贴图的显卡运行,移动端可以在OpenGL ES3.0或以上设备运行,并且延迟渲染不支持正交投影模式摄像机

  • 前向渲染路径:
    – 前向渲染支持Unity所有图形功能,一部分最亮的灯光以完全逐像素渲染,4个点光源以逐顶点方式渲染,其余灯光使用更加快速的SH(球谐)光照渲染
    – 灯光的渲染方式具体以下决定
    –Not Important总是以逐顶点或SH
    – Important总是以逐像素
    – 最亮的平行光总是以逐像素
    – 如果逐像素灯光少于项目质量设置中Pixel Light数量,其余比较亮的灯光以逐像素渲染

  • Unity官方例子,如下图

  • 基础Pass包含一个逐像素的平行光和所有逐顶点或SH灯光,并且包含所有来自Shader光照贴图、环境光、自发光,平行光能投射阴影,但灯光贴图不能接受SH灯光照明

  • 其他逐像素灯光,每个灯光会产生额外的Pass,前线渲染只支持一个投射阴影的平行光,额外Pass中灯光默认不会投射阴影, 需要支持的话需要添加变体编译

实现阴影效果
Shader "Unlit/ShadowLambert"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)
    }
    SubShader
    {
        // 基础Pass 平行光产生阴影
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // 在当前pass渲染的每个灯光编译出不同Shader变体
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 normal:TEXCOORD0;
                float4 vertex:TEXCOORD1;
                //使用预定义宏保存阴影坐标 2代表TEXCOORD2 这个宏在AutoLight.cginc
                SHADOW_COORDS(2)
            };

            fixed4 _MainColor;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = v.normal;
                o.vertex = v.vertex;
                // 这个宏也定义在AutoLight.cginc,变换阴影贴图的纹理坐标存入结构体o中
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 n = UnityObjectToWorldNormal(i.normal);
                n = normalize(n);
                // 原来这样计算光源方向fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
                // 使用_WorldSpaceLightPos0时,会导致点光源和聚光灯位置对光照效果不起作用
                // i.vertex是float4
                float3 l = WorldSpaceLightDir(i.vertex);
                l = normalize(l);
                float4 worldPos = mul(unity_ObjectToWorld, i.vertex);

                // lambert
                fixed ndotl = saturate(dot(n, l));
                fixed4 c = _LightColor0 * _MainColor * ndotl;

                // 加4个点光源光照
                c.rgb += Shade4PointLights(
                     //4个点光源坐标、颜色、衰减、世界坐标、世界法线
                    unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
                    unity_LightColor[0].rgb,unity_LightColor[1].rgb,
                    unity_LightColor[2].rgb,unity_LightColor[3].rgb,
                    unity_4LightAtten0, worldPos.rgb, n) * _MainColor;

                // 环境光
                c += unity_AmbientSky;

                // 计算阴影系数
                UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb);

                c.rgb += shadowmask;

                return c;
            }
            ENDCG
        }

        // 为除主光外其他逐像素灯光产生投影
        Pass
        {
            Tags{"LightMode" = "ForwardAdd"}

            // 使绘制的图像与上面的Pass完全混合
            Blend One One

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdadd_fullshadows
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct v2f{
                float4 pos :SV_POSITION;
                float3 normal :TEXCOORD0;
                float4 vertex : TEXCOORD1;
                SHADOW_COORDS(2)
            };

            fixed4 _MainColor;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = v.normal;
                o.vertex = v.vertex;
                // 这个宏也定义在AutoLight.cginc,变换阴影贴图的纹理坐标存入结构体o中
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 n = UnityObjectToWorldNormal(i.normal);
                n = normalize(n);
                // 原来这样计算光源方向fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
                // 使用_WorldSpaceLightPos0时,会导致点光源和聚光灯位置对光照效果不起作用
                // i.vertex是float4
                float3 l = WorldSpaceLightDir(i.vertex);
                l = normalize(l);
                float4 worldPos = mul(unity_ObjectToWorld, i.vertex);

                // lambert
                fixed ndotl = saturate(dot(n, l));
                fixed4 c = _LightColor0 * _MainColor * ndotl;

                // 加4个点光源光照
                c.rgb += Shade4PointLights(
                     //4个点光源坐标、颜色、衰减、世界坐标、世界法线
                    unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
                    unity_LightColor[0].rgb,unity_LightColor[1].rgb,
                    unity_LightColor[2].rgb,unity_LightColor[3].rgb,
                    unity_4LightAtten0, worldPos.rgb, n) * _MainColor;

                // 环境光
                c += unity_AmbientSky;

                // 计算阴影系数
                UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb);

                c.rgb += shadowmask;

                return c;
            }


            ENDCG
        }
    }
    // 阴影投射Pass
    FallBack "Diffuse"
}


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/902284.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号