热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

OpenGL超级宝典笔记——镜面光与法线平均

2019独角兽企业重金招聘Python工程师标准光照效果仅仅使用环境光和漫反射光的光照效果,喷气式飞机表面的颜色看起来比较平淡。在渲染木材,泥土&

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

光照效果

仅仅使用环境光和漫反射光的光照效果,喷气式飞机表面的颜色看起来比较平淡。在渲染木材,泥土,布料,纸箱上等这些表面粗糙的物体上,使用环境光和漫反射光的光照效果就基本足够了。但是在为光滑的金属物体建模时,为了使其显得更加逼真,仅仅使用环境光和漫反射光是不够的,还需要镜面光的效果。

镜面亮点

镜面光照和材料属性可以为物体表面添加光泽和亮斑的效果。当入射光与观察者的角度较小时,可以看到镜面加亮的效果。镜面亮点就是几乎所有的光照射在物体表面上并被反射开来。添加镜面光的成分:

GLfloat ambientLight = {0.3f, 0.3f, 0.3f, 1.0f};

Glfloat diffuseLight = {0.7f, 0.7f, 0.7f, 1.0f};

//specular light

GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};

....

//Enable Lighting

glEnable(GL_LIGHTING);

//set up and enable light0glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);

glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);

glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

glEnable(GL_LIGHT0);

specular[]为镜面光成分指定了一个非常亮的白光。模拟太阳当空照的效果微笑。下面的语句就是指定镜面光的成分。

glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

仅仅添加镜面光的效果,我们不会再喷气式飞机上看到什么变换。我们还需要为其指定材料的镜面光反射属性。

镜面发射

给材料添加镜面反射属性的代码段如下:

GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};

....//Enable color tracking

glEnable(GL_COLOR_MATERIAL);

//set material properties to follow

glColorglColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

//使后面的材料具有完全的镜面反射效果以及强光泽

glMaterialfv(GL_FRONT, GL_SPECULAR, specref);

glMateriali(GL_FRONT, GL_SHININESS, 128);

代码在设置镜面反射属性之前,为材料的环境光和漫反射光属性开启颜色追踪。材料的镜面光反射属性我们另外单独指定了一个固定不变的值。指定材料的镜面光反射属性为(1.0f, 1.0f, 1.0f, 1.0f)代表着完全发射入射的镜面光。在glMaterialfv(GL_FRONT, GL_SPECULAR, specref);之后的多边形将采用此材料属性。
在没有重新调用glMaterial之前,所有的材料都将具有此属性。

镜面指数

在上面的例子中在强烈的镜面光照射和材料镜面反射(完全发射)的效果下导致飞机显示为纯白,只有远离光源的表面除外(未受到光照,显示黑色)。

glMateriali(GL_FRONT, GL_SHININESS, 128);

GL_SHININESS属性设置材料的镜面指数,指定了镜面加亮的大小和集中性。如果指定为0,即没有聚焦,即整个多边形均匀加亮。通过设置这个值可以缩小镜面加亮的范围,增加镜面加亮的集中度,制造亮点的效果。这个值越小,表示材质越粗糙,点光源发射的光线照射到上面,产生较大的亮点。这个值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。这个参数值的范围为1-128。

设置为128的效果如下:

image

设置为0时效果如下:(被照射到的多边形整个都加亮了,没被照射到的显示黑色)

image

设置环境的代码如下:

void SetupRC()

{

// Light values and coordinates

GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, 1.0f };

GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f };

GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};

glEnable(GL_DEPTH_TEST);

// Hidden surface removal

glFrontFace(GL_CCW);

// Counter clock-wise polygons face out

glEnable(GL_CULL_FACE);

// Enable lighting

glEnable(GL_LIGHTING);

// Setup and enable light 0

glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);

glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight);

glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

glEnable(GL_LIGHT0);

GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};

// Enable color tracking

glEnable(GL_COLOR_MATERIAL);

// Set Material properties to follow glColor values

glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

glMaterialfv(GL_FRONT, GL_SPECULAR, specref);

glMateriali(GL_FRONT, GL_SHININESS, 128);

// Light blue background

glClearColor(0.0f, 0.0f, 1.0f, 1.0f );

glEnable(GL_NORMALIZE);

}

法线平均

image

