技术文章 > OpenGL基础篇

OpenGL基础篇

2017-12-13 09:19

文档管理软件,文档管理系统,知识管理系统,档案管理系统的技术资料:
本人水平有限,如有问题请以文章形式提出,大家可以讨论吗...
[OPENGL怎么用]
OPENGL编程类似C编程,实际接口就是C,所以熟悉C是必要的
一般编程可用到的函数库包括:
OPENGL实用库:函数以glu开头
OPENGL辅助库:函数以aux开头
Windows专用函数库:函数以wgl开头
Win32API:无专用前缀
OPENGL中有115个核心函数,可以在任何OPENGL平台上使用
OPENGL实用库比上面这115个函数高一级,提供高级调用
OPENGL辅助库本来是提供初学者入门的函数,不保证在任何平台的使用
但恰好可以在WIN32下使用,所以本讲座将大量引用
WIN32下OPENGL编程有两个方便途径:
1使用辅助库
2使用C++基于消息驱动的编程
显然1要简单一些,入门从这里开始吧。
[用之前的准备]
1
首先你需要下列*.lib包含在你的工程中:
opengl32.lib glu32.lib glaux.lib
本讲座所有例子“将”在VC5下调试通过,所以从
project->setting->link->general->object/libary modules
中加入上面三个*.lib
(这些LIB,VC4以上版本已经自带,加入即可,不用在四处搜寻文件)
2
另外在你的运行程序路径下或\win95\system\下你需要一些*.dll动态连接库
opengl32.dll glu32.dll rxddi.dll mga.drv
如果谁需要上述文件,跟我打个招呼
别跟我说要Visual C++ 5.0 呦


[编程入门]
这里我将给出一个小例子让大家熟悉用辅助库的编程结构:

// GLOS.H
//////////////////////////////////////////////////////////
// This is an OS specific header file
//判别操作系统的基本头文件

#include

// disable data conversion warnings

#pragma warning(disable : 4244) // MIPS
#pragma warning(disable : 4136) // X86
#pragma warning(disable : 4051) // ALPHA
//////////////////////////////////////////////////////////
//opengl.cpp
//主程序
#include "glos.h"
#include
#include
#include "windows.h"

void main(void)
{
/*初始化:*/
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
//窗口显示单缓存和RGB(彩色)模式
auxInitPosition(0,0,500,500);
//大小x=500 y=500 (0,0)是屏幕左上点
auxInitWindow("sample1");
//窗口初始化,参数是标题
glClearColor(0.0,0.0,0.0,0.0);
//将窗口清为黑色
glClear(GL_COLOR_BUFFER_BIT);
//将颜色缓存清为glClearColor命令所设置的颜色
//即背景色

/*绘图*/
glColor3f(1.0,0.0,0.0);
//选颜色(R,G,B),参数0glRectf(-0.5,-0.5,0.5,0.5);
//画个方块

glFlush();
//强制绘图,不驻留缓存
_sleep(1000);
//windows函数,显示1秒(单位是毫秒)
}
//end of sample
根据注释,应该看出程序功能:显示红色方块(2D)一秒钟。
主程序结构不外乎:
初始化 + 绘图 + 其它功能调用,现在不用过分追究函数细节,知道是干什么的就可以乐,好吗?
我想结束本节前让大家实现第一个3D的例子,很简单而且好看的,但是我们必须被迫不得不多学些东东:

1.OPENGL函数、变量命名准则

变量:
前缀 类型 对应C变量
b 8-bit int signed char
s 16-bit int short
i 32-bit int long
f 32-bit float float
d 64-bit float double
ub 8-bit unsigned int unsigned char
us 16-bit unsigned int unsigned short
ui 32-bit unsigned int unsigned long
例如1.0f实际就是1.0,一般写成1.0f看上去好辨认一些。

函数:
函数参数类型作为函数后缀
例如:glVertex2i(2,4)表明是opengl基本函数(gl-)
是绘点的函数(-Vertex-)
是两个整型参数(-2i)
将来对一个函数掐头去尾就知道它是干什么的乐。

2.CALLBACK函数
是一些用来让系统调用的函数,系统要调用它们,例如显示、接受键盘输入...
你就好比擂积木一样把它们列在主程序初始化后(顺序一般无关紧要),
系统会自动执行它们,例如:
void CALLBACK display(void)中你写好乐想画什么东东,然后主程序中使用auxMainLoop(display);就可以让这个东东一直显示乐,很类似VC中的OnPaint()

3.OPENGL基本库的绘图方法

glBegin(类型);
glColor3f(...);
glVertex(...)
glColor3f(...);
glVertex(...);
...
glEnd();

先由“类型”指定画什么平面:
GL_POINTS 单个顶点集
GL_LINES 多组线,2个点一条线
GL_GL_POLYGON 单个简单填充凸多边形
GL_TRAINGLES 多组三角型,3个点一个三角
..用到在说吧(自己可以在VC HELP INDEX中查找)
//////////////////////////////////////////////////////
//sample.cpp

#include "glos.h"
#include
#include
#include "windows.h"

void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//这里不用管reshape(),只是一个用于窗口改变大小时的处理
//与绘图无关,后面会讲到
//这里的重点是display(),请注意绘图的方法
void myinit(void)
{
/*初始化:*/
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
//窗口显示单缓存和RGB(彩色)模式
auxInitPosition(0,0,500,500);
//大小x=500 y=500 (0,0)是屏幕左上点
auxInitWindow("sample1");
//窗口初始化,参数是标题
glClearColor(0.0,0.0,0.0,0.0);
//将窗口清为黑色
glClear(GL_COLOR_BUFFER_BIT);
//将颜色缓存清为glClearColor命令所设置的颜色
//即背景色
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-20.0,20.0,-20.0*(GLfloat)h/(GLfloat)w,
20.0*(GLfloat)h/(GLfloat)w,-50.0,50.0);
else
glOrtho(-20.0*(GLfloat)h/(GLfloat)w,
20.0*(GLfloat)h/(GLfloat)w,-20.0,20.0,-50.0,50.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void CALLBACK display(void)
{
glBegin(GL_TRIANGLE_STRIP);//画连续填充的多个三角
glColor3f(1.0,0.0,0.0);
glVertex3f(15.0,0.0,0.0);
glColor3f(0.0,1.0,0.0);
glVertex3f(-15.0,0.0,0.0);
glColor3f(0.0,0.0,1.0);
glVertex3f(0.0,15.0,15.0);
//第一个三角
glColor3f(0.0,1.0,1.0);
glVertex3f(10.0,15.0,-15.0);
//第二个三角
glColor3f(1.0,1.0,0.0);
glVertex3f(15.0,0.0,0.0);
//第三个三角
glEnd();
glFlush();
}

void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
所谓连续填充就是1、2、3点组成三角1
2、3、4点组成三角2...
这里一共画乐3个向连接的彩色三角,组成乐一个由三个彩色平面围成的空心三角椎...enjoy it


上回书说道有个reshape需要进一步讲解这个函数功能是对用户改变窗口大小的操作进行一些重绘的动作(类似VC中的OnResize)。其中用到了一些变换的概念我希望大家已经具备初步的计算机图形学的知识,这将有利于这部分的理解。如果还没有,也没关系,我尽量讲解的通俗一些对于3D绘图,把其中3D坐标写成齐次坐标系后,是4*4的矩阵形式(详细…………………………………………………………
可以参阅相关文献,后面也会讲到。)任何投影、旋转...操作都可以看成是矩阵相乘的操作。矩阵操作----这个概念一定要形成!!
例如在2D中普通的旋转变换,可以写成:
|cosθ sinθ 0 |
[x“ y“ 1]=[x y 1] |-sinθ cosθ 0 |
|0 0 1 |
3D中道理完全一样,一个3D图形的显示包括下面步骤:
1.视点变换(将相机放在合适的地方对准3D景物)
2.模型变换(将3D物体放在合适的地方)
3.投影变换(将相机镜头调整,使3D景物投影在2D胶片上)
4.视口变换(决定胶片大小)
其中1、2并没有本质区别。

里面有几个关键性函数:

一 几何变换
1.
void glTranslated(
GLdouble x,
GLdouble y,
GLdouble z
);
void glTranslatef(
GLfloat x,
GLfloat y,
GLfloat z
);
目标沿X Y Z轴平移 x y z

2.
void glRotated(
GLdouble angle,
GLdouble x,
GLdouble y,
GLdouble z
);
void glRotatef(
GLfloat angle,
GLfloat x,
GLfloat y,
GLfloat z
);
目标分别以X Y Z轴为轴逆时针旋转 x y z

3.
void glScaled(
GLdouble x,
GLdouble y,
GLdouble z
);
void glScalef(
GLfloat x,
GLfloat y,
GLfloat z
);
目标在X Y Z 方向缩放,缩放因子为 x y z

二 投影变换

1.正射投影(即没有“近大远小”)
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top,
GLdouble near,GLdouble far)
创建一个平行视景体(的矩阵),即投射线是平行线,把第一个矩形
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
视景投影到第二个矩形视景上。并用这个矩阵乘以当前矩阵,以完成变换。
近景第一个矩形的左上角三维空间坐标(left,bottom,-near),右下角的三维空间坐标(right,top,-near);
远景第二个矩形左上角三维空间坐标(left,bottom,-far),右下角的三维空间坐标(right,top,-far);

2.透射投影(“近大远小”)
void glFrustum(
GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble znear,
GLdouble zfar
);
创建一个型如棱台的视景体,其近截取面由left right bottom top znear
确定,远截取面由从视点投影近截取面到Z轴zfar位置决定

三 视口变换
void glViewport(GLint x,GLint y,GLsize width,GLsize height);
这个函数定义一个视口,x和y是视口在屏幕窗口坐标系中左上坐标
缺省是(0,0)。width height是宽和高。
注意使用中视口长宽比例的调整会导致图象变形。因此reshape()中
要检测窗口尺寸,修正视口大小,保证图象不变形。

附:
void glMatrixMode(GLenum mode );
把当前矩阵转换为指定的矩阵类型

这三个函数的利用见例子中的中文说明:
////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
/////////////////////////////////////////////////////////////
void myinit(void)
{
/*初始化:*/
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
//窗口显示单缓存和RGB(彩色)模式
auxInitPosition(0,0,500,500);
//大小x=500 y=500 (0,0)是屏幕左上点
auxInitWindow("sample1");
//窗口初始化,参数是标题
glClearColor(0.0,0.0,0.0,0.0);
//将窗口清为黑色
glClear(GL_COLOR_BUFFER_BIT);
//将颜色缓存清为glClearColor命令所设置的颜色
//即背景色
}
//////////////////////////////////////////////////
void CALLBACK reshape(GLsizei w,GLsizei h)
{
//设定视口X Y方向不要大于500单位
if(w<=500&&h<=500)
glViewport(0,0,w,h);
if(w>500&&h<=500)
glViewport(0,0,500,h);
if(w<=500&&h>500)
glViewport(0,0,w,500);
if(w>500&&h>500)
glViewport(0,0,500,500);

//进入世界坐标系,准备变换
//必要的步骤,初始化变换矩阵步骤: 1确定类型 2清成单位阵
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//定义一个合适的视景体
if(w<=h)
glOrtho(-20.0,20.0,-20.0*(GLfloat)h/(GLfloat)w,
20.0*(GLfloat)h/(GLfloat)w,-50.0,50.0);
else
glOrtho(-20.0*(GLfloat)h/(GLfloat)w,
20.0*(GLfloat)h/(GLfloat)w,-20.0,20.0,-50.0,50.0);

//把变换结果返回视点坐标系
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
//////////////////////////////////////
//画三棱锥,功能同上一讲
void draw(void)
{
glBegin(GL_TRIANGLE_STRIP);
glColor3f(1.0,0.0,0.0);
glVertex3f(15.0,0.0,0.0);
glColor3f(0.0,1.0,0.0);
glVertex3f(-15.0,0.0,0.0);
glColor3f(0.0,0.0,1.0);
glVertex3f(0.0,15.0,15.0);
//
glColor3f(0.0,1.0,1.0);
glVertex3f(10.0,15.0,-15.0);
//
glColor3f(1.0,1.0,0.0);
glVertex3f(15.0,0.0,0.0);
//
glEnd();
}
////////////////////////////////////////
void CALLBACK display(void)
{
//按上个例子中方法画第一个三棱锥
glClearColor(0.0,0.0,0.0,1.0);
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0,0.0,-25.0);
draw();

//开始画第二个三棱锥
//变换开始,清变换矩阵为单位阵
glLoadIdentity();
//先沿X Y Z平移-5 -10 -4,屏幕上看就是向左下移动
//Z方向由于是平行投影,没有“近大远小”所以看不出效果
glTranslatef(-5.0,-10.0,-4.0);
//再沿Z轴转90度
glRotatef(90,0.0,0.0,1.0);
draw();

//绘图工作完成,强制绘图结束
glFlush();
}

