计算机系统应用教程网站

网站首页 > 技术文章 正文

OpenGL绘制城堡报告加源码计算机图形学 城堡

btikc 2024-10-31 12:29:19 技术文章 6 ℃ 0 评论

目 录

实验一:分形图形绘制11

一、实验目的11

二、实验内容11

三、实验心得1111

实验二:三维场景绘制1212

一、实验目的1212

二、实验内容1212

三、实验心得1919

实验一:分形图形绘制

一、实验目的

  1. 理解OpenGL 中glut 程序框架;
  2. 掌握二维基本图形绘制算法;
  3. 利用二维基本图形绘制算法,扩展对其他复杂图形的绘制理解。

二、实验内容

1、实验算法

  1. 看懂“lineDDA模板.cpp”文件后,修改程序,将直线生成算法改为中点Bresenham算法,算法思想如下:
  2. 绘制分形图形。

绘制分形三角形算法思想:求出三角形ABC三条边AB,BC,AC的中点坐标D,E,F,绘制三角形DEF,再对三角形ADF,BDE,CEF重复进行上述操作即可得到分形三角形。

绘制Koch雪花的算法思想:计算得到一条边经过一次分形后的五个点的坐标,然后对分得的四条边反复进行变换,对三角形的三边进行上述变换即可得到Koch雪花。

绘制分形地毯图形思想:计算得到中心正方形的周围8个小正方形的左上顶点坐标与边长,绘制8个小正方形,然后对8个小正方形分别进行上述操作即可得到分形地毯。

2、源程序

源程序如下:

#include <stdio.h>

#include <time.h>

#include <math.h>

#include <GL/glut.h>

#define ROUND(a) ((int )(a+0.5)) // 求某个数的四舍五入值

#define PI 3.141592654 // pi的预定义

int xf = 100, yf = 200; // 定义测试数据坐标x0,y0

int xl = 500, yl = 200; // 定义测试数据坐标x1,y1

int xt = xf + (xl - xf) / 2.0; // 画等边三角形时的第三个坐标x0

int yt = yf + (xl - xf) / 2.0 * tan(3.1415926 / 3.0);

// 画等边三角形时的第三个坐标x0

// 窗口初始化函数,初始化背景色,投影矩阵模式和投影方式

void init(void)

{

glClearColor(0.396, 0.655, 0.890, 0.0); //指定窗口的背景色为蓝色

glMatrixMode(GL_PROJECTION); //对投影矩阵进行操作

gluOrtho2D(0.0, 600.0, 0.0, 600.0); //使用正投影

}

//绘制直线的函数

void lineDDA(GLint xa, GLint ya, GLint xb, GLint yb)

{

GLint dx = xb - xa, dy = yb - ya; //计算x,y方向的跨距

int steps, k; //定义绘制直线像素点的步数

float xIcre, yIcre, x = xa, y = ya; //定义步长的增量


//取X,Y方向跨距较大的值为步数

if (abs(dx) > abs(dy))

steps = abs(dx);

else

steps = abs(dy);

//根据步数来求步长增量

xIcre = dx / (float)steps;

yIcre = dy / (float)steps;

//从起点开始绘制像素点

for (k = 0;k <= steps; k++)

{

glBegin(GL_POINTS);

glVertex2f(x, y);

glEnd();

x += xIcre;

y += yIcre;

}

}

// 中点Bresenham算法绘制直线

void MidBresenhamLine(GLint xa, GLint ya, GLint xb, GLint yb)

