普通模式 顶点数组模式 显示列表模式
顶点数组
观察bunny的绘制,发现它是由法线向量和顶点信息构成的,这两个信息分别由normals数组和vertexs数组维护,它们存储了bunny的具体信息。
此外,还有一个数组face_indices,它是作为索引数组存在的,里面存储的是下标信息,用于指向normals数组和vertex数组的信息。
在顶点数组中,我们只需告诉OpenGL这三个数组分别是什么,就可以完成快速绘制,减少函数调用消耗,让数据一次性传输,而不是一个个传输,而且数据可以是非静态的。
使用顶点数组的时候,我们需要先开启特定的顶点数组,然后再指定具体的顶点坐标与下标索引。
开启顶点数组:
glEnableClientState(GL_VERTEX_ARRAY);//启用顶点坐标数组
glEnableClientState(GL_NORMAL_ARRAY);//启用法线向量数组
指定顶点数组:
glVertexPointer(3,GL_FLOAT, 0, vertices);//指定顶点
glNormalPointer(GL_FLOAT, 0, normals);//指定法向量
指定下标数组:
glDrawElements(shape_type,count , array_type, array);
显示列表
显示列表的使用比较简单,生成显示列表并分配后,把需要加载到显示列表的物体代码写上即可。
lid = glGenLists(1);
glNewList(lid, GL_COMPILE);
……
glEndList();
拾取
拾取也就是判断是否点中某个物体的方法。它和鼠标的操作相关,所以它是基于鼠标回调事件的,在此之前,我们需要先指定我们需要拾取哪些物体,以及它们的编号。
指定拾取物体在绘制物体时完成,首先,需要进行初始化:
glInitNames(); // 初始化名字
具体指定的代码如下:
glPushName(Name1);
draw1();
glPopName();
glPushName(Name2);
draw2();
glPopName();
也可以用glLoadName(Name),它等价于glPopName(),glLoadName(Name)。
glPushName和glPushMatrix一样,是可以嵌套调用的,在这次实验我们没有使用这个功能。
在渲染模式下,以上函数调用都会被忽略,所以可以实现两种模式共用一个函数而不发生冲突。
然后,我们关注回调事件,当我们鼠标左键点击了屏幕时,触发鼠标回调事件,开始我们需要指定选中的结果存储到哪个数组,并且需要给出数组大小:
glSelectBuffer(BUFSIZE, selectBuf);
之后,开启选择模式
glRenderMode(GL_SELECT);
在选择模式下,我们先把矩阵模式切换为投影,然后指定投影方式,设置选择矩阵,这时候再进行重绘,重绘过程中又要把矩阵模式切换为模型,之后再切换为投影。这时,OpenGL会返回选中物体的下标以及深度信息等。
gluPickMatrix((GLdouble)x,(GLdouble)(viewport[3] -y), 1, 1, viewport);//设置选择矩阵
由于为3D图形,所以一次点击可能点中多个物体,我们需要按照深度排序,然后得到深度最小的物体作为当前选中物体,对于选中物体,把物体的颜色数组进行修改,这时就完成了拾取物体并修改颜色的功能。
对于颜色,我们采取了一开始产生随机数生成。
实验数据记录和处理
模式 | FPS |
Naive | 45 |
Vertex array | 190 |
Display list | 65 |
可以看到,显示列表略胜于普通模式,顶点数组明显优于前两者。
// glutEx1.cpp : 定义控制台应用程序的入口点。
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "glut.h"
#define BUFSIZE 100
float whRatio;
float fTranslate;
float fRotate = 156.5f;
float fScale = 1.0f; // set inital scale value to 1.0f
bool bPersp = true;
bool bAnim = false;
bool bWire = false;
bool isTableWhite = true;
bool isBunnyWhite[19];
int wHeight = 0;
int wWidth = 0;
GLfloat bunny_diffuse[19][3];
GLfloat table_diffuse[3];
GLint dl = 0;
GLenum mode = GL_RENDER;
GLuint selectBuf[BUFSIZE];
void Draw_Leg();
int drawMode = 0;
extern void drawNaive();
extern void drawVA();
extern GLint Gen3DObjectList();
void drawDL() {
glCallList(dl);
}
void drawBunny()
{
glRotatef(90, 1, 0, 0);
glScalef(3, 3, 3);
if (drawMode == 0)
drawNaive();
else if (drawMode == 1)
drawVA();
else
drawDL();
}
void Draw_Desk();
void setColor(int index)
{
if (isBunnyWhite[index] == false) {
glMaterialfv(GL_FRONT, GL_DIFFUSE, bunny_diffuse[index]);//设置多边形正面漫反射属性
}
else {
GLfloat mat_diffuse[] = { 1.0f,1.0f,1.0f };
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);//设置多边形正面漫反射属性
}
}
void Draw_Triangle() // This function draws a triangle with RGB colors
{
glInitNames();//初始化名字
glPushName(0);//初始化名字栈
glLoadName(1);//加载名字
setColor(1);//设置颜色
glPushMatrix();
glTranslatef(-1, -1, 5.5);
drawBunny();
glPopMatrix();
glLoadName(2);//加载名字
setColor(2);//设置颜色
glPushMatrix();
glTranslatef(0, -1, 5.5);
drawBunny();
glPopMatrix();
glLoadName(3);//加载名字
setColor(3);//设置颜色
glPushMatrix();
glTranslatef(1, -1, 5.5);
drawBunny();
glPopMatrix();
glLoadName(4);//加载名字
setColor(4);//设置颜色
glPushMatrix();
glTranslatef(-1, 1, 5.5);
drawBunny();
glPopMatrix();
glLoadName(5);//加载名字
setColor(5);//设置颜色
glPushMatrix();
glTranslatef(0, 1, 5.5);
drawBunny();
glPopMatrix();
glLoadName(6);//加载名字
setColor(6);//设置颜色
glPushMatrix();
glTranslatef(1, 1, 5.5);
drawBunny();
glPopMatrix();
glLoadName(7);//加载名字
setColor(7);//设置颜色
glPushMatrix();
glTranslatef(-1, 0, 5.5);
drawBunny();
glPopMatrix();
glLoadName(8);//加载名字
setColor(8);//设置颜色
glPushMatrix();
glTranslatef(0, 0, 5.5);
drawBunny();
glPopMatrix();
glLoadName(9);//加载名字
setColor(9);//设置颜色
glPushMatrix();
glTranslatef(1, 0, 5.5);
drawBunny();
glPopMatrix();
glLoadName(10);//加载名字
setColor(10);//设置颜色
glPushMatrix();
glTranslatef(-1, -1, 7.5);
drawBunny();
glPopMatrix();
glLoadName(11);//加载名字
setColor(11);//设置颜色
glPushMatrix();
glTranslatef(0, -1, 7.5);
drawBunny();
glPopMatrix();
glLoadName(12);//加载名字
setColor(12);//设置颜色
glPushMatrix();
glTranslatef(1, -1, 7.5);
drawBunny();
glPopMatrix();
glLoadName(13);//加载名字
setColor(13);//设置颜色
glPushMatrix();
glTranslatef(-1, 1, 7.5);
drawBunny();
glPopMatrix();
glLoadName(14);//加载名字
setColor(14);//设置颜色
glPushMatrix();
glTranslatef(0, 1, 7.5);
drawBunny();
glPopMatrix();
glLoadName(15);//加载名字
setColor(15);//设置颜色
glPushMatrix();
glTranslatef(1, 1, 7.5);
drawBunny();
glPopMatrix();
glLoadName(16);//加载名字
setColor(16);//设置颜色
glPushMatrix();
glTranslatef(-1, 0, 7.5);
drawBunny();
glPopMatrix();
glLoadName(17);//加载名字
setColor(17);//设置颜色
glPushMatrix();
glTranslatef(0, 0, 7.5);
drawBunny();
glPopMatrix();
glLoadName(18);//加载名字
setColor(18);//设置颜色
glPushMatrix();
glTranslatef(1, 0, 7.5);
drawBunny();
glPopMatrix();
glLoadName(19);//加载名字
Draw_Desk();
glPopName();
}
void Draw_Desk()
{
if (isTableWhite == true) {
GLfloat mat_diffuse[] = { 1.0f,1.0f,1.0f };
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);//设置多边形正面漫反射属性
}
else {
glMaterialfv(GL_FRONT, GL_DIFFUSE, table_diffuse);//设置多边形正面漫反射属性
}
glPushMatrix();
glTranslatef(0, 0, 3.5);
glScalef(5, 4, 1);
glutSolidCube(1.0);
glPopMatrix();
glPushMatrix();
glTranslatef(1.5, 1, 1.5);
Draw_Leg();
glPopMatrix();
glPushMatrix();
glTranslatef(-1.5, 1, 1.5);
Draw_Leg();
glPopMatrix();
glPushMatrix();
glTranslatef(1.5, -1, 1.5);
Draw_Leg();
glPopMatrix();
glPushMatrix();
glTranslatef(-1.5, -1, 1.5);
Draw_Leg();
glPopMatrix();
}
void Draw_Leg()
{
glScalef(1, 1, 3);
glutSolidCube(1.0);
}
void updateView(int width, int height)
{
glViewport(0, 0, width, height);//设置视窗大小
glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影
glLoadIdentity(); //初始化矩阵为单位矩阵
whRatio = (GLfloat)width / (GLfloat)height; //设置显示比例
if (bPersp) {
gluPerspective(45.0f, whRatio, 0.1f, 100.0f); //透视投影
//glFrustum(-3, 3, -3, 3, 3,100);
}
else {
glOrtho(-3, 3, -3, 3, -100, 100); //正投影
}
glMatrixMode(GL_MODELVIEW); //设置矩阵模式为模型
}
void reshape(int width, int height)
{
if (height == 0) //如果高度为0
{
height = 1; //让高度为1(避免出现分母为0的现象)
}
wHeight = height;
wWidth = width;
updateView(wHeight, wWidth); //更新视角
}
void idle()
{
glutPostRedisplay();
}
float center[] = {0, -0.8, -6};
float eye[] = {0, 1.2, 2};
void redraw();
//鼠标点击事件
void mouse(int mouse, int status, int x, int y)
{
//左键点击 鼠标按下状态时
if (mouse == GLUT_LEFT_BUTTON && status == GLUT_DOWN) {
//存储视角
GLint viewport[4];
glSelectBuffer(BUFSIZE, selectBuf);//选择返回数据的数组
glGetIntegerv(GL_VIEWPORT, viewport);//得到视角
glRenderMode(GL_SELECT);//进入选择模式
glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影
glPushMatrix();//矩阵入栈
glLoadIdentity();//初始化为单位矩阵
gluPickMatrix((GLdouble)x,
(GLdouble)(viewport[3] - y), 1, 1, viewport);//设置选择矩阵
if (bPersp) {
gluPerspective(45.0f, whRatio, 0.1f, 100.0f); //透视投影
//glFrustum(-3, 3, -3, 3, 3,100);
}
else {
glOrtho(-3, 3, -3, 3, -100, 100); //正投影
}
redraw(); //重绘
int hits = glRenderMode(GL_RENDER); //进入渲染模式,得到选中物体个数
if (hits >= 1) //如果选中物体个数大于1
{
int choose = selectBuf[3]; //得到选中物体名字
int depth = selectBuf[1]; //得到选中物体深度
printf("2 = %d\n", selectBuf[2]);
printf("4 = %d\n", selectBuf[4]);
for (int i = 0; i < hits; i++)
{
if (selectBuf[i * 4 + 1] < (GLuint)depth)//获取深度最小的物体(selectBuff是按照ID从小到大排列的)
{
choose = selectBuf[i * 4 + 3];
depth = selectBuf[i * 4 + 1];
}
}
//更改选中物体的颜色
if (choose >= 1 && choose <= 18) {
isBunnyWhite[choose] = !isBunnyWhite[choose];
}
else if (choose == 19) {
isTableWhite = !isTableWhite;
}
}
//设置矩阵模式为投影
glMatrixMode(GL_PROJECTION);
glPopMatrix();//抛出矩阵
//设置矩阵模式为模型
glMatrixMode(GL_MODELVIEW);
}
}
void key(unsigned char k, int x, int y)
{
switch(k)
{
case 27:
case 'q': {exit(0); break; } //退出
case 'p': {bPersp = !bPersp; break; } //切换投影模式
case ' ': {bAnim = !bAnim; break;} //旋转
case 'o': {bWire = !bWire; break;} //切换线、面显示
case '0': {drawMode++; drawMode %= 3; break;}//切换渲染方式
case 'a': { //左移
eye[0] += 0.2f;
center[0] += 0.2f;
break;
}
case 'd': { //右移
eye[0] -= 0.2f;
center[0] -= 0.2f;
break;
}
case 'w': { //上移
eye[1] -= 0.2f;
center[1] -= 0.2f;
break;
}
case 's': { //下移
eye[1] += 0.2f;
center[1] += 0.2f;
break;
}
case 'z': { //前移
eye[2] -= 0.2f;
center[2] -= 0.2f;
break;
}
case 'c': { //后移
eye[2] += 0.2f;
center[2] += 0.2f;
break;
}
}
updateView(wHeight, wWidth); //更新视角
}
void getFPS()
{
static int frame = 0, time, timebase = 0;
static char buffer[256];//字符串缓冲区
char mode[64];//模式
if (drawMode == 0) //普通
strcpy(mode, "naive");
else if (drawMode == 1) //顶点数组
strcpy(mode, "vertex array");
else //显示列表
strcpy(mode, "display list");
frame++;
time=glutGet(GLUT_ELAPSED_TIME);
//返回两次调用glutGet(GLUT_ELAPSED_TIME)的时间间隔,单位为毫秒
if (time - timebase > 1000) {//时间间隔差大于1000ms时
sprintf(buffer,"FPS:%4.2f %s",
frame*1000.0/(time-timebase), mode);//写入buffer中
timebase = time; //上一次的时间间隔
frame = 0;
}
glutSetWindowTitle(buffer);//设置窗口标题
}
void redraw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色和深度缓存
glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); //初始化矩阵为单位矩阵
gluLookAt(eye[0], eye[1], eye[2],
center[0], center[1], center[2],
0, 1, 0); // 场景(0,0,0)的视点中心 (0,5,50),Y轴向上
if (bWire) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//设置多边形绘制模式:正反面,线型
}
else {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//设置多边形绘制模式:正反面,填充
}
glEnable(GL_DEPTH_TEST);//开启深度测试
glEnable(GL_LIGHTING); //开启光照模式
GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };//白色
GLfloat light_pos[] = {5,5,5,1};//光源位置
glLightfv(GL_LIGHT0, GL_POSITION, light_pos);//光源位置
glLightfv(GL_LIGHT0, GL_AMBIENT, white);//环境光白色
glEnable(GL_LIGHT0);//开启第0号光源
glRotatef(fRotate, 0, 1.0f, 0); //旋转
glRotatef(-90, 1, 0, 0);
glScalef(0.2, 0.2, 0.2);//缩放
Draw_Triangle();//绘制场景
if (bAnim) fRotate += 0.5f;//旋转因子改变
getFPS();//得到fps
glutSwapBuffers();//交换缓冲区
}
void generateColor()
{
for (int i = 1; i <= 18; i++) {
//随机生成兔子颜色
GLfloat x = (float)(rand() % 1001) * 0.001f;
GLfloat y = (float)(rand() % 1001) * 0.001f;
GLfloat z = (float)(rand() % 1001) * 0.001f;
bunny_diffuse[i][0] = x;
bunny_diffuse[i][1] = y;
bunny_diffuse[i][2] = z;
}
//随机生成桌子颜色
GLfloat x = (float)(rand() % 1001) * 0.001f;
GLfloat y = (float)(rand() % 1001) * 0.001f;
GLfloat z = (float)(rand() % 1001) * 0.001f;
table_diffuse[0] = x;
table_diffuse[1] = y;
table_diffuse[2] = z;
}
int main(int argc, char *argv[])
{
srand(unsigned(time(nullptr)));
glutInit(&argc, argv);//对glut的初始化
glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
//初始化显示模式:RGB颜色模型,深度测试,双缓冲
glutInitWindowSize(480, 480);//设置窗口大小
int windowHandle = glutCreateWindow("Simple GLUT App");//设置窗口标题
glutDisplayFunc(redraw); //注册绘制回调函数
glutReshapeFunc(reshape); //注册重绘回调函数
glutKeyboardFunc(key); //注册按键回调函数
glutIdleFunc(idle);//注册全局回调函数:空闲时调用
glutMouseFunc(mouse); //注册鼠标回调函数
memset(isBunnyWhite, true, sizeof(isBunnyWhite));//初始化兔子颜色数组
generateColor();//随机生成兔子和桌子的颜色
dl = Gen3DObjectList();//生成显示列表
glutMainLoop(); // glut事件处理循环
return 0;
}
bunny.cpp
#ifdef _WIN32
#include <windows.h>
#endif
#include <GL/gl.h>
#include <GL/glu.h>
#include <stdio.h>
#pragma warning(disable: 4305)
// 8146 Verticies
// 8146 Normals
// 16301 Triangles
//数组里的数据非常害怕,它们吓得躲了起来
short face_indicies[16301][3];
GLfloat normals [8146][3];
GLfloat vertices [8146][3];
#define STR2(x) #x
#define STR(x) STR2(x)
#define MSG(desc) message(__FILE__ "(" STR(__LINE__) ") : ------- " desc " -------")
void drawVA()
{
glEnableClientState(GL_VERTEX_ARRAY);//启用顶点坐标数组
glEnableClientState(GL_NORMAL_ARRAY);//启用法线向量数组
glVertexPointer(3, GL_FLOAT, 0, vertices);
glNormalPointer(GL_FLOAT, 0, normals);
glDrawElements(GL_TRIANGLES, sizeof(face_indicies) / sizeof(face_indicies[0])*3, GL_UNSIGNED_SHORT, face_indicies);
}
void drawNaive()
{
glBegin (GL_TRIANGLES);
for(int i=0;i<(sizeof(face_indicies)/sizeof(face_indicies[0]));i++)
{
for(int j=0;j<3;j++)
{
int idx=face_indicies[i][j];
glNormal3fv(&normals[idx][0]);
glVertex3fv(&vertices[idx][0]);
}
}
glEnd ();
}
GLint Gen3DObjectList()
{
GLint lid = glGenLists(1); //生成一个空的显示列表
glNewList(lid, GL_COMPILE); // 用于创建和替换一个显示列表函数原型
// 指定显示列表的名称,编译模式:只编译
glBegin(GL_TRIANGLES);
for (int i = 0; i<(sizeof(face_indicies) / sizeof(face_indicies[0])); i++)
{
for (int j = 0; j<3; j++)
{
int idx = face_indicies[i][j];
glNormal3fv(&normals[idx][0]);
glVertex3fv(&vertices[idx][0]);
}
}
glEnd();
glEndList();
return lid; //返回显示列表编号
};