void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxMainLoop(display);
}

//sample ends here
/////////////////////
如果大家运行一下就知道上述函数的作用,无非是把物体移动、转动、变形缩放、透视...
我想有了前3讲,OPENGL的基本原理已经如此了它就是提供了一个标准的计算机图形学所使用的数学模型到显示的接口,只要具备图形学知识,掌握OPENGL API函数使用,绘图很EASY啦基础部分已经完毕,慢慢再来谈高级一些的绘图功能如:纹理、光源、动画...
真诚希望大家提提意见和高论 :)


前面三篇文章已经把OPENGL的编程基本结构描述完毕。以后会在这个基础上逐渐深化,不断增添新内容。这一篇是讲述键盘操作和动画基础(实际还差的远哪)。只是个简单的能由用户控制的动画,让物体前后移动,左右旋转。是我们自己的第一个QUAKE!当然这个版本谁买谁上当,呵呵。
这篇的另一个目的就是加深前面对于CALLBACK函数的认识以及对于变换的直观解释,任何变换你都可以从屏幕上通过自己的操作看出来:
我只把和以前变化的部分标记中文解释

////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"

void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//注意到乐吗?这里新加乐4个CALLBACK函数
//类似display() reshape(),也由主函数调用它们
//实现对键盘输入的响应
void CALLBACK left(void);//按 LEFT
void CALLBACK right(void);//按 RIGHT
void CALLBACK up(void);//按 UP
void CALLBACK down(void);//按 DOWN

//两个全局变量,z_motion用来控制物体远近
//rotate用来控制物体旋转
static int z_motion=0,rotate=0;

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
if(w<=500&&h<=500)
glViewport(0,0,w,h);
if(w>500&&h<=500)
glViewport(0,0,500,h);
if(w<=500&&h>500)
glViewport(0,0,w,500);
if(w>500&&h>500)
glViewport(0,0,500,500);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
///*if(w<=h)
// glOrtho(-20.0,20.0,-20.0*(GLfloat)h/(GLfloat)w,
// 20.0*(GLfloat)h/(GLfloat)w,-50.0,50.0);
//else
// glOrtho(-20.0*(GLfloat)h/(GLfloat)w,
// 20.0*(GLfloat)h/(GLfloat)w,-20.0,20.0,-50.0,50.0);
//*/
/************************************************************/
//这里我们换一种投影方法:透射投影,有立体感的说
//取代上次的平行投影。前4个参数是控制第一个截取面的
//left right top bottom,然后两个参数控制近截取面的
//Z坐标,远截取面的Z作标,函数声名如下:
//void glFrustum(GLdouble left,GLdouble right,
// GLdouble bottom,GLdouble top,
// GLdouble near,GLdouble far);
/************************************************************/
glFrustum(-20.0,20.0,-20.0,20.0,10.0,50.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void draw(void)
{
glBegin(GL_TRIANGLE_STRIP);
glColor3f(1.0,0.0,0.0);
glVertex3f(15.0,0.0,0.0);
glColor3f(0.0,1.0,0.0);
glVertex3f(-15.0,0.0,0.0);
glColor3f(0.0,0.0,1.0);
glVertex3f(0.0,15.0,15.0);
//
glColor3f(0.0,1.0,1.0);
glVertex3f(10.0,15.0,-15.0);
//
glColor3f(1.0,1.0,0.0);
glVertex3f(15.0,0.0,0.0);
//
glEnd();
}

void CALLBACK display(void)
{
glClearColor(0.0,0.0,0.0,1.0);
glClear(GL_COLOR_BUFFER_BIT);

//glPushMatrix();
glLoadIdentity();

//根据z_motion rotate两个参数确定变换的具体数值:
//z_motion改变时,物体从原Z坐标-25,平移z_motion个单位
glTranslatef(0.0,0.0,-25.0+z_motion);
//rotate改变时,物体延Y轴(上下方向)旋转5*rotate度
glRotatef(rotate*5.0,0.0,1.0,0.0);

draw();
//glPopMatrix();

glFlush();
}
void CALLBACK left(void)
{
//每当按下LEFT,rotate数值 +1
//以下函数都类似
rotate++;
}
void CALLBACK right(void)
{
rotate--;
}
void CALLBACK up(void)
{
z_motion++;
}
void CALLBACK down(void)
{
z_motion--;
}
void main(void)
{
myinit();

//用辅助库的函数调用把left() right() up() down()
//设为标准键盘输入处理函数
auxKeyFunc(AUX_LEFT,left);
auxKeyFunc(AUX_RIGHT,right);
auxKeyFunc(AUX_UP,up);
auxKeyFunc(AUX_DOWN,down);

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////
如果运行这个程序会发现有较明显的闪烁感,这是单缓存模式造成的以后会讲到动画应采用双缓存模式,这样可以避免闪烁


这回可能是OPENGL最简单的内容:颜色。
一 RGB模式
一般来讲实现彩色是用RGB三基色来调配的。这就是
RGB模式,我们前面一直用这种方法
(例如:
glColor3f(1.0,0.0,0.0);
glVertex3f(0.0,0.0,0/0);
绘制一个红色点。)
void glColor3{b s i f d ub us ui}(TYPE r,TYPE g, TYPE b);
void glColor4{b s i f d ub us ui}(TYPE r,TYPE g, TYPE b,TYPE a);
void glColor3{b s i f d ub us ui}v(TYPE *v);
void glColor4{b s i f d ub us ui}v(TYPE *v);
{}内是任选一种数值精度(看前面的介绍);
参数a是表征透明度的Alpha值。
后两个带v后缀的函数表明他们的参数是向量(详细使用看本篇的例子)。
以glColor3f为例,其参数取值范围-1.0--1.0,其它数值类型的函数将
自动把参数均匀影射到这个区间,例如:
后缀 类型 MIN MIN映射 MAX MAX映射
b 1byte整数 -128 -1.0 127 1.0
二 颜色索引模式
使用
void glIndex{s i f d}(TYPE c);
void glIndex{s i f d}(TYPE *c);
来从颜色索引表中选取颜色。设置当前颜色索引值(调色板号),大于
总数时取模。
前面所有例子都是RGB模式,所以这里给出一个颜色索引的例子:
//sample.cpp
//////////////////////////////
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void InitPalette(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_INDEX);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);//GL_FLAT填色模式
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
if(w<=500&&h<=500)
glViewport(0,0,w,h);
if(w>500&&h<=500)
glViewport(0,0,500,h);
if(w<=500&&h>500)
glViewport(0,0,w,500);
if(w>500&&h>500)
glViewport(0,0,500,500);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-20.0,20.0,-20.0*(GLfloat)h/(GLfloat)w,
20.0*(GLfloat)h/(GLfloat)w,-50.0,50.0);
else
glOrtho(-20.0*(GLfloat)h/(GLfloat)w,
20.0*(GLfloat)h/(GLfloat)w,-20.0,20.0,-50.0,50.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void draw(void)
{
GLint n;

//首先给定三角扇的坐标信息
GLfloat pp[8][2]={{7.0,-7.0},{0.0,-10.0},{-7.0,-7.0},{-10.0,0.0},
{-7.0,7.0},{0.0,10.0},{7.0,7.0},{10.0,0.0}};

//先画前两个点,然后用循环读取前面的向量(数组)信息绘制
//完整图形
glBegin(GL_TRIANGLE_FAN);
glVertex2f(0.0,0.0);
glVertex2f(10.0,0.0);
for(n=0;n<8;n++)
{

//每次从颜色查找表中找出新颜色,然后以这个颜色绘制三角扇
//注意glVertex2fv()中v后缀的使用,以前没有碰到过,v后缀代表
//参数是个向量(数组)
glIndexi(n+1);
glVertex2fv(pp[n]);
}
glEnd();
}

void InitPalette(void)
{
//这是本例子的关键,初始化颜色查找表,这里一共定义
//乐8种颜色,是从蓝到青的渐进。
GLint i;
static GLfloat rgb[][3]={{0.0,0.0,0.2},{0.0,0.0,0.4},{0.0,0.0,0.6},
{0.0,0.0,1.0},{0.0,0.2,1.0},{0.0,0.4,1.0},{0.0,0.8,1.0},{0.0,1.0,1.0}};

//调用简单的辅助库函数设置系统调色板,把刚才8个颜色定为全部
//颜色索引的内容
for(i=0;i<8;i++)
auxSetOneColor(i+1,rgb[i][0],rgb[i][1],rgb[i][2]);
}

void CALLBACK display(void)
{
//首先调用自定义的InitPalette()初始化调色板
InitPalette();
glClearColor(0.0,0.0,0.0,1.0);
glClear(GL_COLOR_BUFFER_BIT);

glLoadIdentity();
draw();
glFlush();
}

void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
下次将介绍OPENGL的光照和材质效果,一个步入3D的阶梯,
大家快来呀 ......


这部分是最重要的部分,前面只是基础。这里会介绍光照处理、明暗处理、光源
设置、材质定义以及相关计算机图形学的概念。
一般来说产生3D图象的步骤:
1 建模
2 将几何模型经变换投影到2D透视图
3 确定场景所有可见面,进行消隐
4 计算场景颜色
我们已经再前面介绍乐1 2 两步
消隐是OPENGL的工作,我们不必关心
所以4就是这里的重点。

(一)光照
分为:反射、透射光
1 简单光照模型
简单光照模型只考虑物体表面反射光的视觉影响。假定物体表面光滑不透明而且由理想材料构成,环境假设为白光照明。
一般反射光分为:环境反射、漫反射和镜面反射3个分量。

环境反射光(Ambient Light):入射光均匀从周围环境入射至表面并个方向等量反射。
漫反射光(Diffuse Light):特定光源在物体表面的反射光中那些向各个方向均匀反射的光。
镜面反射光(Specular Light):朝一定方向的反射光,例如光源在金属球上产生的高光(Highlight)。
详细可参阅大学物理。呵呵

介绍一下重要的函数:
(1)
void glLight{if}[v](GLenum light,GLenum pname,TYPE param)
设置光源特性。

light是名字例如:GL_LIGHT0,GL_LIGHT1...GL_LIGHT7。

pname 缺省值 说明
GL_AMBIENT 0,0,0,1 RGBA模式的环境光
GL_DIFFUSE 1,1,1,1 RGBA模式的漫反射光
GL_SPECULAR 1,1,1,1 RGBA模式的镜面光
GL_POSTION 1,0,1,0 光源位置齐次坐标
GL_SPOT_DIRECTION 0,0,-1 点光源聚光方向矢量(x,y,z,w)
GL_SPOT_EXPONENT 0 点光源聚光指数
GL_SPOT_CUTOFF 180 点光源聚光发散半角
GL_CONSTANT_ATTENUATION 1 常数衰减因子
GL_LINER_ATTENUATION 0 线性衰减因子
GL_QUADRATIC_ATTENUATION 0 平方衰减因子
说明:GL_DIFFUSE GL_SPECULAR的缺省值只用于GL_LIGHT0,
其他光源GL_DIFFUSE GL_SPECULAR缺省值为:(0.0,0.0,0.0,1.0)

!!!我可能前面忘说了!!!
TYPE就是{}中的那些参数类型,例如:i就是int,f就是float。
v是可选,表明可以数组作为参数,定义一组光源。
(2)启用光照/关闭光源
void glEnable(GLenum cap)
void glDisable(GLenum cap)
例如使光源有效:
glEnable(GL_LIGHT0);

下面给出简单光照的例子:
/////////////////////////////////////////
//sample.cpp

#include "glos.h"
#include
#include
#include "windows.h"

void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//glShadeModel用来确定颜色填充模式,缺省的GL_SMOOTH的效果较好,但计算
//量大,如果你加上下面这句,那么填色是按照几何模型平面填充的,计算量
//大大减小,但是效果不好看。
// glShadeModel(GL_FLAT);

//定义一个光源的位置坐标
GLfloat light_position[]={1.0,1.0,1.0,0.0};
glLightfv(GL_LIGHT0,GL_POSITION,light_position);

//定义光源的漫反射颜色(兰色)以及环境光(红色),如果你上机试试这个
//程序,就可以看出光源的效果。如果没条件,可以想象一下:在淡淡的红色
//背景光下,受光照的部分呈现纯蓝色,而背光部分呈现红色。
//你还可以更详细按照上面表格指定其他属性,这里其他就用缺省的了。
GLfloat light_diffuse[]={0.0,0.0,1.0,1.0};
glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
GLfloat light_ambient[]={1.0,0.0,0.0,1.0};
glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);