{

// 斜率k的四个状态

// K01表示0<k<=1;

// KG1表示k>1;

// K_10表示-1<=k<0;

// KL_1表示k<-1

const int K01 = 0, KG1 = 1, K_10 = 2, KL_1 = 3;

int flag; // 标识斜率k的状态

GLint dx, dy, d, upIncre, downIncre, x, y;

// 使b的横坐标大于a的横坐标

if (xa > xb)

{

x = xb; xb = xa; xa = x;

y = yb; yb = ya; ya = y;

}

if (yb >= ya && yb - ya < xb - xa)

flag = K01; // K01表示0<k<=1;

else if (yb - ya > xb - xa)

flag = KG1; // KG1表示k>1;

else if (yb <= ya && yb - ya > xa - xb)

flag = K_10; // K_10表示-1<=k<0;

else

flag = KL_1; // KL_1表示k<-1

x = xa;

y = ya;

dx = xb - xa; // 计算增量dx

dy = yb - ya; // 计算增量dy

// 当0<k<=1时

if (flag == K01)

{

d = dx - 2 * dy; // 计算d初值

upIncre = 2 * dx - 2 * dy; // 计算步长增量

downIncre = -2 * dy;

}

// 当k>1时

if (flag == KG1)

{

d = 2 * dx - dy; // 计算d初值

upIncre = 2 * dx; // 计算步长增量

downIncre = 2 * dx - 2 * dy;

}

// 当-1<=k<0时

if (flag == K_10)

{

d = -dx - 2 * dy; // 计算d初值

upIncre = -2 * dy; // 计算步长增量

downIncre = -2 * dx - 2 * dy;

}

// 当k<-1时

if (flag == KL_1)

{

d = -2 * dx - dy; // 计算d初值

upIncre = -2 * dx - 2 * dy; // 计算步长增量

downIncre = -2 * dx;

}

// 开始绘制直线

glBegin(GL_POINTS);

// 斜率为无穷大,即为一条竖直线时单独考虑

if (dx == 0)

{

// 使B的纵坐标更大

if (ya > yb)

{

y = yb; yb = ya; ya = y;

}

y = ya;

// 从A点向上绘制直线

while (y < yb)

{

glVertex2i(x, y);

y++;

}

}

else

{

// 横向扫描,当扫描到B点横坐标时退出

while (x <= xb)

{

glVertex2i(x, y);

// 如果直线斜率满足(0,1]或[-1,0)时,X方向为最大位移方向且X增加

if (flag == K01 || flag == K_10)

x++;

// 如果直线斜率满足(1, ∞)时,Y方向为最大位移方向且Y增加

if (flag == KG1)

y++;

// 如果直线斜率满足(-∞, -1)时,Y方向为最大位移方向且Y递减

if (flag == KL_1)

y--;

// 当判据d<0时进入

if (d < 0)

{

// 如果直线斜率满足(0,1]时,y增加

if (flag == K01)

y++;

// 如果直线斜率满足(-∞, -1)时,x增加

if (flag == KL_1)

x++;

// 更新判据d

d += upIncre;

}

// d>0时

else

{

// 如果直线斜率满足[-1,0)时,y递减

if (flag == K_10)

y--;

// 如果直线斜率满足(1, ∞)时,x增加

if (flag == KG1)

x++;

// 更新判据d

d += downIncre;

}

}

}

glEnd(); // 结束绘制

}

// 递归绘制Koch分形图形的一条边,n为递归次数,inside为Koch图形的朝向

// inside为1表示朝上或朝内,为0表示朝下或朝外

void Koch(float x0, float y0, float x1, float y1, int n, int inside)

