系列教程第四篇,本来打算昨天写的,有些小偷懒就今天写了,这一期我们来讨论一下关于镜面反射的基本原理和具体代码.这一篇是承接着上一篇《Esfog_UnityShader教程_漫反射DiffuseReflection》来讲述的,如果你还没有看或者对漫反射不是很了解的话,建议点击链接去看一下,这样子有助于你更好的理解本篇教程.


镜面反射SpecularReflection


  上一篇关于漫反射的讲解中,我们说过光照处理里面两个最常见的课题也就是漫反射和镜面反射(高光)。那么这节我们就来说一下镜面反射,顺带把环境光也说一下,这样做出来的效果更好一些.类似于上一篇,我们还是来先说明一下镜面反射(为了方面,下面一律称高光)。

  如上图(图片来自网络),这两种反射给人的直观感受,我相信大家一定在生活中有所了解,上次我们说过漫反射的效果让我们对物体的某一个位置,无论我们从哪个位置观察它,我们看到的亮度都是一样的.而镜面反射则不然,你从不同的位置去观察物体的同一个点的受光照情况,看到的结果是不一样的,因为光照的反射不再是各个方向的反射量均匀了,而是在与反射光线与视线的偏差大小有着反比关系,如果你的视线与反射光线重合,那么看到的将是最亮的,如果角度大于90度那将看不到高光效果。一个比较好的例子就是在阳光下我们观察表面很光滑的汽车,你会发现汽车外壳上的高光区域会随着你观察位置的不同而移动和变化。

 

  如上图(图片取自《Cg Programming in Unity》),要计算镜面反射,我们需要知道的东西可能要比漫反射多一些,一共4个向量,光的入射方向L(反方向,下同),表面法线N,观察的目标位置到摄像机的向量V(视线),以及反射光线R,和漫反射一样,我们这里只考虑平行光的情况,其他类型的光源于其有所不同,大家自行了解,这里就不赘述了,如果有地方用到了,也可以到时候具体说明.

  参考着上面这幅图,我们来说明一下它的计算原理,前面提到其实最终决定我们看到的光的强弱的是视线V和反射向量R,这两个向量的夹角越小说明越接近光线经过物体表面的反射直接反射到我们的眼睛(摄像机).两者的关系很类似与上一篇漫反射中入射光线L和法线N的关系.所以我们也通过V·R = |V|*|R|*cosθ.大家通过这个公式也应该可以明白为什么上面说镜面反射对于物体的同一个点你在不同位置观察看到的结果并不一样了吧.既然这里我们只用到了两个向量就可以决定最终的影响高光的因子,那为什么前面说需要四个向量呢,那是因为反射光线R是需要通过法线N和入射光线L进行计算得来的.

  我们先来说明一下如何通过N和L来计算得到R.

  如图所示(图片取自网友butwang博客),不难看出我们只要计算出一个向量s然后对其乘以2就可以得到2s然后根据向量的加法规则,我们可以利用L+2s = R来获取最终结果.那么首先来求向量s,要计算s就要利用L和L在N上的投影向量,因为N·L = |N|*|L|*cosθ,若N为单位向量,则|N| = 1所以N·L = |L|*cosθ,L在N上的投影距离为N·L,然后再将结果乘以N的单位向量,所以我们要先将N规范化才行,我们假设N就是规范化后的单位向量那么,L在N上的投影向量则为(L·N)*N,那么通过向量减法我们可以计算出s = (L·N)*N - L,进而计算出R = 2s + L = 2((L·N)*N-L) +L = 2N(N·L) - L.不过这么繁琐的逻辑,Cg已经帮我们搞定了,我们只需要通过reflect(L,N)函数就可以计算出反射向量,第一个参数是光的入射反向(注意,这里是真正的入射方向,不是反方向),第二个参数是法向量.

  下面给出《The Cg Tutorial》中给出的镜面反射计算公式(它的公式和下面的略有不同,我做了下变化,效果都是一样的):

  

  先来解释一下这个公式,这个Ks是材质的反射颜色,和上一篇中漫反射公式里的Kd有些相似,Kd一般设置成贴图颜色即可,但是Ks一般不可以,我个人理解它是用来设置物体表面受到高光的时候应该呈现的一种高光颜色,如果你有相应的高光贴图,那么你可以利用Ks来读取贴图颜色来为不同的位置呈现不同的高光颜色,如果没有的话,默认设置成纯白就可以了,lightColor就是光源的颜色没什么好说的。facing这个要说一下,我们计算光照强度的时候用了V和R,但有的时候V和R虽然角度小于90度属于有效范围,但是这时候如果L和N的夹角已经大于90度了,实际上这个物体不应该在收到这个光照影响了,但是如果我们只看V和R,那么可能通过reflect函数计算出来的R向量与V的夹角不一定会大于90度,这样子我们就会使本不应该受到光照的地方受到了光照。所以我们加了一个facing变量,如果N和L夹角大于90度,则facing为0,否则为1。而对于max(V·R,0)和我们上节的max(N·L,0)差不多,这里就不说了,在max的外层还有一个指数shininess,这个是用来调整光泽度的,shininess越大,说明物体的表面越光泽,那么你看到的亮斑就越小越集中,否则越大越分散.为什么这么来实现,我也不太清楚,一般设为10较为适合。

  前面提到,我们要顺带说一下环境光的问题,这里简单描述一下,现实生活中,一个真实的物体除了从光源出直接接受光照之外,还会受到周围其它物体反射出去的光,有时候即使物体本身并没有受到光源直接照射,也会呈现出一定的亮度.在渲染中我们把这些受到其它物体反射所得到的光统称为环境光.在unity中场景中所有的物体使用统一的环境光,在Edit->Render Setting->Ambient Light你可以设置它,一般比较微弱.它无法与真实世界的效果相媲美,只是一种大致的模拟效果。下面给出《The Cg Tutorial》中给出的环境光计算公式:

  

  其中Ka是材质关于环境光的系数,这个我理解成和漫反射中的Kd保持一致就行了,globalAmbient这个就是我们刚才设置的漫反射颜色.

  最终我们要把这些颜色加在一起作为最终的结果 Color = ambient + diffuse + specular.下面看一下具体代码吧.

  

 Shader "Esfog/SpecularReflection"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_SpecColor("SpecularColor",Color) = (,,,)