//关于GL_LIGHTING 的说明:
//If enabled, use the current lighting parameters to compute the
//vertex color or index. If disabled, associate the current color
//or index with each vertex. 如果Enabled,使用当前光照参数计算每个
//点的颜色。你可以试试去掉这句,那么缺省的白色漫反射光源会代替你的
//灰色光源你将看见一个白色的没有立体感的球。
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

//关于GL_LESS的说明
//Passes if the incoming z value is less than the stored z value.
//This is the default value.
//用glEnable(GL_DEPTH_TEST)激活深度比较,然后定义如果z坐标小于buffer中
//的值(当前点z较小,更靠近观察点),则显示,实际就是消隐。
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-2.0,2.0,-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-2.0,2.0,-10.0,10.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void draw(void)
{
//调用辅助库函数画一个实心圆球。半径1.0
auxSolidSphere(1.0);
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glLoadIdentity();
draw();
glFlush();
}

void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
/////////////////////////////////////////////


紧接上一次,这回讲材质:
OPENGL通过材料对R、G、B的近似反光率来近似定义材料颜色。
也分为环境、漫反射、镜面反射成分。他们决定材料对环境光、漫反射光和
镜面反射光的反射程度。将材料的特性与光源特性结合就是观察的最终显示
效果。例如红色塑料球,大部分是红色,在光源形成的高光处,则出现光源
的特性颜色。很EASY,不是么?

材质的定义:
void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);
其中:
face:可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明当前材质应用
到物体的哪一个表面上。
pname说明特定材质属性(很类似上一次光源的定义):

pname 缺省值 说明

GL_AMBIENT 0.2,0.2,0.2,1.0 材质的环境反射光
GL_DIFFUSE 0.8,0.8,0.8,1.0 材质的漫反射光
GL_AMBIENT_AND_DIFFUSE 材质的环境光和漫反射光颜色
GL_SPECULAR 0.0,0.0.0.0,1.0 材质镜面反射光
GL_SHINESS 0.0 镜面指数(光照度)
GL_EMISSION 0.0,0.0,0.0,1.0 材质的辐射颜色
GL_COLOR_INDEXES 0,1,1 材质的环境光、漫反射光和镜面
反射光颜色
请看下面材质的简单例子:
////////////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
// glShadeModel(GL_FLAT);

//首先定义一个材质,定义方法非常类似前面讲到的光源定义。
GLfloat mat_ambient[]={0.8,0.8,0.8,1.0};
//定义 紫色 的漫反射特性
GLfloat mat_diffuse[]={0.8,0.0,0.8,1.0};
//定义 亮紫色 的镜面反射特性
GLfloat mat_specular[]={1.0,0.0,1.0,1.0};
//定义镜面反射的光亮度
GLfloat mat_shininess[]={50.0};
//将以上材质定义应用
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);

//这里我们简化光源,以显示材质的效果。
//这里我们只指定光源位置,其他默认:白色光源。
//你也可以加入光源的定义,看看光源和材质的合成的效果
//正是因为它们能够合成,才能产生比真实生活中多的多的效果,这也正是
//3D技术吸引人的魅力所在。
GLfloat light_position[]={1.0,1.0,1.0,0.0};
glLightfv(GL_LIGHT0,GL_POSITION,light_position);
// GLfloat light_diffuse[]={0.0,0.0,1.0,1.0};
// glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);

//将光源设置应用
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

