OpenGL 长方体平行斜投影的绘制
题目描述
绘制一个长方体的平行斜投影
分析
题目不长也很容易理解,首先搞明白什么是平行斜投影,这个问题最难的点在于使用OpenGL绘制三维图像,并且将投影和原图的效果都比较清晰和直观地展示出来。
平行斜投影
计算机图形学里面的投影主要分为两种,一种是平行投影,另一种是透视投影。
平行投影就是平行光的投影,类似于太阳光照射人到地面形成影子这样的情形。即光源离被照射物体很远。
透视投影就是灯光照射杯子都桌面这样子的。即光源离被照射物体很近。
而平行投影又分为正投影和斜投影,这个顾名思义,很容易理解。
题目要求的就是平行投影中的后者了。
代码思路
思路一: 三维效果可以建立坐标系,然后把必要元素都画出来,只要有坐标系相信都是看的懂得。
思路二: 曾经看到过鼠标操作OpenGL这样的操作,可以实现拖拉改变图像视角,就很厉害,也很直观。
第一个画出来太丑了,直接第二个吧,不然交的作业老师不高兴给成绩。。。
必要函数介绍
这里介绍的是Glut开源库的介绍,引一个GLUT官方文档先
鉴于这个库已经有20年没有更新了,欢迎大家试着用GLFW和GLEW做。给一个中文版的教程
最后再加一个OpenGL的官方网站。
- [ 如果有大佬搞定了win10下clion的glfw配置(运行时候没有 undefined reference 报错),请救救孩子谢谢!]
鼠标操作
关于鼠标操作,这边引一个博客教程来参考。
比较关键的函数在下表中罗列:
函数名 | 作用 |
---|---|
glutMouseFunc(); | 处理鼠标点击 |
glutMotionFunc); | 处理鼠标拖动 |
glutPassiveMotionFunc() | 追踪鼠标位置 |
视图操作
主要关于三维坐标下的选取观测点以及设置观测角度问题。
还是给出两篇参考吧。
https://blog.csdn.net/peng6662001/article/details/7082436
https://blog.csdn.net/ivan_ljf/article/details/8764737
函数名 | 作用 |
---|---|
glMatrixMode() | 设置矩阵模式(投影/模型/纹理) |
gluPerspective() | 设置透视效果参数 |
gluLookAt() | 设置观测点 |
编程实现
环境
- win10
- Clion
- freeglut
代码
#include <GL/glut.h>
#include <math.h>
#define PI 3.1415926
static int init_angel=90, dx=-5, dy=-5;
static float r=50,h=90.0;
static float angel_trans_unit=float(PI/180.00);
void ChangeSize(int w, int h)
{
// w, h是整个window的宽高
// 这个函数是为了使窗口在被拉伸调整的时候实时重画图像
glViewport(0,0,(GLsizei)w,(GLsizei)h);
// 投影模式
glMatrixMode(GL_PROJECTION);
// 单位矩阵
glLoadIdentity();
// 产生透视效果
gluPerspective(30.0,(float)w/h,1.0,1000.0);
// 模版模式,用于绘图
glMatrixMode(GL_MODELVIEW);
}
void Line(double x0, double y0, double z0, double x1, double y1, double z1){
glLineWidth(3);
glBegin(GL_LINES);
glVertex3d(x0,y0,z0);
glVertex3d(x1,y1,z1);
glEnd();
glFlush();
}
void Cube( double x[], double y[], double z[])
{
// 绘制原长方体(黄色)
glColor3f(1.0,1.0,0.0);
// 用很蠢的的方法把长方体所有线画一遍
Line(x[0],y[0],z[0],x[1],y[0],z[0]);
Line(x[0],y[0],z[0],x[0],y[1],z[0]);
Line(x[0],y[0],z[0],x[0],y[0],z[1]);
Line(x[1],y[1],z[0],x[1],y[0],z[0]);
Line(x[1],y[1],z[0],x[0],y[1],z[0]);
Line(x[0],y[0],z[1],x[0],y[1],z[1]);
Line(x[1],y[0],z[0],x[1],y[0],z[1]);
Line(x[0],y[1],z[0],x[0],y[1],z[1]);
Line(x[0],y[0],z[1],x[1],y[0],z[1]);
Line(x[1],y[1],z[1],x[1],y[1],z[0]);
Line(x[1],y[1],z[1],x[0],y[1],z[1]);
Line(x[1],y[1],z[1],x[1],y[0],z[1]);
}
void AXIS()
// 画个坐标轴
{
// 红色的Z轴
glColor3f(1.0,0.0,0.0);
Line(0,0,-300,0,0,300);
// 绿色的y轴
glColor3f(0.0,1.0,0.0);
Line(0,-300,0,0,300,0);
// 蓝色的x轴
glColor3f(0.0,0.0,1.0);
Line(-300,0,0,300,0,0);
}
void Projection(double x[], double y[], double z[], double l, double alpha)
// 光线沿从(0,0,1)到(l*cos(alpha), l*sin(alpha), 0)方向
{
double p[8][2];
int count = 0;
// 求出8个顶点的投影点
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
for(int k=0;k<2;k++){
p[count][0] = x[i] + z[k]*l*cos(alpha);
p[count][1] = y[j] + z[k]*l*sin(alpha);
count++;
}
// 投影的平面图是二维的
// 把所有个投影点都交叉相连,最外围轮廓就是投影了
//
glColor3f(0.0,1.0,1.0);
for(int i=0;i<8;i++)
for(int j=i+1;j<8;j++){
Line(p[i][0],p[i][1],0,p[j][0],p[j][1],0);
}
glFlush();
}
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0f,1.0f,1.0f);
glLoadIdentity();
// gluLookAt()函数是指相机的位置
// 一直看向原点,即保持原点是视野中心
gluLookAt(r*cos(angel_trans_unit*init_angel), r*sin(angel_trans_unit*h), r*sin(angel_trans_unit*init_angel),
0.0, 0.0, 0.0,
0.0, 1, 0.0);
// 设置长方体的一对对角点的坐标
double x[] = {-1.0, -6.0}, y[] = {-1.0, -6.0}, z[] = {-1.0, -5.0};
// 设置平行光的参数
double l, alpha;
l = 1;
alpha = PI/4;
AXIS();
Cube(x, y, z);
Projection(x, y, z, l, alpha);
// 实现双缓冲的函数
glutSwapBuffers();
}
void Mouse(int button, int state, int x, int y)
// 鼠标点击处理
{
// 记录鼠标按下位置
if(state == GLUT_DOWN)
dx = x,dy = y;
}
void onMouseMove(int x,int y)
// 鼠标拖动处理
// (x,y):鼠标在屏幕平面的的新位置
{
// 控制左右旋转
init_angel += x - dx;
// 控制上下旋转
h += 1 * (y - dy);
// 注意gluLookAt()的参数设置,h有些数值不能取
if((int(h) % 180)==0) h++;
// 保存此时的坐标
dx = x,dy = y;
}
int main(int argc, char* argv[]) {
// 初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowSize(1200, 700);
glutCreateWindow("HomeWork2_1");
// 可以体现图形在视野点看来的前后关系
glEnable(GL_DEPTH_TEST);
// 设置视图效果(透视投影、缩放)
glutReshapeFunc(ChangeSize);
// 两个设置,前者是为了画图,后者是为了拖动操作更流畅
glutDisplayFunc(RenderScene);
glutIdleFunc(RenderScene);
// 鼠标动作的设定
glutMouseFunc(Mouse);
glutMotionFunc(onMouseMove);
// 最后日常进入主循环
glutMainLoop();
return 0;
}
效果图
上图:红绿蓝是坐标轴,分别对应ZYX轴,平行光向量为(
2
2
\frac{\sqrt{2}}{2}
22,
2
2
\frac{\sqrt{2}}{2}
22,-1),投影在z=0平面上。
思考与拓展
- 画图部分的代码应该可以在进一步,算法功底不够深,思考怎么优化
- 平行斜投影部分怎么才能做到更好的效果,这样子的乱遭遭的线不是个好主意