{

// n>0时继续递归

if (n > 0)

{

// A,B,C,D,E点为一条线上一次分形后的五个顶点

float xa, ya, xb, yb, xc, yc, xd, yd, xe, ye;

xa = x0; // A点的横坐标与X0相等

ya = y0; // A点的纵坐标与Y0相等

xb = x0 + (x1 - x0) / 3.0; // B点为靠近(X0, Y0)的三等分点

yb = y0 + (y1 - y0) / 3.0;

// 如果朝向为上或内,C点的坐标值计算如下

if (inside)

{

xc = (x1 + x0) / 2.0 + (y0 - y1) * sqrt(3.0) / 6.0;

yc = (y1 + y0) / 2.0 + (x1 - x0) * sqrt(3.0) / 6.0;

}

// 如果朝向为下或外,C点的坐标值计算如下

else

{

xc = (x1 + x0) / 2.0 - (y0 - y1) * sqrt(3.0) / 6.0;

yc = (y1 + y0) / 2.0 - (x1 - x0) * sqrt(3.0) / 6.0;

}

// D点为靠近(X1, Y1)的三等分点

xd = x0 + 2 * (x1 - x0) / 3.0;

yd = y0 + 2 * (y1 - y0) / 3.0;

xe = x1; // E点的横坐标等于X1

ye = y1; // E点的纵坐标等于Y1

Koch(xa, ya, xb, yb, n - 1, inside); // 对边AB进行递归

Koch(xb, yb, xc, yc, n - 1, inside); // 对边BC进行递归

Koch(xc, yc, xd, yd, n - 1, inside); // 对边CD进行递归

Koch(xd, yd, xe, ye, n - 1, inside); // 对边DE进行递归

}

// n=0时递归结束,绘制直线(x0, y0)到(x1, y1)

else

MidBresenhamLine(x0, y0, x1, y1);

}

// 递归绘制分形三角形,n为递归次数

void Triangle(float x0, float y0, float x1, float y1, float x2, float y2, int n)

{

// MID0为边(x1, y1)与(x2, y2)中点

// MID1为边(x0, y0)与(x2, y2)中点

// MID2为边(x0, y0)与(x1, y1)中点

float midx0, midy0, midx1, midy1, midx2, midy2;

// n>0时进入递归

if (n > 0)

{

// 计算MID0的坐标

midx0 = (x1 + x2) / 2.0;

midy0 = (y1 + y2) / 2.0;

// 计算MID1的坐标

midx1 = (x0 + x2) / 2.0;

midy1 = (y0 + y2) / 2.0;

// 计算MID2的坐标

midx2 = (x0 + x1) / 2.0;

midy2 = (y0 + y1) / 2.0;

// 对(x0, y0),MID1,MID2组成的三角形递归

Triangle(x0, y0, midx1, midy1, midx2, midy2, n - 1);

// 对MID0,(x1, y1),MID2组成的三角形递归

Triangle(midx0, midy0, x1, y1, midx2, midy2, n - 1);

// 对MID0,MID1,(x2, y2)组成的三角形递归

Triangle(midx0, midy0, midx1, midy1, x2, y2, n - 1);

}

// n=0时开始绘制三角型的三条边

else

{

MidBresenhamLine(x0, y0, x1, y1);

MidBresenhamLine(x1, y1, x2, y2);

MidBresenhamLine(x0, y0, x2, y2);

}

}

// 绘制内部填充的正方形,(x0, y0)为正方形左上方顶点,side为正方形边长

void DrawSquare(double x0, double y0, double side)

{

glBegin(GL_LINES);

// 从y=y0扫描到y=y0-side,绘制从(x0, y)到(x0+side, y)的直线

for (int y = y0; y > y0 - side; y--)

{

glVertex2d(x0, y);

glVertex2d(x0 + side, y);

}

glEnd();

}

// 绘制分形地毯图形,(x, y)为中心正方形的左上顶点,side为其边长,n为递归次数

void DrawCarpet(double x, double y, double side, int n)