//着色消隐
//*******其实说白乐,这就是大名鼎鼎的 Z-BUFFER 呀************//
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-2.0,2.0,-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-2.0,2.0,-10.0,10.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void draw(void)
{
auxSolidSphere(1.0);
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glLoadIdentity();
draw();
glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////

通过以上的例子,我们会看到材质定义的实现和光源的效果是一样的,就我们日常的感觉,定义材质更加符合人们的习惯。而光源使用白光。这里我们看到的是一个紫色的球,其高光部分呈现亮紫色。

作为比较,下面我们给出一个12个彩色球的例子,大家可以看出各种光源、材质应用的效果,在一起比较。
给出例子之前,介绍两个函数:
void glPushMatrix();
void glPopMatrix();
我前面已经讲过,所有几何投影变换都是矩阵相乘的结果。如果你希望保持一个初始坐标点,就要用到这两个重要函数:矩阵入栈和矩阵出栈。
你可以这样理解,为了在左上角画一个球,你先保存当前矩阵,glPushMatrix()(入栈),然后把坐标平移到左上角,然后调用auxSolidSphere(1.0),画一个半径1.0的球,这时再调用glPopMatrix()(矩阵出栈),就又回到原始坐标点,这时的球就在左上角乐。这个程序很长,但实际上,长的部分统统是重复画12个球的工作,所以也比较好理解。
//////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.1,0.1,0.0);
glClear(GL_COLOR_BUFFER_BIT);
// glShadeModel(GL_FLAT);

//首先定义光源
GLfloat light_ambient[]={0.0,0.0,0.0,1.0};
GLfloat light_diffuse[]={1.0,1.0,1.0,1.0};
GLfloat light_specular[]={1.0,1.0,1.0,1.0};
GLfloat light_position[]={0.0,3.0,2.0,0.0};
GLfloat Imodel_ambient[]={0.4,0.4,0.4,1.0};

//应用光源
glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
glLightfv(GL_LIGHT0,GL_POSITION,light_position);
glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

//初始化Z BUFFER
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-6.0,6.0,-6.0*(GLfloat)h/(GLfloat)w,
6.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-6.0*(GLfloat)h/(GLfloat)w,
6.0*(GLfloat)h/(GLfloat)w,-6.0,6.0,-10.0,10.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void draw(void)
{
auxSolidSphere(1.0);
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//建立材质数据库
GLfloat no_mat[]={0.0,0.0,0.0,1.0};
GLfloat mat_ambient[]={0.7,0.7,0.7,1.0};
GLfloat mat_ambient_color[]={0.8,0.8,0.2,1.0};
GLfloat mat_diffuse[]={0.1,0.5,0.8,1.0};
GLfloat mat_specular[]={1.0,1.0,1.0,1.0};
GLfloat no_shininess[]={0.0};
GLfloat low_shininess[]={5.0};
GLfloat high_shininess[]={100.0};
GLfloat mat_emission[]={0.3,0.2,0.2,0.0};
//////////////////////////////////////////////////////////
//1-1 仅有漫反射光,无环境光和镜面光
glPushMatrix();
glTranslatef(-3.75,3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//1-2 有漫反射光,并且有低高光,无环境光
glPushMatrix();
glTranslatef(-1.25,3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,low_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//1-3 有漫反射光和镜面光,很亮的高光,无环境光
glPushMatrix();
glTranslatef(1.25,3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,high_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//1-4 有漫反射光和辐射光,无环境光和镜面反射光
glPushMatrix();
glTranslatef(3.75,3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
draw();
glPopMatrix();
//////////////////////////////////////////////////////////////
//2-1 有漫反射光和环境光,无镜面反射光
glPushMatrix();
glTranslatef(-3.75,0.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//2-2 有漫反射光、环境光和镜面光,而且有低高光
glPushMatrix();
glTranslatef(-1.25,0.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,low_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//2-3 有漫反射光环境光和镜面光,而且有很亮的高光
glPushMatrix();
glTranslatef(1.25,0.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,high_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//2-4有漫反射光、环境光和辐射光,无镜面光
glPushMatrix();
glTranslatef(3.75,0.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
draw();
glPopMatrix();
///////////////////////////////////////////////////////////////
//3-1 有漫反射光和有颜色的环境光,无镜面光
glPushMatrix();
glTranslatef(-3.75,-3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//3-2有漫反射光和有颜色的环境光以及镜面光,且有低高光
glPushMatrix();
glTranslatef(-1.25,-3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,low_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//3-3 有漫反射光和有颜色的环境光以及镜面光,且有很亮的高光
glPushMatrix();
glTranslatef(1.25,-3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,high_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
draw();
glPopMatrix();

//3-4 有漫反射光和有颜色的环境光以及辐射光,无镜面光
glPushMatrix();
glTranslatef(3.75,-3.0,0.0);
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
draw();
glPopMatrix();

glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}


这次把材质完全搞定,呵呵
上次12个不同的立体材质球的程序运用glMaterialfv()来改变材质
有其固有的系统消耗。

另外同样的功能可以使用:
void glColorMaterial(GLenum face,GLenum mode)来实现。
face的取值:
GL_FRONT GL_BACK GL_FRONT_AND_BACK
mode的取值:
GL_AMBIENT GL_DIFFUSE GL_AMBIENT_AND_DIFFUSE GL_SPECULAR GL_EMISSION

在想要使用这个函数时,要启用glEnable(GL_COLOR_MATERIAL)来是函数工作
在绘图时使用glColor*()来改变材质颜色,或用glMaterial()来改变材质成分
使用完毕,要用glDisable(GL_COLOR_MATERIAL)来关闭这种材质模式。

例如:
glColorMaterial(GL_FRONT,GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);

glColor3f(0.3,0.5,0.7);
//画一些物体:
..

glColor3f(0.0,1.0,0.0);
//再画另一些物体:
..

glDisable(GL_COLOR_MATERIAL);

说明:
当需要改变场景中大部分方面的单个材质时,最好调用glColorMaterial()
当修改不只一个材质参数,最好调用glMaterial*(),这是目前我体会到
唯一的使用功能上的不同点吧。
请看下面例子:
/////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.1,0.1,0.0);
glClear(GL_COLOR_BUFFER_BIT);
// glShadeModel(GL_FLAT);

//定义一个白色简单光源
GLfloat light_position[]={0.0,3.0,2.0,0.0};
glLightfv(GL_LIGHT0,GL_POSITION,light_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

//启用消隐
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);

//启用颜色材质模式
glColorMaterial(GL_FRONT,GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-6.0,6.0,-6.0*(GLfloat)h/(GLfloat)w,
6.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-6.0*(GLfloat)h/(GLfloat)w,
6.0*(GLfloat)h/(GLfloat)w,-6.0,6.0,-10.0,10.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//先画一个黄色材质的球
glLoadIdentity();
glTranslatef(-0.7,0.0,0.0);
glColor3f(1.0,1.0,0.0);
auxSolidSphere(1.0);

//再在黄球的右边2.7处画一个红色的球
glLoadIdentity();
glRotatef(-60.0,1.0,0.0,0.0);
glTranslatef(2.7,0.0,0.0);
glColor3f(1.0,0.0,0.0);
auxSolidSphere(1.0);

//再再黄球左边(两球之间)靠后的位置画一个青色三角锥
glLoadIdentity();
glTranslatef(-1.0,-1.0,-5.0);
glRotatef(30.0,1.0,0.0,0.0);
glColor3f(0.0,1.0,1.0);
auxSolidCone(2.0,2.0);

glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////
通过这个例子,我们除了可以熟悉glColorMaterial()的使用,还可以体会到OPENGL在消隐方面为我们做的工作。记的我们前面的用彩色平面绘制三角锥的例子吗?那里可是哪个平面后画,哪个平面遮挡以前的平面。这里我们看到虽然三角锥虽然最后画出,但是OPENGL的消隐功能已经保证乐它实际应该被消隐。你可以试试取消myinit()中消隐的两句程序,那么三角锥就跑到前面来乐,呵呵。


OPENGL的位图和图象

与一般的位图定义不同,OPENGL中的位图是指用每个象素只有一位信息;
而图象一个象素可以包括多个信息(R、G、B、Alpha值)。
另外位图可以用于掩码,遮掩别的图象,而图象的数据则简单的覆盖先前
的存在的数据或者与之融合。

(一)位图(BITMAP)和字符(FONT)
常常用来对窗口相应区域屏蔽,比如当前颜色为红色,则在矩阵中元素值为
1的地方用红色取代,0的地方保持不变。位图常用在字符显示。

光栅位置:
void glRasterPos{234}{sifd}[v](TYPE x,TYPE y,TYPE z,TYPE w);
设置当前所画位图或图象的原点。一般颜色的设置应该放在glRasterPos*()
的前面,则紧跟其后的位图都继承当前设置的这种颜色。

位图显示:
void glBitmap(GLsizei width,GLsizei height,GLfloat xbo,GLflaot ybo,
GLfloat xbi,GLfloat ybi,const GLubyte *bitmap);
xbo,ybo定义位图的原点,详细可参见例子:
//////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//定义字符位图,从下到上(即第一个数据是字模的最下一行,依次类推):
// 11111111
// 11111111
// 11000011
// 11000011
// 11000011
// 11111111
// 11111111
// 11000011
// 11000011
// 11000011
// 11111111
// 11111111
//组成一个 8 字
GLubyte rasters[12]={0xff,0xff,0xc3,0xc3,0xc3,0xff,0xff,
0xc3,0xc3,0xc3,0xff,0xff};

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//描述位图数据在计算机内存中的存储方式,不必深究
glPixelStorei(GL_UNPACK_ALIGNMENT,1);

}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,w,0,h,-1.0,1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//先定义红色填充:
glColor3f(1.0,0.0,1.0);
glRasterPos2i(100,200);
//在2个不同位置绘制 8 (紫色)
glBitmap(8,12,0.0,0.0,20.0,20.0,rasters);
glBitmap(8,12,0.0,0.0,20.0,20.0,rasters);

//绘制另一个 8 (黄色)
glColor3f(1.0,1.0,0.0);
glRasterPos2i(150,200);
glBitmap(8,12,0.0,0.0,0.0,0.0,rasters);

glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
///////////////////////////////////////////////////////

(二)图象

字符显示只是个小内容,我想大家也许会更关心图象的显示
1.象素读写
OPENGL提供基本象素读写函数:
void glReadPixels( GLint x, GLint y, GLsizei width, GLsizei height,
GLenum format, GLenum type, GLvoid *pixels );
函数参数(x,y)定义图象区域左下角坐标,width height描述图象宽高。
pixel是指针,指向图象数据数组。format指出数据元素格式(索引或
R、G、B、A值):

format: 说明:
GL_INDEX 单个颜色索引
GL_RGB 依次 红、绿、蓝分量
GL_RED 单个红色分量
GL_GREEN 单个绿色分量
GL_BLUE 单个蓝色分量
GL_ALPHA 单个Alpha值
GL_LUMINANCE_ALPHA
GL_STENCIL_INDEX 单个模板索引
GL_DEPTH_COMPONENT 单个深度分量

而type指出元素数据类型:
type:
GL_UNSIGNED_BYTE 无符号8位整数
GL_BYTE 8位整数
GL_BITMAP 无符号8位整数数组中单个数位
GL_UNSIGNED_SHORT 无符号16位整数
GL_SHORT 16位整数
GL_UNSIGNED_INT 无符号32位整数
GL_INT 32位整数
GL_FLOAT 单精度浮点数

类似的写象素函数:
void glDrawPixels( GLsizei width, GLsizei height, GLenum format,
GLenum type, const GLvoid *pixels );

2.象素拷贝
void glCopyPixels( GLint x, GLint y, GLsizei width,
GLsizei height, GLenum type );
这个函数很类似先用glReadPixels()然后调用glDrawPixels(),但是它不消耗
系统内存,只是拷贝,
type可以是:GL_COLOR GL_STENCIL GL_DEPTH
在拷贝前,type要按如下方式转换成format:
1)type为GL_DEPTH GL_STENCIL 那么format 对应应该是GL_DEPTH_COMPONENT
或GL_STENCIL_INDEX
2)type为GL_COLOR 那么format 应该是GL_RGB或GL_COLOR_INDEX。

3.图象缩放
void glPixelZoom(GLfloat zoomx,GLfloat zoomy);
zoomx zoomy是X Y方向的缩放因子。缺省是1.0。
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

// glPixelStorei(GL_UNPACK_ALIGNMENT,1);

}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
gluOrtho2D(0.0,15.0,0.0,15.0*(GLfloat)h/(GLfloat)w);
else
gluOrtho2D(0.0,15.0*(GLfloat)w/(GLfloat)h,0.0,15.0);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void draw()
{
//绘制一个彩色三角形(2D)
glBegin(GL_TRIANGLES);
glColor3f(1.0,0.0,0.0);
glVertex2f(2.0,3.0);
glColor3f(0.0,1.0,0.0);
glVertex2f(12.0,3.0);
glColor3f(0.0,0.0,1.0);
glVertex2f(7.0,12.0);
glEnd();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//在屏幕上方绘制原始图象
glPushMatrix();
glLoadIdentity();
glTranslatef(4.0,8.0,0.0);
glScalef(0.5,0.5,0.5);
draw();
glPopMatrix();

int i;
for(i=0;i<5;i++)
{
//循环5次画出5个拷贝
//先定义显示缩放因子(递增)
glPixelZoom(1.0+0.1*i,1.0+0.1*i);
//再定义拷贝原点(越来越向右上)
glRasterPos2i(1+i*2,i);
//图象拷贝
glCopyPixels(160,310,180,160,GL_COLOR);
}

glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////

这个例子在屏幕上方中央绘制一个彩色三角然后在下面从左到右,依次绘制它的拷贝,拷贝位置不断向右上方向而且(通过增加缩放因子)新的拷贝变的越来越大。
当然后绘制的拷贝会遮盖前面的图形。


OPENGL的纹理
在3D图形中,纹理映射是广泛使用的。纹理映射也是相当复杂的过程:
一 定义纹理
二 控制滤波
三 说明映射方式
四 绘制场景给出顶点的纹理坐标和几何坐标
注意!!纹理映射只能在RGBA模式下使用,不适用于颜色索引模式

1.纹理定义
void glTexImage2D( GLenum target, GLint level, GLint components,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *pixels );
定义一个二维纹理映射。
target是常数 GL_TEXTURE_2D
level表示多级分辨率的纹理图象的级数。若只有一种分辨率,level为0。
components是从1到4的整数,1:选择R;2:选择R A;3:选择R G B;
4:选择R G B A;
width height是纹理的尺寸。
format和type描述映射格式和数据类型。它们与前面讲的glDrawPixels()中
的意义相同。你可以把纹理当成贴图来看待。
另外还有一维纹理定义:
void glTexImage1D( GLenum target, GLint level, GLint components,
GLsizei width, GLint border, GLenum format,
GLenum type, const GLvoid *pixels );
不同之处在于target是常数GL_TEXTURE_1D,例外提供的数据应该是一维数组。
一般纹理数据维数应该是2的幂次,有时还要根据类型加上边界数据。

2.纹理控制(滤波和重复与缩限)
所有纹理控制通过:
void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param);
来实现,target可以是GL_TEXTURE_1D GL_TEXTURE_2D来说明是一维还是二维
纹理。pname和param的可能取值见下:
pname: param:
GL_TEXTURE_WRAP_S GL_CLAMP
GL_REPEAT
GL_TEXTURE_WRAP_T GL_CLAMP
GL_REPEAT
GL_TEXTURE_MAG_FILTER GL_NEAREST
GL_LINEAR
GL_TEXTURE_MIN_FILTER GL_NEAREST
GL_NEAREST_MIPMAP_NEAREST
GL_NEAREST_MIPMAP_LINEAR
GL_LINEAR_MIPMAP_NEAREST
GL_LINEAR_MIPMAP_LINEAR
2.1 滤波
原始纹理图象是个方形图象,把它映射到奇形怪状的物体上,一般不可能图象
上的一个象素对应屏幕的一个象素。因此局部放大缩小时,就要定义合适的滤
波方式(以2D为例):
void glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
void glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
前者是放大滤波(GL_TEXTURE_MAG_FILTER),
后者是缩小滤波(GL_TEXTURE_MIN_FILTER);
另外,GL_NEAREST是利用最坐标最靠近象素中心的纹理元素,这有可能使图样
走型,但计算速度快;GL_LINEAR利用线形插值,效果好但计算量大。

2.2重复与缩限
纹理映射可以重复映射或者缩限映射,重复映射时纹理可以在自己的坐标S T方
向重复。
对于重复映射:
void glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
void glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
参数GL_REPEAT改为GL_CLAMP,则缩限,所有大于1的纹理元素值置为1。所有小于
0的纹理元素值置为0。

3. 映射方式
处理纹理本身图案颜色和物体本身颜色的关系:
void glTexEnv{if}[v](GLenum target,GLenum pname,TYPE param);
target必须是GL_TEXTURE_ENV;

pname是GL_TEXTURE_ENV_MODE,则param可以是 GL_DECAL GL_MODULATE或
GL_BLEND,说明纹理值与原来颜色不同的处理方式。
pname是GL_TEXTURE_ENV_COLOR,则参数param是包含4个浮点数(R、G、B、A)
的数组。这些值只在采用GL_BLEND纹理函数时才采用。

4. 纹理坐标
坐标的定义:纹理图象是方形的,纹理坐标可定义成s,t,r,q坐标,仿照齐次
坐标系的x,y,z,w坐标。
void glTexCoord{1234}{sifd}[v](TYPE coords);
设置当前纹理坐标,此后调用glVertex*()所产生的顶点都赋予当前的纹理坐标。

5. 坐标自动产生
有时不需要为每个物体顶点赋予纹理坐标,可以使用
void glTexGen{if}(GLenum coord,GLenum pname,TYPE param);
coord为:GL_S GL_T GL_R或GL_Q,指明哪个坐标自动产生

pname为GL_TEXTURE_GEN_MODE时
param为常数:GL_OBJECT_LINEAR GL_EYE_LINEAR或GL_SPHERE_MAP,它们决定用
哪个函数来产生纹理坐标

pname为GL_OBJECT_PLANE或GL_EYE_PLANE,param时一个指向参数数组的指针。

先请看一个简单的例子:
////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//创建纹理图象的子程序
#define TEXTUREWIDTH 64
#define TEXTUREHEIGHT 64
GLubyte Texture[TEXTUREWIDTH][TEXTUREHEIGHT][3];
void makeTexture(void)
{
int i,j,r,g,b;
for(i=0;i{
for(j=0;j{
r=(i*j)%255;
g=(4*i)%255;
b=(4*j)%255;
Texture[i][j][0]=(GLubyte)r;
Texture[i][j][1]=(GLubyte)g;
Texture[i][j][2]=(GLubyte)b;
}
}
}

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//创建纹理图象的原始数据保存在Texture[][][]中
makeTexture();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);

//定义二维纹理
glTexImage2D(GL_TEXTURE_2D,0,3,TEXTUREWIDTH,
TEXTUREHEIGHT,0,GL_RGB,GL_UNSIGNED_BYTE,
&Texture[0][0][0]);
//控制滤波
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

//说明映射方式
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);

//这个应该很熟了,启用纹理模式
glEnable(GL_TEXTURE_2D);
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//定义立体视景体
gluPerspective(60.0,1.0*(GLfloat)w/(GLfloat)h,1.0,30.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-3.6);
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glBegin(GL_QUADS);//绘制四边形
//先绘制正方形,用来显示实际未变形的纹理图样
glTexCoord2f(0.0,0.0);glVertex3f(-2.0,-1.0,0.0);
glTexCoord2f(0.0,1.0);glVertex3f(-2.0,1.0,0.0);
glTexCoord2f(1.0,1.0);glVertex3f(0.0,1.0,0.0);
glTexCoord2f(1.0,0.0);glVertex3f(0.0,-1.0,0.0);

//绘制一个不规则四边形,用来显示纹理是如何随物体形状而变形的。
glTexCoord2f(0.0,0.0);glVertex3f(0.0,-1.0,0.0);
glTexCoord2f(0.0,1.0);glVertex3f(0.0,1.0,0.0);
glTexCoord2f(1.0,1.0);glVertex3f(1.41421,1.0,-1.41421);
glTexCoord2f(1.0,0.0);glVertex3f(1.41421,-1.0,-1.41421);
glEnd();

glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
///////////////////////////////////////////////////////////
从例子来看,除了纹理的定义和控制比较麻烦和不容易理解外,其应用是十分方便的。只须从纹理的坐标系选出合适点附在实际物体顶点上即可。对于复杂的纹理定义和控制,你也可以自行改变一些参数,看看效果如何。例如把GL_LINEAR改成GL_NEAREST,则纹理的明显变的粗糙,但计算结果却快的多。
你也可以改动glTexCoord2f()的参数,看看如何选定纹理的一部分(例子中是选定全部纹理)来贴图。例如1.0改成0.5则选择实际纹理的左上1/4部分来贴图。
下次将给出一个更复杂的纹理应用的例子和说明。18:03 98-1-21


这次将结束纹理的内容。紧接上次,在上次平面纹理贴图中,我们先
定义了一个数组(一维或二维...)来定义纹理的数据,所以纹理本身
是一个N维空间,有自己的坐标和顶点。在上次的例子中,我们学会了
如何把纹理数据中的坐标和屏幕物体坐标相结合,就象把一块布料扯成
合适的形状贴在物体表面。而上次唯一没有使用的函数是纹理坐标的自
动产生(最后一个给出的函数),它的意义是产生一个环境纹理,所有
环境内的物体都赋予此纹理,很象一个特殊光源。
/////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//定义一个一维纹理的数据,从生成来看,保持红色、兰色分量255(MAX),
//所以是渐变的紫色纹理,饱和度不断变化。
#define TEXTUREWIDTH 64
GLubyte Texture[3*TEXTUREWIDTH];
void makeTexture(void)
{
int i;
for(i=0;i{
Texture[3*i]=255;
Texture[3*i+1]=255-2*i;
Texture[3*i+2]=255;
}
}
GLfloat sgenparams[]={1.0,1.0,1.0,0.0};

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//创建纹理
makeTexture();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
//控制纹理
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage1D(GL_TEXTURE_1D,0,3,TEXTUREWIDTH,0,
GL_RGB,GL_UNSIGNED_BYTE,Texture);
//唯一与前面例子不同的地方:启用纹理坐标自动产生,生成环境纹理
//纹理的方向S
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
glTexGenfv(GL_S,GL_OBJECT_PLANE,sgenparams);
//启用纹理
glEnable(GL_TEXTURE_1D);
glEnable(GL_TEXTURE_GEN_S);

//启用消隐
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

//一些绘图控制,详细可参阅VC5联机帮助
glEnable(GL_CULL_FACE);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glMaterialf(GL_FRONT,GL_SHININESS,64.0);
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(30.0,1.0,0.0,0.0);
//功能强大的辅助库函数:呵呵画出一个大茶壶。
auxSolidTeapot(1.5);
glPopMatrix();
glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////
至此纹理的全部内容已经完毕。从运行结果来看,一个物体全部进行了表面的纹理映射。


此次讲解OPENGL复杂建模方式,将分几个部分完成,这篇先介绍图原扩展:
如何利用专用函数精确绘制平面图形。下次会讲解如何利用法向量生成曲面。
1.点和线
void glPointSize(GLfloat size);
设置点的宽度,size必须>0,缺省1

void glLineWidth(GLfoat width);
设置线宽,width>0,缺省为1

void glLineStipple(GLint factor,GLushort pattern);
设置线的模式,factor用于对模式进行拉伸的比例因子,pattern是线的模式
例如11001100是虚线(1绘制,0不绘制)

必须要启用glEnable(GL_LINE_STIPPLE)才能使用以上函数,不再使用时调用
glDisable(GL_LINE_STIPPLE)关闭,这与以前的glEnable();glDisable();的
用法都是类似的。请看下面例子:
///////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,600,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

glShadeModel(GL_FLAT);
}
/*
void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
*/

//自定义的绘制直线的函数,参数为起始点和终止点坐标
void line2i(GLint x1,GLint y1,GLint x2,GLint y2)
{
glBegin(GL_LINES);
glVertex2f(x1,y1);
glVertex2f(x2,y2);
glEnd();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//首先绘制一系列点,点的大小不断增加
int i;
glColor3f(0.8,0.6,0.4);
for(i=1;i<=10;i++)
{
glPointSize(i*2);
glBegin(GL_POINTS);
glVertex2f(30.0+((GLfloat)i*50.0),330.0);
glEnd();
}
//再绘制两条虚线,第二条比第一条松散一些,由pattern参数即可看出
glLineWidth(1.0);
glEnable(GL_LINE_STIPPLE);
glLineStipple(1,0x0f0f);//间隔1位
glColor3f(1.0,0.0,0.0);
line2i(20,250,250,250);
glLineStipple(1,0x00ff);//间隔2位
glColor3f(0.0,0.0,1.0);
line2i(300,250,550,250);

//改变线的绘制宽度的效果--加宽
//重新画出上面两条虚线
glLineWidth(5.0);
glEnable(GL_LINE_STIPPLE);
glLineStipple(1,0x0f0f);
glColor3f(1.0,0.0,0.0);
line2i(50,150,250,150);
glLineStipple(1,0x00ff);
glColor3f(0.0,0.0,1.0);
line2i(300,150,550,150);

glFlush();
}
void main(void)
{
myinit();

// auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
//////////////////////////////////////////////

2.多边形
void glPolygonMode(GLenum face,GLenum mode);
控制多边形指定面的绘图模式,
face为:GL_FRONT GL_BACK或GL_FRONT_AND BACK
mode为:GL_POINT GL_LINE或GL_FILL表示多边型的轮廓点、轮廓线和填充模式
的绘制方式。缺省是填充方式。

void glPolygonStipple(const GLubyte *mask);
其中mask必须是指向32*32的位图指针,1是绘制、0不绘制

使用上述函数也要调用:
glEnable(GL_POLYGON-STIPPLE);
glDisable(GL_POLYGON_STIPPLE);
请看下面例子:
/////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//定义填充模式32*32点阵
GLubyte pattern[]={
0x00,0x01,0x80,0x00,
0x00,0x03,0xc0,0x00,
0x00,0x07,0xe0,0x00,
0x00,0x0f,0xf0,0x00,
0x00,0x1f,0xf8,0x00,
0x00,0x3f,0xfc,0x00,
0x00,0x7f,0xfe,0x00,
0x00,0xff,0xff,0x00,
0x01,0xff,0xff,0x80,
0x03,0xff,0xff,0xc0,
0x07,0xff,0xff,0xe0,
0x0f,0xff,0xff,0xf0,
0x1f,0xff,0xff,0xf8,
0x3f,0xff,0xff,0xfc,
0x7f,0xff,0xff,0xfe,
0xff,0xff,0xff,0xff,

0xff,0xff,0xff,0xff,
0x7f,0xff,0xff,0xfe,
0x3f,0xff,0xff,0xfc,
0x1f,0xff,0xff,0xf8,
0x0f,0xff,0xff,0xf0,
0x07,0xff,0xff,0xe0,
0x03,0xff,0xff,0xc0,
0x01,0xff,0xff,0x80,
0x00,0xff,0xff,0x00,
0x00,0x7f,0xfe,0x00,
0x00,0x3f,0xfc,0x00,
0x00,0x1f,0xf8,0x00,
0x00,0x0f,0xf0,0x00,
0x00,0x07,0xe0,0x00,
0x00,0x03,0xc0,0x00,
0x00,0x01,0x80,0x00
};

void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,400,400);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

glShadeModel(GL_FLAT);
}
/*
void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
*/

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//选用兰色作为填充色
glColor3f(0.0,0.0,1.0);
//启用多边形绘制模式
glEnable(GL_POLYGON_STIPPLE);
//利用定义好的填充模式绘制多边形
glPolygonStipple(pattern);
//绘制长方形
glRectf(48.0,80.0,210.0,305.0);

glFlush();
}
void main(void)
{
myinit();

// auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
例子中的运行结果是给出一个表面有定义图样的长方形


这里讲解OPENGL的曲线生成
1.曲线定义
void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride,
GLint order,const TYPE *points);

target指出控制点的意义以及在points参数中需要多少值。具体如下:

target 意义
GL_MAP1_VERTEX_3 X Y Z顶点坐标
GL_MAP1_VERTEX_4 X Y Z W顶点坐标
GL_MAP1_INDEX 颜色索引
GL_MAP1_COLOR_4 R G B A
GL_MAP1_NORMAL 法向量
GL_MAP1_TEXTURE_COORD_1 S 纹理坐标
GL_MAP1_TEXTURE_COORD_2 S T纹理坐标
GL_MAP1_TEXTURE_COORD_3 S T R纹理坐标

u1,u2是曲线变量U的范围(具体可以参阅图形学书籍)一般是0到1
stride是跨度,表示points中控制点偏移量(或说是控制点的维数)
order是阶数,与控制点数一样
points是控制点坐标
曲线定义后要启用才能进行绘制,同样用glEnable(),glDisable()。
2.曲线计算绘制
void glEvalCoord1{fd}[v](TYPE u);
利用控制点产生曲线U坐标下某点的坐标,调用一次只能产生一个坐标。
一般的曲线的绘制方法是让U变化从0到1(步长自定)然后把这些坐标
用直线连接起来。
用以上这两个函数就可以完成绘制曲线的功能。

另外还有两个函数可以实现类似功能:
void glMapGrid1{fd}(GLint n,TYPE u1,TYPE u2);
定义一个空间网格,从u1到u2分为n步,是等间隔的(曲线参数)。
void glEvalMesh1(GLenum mode,GLint p1,GLint p2);
计算并绘制坐标点。mode可以是GL_POINT、GL_LINE即延曲线绘点或连接直线段
它等价于:
glBegin(GL_POINT);
for(i=p1;i<=p2;i++)
glEvalCoord1(u1+i*(u2-u1)/n);
glEnd();
下面给出一个BEZIER曲线的例子:
////////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//定义四个控制点的坐标
GLfloat points[4][3]={
{-4.0,-4.0,0.0},{-2.0,4.0,0.0},{2.0,-4.0,0.0},{4.0,4.0,0.0}};

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//定义曲线,并启用绘制曲线的模式
glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,4,&points[0][0]);
glEnable(GL_MAP1_VERTEX_3);

glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glColor3f(0.0,0.0,1.0);
glBegin(GL_LINE_STRIP);
//首先以30步的直线段连接,来绘制曲线,注意使用GL_LINE_STRIP来连接直线段
int i;
for(i=0;i<=30;i++)
glEvalCoord1f((GLfloat)i/30.0);
glEnd();

//下面绘制出4个控制点
glPointSize(4.0);
glColor3f(1.0,0.0,0.0);
glBegin(GL_POINTS);
for(i=0;i<4;i++)
glVertex3fv(&points[i][0]);
glEnd();

glFlush();

}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
本例子绘制出一个有4个控制点的BEZIER曲线。曲线经过头尾两个控制点中间曲线形状由控制点次序和位置决定,总之落在其包围的四边形以内。
下次将会用大篇幅介绍曲面的生成和其表面纹理、颜色的应用



曲面的构造可以是网格线和填充曲面形式,其实与曲线很类似只是变为
二维而已。
1.曲面定义
void glMap2{fd}(GLenum target,TYPE u1,TYPE u2,GLint ustride,GLint uorder,
TYPE v1,TYPE v2,GLint vstride,GLint vorder,TYPE points);
target的定义同上次介绍的曲线中target的定义。
U V是二维曲面坐标
uorder,vorder;ustride,vstride的定义都类似曲线定义。
points是控制点坐标

2.曲面任意一点的计算
void glEvalCoord2{fd}[v](TYPE u,TYPE v);
以曲线坐标U V来计算曲面内任意一点的世界坐标的位置

3.曲面绘制的控制
void glMapGrid2{fd}(GLenum nu,TYPE u1,TYPE u2,GLenum nv,TYPE v1,TYPE v2);
定义曲面参数空间均匀网格,从u1到u2分为等间隔nu步,从v1到v2分为等间隔nv步。

下面给出一个以网格线描绘曲面的例子:
////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//控制点坐标
GLfloat points[4][4][3]={
{{-1.5,-1.5,2.0},{-0.5,-1.5,2.0},
{0.5,-1.5,-1.0},{1.5,-1.5,2.0}},
{{-1.5,-0.5,1.0},{-0.5,1.5,2.0},
{0.5,0.5,1.0},{1.5,-0.5,-1.0}},
{{-1.5,0.5,2.0},{-0.5,0.5,1.0},
{0.5,0.5,3.0},{1.5,-1.5,1.5}},
{{-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
{0.5,0.5,1.0},{1.5,1.5,-1.0}}};

//为了清楚显示控制点而设置的一组颜色
GLfloat color[4][3]={
{1.0,0.0,0.0},{0.0,1.0,0.0},{0.0,0.0,1.0},{1.0,1.0,1.0}};

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//利用glEnable()来启用曲面模式
glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&points[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);

glMapGrid2f(20,0.0,1.0,20,0.0,1.0);

glEnable(GL_DEPTH_TEST);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glColor3f(0.0,0.0,1.0);
glPushMatrix();
glRotatef(35.0,1.0,1.0,1.0);
//用直线段的连接描绘曲面结构
glBegin(GL_LINE_STRIP);
int i,j;
//在U V方向各画出8条网格线,用以构造曲面结构
for(j=0;j<=8;j++)
{
//在U方向用30条直线段描绘一条曲线
glBegin(GL_LINE_STRIP);
for(i=0;i<=30;i++)
glEvalCoord2f((GLfloat)i/30.0,(GLfloat)j/8.0);
glEnd();

//在V方向用30条直线段描绘一条曲线
glBegin(GL_LINE_STRIP);
for(i=0;i<=30;i++)
glEvalCoord2f((GLfloat)j/8.0,(GLfloat)i/30.0);
glEnd();
}

//用不同的颜色把控制点显示出来
glPointSize(4.0);
glBegin(GL_POINTS);
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
glColor3fv(color[i]);
glVertex3fv(&points[i][j][0]);
}
glEnd();

glPopMatrix();
glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
///////////////////////////////////////////////////////

应用中常常不满足曲面的框架结构。下面就介绍填充曲面的绘制方法:

void glEvalMesh2(GLenum mode,GLint p1,GLint p2,GLint q1,GLint q2);
把用glMapGrid2{fd}()设置的网格应用到已经启用的曲面计算上。
mode可以是GL_POINT GL_LINE GL_FILL。顾名思义,GL_FILL就是生成填充曲面

这里给出一个有光照处理的BEZIER曲面的例子(这个曲面除了绘制方法不同,数学
形式和前面的是一样的)。
///////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

GLfloat points[4][4][3]={
{{-1.5,-1.5,2.0},{-0.5,-1.5,2.0},
{0.5,-1.5,-1.0},{1.5,-1.5,2.0}},
{{-1.5,-0.5,1.0},{-0.5,1.5,2.0},
{0.5,0.5,1.0},{1.5,-0.5,-1.0}},
{{-1.5,0.5,2.0},{-0.5,0.5,1.0},
{0.5,0.5,3.0},{1.5,-1.5,1.5}},
{{-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
{0.5,0.5,1.0},{1.5,1.5,-1.0}}};
GLfloat color[4][3]={
{1.0,0.0,0.0},{0.0,1.0,0.0},{0.0,0.0,1.0},{1.0,1.0,1.0}};

//初始化光照、材质的过程
void initlights(void)
{
GLfloat ambient[]={0.4,0.6,0.2,1.0};
GLfloat position[]={0.0,1.0,3.0,1.0};
GLfloat mat_diffuse[]={0.2,0.4,0.8,1.0};
GLfloat mat_specular[]={1.0,1.0,1.0,1.0};
GLfloat mat_shininess[]={80.0};
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
glLightfv(GL_LIGHT0,GL_POSITION,position);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
}

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&points[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
// glEnable(GL_NORMALIZE);
glMapGrid2f(20,0.0,1.0,20,0.0,1.0);

glEnable(GL_DEPTH_TEST);
//初始化光照、材质
initlights();
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glColor3f(0.0,0.0,1.0);
glPushMatrix();
glRotatef(35.0,1.0,1.0,1.0);

//将原来网格的绘制部分注释掉,代替以填充曲面的绘制方法
glEvalMesh2(GL_FILL,0,20,0,20);
int i,j;
/* for(j=0;j<=8;j++)
{
glBegin(GL_LINE_STRIP);
for(i=0;i<=30;i++)
glEvalCoord2f((GLfloat)i/30.0,(GLfloat)j/8.0);
glEnd();

glBegin(GL_LINE_STRIP);
for(i=0;i<=30;i++)
glEvalCoord2f((GLfloat)j/8.0,(GLfloat)i/30.0);
glEnd();
}
*/

glPointSize(4.0);
glBegin(GL_POINTS);
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
glColor3fv(color[i]);
glVertex3fv(&points[i][j][0]);
}
glEnd();
glPopMatrix();
glFlush();

}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////////
一个真正具有实体的曲面就展现出来乐。控制点的不同,可以控制曲面的形状
可以看出OPENGL的强大功能,只用很少的原代码就产生真实的3D效果。
在以此篇结束复杂建模前,给出一个绘制NURBS曲面(非均匀B样条曲面)的例子,
这里用到乐一些OPENGL实用库提供的专门函数。所有的新东东都在程序中做乐注
释,注释给出过程的含义,具体函数的操作可以详细查阅联机手册。
///////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"

void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//定义一组控制点的存储空间
GLfloat points[4][4][3];
//定义一个指向NURBS曲面对象的指针
GLUnurbsObj *theNurb;

//用来生成控制点的过程,取代被注释掉的原来直接赋值方式生成控制点
void init_surface(void)
{
int u,v;
for(u=0;u<4;u++)
{
for(v=0;v<4;v++)
{
points[u][v][0]=2.0*((GLfloat)u-1.5);
points[u][v][1]=2.0*((GLfloat)v-1.5);
if((u==1||u==2)&&(v==1||v==2))
points[u][v][2]=3.0;
else
points[u][v][2]=-3.0;
}
}
}
/*
GLfloat points[4][4][3]={
{{-1.5,-1.5,2.0},{-0.5,-1.5,4.0},
{0.5,-1.5,3.0},{1.5,-1.5,2.0}},
{{-1.5,-0.5,3.0},{-0.5,1.5,4.0},
{0.5,0.5,2.0},{1.5,-0.5,-1.0}},
{{-1.5,0.5,2.0},{-0.5,0.5,4.0},
{0.5,0.5,5.0},{1.5,-1.5,1.5}},
{{-1.5,1.5,-2.0},{-0.5,1.5,3.0},
{0.5,0.5,1.0},{1.5,1.5,-1.0}}};
*/
GLfloat color[4][3]={
{1.0,0.0,0.0},{0.0,1.0,0.0},{0.0,0.0,1.0},{1.0,1.0,1.0}};

void initlights(void)
{
GLfloat ambient[]={0.4,0.6,0.2,1.0};
GLfloat position[]={0.0,1.0,3.0,1.0};
GLfloat mat_diffuse[]={0.2,0.4,0.8,1.0};
GLfloat mat_specular[]={1.0,1.0,1.0,1.0};
GLfloat mat_shininess[]={80.0};
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
glLightfv(GL_LIGHT0,GL_POSITION,position);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
//首先初始化控制点
init_surface();
//创建一个NURBS曲面的对象
theNurb=gluNewNurbsRenderer();
//修改此曲面对象的属性
gluNurbsProperty(theNurb,GLU_SAMPLING_TOLERANCE,25.0);
gluNurbsProperty(theNurb,GLU_DISPLAY_MODE,GLU_FILL);
}

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&points[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glMapGrid2f(20,0.0,1.0,20,0.0,1.0);

glEnable(GL_DEPTH_TEST);
initlights();
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
//B样条曲面(NURBS)的控制向量,可以参阅图形学的相关书籍
GLfloat knots[8]={0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0};

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glColor3f(0.0,0.0,1.0);
glPushMatrix();
glRotatef(35.0,1.0,1.0,1.0);
glRotatef(-60.0,1.0,0.0,0.0);
glScalef(0.5,0.5,0.5);

//注释掉原来的曲面绘制函数,代之以新的NURBS曲面绘制函数
// glEvalMesh2(GL_FILL,0,20,0,20);
//定义曲面的数学模型,确定其形状
gluNurbsSurface(theNurb,
8,knots,
8,knots,
4*3,
3,
&points[0][0][0],
4,4,
GL_MAP2_VERTEX_3);
//结束曲面的绘制,此结构很类似于标准的glBegin()...glEnd()
gluEndSurface(theNurb);

int i,j;
glPointSize(4.0);
glBegin(GL_POINTS);
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
glColor3fv(color[i]);
glVertex3fv(&points[i][j][0]);
}
glEnd();
glPopMatrix();
glFlush();

}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
//////////////////////////////////////////////////////////////////
至此,复杂建模告于段落。下次介绍特殊光照



为什么3D作图常常能产生另人震惊的效果?因为利用3D作图,你可以生成一些
现实中难得实现的真实的感受。特别是一些特殊的光影效果。
其实光源前面已经讲的很全面了,只是缺少一些专门的例子。这里我们来稍微
加深一下认识,我们将在例子中看到一个地方的光源对不同物体发出不同的光
这在现实中是少见的吧?

1.双面光照:
void glLightModeli(LIGHT_MODEL_TWO_SIDE,GL_TRUE);
光照计算通常是对多边形进行的。一般设置光照的条件总是对正面的多边形,而
不考虑背面,但是如果考虑一个物体被劈开,光源的个数又会影响可见性,那么
有必要对多边形双面都进行计算,这就是上面函数的功能;取消这一功能只须把
第三个参数改为GL_FALSE。

2.聚光源
定义聚光源的函数仍是利用glLightfv(),需要设定的参数包括:光源位置、光源
发散半角和光源聚光方向。

具体实现可以看下面例子:
//////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"

void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void initlights(void)
{
//定义物体材质特性的数值
GLfloat mat_ambient[]={0.2,0.2,0.2,1.0};
GLfloat mat_diffuse[]={0.8,0.8,0.8,1.0};
GLfloat mat_specular[]={1.0,1.0,1.0,1.0};
GLfloat mat_shininess[]={80.0};

//定义第0个光源的属性(这是平行的环境光,没有聚光效果)
GLfloat light0_diffuse[]={0.0,0.0,1.0,1.0};
GLfloat light0_position[]={1.0,1.0,1.0,0.0};
//定义第一个光源的属性(聚光灯一)
GLfloat light1_ambient[]={0.2,0.2,0.2,1.0};
GLfloat light1_diffuse[]={1.0,0.0,0.0,1.0};
GLfloat light1_specular[]={1.0,0.6,0.6,1.0};
GLfloat light1_position[]={-3.0,-3.0,3.0,1.0};
GLfloat spot_direction[]={1.0,1.0,-1.0};

//定义第二个光源的属性(聚光灯二)
GLfloat light2_ambient[]={0.2,0.6,0.2,1.0};
GLfloat light2_diffuse[]={0.0,1.0,0.0,1.0};
GLfloat light2_specular[]={0.0,1.0,0.0,1.0};
GLfloat light2_position[]={-3.0,-3.0,3.0,1.0};
GLfloat spot2_direction[]={1.0,1.0,-1.0};
//!!!我们看到第一和第二个聚光源除了在颜色的定义上一个偏红,一个偏绿
//其他没有任何不同,所以如果象我们后面作的,对一个物体开启一个光源,对
//另一个物体开启另一个光源,就会产生一个点光源对不同物体发出不同光的效果

//将前面的属性定义加以应用
glLightfv(GL_LIGHT0,GL_DIFFUSE,light0_diffuse);
glLightfv(GL_LIGHT0,GL_POSITION,light0_position);

glLightfv(GL_LIGHT1,GL_AMBIENT,light1_ambient);
glLightfv(GL_LIGHT1,GL_DIFFUSE,light1_diffuse);
glLightfv(GL_LIGHT1,GL_SPECULAR,light1_specular);
glLightfv(GL_LIGHT1,GL_POSITION,light1_position);
//定义聚光灯发散角
glLightf(GL_LIGHT1,GL_SPOT_CUTOFF,30.0);
//定义聚光灯发射方向的向量
glLightfv(GL_LIGHT1,GL_SPOT_DIRECTION,spot_direction);

glLightfv(GL_LIGHT2,GL_AMBIENT,light2_ambient);
glLightfv(GL_LIGHT2,GL_DIFFUSE,light2_diffuse);
glLightfv(GL_LIGHT2,GL_SPECULAR,light2_specular);
glLightfv(GL_LIGHT2,GL_POSITION,light2_position);

glLightf(GL_LIGHT2,GL_SPOT_CUTOFF,30.0);
glLightfv(GL_LIGHT2,GL_SPOT_DIRECTION,spot2_direction);

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

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHT2);

}

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
initlights();
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//首先标出聚光源一、二的位置(同一位置)
glPushMatrix();
glTranslated(-3.0,-3.0,3.0);
glDisable(GL_LIGHTING);
glColor3f(1.0,0.0,0.0);
auxWireCube(0.1);
glEnable(GL_LIGHTING);
glPopMatrix();

//关掉第二个光源,只启用第一个光源(红),绘制球体一
glPushMatrix();
glDisable(GL_LIGHT2);
glTranslated(0.0,2.0,0.0);
auxSolidSphere(2.0);
glPopMatrix();

//关掉第一个光源,只启用第二个光源(绿),绘制球体二
glPushMatrix();
glDisable(GL_LIGHT1);
glEnable(GL_LIGHT2);
glTranslated(0.0,-2.0,0.0);
auxSolidSphere(2.0);
glPopMatrix();

glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
///////////////////////////////////////////////////////////////
一个现实中难以见到的情景出现了。还不快试试?

结束光源之前,再给出一个光源移动的例子,其中用到了鼠标消息的响应,实现
非常简单,以OPENGL的辅助库提供的方式调用一个CALLBACK函数即可,和以前讲
的响应键盘输入的方法完全一样。
//////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

//控制光源移动的变量
static int step=0;

//鼠标响应的CALLBACK函数
void CALLBACK movelight(AUX_EVENTREC *event)
{
step=(step+15)%360;
}

void initlights(void)
{
GLfloat mat_ambient[]={0.2,0.2,0.2,1.0};
GLfloat mat_diffuse[]={0.8,0.8,0.8,1.0};
GLfloat mat_specular[]={1.0,1.0,1.0,1.0};
GLfloat mat_shininess[]={80.0};

GLfloat light0_diffuse[]={0.0,0.0,1.0,1.0};
GLfloat light0_ambient[]={0.2,0.5,0.5,1.0};


glLightfv(GL_LIGHT0,GL_DIFFUSE,light0_diffuse);
glLightfv(GL_LIGHT0,GL_AMBIENT,light0_ambient);


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

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

}

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
initlights();
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
GLfloat position[]={0.0,0.0,1.5,1.0};

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glPushMatrix();
glTranslatef(0.0,0.0,-5.0);
glPushMatrix();

//光源的位置只由旋转变量step决定,每按下鼠标左键一下,step的值便会改变
//导致光源位置的改变
glRotated((GLdouble)step,-1.0,1.0,1.0);
glLightfv(GL_LIGHT0,GL_POSITION,position);
glTranslated(0.0,0.0,1.5);

glDisable(GL_LIGHTING);
glColor3f(1.0,1.0,0.0);
auxWireSphere(0.25);
glEnable(GL_LIGHTING);

glPopMatrix();
auxSolidTorus(0.5,2.5);
glPopMatrix();

glFlush();

}
void main(void)
{
myinit();

auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEDOWN,movelight);
auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
/////////////////////////////////////////////////////////////
在例子中,黄色的小球表示当前光源的位置,它的旋转导致了环状体表面受光照部分的光影的变化,每按下鼠标左键一下,光源就会作响应的旋转。


OPENGL的特殊效果

1 融合
前面从未接触过透明或半透明的物体,因为我们从未启用过融合处理
所谓融合就是假设在RGBA模式下,源色为(Rs,Gs,Bs,As),目标色为
(Rd,Gd,Bd,Ad),源因子为(Sr,Sg,Sb,Sa),目的因子为(Dr,Dg,Db,Da)
则融合的最终效果为:(Rs*Sr+Rd*Dr,Gs*Sg+Gd*Dg,Bs*Sb+Bd*Db,As*Sa+Ad*Da)
然后再归一。公式挺复杂,不过仔细看看,跟平常的融合倒是定性一致。
关键就是如何设定融合因子(Sr,Sg,Sb,Sa)(Dr,Dg,Db,Da)来实现不同的融合效果
利用函数:
void glBlendFunc(GLenum sfactor,GLenum dfactor);
其中两个参数可以取下面值:

取值 相关因子 计算后融合因子
GL_ZERO 源、目的 (0,0,0,0)
GL_ONE 源、目的 (1,1,1,1)
GL_DST_COLOR 源 (Rd,Gd,Bd,Ad)
GL_SRC_COLOR 目的 (Rs,Gs,Bs,As)
GL_ONE_MINUS_DST_COLOR 源 (1,1,1,1)-(Rd,Gd,Bd,Ad)
GL_ONE_MINUS_SRC_COLOR 目的 (1,1,1,1)-(Rs,Gs,Bs,As)
GL_SRC_ALPHA 源、目的 (As,As,As,As)
GL_ONE_MINUS_SRC_ALPHA 源、目的 (1,1,1,1)-(As,As,As,As)
GL_DST_ALPHA 源、目的 (Ad,Ad,Ad,Ad)
GL_ONE_MINUS_DST_ALPHA 源、目的 (1,1,1,1)-(Ad,Ad,Ad,Ad)
GL_SRC_ALPHA_SATURATE 源 (f,f,f,1)-min(As,1-Ad)

还要利用glEnable(GL_BLEND) glDisable(gl_blend)来启用、关闭融合处理。
////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//设置融合效果并启用
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
// glDepthFunc(GL_LESS);
// glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
gluOrtho2D(0.0,1.0,0.0,1.0*(GLfloat)h/(GLfloat)w);
else
gluOrtho2D(0.0,1.0*(GLfloat)w/(GLfloat)h,0.0,1.0);
glMatrixMode(GL_MODELVIEW);
}

void CALLBACK display(void)
{

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//三个不同颜色和透明度的方块重合,颜色融合的效果
glColor4f(1.0,0.0,0.0,0.7);
glRectf(0.25,0.4,0.75,0.9);

glColor4f(0.0,1.0,0.0,1.0);
glRectf(0.1,0.1,0.6,0.6);

glColor4f(0.0,0.0,1.0,0.3);
glRectf(0.4,0.1,0.9,0.6);

glFlush();

}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
/////////////////////////////////////////////////////////
你可以试试调节参数大小,体会一下个参数的含义。

2 反走样Anti-aliasing
由于计算机以离散点生成图形,生成图形必然与真实景物存在差距,这种差距
表现为:直线或光滑曲面的锯齿、花纹失去原有色彩形状、细小物体在画面的
消失等。统统叫做走样。反走样可以减少这种情况。粗略设想一下,就是把原
来边界的地方锯齿部分用低饱和度的点补上,这样既不影响整体轮廓,又获得
较好的平滑效果。反走样前提供“提示”采用函数:

void glHint(GLenum target,GLenum hint);
其中hint可以是:GL_FASTEST 给出最有效的选择
GL_NICEST 给出最高质量的选择
GL_DONT_CARE 没有选择

target 意义

GL_POINT_SMOOTH_HINT 指定点、
GL_LINE_SMOOTH_HINT 线、
GL_POLYGON_SMOOTH_HINT 多边形的采样质量

GL_FOG_HINT 指出雾化计算是按每个象素进行(GL_NICEST)
还是按每个顶点进行(GL_FASTEST)

GL_PERSPECTIVE_CORRECTION_HINT 指定颜色纹理插值的质量
其中GL_PERSPECTIVE_CORRECTION_HINT用以纠正单纯线性插值带来的观察错误。

当然最主要的工作还是glEnable()来完成的。

先给出一个点、线反走样的例子。需要说明的是这个工作最好在RGBA模式下进行,
首先利用glEnable()(参数为GL_POINT_SMOOTH GL_LINE_SMOOTH或
GL_POLYGON_SMOOTH)启用反走样。在RGBA模式下启用反走样,必须启用融合处理。
而且最常用的融合因子分别是:GL_SRC_ALPHA(源)和GL_ONE_MINUS_SRC_ALPHA
或GL_ONE(目的)。

///////////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

//例子所有新内容都在一下4句,你可以试试取消反走样的语句(1、4)
//则可以很清楚的比较一下反走样对图象质量的改进。
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glHint(GL_LINE_SMOOTH_HINT,GL_DONT_CARE);

glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glLineWidth(5.0);
glColor4f(1.0,1.0,0.0,0.7);
glPushMatrix();
glRotatef(45.0,1.0,1.0,0.0);
auxWireOctahedron(2.0);
glPopMatrix();
glFlush();

}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
///////////////////////////////////////////////////////////////////