_Shininess("Shininess",Float) =
}
SubShader
{
Pass
{
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma target 5.0
uniform float4 _LightColor0;
uniform sampler2D _MainTex;
uniform float _Shininess;
uniform float4 _SpecColor;
struct VertexOutput
{
float4 pos:SV_POSITION;
float4 posWorld:TEXCOORD0;
float3 normal:TEXCOORD1;
float2 uv:TEXCOORD2;
}; VertexOutput vert(appdata_base input)
{
VertexOutput o;
o.pos = mul(UNITY_MATRIX_MVP,input.vertex);
o.posWorld = mul(_Object2World,input.vertex);
o.normal = normalize(mul(float4(input.normal,0.0),_World2Object).xyz);
o.uv = input.texcoord.xy;
return o;
} float4 frag(VertexOutput input):COLOR
{
float3 normalDir = normalize(input.normal);
float3 viewDir = normalize(float3(_WorldSpaceCameraPos - input.posWorld));
float4 Kd = tex2D(_MainTex,input.uv);
float4 Ks = _SpecColor;
float4 Ka = Kd;
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 ambientLighting = Ka.rgb * UNITY_LIGHTMODEL_AMBIENT.rgb;
float3 diffuseReflection = Kd.rgb * _LightColor0.rgb * max(0.0,dot(normalDir,lightDir));
float facing;
if(dot(normalDir,lightDir)<=)
{
facing = ;
}
else
{
facing = ;
}
float3 SpecularReflection = facing * _LightColor0.rgb * _SpecColor.rgb * pow(max(,dot(reflect(-lightDir,normalDir),viewDir)),_Shininess);
return float4(ambientLighting + diffuseReflection + SpecularReflection,);
}
ENDCG
}
}
FallBack "Diffuse"
}

  有的地方和漫反射的基本相同,我只解释这节中的新地方.

  第6~7行在Properties中我们定义了一个新的变量_Shininess,float类型的,和Color类型的_SpecColor,其中_Shininess用来调节物体表面的光泽度.原理在前面解释过了._SpecColor用来调整高光反射颜色,如果你有高光贴图的话这里就改成2d类型.

  第21~22行这两个变量我们在后面要使用,所以声明一下和Properties中的关联.

  第26行,我们在VertexOuput中多定义了一个变量,由于我们要计算视线向量,所以必须知道物体在世界空间中的位置(这个不一定,你可以在任何其他空间计算,只要参与计算的两个向量在一个空间就可以).

  第35行,直接对模型空间的点左乘_Object2World矩阵转到世界空间就可以得到顶点在世界空间的位置了。

  第45~47行为分别为漫反射,高光,环境光的反射系数赋值,注意高光与另外两者的区别.

  第49行利用上面的环境光计算公式计算出环境光,其中UNITY_LIGHTMODEL_AMBIENT是Unity提供给我们直接获取场景中环境光颜色的变量.

  第51~59行计算镜面反射公式中的facing,原理前面说过.

  第60行利用镜面反射公式计算出高光颜色.有两点要注意,第一是给reflect函数传入射光的时候,传的不是反方向,而是真正的入射方向,所以要给我们之前方式计算出来的lightDir前面加个负号,第二点就是Cg中通过pow来计算指数,这个函数一般的语言都会提供,大家应该见过.

  第61行把我们计算的漫反射,高光,环境光加在一起作为最终的颜色就可以了.

  (~ o ~)~系列教程的第四篇到此结束了,结合前一篇我们大致上对最基本的光照模型有了一些了解,下一篇也许会结合两者做一个实例,或者继续讲其他的地方,之后的内容可能会用到更多的数学原理和思考方法,其实Shader难得不是语法,难得是理解背后的真像,去探索背后的知识,我觉得比仅仅会使用要收获的更多。说实话,我不是一个随便的人,所以写帖子总是力求能写好,写透彻一些,这也让我每写一篇文章都要花费大量的时间,但这其中也让我自己在写教程的过程中能有所收获,发现自己理解不到位的地方,即使补充,如果这些内容能给大家带来一些启发,我也觉得我没有白写。谢谢大家的支持.

  继续用上篇中的模型来大概展示一下效果.

  

  上面是使用上篇中漫反射的效果

  

  上面是使用了本篇课的ambient+diffuse+specular的效果.质感一下子就出来了,没有加高光贴图,要么会效果更好。

  尊重他人智慧成果,欢迎转载,请注明作者esfog,原文地址http://www.cnblogs.com/Esfog/p/3589784.html

