引言
在OpenGL中有三种类型的光:方向光(directional)、点光(point)、聚光(spotlight)。本教程将从方向光讲起,首先我们将使用GLSL来模仿OpenGL中的光。
我们将向shader中逐渐添加环境光、散射光和高光效果。

后面的教程中我们将使用逐像素光照以获得更好的效果。

接下来我们将实现逐像素的点光和聚光。这些内容与方向光很相近,大部分代码都是通用的。

在卡通着色的教程中我们接触过在GLSL中如何访问OpenGL状态中关于光源的部分,这些数据描述了每个光源的参数。

  1. struct gl_LightSourceParameters
  2. {
  3. vec4 ambient;
  4. vec4 diffuse;
  5. vec4 specular;
  6. vec4 position;
  7. vec4 halfVector;
  8. vec3 spotDirection;
  9. float spotExponent;
  10. float spotCutoff; // (range: [0.0,90.0], 180.0)
  11. float spotCosCutoff; // (range: [1.0,0.0],-1.0)
  12. float constantAttenuation;
  13. float linearAttenuation;
  14. float quadraticAttenuation;
  15. };
  16. uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];
  17. struct gl_LightModelParameters
  18. {
  19. vec4 ambient;
  20. };
  21. uniform gl_LightModelParameters gl_LightModel;

在GLSL中也同样可以访问材质参数:

  1. struct gl_MaterialParameters
  2. {
  3. vec4 emission;
  4. vec4 ambient;
  5. vec4 diffuse;
  6. vec4 specular;
  7. float shininess;
  8. };
  9. uniform gl_MaterialParameters gl_FrontMaterial;
  10. uniform gl_MaterialParameters gl_BackMaterial;

在OpenGL程序中,这些参数中的大部分,不论属于光源还是材质,用起来都是相似的。我们将使用这些参数实现自己的方向光。

方向光I
本节的公式来自《OpenGL编程指南》中“和光照有关的数学知识”这一章。
我们从散射光开始讨论。在OpenGL中假定,不管观察者的角度如何,得到的散射光强度总是相同的。散射光的强度与光源中散射光成分以及材质中散射光反射系数相关,此外也和入射光角度与物体表面法线的夹角相关。

OpenGL用下面的公式计算散射光成分:

I是反射光的强度,Ld是光源的散射成分(gl_LightSource[0].diffuse),Md是材质的散射系数(gl_FrontMaterial.diffuse)。
这个公式就是Lambert漫反射模型。Lambert余弦定律描述了平面散射光的亮度,正比于平面法线与入射光线夹角的余弦,这一理论提出已经超过200年了。
在顶点shader中要实现这个公式,需要用到光源参数中的方向、散射成分强度,还要用到材质中的散射成分值。因此使用此shader时,在OpenGL中需要像在平时一样设置好光源。注意:由于没有使用固定功能流水线,所以不需要对光源调用glEnable。
要计算余弦值,首先要确保光线方向向量(gl_LightSource[0].position)与法线向量都是归一化的,然后就可以使用点积得到余弦值。注意:对方向光,OpenGL中保存的方向是从顶点指向光源,与上面图中画的相反。
OpenGL将光源的方向保存在视点空间坐标系内,因此我们需要把法线也变换到视点空间。完成这个变换可以用预先定义的一致变量gl_NormalMatrix。这个矩阵是模型视图变换矩阵的左上3×3子矩阵的逆矩阵的转置。
以下就是上述内容的顶点shader代码:

  1. void main()
  2. {
  3. vec3 normal, lightDir;
  4. vec4 diffuse;
  5. float NdotL;
  6. /* first transform the normal into eye space and normalize the result */
  7. normal = normalize(gl_NormalMatrix * gl_Normal);
  8. /* now normalize the light's direction. Note that according to the
  9. OpenGL specification, the light is stored in eye space. Also since
  10. we're talking about a directional light, the position field is actually
  11. direction */
  12. lightDir = normalize(vec3(gl_LightSource[0].position));
  13. /* compute the cos of the angle between the normal and lights direction.
  14. The light is directional so the direction is constant for every vertex.
  15. Since these two are normalized the cosine is the dot product. We also
  16. need to clamp the result to the [0,1] range. */
  17. NdotL = max(dot(normal, lightDir), 0.0);
  18. /* Compute the diffuse term */
  19. diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
  20. gl_FrontColor =  NdotL * diffuse;
  21. gl_Position = ftransform();
  22. }