{

// 中心正方形周围的8个小正方形的左上顶点坐标

double square0[2], square1[2], square2[2], square3[2],

square4[2], square5[2], square6[2], square7[2];

double len = side / 3.0; // 8个小正方形的边长

DrawSquare(x, y, side); // 以(x, y)为左上顶点,side为边长绘制实心正方形

// n>0时进入递归

if (n > 0)

{

// 计算第1个小正方形的左上顶点的坐标值

square0[0] = x - 2 * len;

square0[1] = y + 2 * len;

// 计算第2个小正方形的左上顶点的坐标值

square1[0] = x + len;

square1[1] = y + 2 * len;

// 计算第3个小正方形的左上顶点的坐标值

square2[0] = x + 4 * len;

square2[1] = y + 2 * len;

// 计算第4个小正方形的左上顶点的坐标值

square3[0] = x - 2 * len;

square3[1] = y - len;

// 计算第5个小正方形的左上顶点的坐标值

square4[0] = x + 4 * len;

square4[1] = y - len;

// 计算第6个小正方形的左上顶点的坐标值

square5[0] = x - 2 * len;

square5[1] = y - 4 * len;

// 计算第7个小正方形的左上顶点的坐标值

square6[0] = x + len;

square6[1] = y - 4 * len;

// 计算第8个小正方形的左上顶点的坐标值

square7[0] = x + 4 * len;

square7[1] = y - 4 * len;


// 对8个小正方形进行递归处理

DrawCarpet(square0[0], square0[1], len, n - 1);

DrawCarpet(square1[0], square1[1], len, n - 1);

DrawCarpet(square2[0], square2[1], len, n - 1);

DrawCarpet(square3[0], square3[1], len, n - 1);

DrawCarpet(square4[0], square4[1], len, n - 1);

DrawCarpet(square5[0], square5[1], len, n - 1);

DrawCarpet(square6[0], square6[1], len, n - 1);

DrawCarpet(square7[0], square7[1], len, n - 1);

}

}

// 绘制回调函数

void display()

{

glClear(GL_COLOR_BUFFER_BIT); // 设定颜色缓存中的值

glColor3f(1.0, 1.0, 1.0); // 设置直线颜色为白色

//lineDDA(50, 500, 300, 100); // 调用DDA算法函数绘制直线

//MidBresenhamLine(50, 500, 400, 200); // 调用中点Bresenham算法绘制直线


// 对三角形的三条边分别调用Koch分形图形绘制函数

//Koch(xf, yf, xl, yl, 4, 0);

//Koch(xf, yf, xt, yt, 4, 1);

//Koch(xt, yt, xl, yl, 4, 1);

//Triangle(xt, yt, xf, yf, xl, yl, 5); // 调用分形三角形绘制函数绘制图形

DrawCarpet(200, 400, 200, 4); // 调用分形地毯绘制函数绘制图形

glFlush(); //立即执行

}

// 主函数

int main(int argc, char ** argv)

{

glutInit(&argc, argv); // 初始化GLUT库OpenGL窗口的显示模式


glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // 初始化窗口的显示模式

glutInitWindowSize(600, 600); // 设置窗口的尺寸

glutInitWindowPosition(100, 100); // 设置窗口的位置

glutCreateWindow("Hello OpenGL!"); // 创建窗口标题为“Hello OpenGL!”

init(); // 初始化

glutDisplayFunc(display); // 执行画图程序

glutMainLoop(); // 启动主GLUT事件处理循环

}

3、实验结果

  1. 中点Bresenham算法绘制直线如图1-1所示:

图1-1 中点Bresenham算法绘制直线结果

  1. 绘制分形三角形如图1-2:

图1-2 分型三角形绘制结果

  1. 绘制Koch雪花如图1-3所示:

图1-3 Koch雪花绘制结果

  1. 绘制分形地毯如图1-4所示:

图1-4 分型地毯绘制结果


三、实验心得

通过本次实验,我对图形学有了更深刻的认识,收获很大。

第一次使用OpenGL编程,因为有了C语言的基础,上手并不是特别困难,阅读理解了模板之后,便能自己修改程序完成中点Bresenham算法,对直线的绘制算法有了更全面的认识,把书本上的知识通过代码实现,并且能够看到相当不错的结果,这是很令人欣慰的;分形图形的绘制,是对数学变换以及递归算法的应用,通过自己的观察与计算,能够绘制出较为复杂的图形,分形三角形的绘制相对简单,找出中点即可,Koch图形的绘制需要找出变换后的五个顶点坐标,这需要进一步的计算才能实现,分形地毯的绘制是自己额外绘制的,也是不难实现的,分形图形的绘制让我体会到了数学与图形学的美,对课程有了更加浓厚的兴趣。