Esfog_UnityShader教程_镜面反射SpecularReflection的更多相关文章

  1. Esfog_UnityShader教程_前言

    很多人在学习Unity的时候对Shader都是一知半解,作为刚入职半年的新人接触Shader的时间也并不长,正因为是新人才能体会到学习Shader时候所遇到的困难和迷茫,无奈于资料不好找,网上难得的几 ...

  2. Esfog_UnityShader教程_漫反射DiffuseReflection

    这篇是系列教程的第三篇,最近工作比较紧,所以这个周六周日就自觉去加了刚回来就打开电脑补上这篇,这个系列的教程我会尽量至少保证一周写一篇的.如果大家看过我的上一篇教程<Esfog_UnitySha ...

  3. Esfog_UnityShader教程_遮挡描边(实现篇)

     在上一篇中,我们基本上说明了遮挡描边实现的一种基本原理.这一篇中我们将了解一下基于这种原理的具体实现代码.本篇中的内容和前几篇教程相比,相对比较难一些,建议先有一些基本的Unity的C#脚本编程经验 ...

  4. Esfog_UnityShader教程_遮挡描边(原理篇)

    咳咳,有段时间没有更新了,最近有点懒!把不少精力都放在C++身上了.闲言少叙,今天要讲的可和之前的几篇有所不同了,这次是一个次综合应用.这篇内容中与之前不同主要体现在下面几点上. 1.之前我们写的都是 ...

  5. Esfog_UnityShader教程_逐帧动画

    有段日子没出这个系列的新文章了,今天就拿一个比较常见也比较基础的利用改变Shader来改变不断调整UV实现播放逐帧动画的小功能.很久没写了就当练练手了.在新版本的Unity中早就已经集成了Sprite ...

  6. Esfog_UnityShader教程_溶解效果Dissolve

    溶解效果在游戏中是很常见的,比如在一些神话或者魔法世界中,一些NPC角色在剧情需要时候会身体会渐渐的消失掉.甚至有一些更炫的,比如用火焰喷射器把目标燃尽.这些都可以用到溶解效果.这篇文章主要是讲解一下 ...

  7. C#读写文件的方法汇总_C#教程_脚本之家

    C#读写文件的方法汇总_C#教程_脚本之家 http://www.jb51.net/article/34936.htm

  8. 转载:Hadoop安装教程_单机/伪分布式配置_Hadoop2.6.0/Ubuntu14.04

    原文 http://www.powerxing.com/install-hadoop/ 当开始着手实践 Hadoop 时,安装 Hadoop 往往会成为新手的一道门槛.尽管安装其实很简单,书上有写到, ...

  9. Spring_MVC_教程_快速入门_深入分析

    Spring MVC 教程,快速入门,深入分析 博客分类: SPRING Spring MVC 教程快速入门  资源下载: Spring_MVC_教程_快速入门_深入分析V1.1.pdf Spring ...