由四边形和三角形组成的球体,如果为其的每一个表面都单独指定了法线,那这个球体看起来就是充满棱角。如果我们为每个顶点指定“真实的”法线,那么OpenGL光照计算的所产生的值将会平滑地分布在多边形表面上。这些平面的多边形经过着色之后就像平滑的表面一样。

在理论上,如果我们使用足够多的多边形来绘制球体,那球体表面就会显得平滑,这些多边形就近似于真实的表面。就像使用足够多的小短线来模拟平滑的曲线一样。如果把每个顶点都当成真实的表面上的顶点的话,那么这个表面的实际法线值就代表着真实表面的法线。在球体中,法线从球体的中心指向各个顶点。

下图图5.30中的,每一个平面片段都有法线垂直指向它的表面。就像在喷气式飞机中的例子一样。在图5.31中法线并不正交于多边形的平面,而是正交于真实球体的表面,或者球体表面的切线。

image

在球体中,计算真实法线较为简单,球体中心和多边形顶点的连线即是。但在一些复杂的物体中,就没那么简单了。需要取得共享一个顶点的多边形的法线,对其进行平均,来获得更加真实的效果。

综合例子

设置光源位置

//光源位置 GLfloat lightPos[] = {0.0f, 0.0f, 70.0f, 1.0f};
..
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

lightPos数组的前三个值x,y,z指定了光源的位置或者方向。如果最后一个值为1.0,说明光源的真实位置就位于lightPos上,是位置性光源,光线从光源发散开来。如果最后一个值为0.0则代表光在无限远处,光源是方向性光源,所有光线是平行的。

创建聚光灯

GLfloat ambientLight[] = {0.5f, 0.5f, 0.5f, 1.0f};

GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};

GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};

//光源位置

GLfloat lightPos[] = {0.0f, 0.0f, 70.0f, 1.0f};

//聚光灯光照方向朝z轴负方向

GLfloat spotDir[] = {0.0f, 0.0f, -1.0f};

void SetupRC()

{

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

//开启深度测试

glEnable(GL_DEPTH_TEST);

//剔除掉不必要的背面

glFrontFace(GL_CCW);

glEnable(GL_CULL_FACE);

glCullFace(GL_BACK);

//开启光照

glEnable(GL_LIGHTING);

//设置light0光照参数

glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, ambientLight);

glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

//设置光源位置和方向

glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);

glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0f);

//设置全局环境光

glLightModelfv(GL_AMBIENT, ambientLight);

//开启light0

glEnable(GL_LIGHT0);

//开启和设置颜色追踪

glEnable(GL_COLOR_MATERIAL);

//设置多边形正面的材料的环境光和漫反射光属性为颜色追踪

glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);


//设置镜面光的反射属性

glMaterialfv(GL_FRONT, GL_SPECULAR, specref);

glMateriali(GL_FRONT, GL_SHININESS, 128);

}
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0f);设置聚光灯的半夹角,使得聚光灯发射出一个光锥。在此光锥之外的物体不会被照射到。

image

 

在这个例子中,我们使用一个黄色的小灯泡的方式来模拟光源的位置,通过方向键可以调整其位置。并通过右键菜单来调整,物体的着色模式,和球体的近似程度(由多少多边形组成球体)。

image

GL_FLAT着色模式

image

GL_SMOOTH着色模式

image

完整代码示例:

#include "gltools.h"
GLfloat ambientLight[] = {0.5f, 0.5f, 0.5f, 1.0f};
GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};
//光源位置
GLfloat lightPos[] = {0.0f, 0.0f, 70.0f, 1.0f};
//聚光灯光照方向朝z轴负方向
GLfloat spotDir[] = {0.0f, 0.0f, -1.0f};
//用于旋转
static GLfloat xRot = 0.0f;
static GLfloat yRot = 0.0f;
#define FLAT_MODE 1
#define SMOOTH_MODE 2
#define LOW_LEVEL 3
#define MEDIUM_LEVEL 4
#define HIGH_LEVEL 5
int iShade = FLAT_MODE;
int iLevel = LOW_LEVEL;
void SetupRC()
{glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //开启深度测试 glEnable(GL_DEPTH_TEST); //剔除掉不必要的背面 glFrontFace(GL_CCW);glEnable(GL_CULL_FACE);glCullFace(GL_BACK); //开启光照 glEnable(GL_LIGHTING); //设置light0光照参数 glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, ambientLight);glLightfv(GL_LIGHT0, GL_SPECULAR, specular);//设置光源位置和方向 glLightfv(GL_LIGHT0, GL_POSITION, lightPos);glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0f); //设置全局环境光 glLightModelfv(GL_AMBIENT, ambientLight); //开启light0 glEnable(GL_LIGHT0); //开启和设置颜色追踪 glEnable(GL_COLOR_MATERIAL); //设置多边形正面的材料的环境光和漫反射光属性为颜色追踪 glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); //设置镜面光的反射属性 glMaterialfv(GL_FRONT, GL_SPECULAR, specref);glMateriali(GL_FRONT, GL_SHININESS, 128);glEnable(GL_NORMALIZE);
} void RenderScene()
{ if (iShade == FLAT_MODE){glShadeModel(GL_FLAT);} else {glShadeModel(GL_SMOOTH);}glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //矩阵压栈 glPushMatrix();glRotatef(xRot, 1.0f, 0.0f, 0.0f);glRotatef(yRot, 0.0f, 1.0f, 0.0f); //旋转后重新设置光源位置glLightfv(GL_LIGHT0, GL_POSITION, lightPos);glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);glTranslatef(lightPos[0], lightPos[1], lightPos[2]);glColor3ub(255, 0, 0);glutSolidCone(4.0, 6.0, 15, 15);glPushAttrib(GL_LIGHTING_BIT); //关闭光照画小黄球 glDisable(GL_LIGHTING);glColor3ub(255, 255, 0);glutSolidSphere(3.0, 15, 15);glPopAttrib(); //矩阵出栈glPopMatrix();glColor3ub(0, 0, 255); if (iLevel == LOW_LEVEL){glutSolidSphere(30.0f, 8, 8);} else if (iLevel == MEDIUM_LEVEL){glutSolidSphere(30.0f, 10, 10);} else {glutSolidSphere(30.0f, 15, 15);}glutSwapBuffers();
}void ChangeSize(int w, int h)
{
if (h == 0){h = 1;} //设置视口 glViewport(0, 0, w, h);GLfloat faspect = (GLfloat)w/(GLfloat)h; //透视投影变换 glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(35.0, faspect, 1.0, 350.0); //模型视图矩阵 glMatrixMode(GL_MODELVIEW);glLoadIdentity(); //先往显示器里平移 glTranslatef(0.0f, 0.0f, -250.0f);glutPostRedisplay();
} void SpecialKeys(int keys, int x, int y)
{
//改变旋转角度 if (keys &#61;&#61; GLUT_KEY_UP){xRot -&#61; 5.0f;} if (keys &#61;&#61; GLUT_KEY_DOWN){xRot &#43;&#61; 5.0f;} if (keys &#61;&#61; GLUT_KEY_LEFT){yRot &#43;&#61; 5.0f;} if (keys &#61;&#61; GLUT_KEY_RIGHT){yRot -&#61; 5.0f;} if (xRot > 356.0f){xRot &#61; 0.0f;} else if (xRot <-1.0f){xRot &#61; 355.0f;} if (yRot > 356.0f){yRot &#61; 0.0f;} else if (yRot <-1.0f){yRot &#61; 355.0f;}glutPostRedisplay();
} void ProcessMenu(int key)
{
switch(key){ case 1:iShade &#61; FLAT_MODE; break; case 2:iShade &#61; SMOOTH_MODE; break; case 3:iLevel &#61; LOW_LEVEL; break; case 4:iLevel &#61; MEDIUM_LEVEL; break; case 5:iLevel &#61; HIGH_LEVEL; break; default: break;}glutPostRedisplay();
} int main(int args, char *arv[])
{glutInit(&args, arv);glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);glutInitWindowSize(800, 600);glutCreateWindow("spot light");glutDisplayFunc(RenderScene);glutReshapeFunc(ChangeSize);glutSpecialFunc(SpecialKeys); //创建菜单int menuID &#61; glutCreateMenu(ProcessMenu);glutAddMenuEntry("Flag Mode", 1);glutAddMenuEntry("SMOOTH Mode", 2);glutAddMenuEntry("Low Level", 3);glutAddMenuEntry("Midum Level", 4);glutAddMenuEntry("High Level", 5);glutAttachMenu(GLUT_RIGHT_BUTTON);SetupRC();glutMainLoop(); return 0;
}

整个工程移步此处 https://github.com/sweetdark/openglex  spot项目

转:https://my.oschina.net/sweetdark/blog/168486



推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
author-avatar
没了方向的路
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有