再给出一个多边形反走样的例子
///////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

GLfloat mat_ambient[]={0.5,0.5,0.0,1.0};
GLfloat mat_diffuse[]={1.0,0.8,0.1,1.0};
GLfloat position[]={1.0,0.0,1.0,0.0};

glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glLightfv(GL_LIGHT0,GL_POSITION,position);

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
//启用多边形反走样
glEnable(GL_POLYGON_SMOOTH);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE);

glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h)
glOrtho(-5.0,5.0,-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0);
else
glOrtho(-5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w,-5.0,5.0,-5.0,5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glLineWidth(5.0);
glColor4f(1.0,1.0,0.0,0.7);
glPushMatrix();
glRotatef(45.0,1.0,1.0,0.0);
auxSolidIcosahedron(2.0);
glPopMatrix();
glFlush();
}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////////////
比较采用反走样后的结果,可以明显看出多变形直线边界出锯齿得到了平滑。

3 雾化
最后来介绍雾化,雾化不但可以使景物更加真实,而且大大减少计算量,开过
F22的玩家不会忘记雾化的远近直接影响游戏的速度吧。一般的雾化模型是考虑
把物体实际颜色和雾化颜色向融合。具体雾化的浓淡有定义的数学模型来决定
包括线性变化、指数变化和指数平方变化等。定义雾化也很简单,只要遵循下面
步骤:
一 启用雾化 glEnable(GL_FOG);
二 控制雾化 glFog*()
void glFog{if}[v](GLenum,TYPE param);

