Win32 OpenGL编程(11) 光照

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

讨论新闻组及文件

Technorati 标签: OpenGL , 3D , graphic , Lighting , programming , Win32

最近跟风,看了《思维导图》,用XMind为此节画了个思维导图,事实上感觉这种书说起来不一定完全没有用,但是为了显示有用似乎说的太过夸张了,基本上,我认可一图胜千言吧。。。。另外,XMind还算比较好用,特别是上传然后共享的方式比较方便:)基础版还开源。

上面这种图用于解释概念就略显简单,但是用于梳理脉络,复习时回忆概念还算是比较有用的。

上帝说,要有光,就有了光。---- 《旧约•创世纪》

使用OpenGL时,程序员可以暂时充当一下上帝的角色(其实犯了妄自称上帝的戒律),你对电脑说要有光,于是,显示的时候就有了光,呵呵。当然,说的时候需要用电脑的语言。

相对来说,光照在OpenGL中是属于那种概念理解比较简单,但是使用却比较复杂的功能之一,因为光照属于比较容易影响显示效果的因素,所以光照的用法太多,导致可以使用的定制手段相当多,这也就加深了使用的复杂度,基本上,遵循理解核心概念,需要时再去查文档理解每个函数的具体参数使用和意义是种不错的学习方式,当然,要想精通,恐怕对各种光照应用的情况都得有个了解才行。上述思维导图就是核心概念的一个简要列表,以下以此图展开来讨论。

为啥需要光

上帝为啥创造光?上帝没有光看不见东西吗?。。。。。。。。或者因为上帝认为黑暗是不好的,不想呆在黑暗之中。。。。。只有上帝知道

那我们为啥需要光呢?因为我们的世界有上帝创造的光,而OpenGL本质上是对现实世界的一种建模,既然现实世界有太阳,有光,那么模拟现实世界显示的OpenGL怎么能没有光呢?(以上纯属无聊的废话)

以前画3D图形的时候我有意的将物体的每个面都设置成不同的颜色(比如以前那个三角锥),这样我们才能够较为清楚的看出来这是个3D的图形,为什么会这样说呢?那我们先看看事实上,假如我没有那样做,我们看到的是啥。这里以《Win32 OpenGL编程(6) 踏入3D世界 》一文的glSmoothColorPyramid一例为修改蓝本,(原来的源代码见/2009-10-21/glSmoothColorPyramid/),将其4面颜色全部设置为红色。

即将其源代码中表示颜色的数组改为如下形式:

static 
GLfloat fPyramidColors
[] = { 1.0, 0.0, 0.0,
    1.0, 0.0, 0.0,
    1.0, 0.0, 0.0,
    1.0, 0.0, 0.0};

我们看看效果。

image

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的 2009-11-9/glSimpleColorPyramid/目录,获取方式见文章最后关于获取博客完整源代码的说明。

事实上,即便是整个三角锥正以3D的方式不停的旋转,我们看到的效果也类似于一张三角形的红纸片不停的晃悠而已。3D效果荡然无存,很显然,这是有问题的,不能说一个纯色的物体的3D效果会受到影响吧?这算是我们需要光的最简单的一层了。一个三角锥还好说一点,我们看看一个球。此例利用了glut的具体画实心体的函数,以后不再全部利用自己的复杂手工代码来创造那简陋的三角锥了,作为一些概念的讲解,那样的三角锥已经有点过于简单了。(当然,可以用的时候还是会用)因为利用了glut,画球就很简单了(事实上不用也不难),只需要设置好红色,然后用

glutSolidSphere
(0.5, 30, 16);
就能画出下面这种半径为窗口宽度1/4的球。

image 有人能看出来上面的那个是球?我们看到的仅仅是一个圆而已,数学中我们也常常会将某个方向的球的图形抽象成一个圆,但是现实中我们还是很容易看出一个球就是一个球而不是一个圆的面,为啥呢?光啊,因为光照的存在,同一个球在我们眼里事实上不会是如上面那样纯红色的就整个球都是纯红色,会根据光照的方向,强度等因素的不同导致各个部位显示不同。下面是一个加上了光照的红色球体的例子。(上传U酷后颜色变了-_-!可能是因为U酷在上传后进行了2次压缩编码导致的颜色混乱,光源移动效果基本一致,需要真实体验的可以下载源代码编译后运行)

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的2009-11-9/LightSimple/ 目录,获取方式见文章最后关于获取博客完整源代码的说明。

主要源代码如下:

//OpenGL初始化开始