总的来说,完成本次实验,我的整体能力得到了提高,对计算机图形学这门课程的理解更加全面。


实验二:三维场景绘制

一、实验目的

  1. 掌握三维模型建立的一般方法;
  2. 理解OpenGL 中的变换过程;
  3. 理解透视投影与平行投影的不同;
  4. 掌握三维观察实现的基本原理。

二、实验内容

1、实验算法

三维场景绘制:先绘制出基本的三维图形,再通过简单的平移、缩放、旋转变换,得到一系列图形的组合,最后加入光照、材质等属性,设置合适的观测视角,即可实现简单三维场景的绘制。

本实验绘制三维简单城堡,对城堡的每个部分进行了分解,运用了OpenGL显示列表,再对每个部分进行各种变换,最后绘制出完整的城堡模型。

2、源程序

源程序如下:

#include <stdio.h>

#include <GL/glut.h>

#include <math.h>

#define PI 3.1415265359 // 定义常量π

static GLfloat MatSpec[] = {1.0, 1.0, 1.0, 1.0}; // 材料颜色

static GLfloat MatShininess[] = {50.0}; // 光泽度

static GLfloat LightPos[] = {-2.0, 1.5, 1.0, 0.0}; // 光照位置

static GLfloat ModelAmb[] = {0.5, 0.5, 0.5, 0.0}; // 环境光颜色

// 需要画出来的哨塔的数量和墙面的数量

GLint TowerListNum, WallsListNum;

GLint WallsList;

// 哨塔相关参数

int NumOfEdges = 30; // 细分度(其值越高,绘制越精细)

GLfloat LowerHeight = 3.0; // 哨塔上方倒圆台的下底面高度

GLfloat HigherHeight = 3.5; // 哨塔上方倒圆台的上底面高度

GLfloat HR = 1.3; // 哨塔上方倒圆台的上底面半径

// 城墙相关参数

GLfloat WallElementSize = 0.2; // 城墙上方凸起部分的尺寸

GLfloat WallHeight = 2.0; // 城墙上方凸起部分的高度

// 绘制城墙上方凸起部分

void DrawHigherWallPart(int NumElements)

{

glBegin(GL_QUADS); // 开始绘制四边形

// NumElements绘制凸起部分的数目,i小于其值时进入循环

for (int i = 0; i < NumElements; i++)

{

glNormal3f(0.0, 0.0, -1.0); // 设置法向量为Z轴负半轴方向

// 绘制四边形的四个顶点

glVertex3f(i * 2.0 * WallElementSize, 0.0, 0.0);

glVertex3f(i * 2.0 * WallElementSize, WallElementSize, 0.0);

glVertex3f((i * 2.0 + 1.0) * WallElementSize, WallElementSize, 0.0);

glVertex3f((i * 2.0 + 1.0) * WallElementSize, 0.0, 0.0);

}

glEnd(); // 结束绘制

}

// 绘制城墙

void DrawWall(GLfloat Length)

{

glBegin(GL_QUADS); // 开始绘制四边形

glNormal3f(0.0, 0.0, -1.0); // 设置法向量为Z轴负半轴方向

// 绘制四边形的四个点

glVertex3f(0.0, 0.0, 0.0);

glVertex3f(0.0, WallHeight, 0.0);

glVertex3f(Length, WallHeight, 0.0);

glVertex3f(Length, 0.0, 0.0);

glEnd(); // 结束绘制

// i为当前绘制的城墙上的凸起的数目

int i = (int)(Length / WallElementSize / 2);

// 保证凸起的总长度小于城墙的长度

if (i * WallElementSize > Length)

i--; // 如果凸起的总长度大于城墙长度,凸起数目减一

glPushMatrix();

glTranslatef(0.0, WallHeight, 0.0); // 平移变换

DrawHigherWallPart(i); // 绘制城墙上方凸起

glPopMatrix();

}

// 初始化函数,绘制哨塔,城墙

void Init(void)