当GLenum是GL_FOG MODE时,param可以是GL_EXP(指数)
GL_EXP2(指数平方)
GL_LINEAR(线性)

当GLenum是GL_FOG_DENSITY GL_FOG_START GL_FOG_END时,param分别指定不同
雾化数学模型下不同计算公式的参量,具体可以参阅连机手册。

当GLenum时GL_FOG_COLOR时,param是指向颜色向量的指针
三 必要时可以用glHint(GL_FOG_HINT,XX)指定雾化效果
下面给出例子:
///////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);

void myinit(void)
{

auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

GLfloat mat_ambient[]={0.0,0.1,0.8,1.0};
GLfloat mat_diffuse[]={0.0,0.3,0.6,1.0};
GLfloat mat_specular[]={1.0,0.0,1.0,1.0};
GLfloat mat_shininess[]={15.0};
GLfloat position[]={5.0,5.0,5.0,0.0};
GLfloat fogColor[4]={0.6,0.6,0.0,1.0};

glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
glLightfv(GL_LIGHT0,GL_POSITION,position);

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glFrontFace(GL_CW);
// glEnable(GL_POLYGON_SMOOTH);
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA,GL_ONE);

glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);

//启用雾化处理
glEnable(GL_FOG);
{
//采用线性变化的雾化效果
glFogi(GL_FOG_MODE,GL_LINEAR);
//指定雾化颜色(黄色)
glFogfv(GL_FOG_COLOR,fogColor);
//指定按线性变化时计算公式的参量
glFogf(GL_FOG_START,3.0);
glFogf(GL_FOG_END,15.0);
//规定雾化效果的质量
glHint(GL_FOG_HINT,GL_DONT_CARE);
}
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

