二、光照模型:3、基础高光反射

转载自http://blog.csdn.net/lzhq1982/article/details/74941687

上一篇学习了基础漫反射的实现,这一篇我们看看基础高光反射的实现。

基本光照模型中高光反射的计算公式:

image

从上面公式可以看出,我们需要4个变量,入射光线的颜色和强度c(light),材质的高光反射系数m(specular),视角方向v及反射方向r。其中,反射方向r可以由法线 n 和光源方向 I 计算计算得到:

image

然而CG早就给我们提供了计算反射方向的函数reflect(i, n)。

参数:i,入射方向;n,法线方向。

描述:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。

1、逐顶点光照

还是像上篇一样,准备一个胶囊体,放上新建的材质,绑好Shader模板,然后我们改写Shader,直接上代码

Shader "CustomShader/Specular Vertex-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        {           Tags {"LightMode" = "ForwardBase"}          CGPROGRAM           #pragma vertex vert         #pragma fragment frag                       #include "Lighting.cginc"           fixed4 _Diffuse;            fixed4 _Specular;           float _Gloss;           struct appdata          {               float4 vertex : POSITION;               float3 normal : NORMAL;         };          struct v2f          {               float4 vertex : SV_POSITION;                fixed3 color : COLOR;           };          v2f vert (appdata v)            {               v2f o;              o.vertex = mul(UNITY_MATRIX_MVP, 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(viewDir, reflectDir)), _Gloss);               o.color = ambient + diffuse + specular;                 return o;           }           fixed4 frag (v2f i) : SV_Target         {               fixed4 col = fixed4(i.color, 1.0);              return col;         }           ENDCG       }   }   FallBack "Specular"}

上面代码也很简单,我们抛去这里的漫反射部分,上一篇已经介绍过了,这里只看高光反射部分。

首先属性里,我们加入了_Specular,控制材质高光反射颜色,_Gloss控制高光区域大小。

关于标签,头文件上一篇介绍过了,这里不再说,颜色属性范围是[0, 1]。所以直接用fixed精度就可以了。_Gloss范围大,用float。这里是逐顶点光照,核心逻辑都在顶点着色器里了。前面是环境光和漫反射,我们重点看高光反射部分,高光反射需要的4个变量,光线强度和颜色我们用_LightColor0,高光反射颜色我们用传入的_Specular,然后我们看v和r,注意,我们要用世界空间的v和r,v是视角方向,就是眼睛看到该点的方向,眼睛是摄像机,所以摄像机减改点就可以得到视角方向:

fixed3 viewDir =

normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

v.vertex是模型空间的点,所以先转化到世界空间,最后不要忘了归一化。

反射方向我们用reflect函数,CG的reflect函数的入射方向要求是由光源指向交点处,所以我们对worldLightDir取反:

fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

最后全部代入公式,得到高光反射颜色:

fixed3 specular =

_LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir, reflectDir)), _Gloss);

最后把环境光,漫反射和高光反射相加即可。

片元着色器啥也没做,直接输出颜色。Fallback返回Shader内置的Specular。

最终效果如下图:

image

我们可以看出高光部分很不平滑,因为高光反射部分是非线性的,而顶点着色器到片元着色器的插值是线性的,这样得出的效果会不准确。那我们来看逐像素的。

2、逐像素光照

直接上代码吧:

Shader "CustomShader/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        {           Tags {"LightMode" = "ForwardBase"}          CGPROGRAM           #pragma vertex vert         #pragma fragment frag                       #include "Lighting.cginc"           fixed4 _Diffuse;            fixed4 _Specular;           float _Gloss;           struct appdata          {               float4 vertex : POSITION;               float3 normal : NORMAL;         };          struct v2f          {               float4 vertex : SV_POSITION;                float3 worldNormal : TEXCOORD0;             float3 worldPos : TEXCOORD1;            };          v2f vert (appdata v)            {               v2f o;              o.vertex = mul(UNITY_MATRIX_MVP, 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 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);             fixed3 worldNormal = normalize(i.worldNormal);              fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (saturate(dot(worldNormal, worldLightDir)));                 fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);              fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir, reflectDir)), _Gloss);               fixed3 color = ambient + diffuse + specular;                return fixed4(color, 1.0);          }           ENDCG       }   }   FallBack "Specular"}

核心代码和逐顶点区别不大,只是放在片元着色器中计算了,当然为了片元着色器中计算用到的法线和顶点的世界坐标,我们把它们放在顶点输出结构体中,并在顶点着色器中计算出世界法线和世界坐标,经插值传给片元着色器进行计算,后面就一样了,不解释,最后看效果:

image

是不是平滑多了,至此,我们实现了一个完整的Phong光照模型。

3、Blinn-Phong光照模型

基础光照的概念和理论这篇里我们还提到了另一种高光反射的实现方法——Blinn光照模型。它没用反射方向r,而是用了一个新的矢量h,它是通过视角方向v和光照方向I相加后再归一化得到的,即:

image

那么Blinn的公式如下:

image

上代码:

Shader "CustomShader/Specular BlinnPhong"{  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"}          CGPROGRAM           #pragma vertex vert         #pragma fragment frag                       #include "Lighting.cginc"           fixed4 _Diffuse;            fixed4 _Specular;           float _Gloss;           struct appdata          {               float4 vertex : POSITION;               float3 normal : NORMAL;         };          struct v2f          {               float4 vertex : SV_POSITION;                float3 worldNormal : TEXCOORD0;             float3 worldPos : TEXCOORD1;            };          v2f vert (appdata v)            {               v2f o;              o.vertex = mul(UNITY_MATRIX_MVP, 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 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);             fixed3 worldNormal = normalize(i.worldNormal);              fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (saturate(dot(worldNormal, worldLightDir)));                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);              fixed3 blinn = normalize(viewDir + worldLightDir);              fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, blinn)), _Gloss);              fixed3 color = ambient + diffuse + specular;                return fixed4(color, 1.0);          }           ENDCG       }   }   FallBack "Specular"}

还是逐像素的光照,和之前的写法没啥区别,只是公式部分用了Blinn的公式,我们注意到这两行就行了:

fixed3 blinn = normalize(viewDir + worldLightDir);

fixed3 specular =

_LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, blinn)), _Gloss);

然后我们看对比效果:

image

可以看出,Blinn-Phong模型的高光反射部分看起来更大、更亮一些。在实际渲染中,我们绝大多数情况会用Blinn-Phong模型。
4、使用Unity内置的函数

从上面代码可以看出,我们经常需要光源方向,视角方向等信息。而上面我们用代码实现的它们,其实只适用于平行光,简单的光照效果,不适用于点光源、聚光灯等复杂的光照模型。而手动计算这些信息很麻烦。Unity为我们提供了内置函数。

image

有了它们我们就方便多了,今后我们会用它们代替计算,有一点需要注意,它们不保证归一化,所以我们要手动归一化。基础光照就介绍到这里,下面我们看基础纹理。

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