随机推荐

  1. 省常中模拟 day1

    第一题: 题目大意: 给出N个数的数列,如果相邻的两个数加起来是偶数,那么就可以把这两个数消掉,求最多能消掉多少数. 解题过程: 1.先自己手工模拟了几组数据,发现不管消除的顺序如何,最终剩下的是一定 ...

  2. jquery事件代理

    在jQuery中,事件代理是指:把事件绑定到父级元素,然后等待事件通过DOM冒泡到该元素时再执行. 在事件侦听过程中有两种触发事件的方式:事件捕获和事件冒泡.事件冒泡更快,效率更高. 事件捕获:事件在 ...

  3. HBase with MapReduce (Summary)

    我们知道,hbase没有像关系型的数据库拥有强大的查询功能和统计功能,本文实现了如何利用mapreduce来统计hbase中单元值出现的个数,并将结果携带目标的表中, (1)mapper的实现 pac ...

  4. Linq二 LinqToSql

    虽然微软已经停止更新了LinqToSql,但是目前的已完全满足目前的需求. 第一步:添加LinqToSql 第二步:将其关联的Sqlserver数据库 第三步:数据库已变成实体类 第四步:可以对数据库 ...

  5. What is a watch descriptor

    http://stackoverflow.com/questions/24342156/what-are-watch-descriptors-really-linux-inotify-subsyste ...

  6. 团队开发——冲刺1.e

    冲刺阶段一(第五天) 冲刺阶段一(第五天) 1.昨天做了什么?优化界面细节. 查看C#资料,再解决自己电脑的问题. 2.今天准备做什么? 为解决自己电脑的问题,查找关于C#的资料,后期做准备.

  7. Ubuntu软件中心打不开,Encountered a section with no Package: header错误之解决

    sudo rm /var/lib/apt/lists/* -vf sudo apt-get update

  8. Python Scopes and Namespaces

    Before introducing classes, I first have to tell you something about Python's scope rules. Class def ...

  9. 如何加载JS

    外部JS的阻塞下载 所有浏览器在下载JS的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等.至到JS下载.解析.执行完毕后才开始继续并行下载其他资源并呈现内容. 有人会问:为什么JS不能像 ...

  10. xmind的第九天笔记