if(w<=h*3)
glOrtho(-6.0,6.0,-2.0*(GLfloat)h*3/(GLfloat)w,
2.0*(GLfloat)h*3/(GLfloat)w,0.0,10.0);
else
glOrtho(-6.0*(GLfloat)h/(GLfloat)w,
6.0*(GLfloat)h/(GLfloat)w,-2.0,2.0,0.0,10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CALLBACK display(void)
{

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//在不同远近(Z方向)绘制同样大小、颜色的环,显示雾化的效果
glPushMatrix();
glTranslatef(-3.0,-1.5,-3.0);
auxSolidTorus(0.6,1.5);
glPopMatrix();

glPushMatrix();
glTranslatef(-0.5,-0.5,-6.0);
auxSolidTorus(0.6,1.5);
glPopMatrix();

glPushMatrix();
glTranslatef(2.0,0.5,-8.0);
auxSolidTorus(0.6,1.5);
glPopMatrix();

glFlush();

}
void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////////////



OPENGL的显示列表

(一)简介
所谓显示列表就是一组预选存储起来的留待以后调用的函数语句。调用此显示列表
时就按次序执行其中函数。以前所有的函数调用可以称之为立即方式(Immediate
mode),现在我们将介绍显示列表方式(Display list)。

显示列表可以优化程序运行性能。它被设计成命令的高速缓存,而不是动态的数据
库缓存。一旦建立列表,删除前,则不能被修改,否则会带来系统效率的降低。
显示列表主要目的就是提高运行效率。例如任何一个几何变换的函数处于显示列表
中时,系统只保留最后的变换矩阵,这样调用时,工作量比现由函数参数生成数学
模型,再矩阵变换效率显然提高。一般来讲显示列表食用于:
一 矩阵操作 将最终变换矩阵保存以提高效率
二 光栅位图和图象编译后的列表与高级的程序数据不同,将是匹配硬件实现的数

三 光、材质和光照模型 省却复杂的模型计算
四 纹理
五 多边形的图案填充模式

注意以下函数一般不用于显示列表,作为用于传递参数或者返回数值的函数,则列
表有可能在参数作用域外被调用,因此即使列表中有这些函数,实际系统也往往按
照立即方式完成,有时甚至会出错。
glDeleteLists() glIsEnable() glFeedbackBuffer() glIsList()
glFinish() glPixelStore() glGenLists() glRenderMode() glGet*()
glSelectBuffer()

(二)实现
创建列表(格式很类似glBegin()...glEnd())

void glNewList(GLuint list,GLenum mode);
说明列表的开始,其后的函数存入列表直至结束标志(见后)。
mode:GL_COMPILE 只编译保存
GL_COMILE_AND_EXECUTE 编译保存后,再立即执行一次

void glEndList(void)
列表结束

要调用时,只需使用
void glCallList(GLuint list);
就象调用子过程一样简单
/////////////////////////////////////////////
例如,你在某处定义:
{
GLuint listName=1;

glNewList(listName,GL_COMPILE);
glColor3f(1.0,0.0,0.0);
glBegin(GL_TRIANGLES);
...
glEnd();
glEndList();

}
再绘制函数中调用:
{
glCallList(listName);
}
即可。
////////////////////////////////////////////////
(三)管理显示列表
上面的例子中我们指定乐使用1显示列表,万一这个索引已经被占用乐呢?
因此需要一套管理列表的工具。

void GLuint glGenList(GLsize range);
系统生成range个相临的未被占用的可用列表空间,返回其索引
若不能正常分配空间,返回0

void GLboolean glIsList(GLuint list);
判断列表是否已经被占用

void GLDeleteLists(GLuint list,GLsizei range);
删除一组连续的显示列表,从list开始,连续删除range个。
说明:当以一个已经存在的索引创建列表时,以前的旧列表被删除。

一个安全的创建可以如下:
listIndex=glGenLists(1);
if(listIndex!=0)
{
glNewList(listIndex,GL_COMPILE);
...
glEndList();
}

(四)多级显示列表
显示列表中可以调用另一个显示列表
例如:
已经创建显示列表1 2 3
现在创建显示列表4
glNewList(4,GL_COMPILE);
glBegin(GL_OLYGON);
glCallList(1);
glCallList(2);
glCallList(3);
glEnd();
glEndList();

由于这部分内容没有任何视觉上的新享受,所以就不给出新例子乐。
大家可以试试自己修改前面的程序,至少我没看出太大差别,xixi。



OPENGL帧缓存和动画
作为最后一关,我们将架设自己即时光影的动画,让没有VOODOO的玩家看看OPENGL
这震撼(至少我是这么认为的吧)的效果,完成所有这将近20次灌水最终目标。

我们前面(好象是第三还是第四次)讲过如何用几何变换实现动画。那时的效果
现在看肯定不尽人意,因为频繁的闪烁不是我们需要的。因为那时(到这之前也
是)采用的是单缓存模式。对正在显示的部分边计算边修改必然造成速度瓶颈,
出现闪烁。一般正规的动画制作在实现上都是通过双缓存实现的(硬件也好,软
件也好)大家可以参考《家用电脑与游戏机》的98-2中的一篇文章。当前台显示
缓存用于显示时,后台缓存已经进行计算,计算完毕把所有内容通过缓存拷贝一
次性完成,防止闪烁的出现。

一 OPENGL帧缓存的实现

1 颜色缓存(Color Buffer)其中内容可以是颜色索引或者RGBA数据,如果用的
OPENGL系统支持立体图,则有左右两个缓存。

2 深度缓存(Depth Buffer) 就是Z-BUFFER,用于保存象素Z方向的数值,深度
大的被深度小的代替,用以实现消隐

3 模板缓存(Stencil Buffer)用以保持屏幕上某些位置图形不变,而其他部分
重绘。例如大家熟悉的开飞机和赛车的游戏的驾驶舱视角,只有挡风外面的景物
变化,舱内仪表等等并不变化。

4 累计缓存(Accumulation Buffer) 只保存RGBA数据,用于合成图象,例如有某
缓存中的位图调入这里合成一幅新图。

二 帧缓存的清除
对高分辨率模式清除缓存是工作量巨大的任务,OPENGL一般先寻求硬件同时完成,
否则软件依次解决。我们前面每次必用的glClearColor()大家已经不陌生吧。

首先设置清除值
void glClearColor(GLclampf red,GLclampf green,GLclampf blue,GLclampf alpha);
void glClearIndex(GLfloat index);
void glClearDepth(GLclampd depth);
void glClearStencil(GLint s);
void glClerAccum(GLfloat red,GLfloat green,GLfloat blue,GLfloat alpha);

然后进行清除
void glClear(GLbitfield mask);
mask: GL_COLOR_BUFFER_BIT|
GL_DEPTH_BUFFER_BIT|
GL_STENCIL_BUFFER_BIT|
GL_ACCUM_BUFFER_BIT

三 双缓存动画
你可以把所有的变换工作看成后台缓存的计算,然后把所有结果拷贝到前台即可。
因此我们只需两个新内容:

首先初始化时调用
auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA);
用AUX_DOUBLE代替AUX_SINGLE设置成双缓存模式