void SceneInit(int w,int h)
{
GLenum err = glewInit();
if (err != GLEW_OK)
{
MessageBox(NULL, _T("Error" ), _T("Glew init failed." ), MB_OK);
exit(-1);
}

GLfloat mat_specular[] = { 1.0, 0.0, 0.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat mat_ambient[] = { 1.0, 0.0, 0.0, 1.0 };
GLfloat mat_diffuse[] = { 1.0, 0.0, 0.0, 1.0 };

glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

glColor3f(0.0, 0.0, 0.0);
}

//这里进行所有的绘图工作
void SceneShow(GLvoid)
{
static GLfloat light_position[] = { 1.0, 0.0, -3.0, 0.0 };
static GLfloat angle = 1.0;

glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲区

glPushMatrix();
glRotatef(angle, 0.0, 1.0, 0.0);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glPopMatrix();
angle += 1.0;

glutSolidSphere(0.5, 30, 16);

glFlush();
}

这里启用光照的顺序就像是上面那个思维导图中表示的一样,一下解释一下各步骤:

1.定义物体的法线

这一步其实很重要,但是我们在这个例子中看不到,原因在于glutSolidSphere函数已经完成了这样的定义,法线的定义有点类似我们以前确定物体表面时用逆/正时针的顶点指定顺序来确定一个物体的正背面一样,法线定义了光线入射时物体相对于光源的方向,这算是个额外的小题目,以后有机会再论及,可以参考《OpenGL 编程指南》的第2.5节。基本上知道法线的概念后,需要调用的就是glNormal*函数,但是法线的计算并不总是那么容易。。。。。。比如此例的球体,还好,此例因为是使用现成的函数,省了此步。

2.创建和选择光源,并设置位置

glEnable (GL_LIGHTING );
glEnable (GL_LIGHT0 );

两句表示启用了第0号光源,此光源默认颜色为白色,位置为(0.0,0.0,1.0, 0.0)。我们利用了其默认颜色。

glLight*函数是专门的光照配置控制函数。

OpenGL Programming Guide 》:

glLight — set light source parameters
C Specification
void glLightf( GLenum light,
GLenum pname,
GLfloat param);
void glLighti( GLenum light,
GLenum pname,
GLint param);
Parameters

light

Specifies a light.
The number of lights depends on the implementation,
but at least eight lights are supported.
They are identified by symbolic names of the form GL_LIGHT
i,
where i ranges from 0 to the value of GL_MAX_LIGHTS - 1.
pname

Specifies a single-valued light source parameter for light.
GL_SPOT_EXPONENT,
GL_SPOT_CUTOFF,
GL_CONSTANT_ATTENUATION,
GL_LINEAR_ATTENUATION, and
GL_QUADRATIC_ATTENUATION are accepted.
param

Specifies the value that parameter pname of light source light
will be set to.

事实上,此函数远远不如看起来的那么简单,上面说了,因为光照属于比较容易影响显示效果的因素,所以光照的用法很多,上述的pname参数指定的n个值就可见一斑。但是本例中使用的方式却很简单,仅仅使用此函数来设定0号光源的位置而已。光照的分类其实包括环境光,散射光,镜面光,发射光(实际指物体的发射颜色emissive color)等属性,这里不详述了。

glLightfv
(GL_LIGHT0
, GL_POSITION
, light_position
);

因为光照模型的位置指定是使用了模型视图,所以我们也可以用模型变换的方式来指定光源的位置,就像光源是个普通的物体一样,此例中使用了旋转模型视图的函数旋转了光源的位置,旋转方式是以Y轴为旋转轴,看到的效果也就是如视频所示,类似月相变化的动画。

3.创建和选择光照模型

光照在OpenGL中有两种模型,一种叫无限远观察者模式,一种叫本地观察者模式,通过glLightModel*函数设置,默认为无限远观察者模型,这里没有修改,即使用了默认值,区别在于是否考虑到观察者位置对物体观察角度不同引起变化,相对来说本地观察者模型会更加逼真一些,但是因为有额外的光照计算,性能也会受到影响。。。。按照OpenGL管理,默认选择的往往是性能更优的,而不是效果更好的。但是,就这么简单的例子,我看不出区别。

OpenGL Programming Guide 》:

glLightModel — set the lighting model parameters
C Specification
void glLightModelf( GLenum pname,
GLfloat param);
void glLightModeli( GLenum pname,
GLint param);
Parameters

pname

Specifies a single-valued lighting model parameter.
GL_LIGHT_MODEL_LOCAL_VIEWER,
GL_LIGHT_MODEL_COLOR_CONTROL, and
GL_LIGHT_MODEL_TWO_SIDE are accepted.
param

Specifies the value that param will be set to.

我们可以通过

glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);