{

/* 绘制哨塔 */

TowerListNum = glGenLists(1); // 生成哨塔显示列表

GLfloat x, z; // 绘制时的法向量x,z分量

GLfloat NVectY; // 哨塔上方部分倒圆台的法向量y分量

glNewList(TowerListNum, GL_COMPILE); // 用于创建和替换一个显示列表函数原型

glBegin(GL_QUADS); // 绘制四边形

// 创建塔身的圆柱体部分

int i = 0;

for (i = 0; i < NumOfEdges - 1; i++)

{

// 计算前两个点法向量的x,z坐标

x = cos((float)i / (float)NumOfEdges * PI * 2.0);

z = sin((float)i / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, 0.0, z); // 设置法向量方向

// 以上述法线方向绘制前两个顶点

glVertex3f(x, LowerHeight, z);

glVertex3f(x, 0.0, z);

// 计算后两个点法向量的x,z坐标

x = cos((float)(i + 1) / (float)NumOfEdges * PI * 2.0);

z = sin((float)(i + 1) / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, 0.0, z); // 设置法向量方向

// 以上述法线方向绘制后两个顶点

glVertex3f(x, 0.0, z);

glVertex3f(x, LowerHeight, z);

}

// 计算最后一个四边形的前两个点法向量的x,z坐标

x = cos((float)i / (float)NumOfEdges * PI * 2.0);

z = sin((float)i / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, 0.0, z); // 设置法向量方向

// 以上述法线方向绘制前两个顶点

glVertex3f(x, LowerHeight, z);

glVertex3f(x, 0.0, z);

// 计算最后一个四边形的后两个点法向量的x,z坐标

x = cos(1.0 / (float)NumOfEdges * PI * 2.0);

z = sin(1.0 / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, 0.0, z); // 设置法向量方向

// 以上述法线方向绘制后两个顶点

glVertex3f(x, 0.0, z);

glVertex3f(x, LowerHeight, z);

// 哨塔下方圆柱体部分绘制完成

// 绘制哨塔上方倒圆台部分

// 计算法向量NVect y分量的值

NVectY = (HR - 1.0) / (LowerHeight - HigherHeight) * (HR - 1.0);

for (i = 0; i < NumOfEdges - 1; i++)

{

// 计算四边形前两个顶点的法向量x,z值

x = cos((float)i / (float)NumOfEdges * PI * 2.0);

z = sin((float)i / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, NVectY, z); // 设置法向量方向

// 绘制四边形前两个顶点

glVertex3f(x * HR, HigherHeight, z * HR);

glVertex3f(x, LowerHeight, z);

// 计算四边形后两个顶点的法向量x,z值

x = cos((float)(i + 1) / (float)NumOfEdges * PI * 2.0);

z = sin((float)(i + 1) / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, NVectY, z); // 设置法向量方向

// 绘制四边形后两个顶点

glVertex3f(x, LowerHeight, z);

glVertex3f(x*HR, HigherHeight, z*HR);

}

// 计算最后一个四边形前两个顶点的法向量x,z坐标

x = cos((float)i / (float)NumOfEdges * PI * 2.0);

z = sin((float)i / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, NVectY, z); // 设置法向量方向

// 绘制最后一个四边形前两个顶点

glVertex3f(x * HR, HigherHeight, z * HR);

glVertex3f(x, LowerHeight, z);

// 计算最后一个四边形后两个顶点的法向量x,z坐标

x = cos(1.0 / (float)NumOfEdges * PI * 2.0);

z = sin(1.0 / (float)NumOfEdges * PI * 2.0);

glNormal3f(x, NVectY, z); // 设置法向量方向

// 绘制最后一个四边形前两个顶点

glVertex3f(x, LowerHeight, z);

glVertex3f(x*HR, HigherHeight, z*HR);

glEnd(); // 绘制结束

glEndList(); // 标志显示列表的结束

/* 绘制哨塔 */

/* 绘制城墙和大门 */

WallsListNum = glGenLists(1); // 创建城墙显示列表

glNewList(WallsListNum, GL_COMPILE); // 说明显示列表的开始

DrawWall(10.0); // 调用城墙绘制函数绘制左侧城墙

glPushMatrix(); // 变换矩阵压栈

glTranslatef(10.0, 0.0, 0.0); // 平移变换

glPushMatrix(); // 变换矩阵压栈

glRotatef(270.0, 0.0, 1.0, 0.0); // 旋转变换

DrawWall(10.0); // 调用城墙绘制函数绘制后侧城墙

glPopMatrix(); // 恢复变换矩阵

glTranslatef(0.0, 0.0, 10.0); // 平移变换

glPushMatrix(); // 变换矩阵压栈

glRotatef(180.0, 0.0, 1.0, 0.0); // 旋转变换

DrawWall(5.0); // 调用城墙绘制函数绘制右侧后方城墙

glRotatef(90.0, 0.0, 1.0, 0.0); // 旋转变换

glTranslatef(0.0, 0.0, 5.0); // 平移变换

DrawWall(5.0); // 调用城墙绘制函数绘制右侧中间城墙

glPopMatrix(); // 恢复变换矩阵

glTranslatef(-5.0, 0.0, 5.0); // 平移变换

glPushMatrix(); // 变换矩阵压栈

glRotatef(180.0, 0.0, 1.0, 0.0); // 旋转变换

DrawWall(5.0); // 调用城墙绘制函数绘制最右侧城墙

glPopMatrix(); // 恢复变换矩阵

glPushMatrix(); // 变换矩阵压栈

glRotatef(90.0, 0.0, 1.0, 0.0); // 旋转变换

glTranslatef(0.0, 0.0, -5.0); // 平移变换

DrawWall(6.0); // 调用城墙绘制函数绘制前方大门右边城墙

// 绘制前方大门

glTranslatef(6.0, 0.0, 0.0); // 平移变换

glBegin(GL_QUADS); // 绘制四边形

glNormal3f(0.0, 0.0, -1.0); // 设置法向量

// 绘制四边形四个顶点

glVertex3f(0.0, WallHeight / 2.0, 0.0);

glVertex3f(0.0, WallHeight, 0.0);

glVertex3f(3.0, WallHeight, 0.0);

glVertex3f(3.0, WallHeight / 2.0, 0.0);

glEnd(); // 绘制结束

i = (int)(3.0 / WallElementSize / 2); // 计算大门上方凸起数目

if (i * WallElementSize > 3.0)

i--; // 如果凸起总长度大于大门长度,凸起数目减一

glPushMatrix(); // 变换矩阵压栈

glTranslatef(0.0, WallHeight, 0.0); // 平移变换

DrawHigherWallPart(i); // 绘制大门上方凸起

glPopMatrix(); // 恢复变换矩阵

glTranslatef(3.0, 0.0, 0.0); // 平移变换

DrawWall(6.0); // 绘制前方大门左侧城墙

glPopMatrix(); // 恢复变换矩阵

glPopMatrix(); // 恢复变换矩阵

glEndList(); // 标识显示列表的结束

/* 绘制城墙和大门 */

}