然后在绘制完毕(glFlush();后)调用
auxSwapBuffers();
进行缓存拷贝。Easy like sunday morning!!
当然不同系统下,这个函数也许不同(毕竟是辅助库函数么),例如X-WINDOWS
下可以使用glxSwapBuffers(),意思完全一样。

先说说下面这个例子的功能:
有一个兰色的环作为主体,有一个黄色高亮的球表示光源的位置。
小球不断从屏幕左方运动到右方,可以看出环状物上光影的变化。
操作:
鼠标左键/右键:开始/停止光源的运动
键盘 上/下/左/右:控制环状物的 前进/后退/旋转

//////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"

void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
void CALLBACK stepDisplay(void);
void CALLBACK startIdleFunc(AUX_EVENTREC *event);
void CALLBACK stopIdleFunc(AUX_EVENTREC *event);

//step是表示环状物旋转的参数;z是控制其前后坐标的参数
static GLfloat step=0.0,z=0.0;
//position是控制光源的位置的参数
static GLfloat position[]={-20.0,0.0,-5.0,1.0};

void myinit(void)
{
//初始化注意是双缓存模式
auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA);

auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);

glFrontFace(GL_CW);

glEnable(GL_LIGHTING);
glFrontFace(GL_CW);
// glEnable(GL_POLYGON_SMOOTH);
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA,GL_ONE);

glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
// glShadeModel(GL_FLAT);
}

void CALLBACK reshape(GLsizei w,GLsizei h)
{

glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
/*if(w<=h*3)
glOrtho(-2.0,2.0,-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-2.0,2.0,-10.0,10.0);
*/

//启用立体的视景,具有近大远小的效果
glFrustum(-6.0,6.0,-6.0,6.0,3.0,20.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

}

void CALLBACK display(void)
{

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

//首先在当前位置设置光源
glPushMatrix();
GLfloat light_ambient[]={0.3,0.5,0.3};
GLfloat light_diffuse[]={1.0,1.0,1.0};
GLfloat light_specular[]={0.8,0.8,0.0};
glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
glLightfv(GL_LIGHT0,GL_POSITION,position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
//在光源旁边绘制黄色高亮的小球,标志光源位置
//小球和光源位置由position[]决定
glTranslatef(position[0],position[1],position[2]-1.0);
GLfloat mat_ambient[]={1.0,0.0,0.0,1.0};
GLfloat mat_diffuse[]={1.0,1.0,0.0,1.0};
GLfloat mat_specular[]={1.0,1.0,0.0,1.0};
GLfloat mat_shininess[]={50.0};
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
auxSolidSphere(0.5);
glPopMatrix();

//在当前位置绘制兰色环状物,其位置由step z共同决定
glPushMatrix();
glTranslatef(0.0,0.0,-8.0+z);
glRotatef(135.0+step,0.0,1.0,0.0);
GLfloat mat2_ambient[]={0.0,0.0,1.0,1.0};
GLfloat mat2_diffuse[]={0.2,0.0,0.99,1.0};
GLfloat mat2_specular[]={1.0,1.0,0.0,1.0};
GLfloat mat2_shininess[]={50.0};
glMaterialfv(GL_FRONT,GL_AMBIENT,mat2_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat2_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat2_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat2_shininess);
auxSolidTorus(2.0,3.5);
glPopMatrix();

glFlush();
//绘制完毕,缓存交换
auxSwapBuffers();

}
void CALLBACK Up(void)
{
//键盘“上”的处理
z=z+0.05;
}
void CALLBACK Down(void)
{
//键盘“下”的处理
z=z-0.05;
}
void CALLBACK Left(void)
{
//键盘“左”的处理
step=step+2.0;
}
void CALLBACK Right(void)
{
//键盘“右”的处理
step=step-2.0;
}

void CALLBACK stepDisplay(void)
{
//系统闲时的调用过程
position[0]=position[0]+0.5;
if(position[0]>20.0) position[0]=-20.0;
display();
}

void CALLBACK startFunc(AUX_EVENTREC *event)
{
//鼠标左键的处理
auxIdleFunc(stepDisplay);
}

void CALLBACK stopIdleFunc(AUX_EVENTREC *event)
{
//鼠标右键的处理
auxIdleFunc(0);
}

void main(void)
{
myinit();

auxReshapeFunc(reshape);
auxIdleFunc(stepDisplay);
auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEDOWN,startFunc);
auxMouseFunc(AUX_RIGHTBUTTON,AUX_MOUSEDOWN,stopIdleFunc);
auxKeyFunc(AUX_UP,Up);
auxKeyFunc(AUX_DOWN,Down);
auxKeyFunc(AUX_LEFT,Left);
auxKeyFunc(AUX_RIGHT,Right);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////////////
其中用到大量CALLBACK函数,分别处理不同消息比较前面演示的动画效果,闪烁的现象明显改善乐(你可以把这个程序两个关于双缓存的地方改成单缓存:设置AUX_SINGLE和去掉auxSwapBuffers(),看看闪烁的多么厉害),至于本身的绘图可能的拖尾现象,只能怪自己机器不好乐。
写到这里,终于打穿乐OPENGL!!!实现当初提出的“长长见识”的设想。
今后有什么新内容我还会尽量补充,大家也可以自由补充。