基础光照模型

模拟正式光照环境生成图像时所需了解的相关概念

模拟真实光照环境生成图像的过程主要分为如下3个步骤

  • 光源发射光线
  • 光线与场景中物体相交,一些光线被吸收,一些光线散射到其他方向
  • 摄像机吸收一些光,产生图像

光源

辐照度:光源发射的光的数量
平行光的辐照度:垂直于光的方向的单位面积上单位时间内穿过的能量

吸收

吸收:改变密度和颜色

散射

散射:改变方向
分为2类

  • 折射:射到物体内部 漫反射
  • 反射:射到外部 高光反射

出射度:出射光线的数量和方向(根据入射光线的数量和方向计算)

着色

根据材质属性和光源信息,使用一个等式(光照模型)计算沿某观察方向的出射度

标准光照模型(Phong光照模型)

只关心直接光照
直接光照:直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光效
基本方法:把进入摄像机的光线分为4部分,各部分使用自己的方法计算贡献度

  • 自发光(emissive):给定一个方向,某表面本身会向该方向发射的辐射量
  • 高光反射(specular):光线从光源照射到模型表面时,表面会在完全镜面反射方向散射的辐射量
  • 漫反射(diffuse):光线从光源照射到模型表面时,该表面会向各方向散射的辐射量
  • 环境光(ambient):其他所有间接光照

逐像素和逐顶点

  • 片元着色器中计算光照模型:逐像素光照
    Phong着色:在面片之间对顶点发现进行插值
  • 顶点着色器中计算光效模型:逐顶点光照
    高洛德着色:在各顶点计算光照,在渲染图元内进行线性插值,输出成像素颜色

Unity中的光

环境光

在Windows/Lighting/Settings中的AmbientOcclusion中设置
在Shader中通过UNITY_LIGHTMODEL_AMBIENT得到环境光的颜色和强度

自发光

在片元着色器输出最后的颜色之前,将材质的自发光颜色添加到输出颜色上

Unity中的漫反射光照模型

漫反射值 = 光的颜色 * 材质的漫反射系数 * 表面法线与光源方向的点积
最终的颜色 = 环境光值+漫反射值

逐顶点光照

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

// 逐顶点的漫反射光照
Shader "Fan/Diffuse Vertex-Level"
{
    Properties
    {
        // 控制材质的漫反射颜色 
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        // 顶点/片元着色器的代码要写在Pass中(而非SubShader中)
        Pass
        {
            // 指定该Pass的光照模式(定义该Pass在Unity的光照流水线中的角色)
            // 定义了LightMode后,可得到Unity内置的光照变量
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 要使用的Unity内置变量包含在如下内置文件中
            #include "Lighting.cginc"

            // 与声明的属性相匹配的变量
            fixed4 _Diffuse;

            struct a2v
            {
                float4 vertex : POSITION;
                // 将模型顶点的法线信息存储到该变量中
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 pos : SV_POSITION;
                // 将在顶点着色器中算得的光照颜色存储在该变量中,传递给片元着色器
                float3 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 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                // 计算漫反射光照结果                              saturate:将参数截取到范围0~1内
                // _LightColor0是内置变量,表示该Pass处理的光源的颜色和强度信息(要定义合适的LightMode标签)
                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
        }
    }
    Fallback "Diffuse"
}

逐像素光照

更平滑,但是在光照无法到达的区域,模型的外观全黑且无明暗变化
顶点着色器将世界空间下的法线传给片元着色器,在片元着色器中计算漫反射光照模型

半兰伯特模型

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

改善模型背光面明暗一样的问题
对于模型的背光面,兰伯特光照模型的点积结果都会映射到0,而在半兰伯特模型中背光面有明暗变化(不同的点积结果映射到不同值)
该模型无物理依据,仅是视觉加强技术
在片元着色器计算漫反射光照的部分中,用半兰伯特模型代替兰伯特模型

Unity中的高光反射光照模型

高光反射值 = 光的颜色 * 材质的高光反射系数 * 视角方向与反射方向的点积的高光参数次方
高光参数影响高光区域的大小
通过reflect(i,n),根据入射方向和法线方向计算反射方向

逐顶点光照

逐像素光照

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

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

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

// 逐顶点的高光反射光照
Shader "Fan/Specular Pixel-Level"
{
    Properties
    {
        // 控制材质的漫反射颜色 
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        // 控制高光区域大小, 成反比
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader
    {
        // 顶点/片元着色器的代码要写在Pass中(而非SubShader中)
        Pass
        {
            // 指定该Pass的光照模式(定义该Pass在Unity的光照流水线中的角色)
            // 定义了LightMode后,可得到Unity内置的光照变量
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 要使用的Unity内置变量包含在如下内置文件中
            #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 = normalize(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"
}

顶点着色器计算世界空间下的法线方向和顶点坐标
将高光相关计算移动到片元着色器中,得到的结果更平滑

Blinn-Phong光照模型

// 高光
                // 入射光线方向关于表面法线的反射方向
                // 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(saturate(dot(reflectDir, viewDir)), _Gloss);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);

引入新的向量代替反射方向
点积:法线方向与新向量的点积
新的向量:对视角方向和光照方向相加后的向量归一化
计算简单:仅需一次向量乘法
结果:高光反射部分更大更亮
这是经验模型

上述几种光照模型的实际效果

各种光照模型的效果.png

Unity提供的在计算光照时可用的内置函数

Unity提供了常用计算函数

输入各空间中的顶点位置

  • 输出对应空间中该点到摄像机的方向
  • 输出世界空间中从该点到光源的光照方向(只能用于前向渲染(Pass的渲染标签中设置),因为只有在前向渲染中光的位置(内置变量_WorldSpaceLightPos0)才能被正确赋值)

将方向向量转到对应空间

未归一化

内置函数输出的方向没有归一化(需开发者主动使用normalize进行归一化)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • 现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是以目前我们所拥有的处理能力无法模拟的。因此OpenGL的...
    IceMJ阅读 1,978评论 1 6
  • 转载自VR设计云课堂[//www.greatytc.com/u/c7ffdc4b379e]Unity S...
    水月凡阅读 1,015评论 0 0
  • 版本记录 前言 OpenGL 图形库项目中一直也没用过,最近也想学着使用这个图形库,感觉还是很有意思,也就自然想着...
    刀客传奇阅读 6,069评论 0 2
  • 现在的我觉睡好了,心情却非常低落。早上六点就从工厂里走了出来,工厂外空无一人,到了公交点,车上只有保安和司机在座...
    游魔阅读 270评论 0 4
  • 七律.无题 文/蜀客 无端一阵怪风摧,坏损湖边三树梅。 落入清波谁可识?休居山客棹迟回。 凭窗长久怜姿宇,对镜萧然...
    寺咀山主人阅读 462评论 3 18