// 绘制函数

void Display(void)

{

glClearColor(1, 1, 1, 0); // 设置背景色

// 以上面颜色清屏并清除深度缓存

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glLoadIdentity(); // 初始化矩阵

glLightfv(GL_LIGHT0, GL_POSITION, LightPos); // 指定光源的位置

glTranslatef(-7.0, -4.0, -20.0); // 平移变换

glRotatef(85.0, 0.0, 1.0, 0.0); // 旋转变换

glRotatef(15.0, 0.0, 0.0, 1.0); // 旋转变换

/* 绘制地面 */

glBegin(GL_POLYGON); // 绘制多边形

glNormal3f(0.0, 1.0, 0.0); // 设置法向量

// 绘制6个顶点

glVertex3f(0.0, 0.0, 0.0);

glVertex3f(10.0, 0.0, 0.0);

glVertex3f(10.0, 0.0, 10.0);

glVertex3f(5.0, 0.0, 15.0);

glVertex3f(0.0, 0.0, 15.0);

glVertex3f(0.0, 0.0, 0.0);

glEnd(); // 绘制结束

/* 绘制地面 */

// 设置全局环境光为双面光

glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

// 执行城墙显示列表

glCallList(WallsListNum);

// 取消设置全局环境光为双面光

glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);