在片断shader中要做的就是使用易变变量gl_Color设置颜色。

  1. void main()
  2. {
  3. gl_FragColor = gl_Color;
  4. }

下图显示了应用此shader的茶壶效果。注意茶壶的底部非常黑,这是因为还没有使用环境光的缘故。

加入环境光非常容易,只需要使用一个全局的环境光参数以及光源的环境光参数即可,公式如下所示:

前面的顶点shader中需要加入几条语句完成环境光的计算:

  1. void main()
  2. {
  3. vec3 normal, lightDir;
  4. vec4 diffuse, ambient, globalAmbient;
  5. float NdotL;
  6. normal = normalize(gl_NormalMatrix * gl_Normal);
  7. lightDir = normalize(vec3(gl_LightSource[0].position));
  8. NdotL = max(dot(normal, lightDir), 0.0);
  9. diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
  10. /* Compute the ambient and globalAmbient terms */
  11. ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
  12. globalAmbient = gl_FrontMaterial.ambient * gl_LightModel.ambient;
  13. gl_FrontColor =  NdotL * diffuse + globalAmbient + ambient;
  14. gl_Position = ftransform();
  15. }

下图显示了最终效果。加入环境光后整个画面都变亮了,不过相对于应用了反射光效果的全局光照模型(global illumination model),这种计算环境光的方式只能算廉价的解决方案。

方向光II
下面介绍OpenGL方向光中的镜面反射部分。我们使用称为Blin-Phong模型的光照模型,这是Phong模型的简化版。在这之前,我们有必要先看看Phong模型,以便于更好地理解Blin-Phong模型。
在Phong模型中,镜面反射成分和反射光线与视线夹角的余弦相关,如下图:

L表示入射光,N表示法线,Eye表示从顶点指向观察点的视线,R是L经镜面反射后的结果,镜面反射成分与α角的余弦相关。
如果视线正好和反射光重合,我们将接收到最大的反射强度。当视线与反射光相分离时,反射强度将随之下降,下降速率可以由一个称为shininess的因子
控制,shininess的值越大,下降速率越快。也就是说,shininess越大的话,镜面反射产生的亮点就越小。在OpenGL中这个值的范围是0
到128。

计算反射光向量的公式:

OpenGL中使用Phong模型计算镜面反射成分的公式:

式中指数s就是shininess因子,Ls是光源中镜面反射强度,Ms是材质中的镜面反射系数。
Blinn提出了一种简化的模型,也就是Blinn-Phong模型。它基于半向量(half-vector),也就是方向处在观察向量以及光线向量之间的一个向量:

现在可以利用半向量和法线之间夹角的余弦来计算镜面反射成分。OpenGL所使用的Blinn-Phong模型计算镜面反射的公式如下:

这个方法与显卡的固定流水线中使用的方法相同。因为我们要模拟OpenGL中的方向光,所以在shader中也使用此公式。幸运的是:OpenGL会帮我们算半向量,我们只需要使用下面的代码:

  1. /* compute the specular term if NdotL is  larger than zero */
  2. if (NdotL > 0.0)
  3. {
  4. // normalize the half-vector, and then compute the
  5. // cosine (dot product) with the normal
  6. NdotHV = max(dot(normal, gl_LightSource[0].halfVector.xyz),0.0);
  7. specular = gl_FrontMaterial.specular * gl_LightSource[0].specular *
  8. pow(NdotHV,gl_FrontMaterial.shininess);
  9. }

完整的Shader Designer工程下载:
http://lighthouse3d.com/wptest/wp-content/uploads/2011/03/ogldirsd.zip

