现在的位置: 首页 > 综合 > 正文

OpenGL基础篇

2013年07月26日 ⁄ 综合 ⁄ 共 39064字 ⁄ 字号 评论关闭

转载自http://www.gameres.com/
 
 

本人水平有限,如有问题请以文章形式提出,大家可以讨论吗...

[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 <windows.h>

// 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 <GL/gl.h>
#include <GL/glaux.h>
#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),参数0<x<1,这里就是红色
glRectf(-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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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 <GL/gl.h>
#include <GL/glaux.h>
#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<TEXTUREWIDTH;i++)
{
for(j=0;j<TEXTUREHEIGHT;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 <GL/gl.h>
#include <GL/glaux.h>
#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<TEXTUREWIDTH;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)关闭,这

抱歉!评论已关闭.