// 执行哨塔显示列表绘制第一个哨塔

glCallList(TowerListNum);

glTranslatef(10.0, 0.0, 0.0); // 平移变换

// 执行哨塔显示列表绘制第二个哨塔

glCallList(TowerListNum);

glTranslatef(0.0, 0.0, 10.0); // 平移变换

// 执行哨塔显示列表绘制第三个哨塔

glCallList(TowerListNum);

glTranslatef(-5.0, 0.0, 5.0); // 平移变换

// 执行哨塔显示列表绘制第四个哨塔

glCallList(TowerListNum);

glTranslatef(-5.0, 0.0, 0.0); // 平移变换

// 执行哨塔显示列表绘制第五个哨塔

glCallList(TowerListNum);

glFlush();// 立即执行

glutSwapBuffers();// 交换缓冲区

}

// 窗口改变函数

void Reshape(int x, int y)

{

glViewport(0, 0, x, y); // 设置视口

glMatrixMode(GL_PROJECTION); // 指定当前操作投影矩阵堆栈

glLoadIdentity(); // 重置矩阵函数,将之前的变换消除

gluPerspective(40.0, (GLdouble)x / (GLdouble)y, 1.0, 200.0); // 投影变换

glMatrixMode(GL_MODELVIEW); // 指定当前操作视景矩阵堆栈

}

// 主函数

int main(int argc, char **argv)

{

glutInit(&argc, argv); // 初始化

// 设置窗口初始显示模式

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

glutInitWindowSize(1200, 600); // 显示框大小

glutInitWindowPosition(100, 100); // 确定显示框左上角的位置

glutCreateWindow("Castle"); // 窗口名字

glEnable(GL_DEPTH_TEST); // 打开深度检测

// 设置正反面都为填充方式

glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

glutDisplayFunc(Display); // 调用绘图函数

glutReshapeFunc(Reshape); // 调用窗口改变函数

// 定义镜面材料颜色

glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, MatSpec);

// 定义材料的光泽度

glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, MatShininess);

glEnable(GL_LIGHTING); // 启用光源

glEnable(GL_LIGHT0); // 使用指定灯光

glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ModelAmb); // 设定全局环境光

Init(); // 调用初始化函数

glutMainLoop(); // 启动主GLUT事件处理循环

return 0;

}

3、实验结果

三维场景绘制结果如图2-1、2-2所示:

图2-1 三维场景绘制结果A


图2-2 三维场景绘制结果B


三、实验心得

通过第二次实验,我对三维场景绘制的认识更加全面,对OpenGL的运用也更加熟练。

三维场景的绘制,相比第一次实验较为复杂,对OpenGL的熟练使用要求更多,需要对OpenGL中的平移、缩放和旋转变换有足够的了解,在绘图过程中,运用了OpenGL的显示列表绘制,大大降低了代码的复杂度,绘制简单三维城堡的的过程中,先是绘制城堡的一小部分,如哨塔的上下两个部分,城墙上面的凸起等,在经过一系列变换,绘制出所有的哨塔,城墙以及城墙上方的凸起等等,绘制过程中,对法向量的计算尤为重要,因为绘制圆柱体和圆台时采用的是绘制四边形合成的方法,所以在绘制每个四边形时法向量的设置需要格外注意,对每个部分的定位也要准确,每一步的绘制都需要考虑到下一步的变换,所以在对绘制过程中的坐标变换时也需要注意。

综上,两次实验的完成,让我一点一滴的开始OpenGL编程,其中的收获是不言而喻的,对课程的学习也起到了重要作用。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表