的调用切换至本地观察者模型。

4.定义物体的材料属性

光是定义了光源而不定义物体怎么反射光,在OpenGL中是不行的,物体的材料属性决定了其将会怎么对光进行反应,与光源属性对应,也有几种与反射光相关材料属性可以设置。我们通过glMateriall*来设置材料属性。

OpenGL Programming Guide 》:

glMaterial — specify material parameters for the lighting model
C Specification

void glMaterialfv( GLenum face,
GLenum pname,
const GLfloat * params);
void glMaterialiv( GLenum face,
GLenum pname,
const GLint * params);
Parameters

face

Specifies which face or faces are being updated.
Must be one of
GL_FRONT,
GL_BACK, or
GL_FRONT_AND_BACK.
pname

Specifies the material parameter of the face or faces that is being updated.
Must be one of
GL_AMBIENT,
GL_DIFFUSE,
GL_SPECULAR,
GL_EMISSION,
GL_SHININESS,
GL_AMBIENT_AND_DIFFUSE, or
GL_COLOR_INDEXES.
params

Specifies a pointer to the value or values that pname will be set to.

上例中:

GLfloat mat_specular
[] = { 1.0, 0.0, 0.0, 1.0 };
GLfloat mat_shininess
[] = { 50.0 };
GLfloat mat_ambient
[] = { 1.0, 0.0, 0.0, 1.0 };
GLfloat mat_diffuse
[] = { 1.0, 0.0, 0.0, 1.0 };

glMaterialfv
(GL_FRONT
, GL_SPECULAR
, mat_specular
);
glMaterialfv
(GL_FRONT
, GL_SHININESS
, mat_shininess
);
glMaterialfv
(GL_FRONT
, GL_AMBIENT
, mat_ambient
);
glMaterialfv
(GL_FRONT
, GL_DIFFUSE
, mat_diffuse
);

这样8行,定义了4中材料的属性,并且我们都是使用GL_FRONT定义正面受到光照(即默认背面无光照),GL_SPECULAR表示镜面颜色,GL_SHININESS表示镜面指数,GL_AMBIENT表示环境颜色,GL_DIFFUSE表示散射颜色,其中散射颜色和环境颜色影响物体所反色的散射光和环境光颜色,(与光源属性对应)有直接光照时,人体主要感知还是以散射颜色为主,无光照时,环境颜色才会占重要地位。镜面颜色决定镜面反射产生的亮点,GL_SHININESS参数设置亮点的大小和亮度,发射光颜色会让物体微微发光(此例未用)。因为比较复杂,上述文字仅仅大概描述了一个概念,具体的参数还是参考《OpenGL Programming Guide 》 并且实际的调整不同数值体验效果为好。其中,《OpenGL Programming Guide 》一书中推荐的Nate Robin的OpenGL教程 的教学程序程序中有两个实例与光照有关,能够体验数值改变时显示的效果。假如大家不想自己编程调试,可以使用教程中的lightposition,lightmateria两个例子体验。

本系列其他文章见OpenGL专题 《Win32 OpenGL系列专题

参考资料

1. 《OpenGL Reference Manual 》,OpenGL参考手册

2. 《OpenGL 编程指南》(《OpenGL Programming Guide 》),Dave Shreiner,Mason Woo,Jackie Neider,Tom Davis 著,徐波译,机械工业出版社

3. 《Nehe OpenGL Tutorials》,Nehe著,在http://nehe.gamedev.net/ 上可以找到教程及相关的代码下载,(有PDF版本教程下载)Nehe自己还做了一个面向对象的框架,作为演示程序来说,这样的框架非常合适。也有中文版 ,各取所需吧。

4. 《OpenGL入门学习》 ,eastcowboy著,这是我在网上找到的一个比较好的教程,较为完善,而且非常通俗。这是第一篇的地址:http://bbs.pfan.cn/post-184355.html

完整源代码获取说明

由于篇幅限制,本文一般仅贴出代码的主要关心的部分,代码带工程(或者makefile)完整版(如果有的话)都能用Mercurial在Google Code中下载。文章以博文发表的日期分目录存放,请直接使用Mercurial克隆下库:

https://blog-sample-code.jtianling.googlecode.com/hg/

Mercurial使用方法见《分布式的,新一代版本控制系统Mercurial的介绍及简要入门

要是仅仅想浏览全部代码也可以直接到google code上去看,在下面的地址:

http://code.google.com/p/jtianling/source/browse?repo=blog-sample-code

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值