【GLSL教程】(六)逐顶点的光照 【转】的更多相关文章

  1. unity shader入门(二)语义,结构体,逐顶点光照

    下为一个逐顶点漫反射光照shader Shader "study/Chapter6/vertexShader"{ Properties{_Diffuse("Diffuse ...

  2. 【GLSL教程】(七)逐像素的光照 【转】

    http://blog.csdn.net/racehorse/article/details/6662540 逐像素的方向光(Directional Light per Pixel) 这一节将把前面的 ...

  3. [Unity Shader] 逐顶点光照和逐片元漫反射光照

    书中的6.4节讲的是漫反射的逐顶点光照和逐片元光照. 前一种算法是根据漫反射公式计算顶点颜色(顶点着色器),对颜色插值(光栅化过程)返回每个像素的颜色值(片元着色器). 第二种算法是获得顶点的法线(顶 ...

  4. 【GLSL教程】(一)图形流水线 【转】

    http://blog.csdn.net/racehorse/article/details/6593719 这是一些列来自lighthouse3d的GLSL教程,非常适合入门.我将边学习边翻译该教程 ...

  5. 【GLSL教程】(五)卡通着色 【转】

    http://blog.csdn.net/racehorse/article/details/6641623 引言 卡通着色可能是最简单的非真实模式shader.它使用很少的颜色,通常是几种色调(to ...

  6. NeHe OpenGL教程 第七课:光照和键盘

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  7. Unity3D脚本中文系列教程(六)

    http://dong2008hong.blog.163.com/blog/static/469688272014031943118/ Unity3D脚本中文系列教程(五) 变量 ◆var colli ...

  8. 【Unity Shader】(六) ------ 复杂的光照(上)

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题.              [Unity Sha ...

  9. CRL快速开发框架系列教程六(分布式缓存解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

随机推荐

  1. 聊聊、Spring 数据源

    平时开发中我们每天都会跟数据库打交道,页面上显示的数字,图片,语音,等等都存在某个地方,而我们就是要从那个地方拿到我们想要的.现在存储数据的方式越来越多,多种多样,但用的最多的还是关系数据库.Spri ...

  2. 浅谈JavaScript中的函数问题

    前面的话:JavaScript可运行在所有主要平台的主流浏览器上,也可运行在每一个主流操作系统的服务器端上.所以呢,要想成为一名优秀的全栈工程师,必须懂得JavaScript语言.这是我整理的JS的部 ...

  3. 只显示前几条数据的sql语句写法 七种数据库中Select Top的使用方法

    七种数据库中Select Top的使用方法 1. Oracle数据库 SELECT * FROM TABLENAME WHERE ROWNUM <= N 2. Infomix数据库 SELECT ...

  4. 【bzoj1336/1337/2823】[Balkan2002]Alien最小圆覆盖 随机增量法

    题目描述 给出N个点,让你画一个最小的包含所有点的圆. 输入 先给出点的个数N,2<=N<=100000,再给出坐标Xi,Yi.(-10000.0<=xi,yi<=10000. ...

  5. C/C++注释规范

    C/C++注释规范 Doxygen是一种开源跨平台的,以类似JavaDoc风格描述的文档系统,完全支持C.C++.Java.Objective-C和IDL语言,部分支持PHP.C#.鉴于Doxygen ...

  6. ZOJ 3874 Permutation Graph ——分治 NTT

    发现每一块一定是按照一定的顺序的. 然后与标号无关,并且相同大小的对答案的影响相同. 然后列出递推式,上NTT+分治就可以了. 然后就可以与输入同阶处理答案了. #include <map> ...

  7. [暑假集训--数论]poj2773 Happy 2006

    Two positive integers are said to be relatively prime to each other if the Great Common Divisor (GCD ...

  8. mongodb学习(2)--- nodeJS与MongoDB的交互(使用mongodb/node-mongodb-native)

    转载:http://www.cnblogs.com/zhongweiv/p/node_mongodb.html 目录 简介 MongoDB安装(windows) MongoDB基本语法和操作入门(mo ...

  9. regression

    单变量线性回归univariate linear regression 代价函数square error cost function : \(J(\theta)=\frac{1}{2m}\sum_{i ...

  10. C#可选参数与具名参数

    可选参数 static void test1() { func1("A"); func1(); Console.ReadKey(); } ) { Console.WriteLine ...