计算机图形学与可视化计算OPENGL 个人作业一
关键词:OpenGL绘制线、点、球,世界的旋转
背景信息
这是2022年春季学期,北京理工大学,计算机学院,软件工程,开设的一门选修课程,为期八周,共32课时,难度很高,自学与听讲的收获参半。
课程的成绩结算方式为考查,分为三次个人作业与一次团队作业,难度层层递进,每次作业都是以上一次为基础。
本篇内容为作业一,整体难度较为容易,耗时1~2天。
注意:编译环境与函数库详见(www.wo deng hui zai xie.net)
2022计算机图形学与可视化计算
目录
(点击跳转)
1 程序使用说明
1.1 向量的运算
1.1.1 功能介绍
1.1.2 执行截图
1.2 图像的绘制
1.2.1 功能介绍
1.2.2 执行截图
2 程序设计说明
2.1 向量的运算
2.1.1 头文件Cvector167.h
2.1.2 文件读取与向量计算、结果输出
2.2 图像的绘制
2.2.1 旋转的实现,空格控制旋转
2.2.2 1000个彩色光滑圆点的绘制
2.2.3 彩色坐标轴与白色线球的绘制
2.2.4 彩色圆圈的绘制
1程序使用说明
1.1 向量的运算
1.1.1 功能介绍
将所需要计算的文档放于run目录下,以test.txt命名。
test.txt中文件必须满足以下格式:
a) 奇数行的内容必须为
向量相加
向量点乘
向量叉乘
向量标准化
向量求模
向量投影
六种向量计算之一。
b) 偶数行的数据必须对应上一行的内容
向量之间以 ’\t’(单引号之间的内容为键盘上的Tab键)间隔,向量内分量以半角逗号分隔,分量可正可负,小数点后的位数不限,但是必须是float可以容纳的大小。程序会将test.txt中的内容按要求计算,按照原来的格式,输出内容到out.txt中,但偶数行内容有所变动,具体变动:将所给的向量按照原来的间隔方式输出,并在行末增加计算结果:一个向量或者浮点数,向量分量、浮点数均只保留两位小数。
1.1.2 执行截图
如下图放置文件
test.txt内容
out.txt内容
1.2 图像的绘制
1.2.1 功能介绍
在上文out.txt文件生成后,该程序会完全自动执行图形的绘制,具体绘制内容如下:
A. 绘制坐标轴X,Y,Z。使用红绿蓝三色的线段表示3个坐标轴。
B. 使用线框模式绘制一个白色球,线粗1。
C. 球内绘制1000个彩色圆点,原点随机改变位置,即视觉效果为躁动状态。
D. 球内的点越靠近球顶部则越红,越靠近球底部则越蓝。
E. 绘制彩色曲线圆圈套住球。
F. 整个世界自动旋转,按空格键可切换旋转和暂停。
1.2.2 执行截图
2 程序设计说明
2.1 向量的运算
2.1.1 头文件Cvector167.h
向量类结构图
向量类程序说明:
建立CVector167类,其中包括float x,y,z,首先是重载操作符,实现[]便捷访问向量的分量,如向量v就可以通过v[]来访问v.x,并且此处的v[]可以作为左值使用。接着是加法、减法、赋值、数乘、判断相等、判断不相等的重载,其中要注意数乘操作需要满足向量在前,标量在后,如(向量v * 标量k)。为了方便生成向量,还编写了构造器CVector(x,y,z)。方法实现了如向量取模、向量点乘、向量标准化、向量赋值、向量投影、向量叉乘。
2.1.2 文件读取与向量计算、结果输出
向量计算总体框架
如图所示实现了若干方法完成out.txt文件的创建,test.txt文件的读取,向量计算方法的判断,字符串转化成为向量类,将结果添加在out.txt文件末尾。
Read函数流程图
Read()函数说明
第一步,打开文件输入流fin,文件输出流fout,并直接关闭fout,实现了空白文本文件out.txt的创建,假若out.txt已经存在,这一步也会清空文件内容
第二步,读取test.txt文件的一行内容,如果此行为空则结束读取循环,关闭文件fin。如果非空,那么根据规则一行向量的运算方式和一行向量数据,必然存在第二行,继续读取下一行,将这两行存入String[2]中,传递给compute函数进行计算以及输出。
由于给出的测试文件向量数量不确定,借此读取一个,计算一个,输出一个即可实现所需的要求。
Compute函数程序流程图
函数的计算是程序的核心。
将传递过来的String[]的String[0] “向量XX”进行识别。
如“向量相加”会给出两个向量的信息,这时将String[1]调用getVector两次,第一次order = 0,第二次order = 1, 分别获取String[1]中的第一个向量和第二个向量。
但是诸如“向量标准化”只需要获取一个向量,此时我们传递String[1]和order = 0,即可。
获取向量后进行相应的运算,并将运算方法,运算结果输出到out.txt文件中,并且都保留两位小数,分量间以半角逗号间隔,向量间以tab间隔,行末为回车符。
getVector函数程序流程图
获取向量函数是该程序的基础。
先判断order == 1与否,如果等于1则遍历String[1]得到第一个空格的位置,将空格下一个字符作为向量的起点开始遍历。
进入一个for循环,先给v[0]赋值,判断第一个字符是否为“-”,如果是我们做一个标记nagetive = -1,否则nagetive就保持初值1;为了叙述的普遍性,我们此处以“-12.63,xxx,xxx”为例:
首先第一个字符为符号,记录nagetive = -1;下一步字符为‘1’,我们将v[0]=10(初值是0),再v[0]+=‘1’-‘0’,此时第一个分量等于1;接下来下一个数字是2,1 → 110 = 10 → 10+2 = 12;如此循环直到读取到第一个非数字,即空格、回车、逗号、小数点,如果是前三者直接进行下一个分量的读取或者读取完毕,如果是小数点则给point赋值1,读取下一个字符,下一个字符为‘6’,我们进行这样的运算 12+=6/point,实现小数位的控制,计算完毕后point*=10控制下一位小数,直到此分量结束,此时将分量乘以nagetive实现正负的控制。给出的题目只有一位小数,但是为了普遍性,上述程序实现了多位的读取功能。
2.2 第二部分——图像的绘制
2.2.1 旋转的实现,空格控制旋转
如图所示,参考了例程,借鉴其旋转方法,键盘输入获取方法,具体实现:
首先创建三个全局变量
float seta = 0;
float seta_add = 0.1;
int space_count = 0;
分别用来控制旋转、每两次变换的差值,记录空格按下的次数。
先来说空格实现控制暂停与旋转,main函数调用glutKeyboardFunc(&myKeyboardFunc);进而调用函数
void myKeyboardFunc(unsigned char key, int x, int y);在此函数中实现空格的计数,当空格计数次为奇数时,将seta_add赋值0,即停止了旋转;当空格计数次为偶数时,seta_add赋值0.1,恢复正常旋转。
接下来是旋转的实现,main函数调用
glutTimerFunc(3, myTimerFunc, 0);进而调用函数
void myTimerFunc(int val);在此函数中实现seta值的增加,每次调用此函数增加值为seta_add,并调用
myDisplay(); 和 他自己本身glutTimerFunc(3, myTimerFunc, 0);
调用myDisplay()函数时候执行函数glRotatef(seta, 0, 1, 0);实现旋转。
2.2.2 1000个彩色光滑圆点的绘制
首先我们先随机产生三个处于-70,70之间的浮点数,并判断是否处于半径为70,圆心在圆点的球内,如果在球内,调用相应的函数生成该点,最终生成共计1000个点。
在生成点的同时,根据点的z值不同,赋予不同的颜色,如果z大于零,接近70的点将越红,如果z小于零,越接近-70的点将越蓝。
生成点之后,在主函数中会调用SetRc()函数,该函数会调用使点光滑的函数。
2.2.3 彩色坐标轴与白色线球的绘制
画坐标轴线首先指定线宽glLineWidth(2),然后开始绘制直线glBegin(GL_LINES),继续指定该轴的颜色glColor3f,以及起点glVertex3f,终点glVertex3f,最后结束绘制glEnd()。
绘制线圈球更加简单,指定颜色glColor3f(1.0f, 1.0f, 1.0f),线宽glLineWidth(1)后直接调用绘制线球函数glutWireSphere(70, 15, 15),其中数据的含义是线球的半径、经纬线条数。
2.2.4 彩色圆圈的绘制
首先指定线宽glLineWidth(3),接下来开始绘制直线线圈glBegin(GL_LINE_LOOP),并且指定半径为GLfloat R = 80.0f。
开始循环直线线圈节点,指定节点数为int n = 100。
写一个循环for (int i = 0; i < n; ++i),每一次循环中使用三角函数给定节点的坐标,并赋予不同但是逐渐变化的色彩。
float y = R * cos(2.0 * Pi / n * i);
float z = R * sin(2.0 * Pi / n * i);
glColor3f((i % 30) / 30.0 + 0.1, (i % 70) / 70.0 + 0.1, (i % 10) / 10.0 + 0.1);
glVertex3f(0, y, z);
最后结束直线线圈的绘制glEnd()。
CVector67.h
#pragma once
class CVector167
{
public:
float x,y,z;
float forth = -31415926;//第四维,赋一个几乎不用的初始值
void Print();
CVector167();
CVector167(float x, float y, float z);
void Set(float x, float y, float z);
CVector167 operator +(const CVector167& v);
CVector167 operator -(const CVector167& v);
CVector167 operator =(const CVector167& v);
bool operator ==(const CVector167& v);
bool operator !=(const CVector167& v);
CVector167 operator *(float k);
float dotMul(CVector167& v);
CVector167 crossMul(CVector167& v);
float len();
CVector167 Normalize();
CVector167 project(CVector167& v);
operator float* () { return &x; }
};
CVector67.cpp
#include "CVector167.h"
#include <math.h>
#include <iostream>
using namespace std;
void CVector167::Print() {
if (forth == -31415926)cout << "x = " << this->x << ", y = " << this->y << ", z = " << this->z << "\n";
else cout << "x = " << this->x << ", y = " << this->y << ", z = " << this->z << ", forth = " << this->forth << "\n";
}
CVector167::CVector167()
{
this->x = 0;
this->y = 0;
this->z = 0;
};
CVector167::CVector167(float x, float y, float z)
{//构造器初始化
this->Set(x, y, z);
}
void CVector167::Set(float x, float y, float z)
{//设初值
this->x = x;
this->y = y;
this->z = z;
}
CVector167 CVector167::operator +(const CVector167& v)
{//加法重载
CVector167 ve;
ve.x = this->x + v.x;
ve.y = this->y + v.y;
ve.z = this->z + v.z;
return ve;
}//无误2022 04 25
CVector167 CVector167::operator -(const CVector167& v)
{//减法
CVector167 ve;
ve.x = this->x - v.x;
ve.y = this->y - v.y;
ve.z = this->z - v.z;
return ve;
}
CVector167 CVector167::operator =(const CVector167& v)
{//赋值
this->x = v.x;
this->y = v.y;
this->z = v.z;
return CVector167(v.x, v.y, v.z);
}
/*
注意!!!!!!!
此处const必须加入,否则可能会出现 向量a,b,
a = b * 2; 按照标量计算习惯应该是 a = (b * 2);
const的缺失会导致报错
*/
bool CVector167::operator ==(const CVector167& v)
{//判别相等
if (this->x == v.x && this->y == v.y && this->z == v.z)return true;
else return false;
}//无误,但是返回值打印的时候会变成 0 1,程序运行的时候确实是布尔值
bool CVector167::operator !=(const CVector167& v)
{//判别不等
if (this->x != v.x || this->y != v.y || this->z != v.z)return true;
else return false;
}
CVector167 CVector167::operator *(float k)
{//数乘 v * k
return CVector167(this->x * k, this->y * k, this->z * k);
}//无误,但是这个只能实现标量在后的情况
//以下函数没有const是因为老师给的参数列表的确如此
float CVector167::dotMul(CVector167& v)
{//点乘 返回float
return (this->x * v.x + this->y * v.y + this->z * v.z);
}//无误
CVector167 CVector167::crossMul(CVector167& v)
{//向量叉乘
return CVector167(this->y * v.z - this->z * v.y,
this->z * v.x - this->x * v.z,
this->x * v.y - this->y * v.x);
}//没问题
float CVector167::len()
{
return (float)sqrt(((double)this->x * this->x) +
((double)this->y * this->y) +
((double)this->z * this->z));
}//此处的double是为了避免出现警告
CVector167 CVector167::Normalize()
{
return CVector167(this->x / this->len(),
this->y / this->len(),
this->z / this->len());
}//无误
CVector167 CVector167::project(CVector167& v)
{//投影
float project_length = this->dotMul(v) / v.len();
return CVector167(v.x * (project_length / v.len()),
v.y * (project_length / v.len()),
v.z * (project_length / v.len()));
}//没问题
Work_01.cpp
#include "stdafx.h"
#include "glut.h"
#include "math.h"
#include "CVector167.h"
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <fstream>//文件输入输出
#include <string>//字符串类
#include <iomanip>//保留两位小数
using namespace std;
CVector167 getVector(string s, int order);//声明
const GLfloat Pi = 3.1415926536;
float seta = 0.0;
float seta_add = 0.1;
int space_count = 0;
void myDisplay(void);
void Compute(string law_data[]) {
ofstream fout;
fout.open("out.txt", ios::app);//在文件末尾增加内容
if (law_data[0] == "向量相加")
{
fout << setiosflags(ios::fixed) << setprecision(2) << "向量相加\n";
CVector167 v1 = getVector(law_data[1], 0);
CVector167 v2 = getVector(law_data[1], 1);
CVector167 v0 = v1 + v2;
fout << v1[0] << "," << v1[1] << "," << v1[2] << " "/*Tab*/;
fout << v2[0] << "," << v2[1] << "," << v2[2] << " "/*Tab*/;
fout << v0[0] << "," << v0[1] << "," << v0[2] << "\n";
}
else if (law_data[0] == "向量投影")
{
fout << setiosflags(ios::fixed) << setprecision(2) << "向量投影\n";
CVector167 v1 = getVector(law_data[1], 0);
CVector167 v2 = getVector(law_data[1], 1);
CVector167 v0 = v1.project(v2);
fout << v1[0] << "," << v1[1] << "," << v1[2] << " "/*Tab*/;
fout << v2[0] << "," << v2[1] << "," << v2[2] << " "/*Tab*/;
fout << v0[0] << "," << v0[1] << "," << v0[2] << "\n";
}
else if (law_data[0] == "向量叉乘")
{
fout << setiosflags(ios::fixed) << setprecision(2) << "向量叉乘\n";
CVector167 v1 = getVector(law_data[1], 0);
CVector167 v2 = getVector(law_data[1], 1);
CVector167 v0 = v1.crossMul(v2);
fout << v1[0] << "," << v1[1] << "," << v1[2] << " "/*Tab*/;
fout << v2[0] << "," << v2[1] << "," << v2[2] << " "/*Tab*/;
fout << v0[0] << "," << v0[1] << "," << v0[2] << "\n";
}
else if (law_data[0] == "向量点乘")
{
fout << setiosflags(ios::fixed) << setprecision(2) << "向量点乘\n";
CVector167 v1 = getVector(law_data[1], 0);
CVector167 v2 = getVector(law_data[1], 1);
float dot = v1.dotMul(v2);
fout << v1[0] << "," << v1[1] << "," << v1[2] << " "/*Tab*/;
fout << v2[0] << "," << v2[1] << "," << v2[2] << " "/*Tab*/;
fout << dot << "\n";
}
else if (law_data[0] == "向量求模")
{
fout << setiosflags(ios::fixed) << setprecision(2) << "向量求模\n";
CVector167 v1 = getVector(law_data[1], 0);
float len = v1.len();
fout << v1[0] << "," << v1[1] << "," << v1[2] << " "/*Tab*/;
fout << len << "\n";
}
else if (law_data[0] == "向量标准化")
{
fout << setiosflags(ios::fixed) << setprecision(2) << "向量标准化\n";
CVector167 v1 = getVector(law_data[1], 0);
CVector167 v0 = v1.Normalize();
fout << v1[0] << "," << v1[1] << "," << v1[2] << " "/*Tab*/;
fout << v0[0] << "," << v0[1] << "," << v0[2] << "\n";
}
else {
cout << "不存在操作“" << law_data[0] << "”\n";
}
fout.close();
}
void Read()
{
ifstream fin;
fin.open("test.txt");//在run目录里面
ofstream fout;
fout.open("out.txt");//在run目录里面
fout.close();//生成文件or删除文件内容
if (!fin.is_open())
{
cout << "无法找到此文件!\n请将test.txt文件置于run目录中。\n";
return;
}
string buff[2];
int i = 0;
while (getline(fin, buff[0]))
{
getline(fin, buff[1]);
Compute(buff);
}
fin.close();
}
CVector167 getVector(string s, int order)
{//文本转化为向量
CVector167 v(0, 0, 0);
int begin = 0;
if (order == 1)
{//获取的是第二个向量的起点
int i;
for (i = 0; i < s.length(); i++)
if (s[i] == ' ' || s[i] == ' '/*Tab*/)break;
begin = i + 1;
}
int j = 0;//x,y,z
int point = 0;
int nagetive = 1;//等于1为正,等于-1为负
for (int k = begin; k < s.length(); k++)
{
if (s[k] == '-')
nagetive = -1;
if ('0' <= s[k] && s[k] <= '9' && point == 0)
v[j] *= 10, v[j] += s[k] - '0';//获取小数点前的数
if (point == 0 && s[k] == '.')
point = 10;
if ('0' <= s[k] && s[k] <= '9' && point != 0) {
v[j] += ((float)s[k] - '0') / point;//获取小数点后的数
point *= 10;
}
if (s[k] == ',')
{
v[j] *= nagetive;
point = 0;
j++;
continue;//下一个分量
}
if (s[k] == ' ' || s[k] == ' '/*Tab*/)
break;//完毕
}
return v;
}
void myKeyboardFunc(unsigned char key, int x, int y) {
if (key == ' ')
space_count++;
if (space_count % 2 == 0)seta_add = 0.1;
else seta_add = 0;
}
void myTimerFunc(int val)
{
seta += seta_add;
myDisplay();
glutTimerFunc(3, myTimerFunc, 0);
}
void SetRC()//变光滑
{
//glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glEnable(GL_POINT_SMOOTH);//光滑
//glEnable(GL_BLEND);
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glTranslatef(0, 0, -200);
//glRotatef(90, 0, 1, 0);
glRotatef(seta, 0, 1, 0);//旋转
for (int count = 0; count < 1000; )
{
double x = -70 + 140.0 * rand() / double(RAND_MAX),
y = -70 + 140.0 * rand() / double(RAND_MAX),
z = -70 + 140.0 * rand() / double(RAND_MAX);
if (x * x + y * y + z * z > 70.0 * 70)continue;
else count++;
double red, blue;
if (z > 0) {
red = z / 70+0.25;
blue = 0;
}
else {
red = 0;
blue = -z / 70+0.25;
}
glPointSize(3.5);
glBegin(GL_POINTS);
glColor3f(0.05 + red, 0.30, 0.05 + blue);//
glVertex3f(x, y, z);
glEnd();
}
glLineWidth(2);//写在绘制的前面,也可以指定球线宽
glBegin(GL_LINES);
glColor3f(1.0f, 0.0f, 0.0f);//x轴
glVertex3f(0, 0, 0);
glVertex3f(100, 0, 0);
glColor3f(0.0f, 1.0f, 0.0f);//y轴
glVertex3f(0, 0, 0);
glVertex3f(0, 100, 0);
glColor3f(0.0f, 0.0f, 1.0f);;//z轴
glVertex3f(0, 0, 0);
glVertex3f(0, 0, 100);
glEnd();
glColor3f(1.0f, 1.0f, 1.0f);
glLineWidth(1);//线宽为1
glutWireSphere(70, 15, 15);
glLineWidth(3);
glBegin(GL_LINE_LOOP);
GLfloat R = 75.0f;
int n = 100;//圆的直线段数
for (int i = 0; i < n; ++i)
{
float y = R * cos(2.0 * Pi / n * i);
float z = R * sin(2.0 * Pi / n * i);
glColor3f((i % 30) / 30.0 + 0.1, (i % 70) / 70.0 + 0.1, (i % 10) / 10.0 + 0.1);
glVertex3f(0, y, z);
}
glEnd();
glPopMatrix();
glFlush();
}
void myReshape(int w, int h)
{
GLfloat nRange = 100.0f;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, GLfloat(w) / h, 1, 1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
int main(int argc, char* argv[])
{
Read();
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(600, 600);
glutCreateWindow("Work_1 刘成坤 1120200167");
glutDisplayFunc(&myDisplay);
glutTimerFunc(3, myTimerFunc, 0);//旋转
glutReshapeFunc(&myReshape);
glutKeyboardFunc(&myKeyboardFunc);//获取键盘数据
SetRC();
glutMainLoop();
return 0;
}
附件(百度云)
//有空再补
提取码:LINK