目录
前言
计算机图形学是研究怎样用数字计算机生成、处理和显示图形的一门学科,是计算机科学中,最为活跃、得到广泛应用的分支之一。在计算机中表示图形、以及利用计算机进行图形的计算、处理和显示的相关原理与算法,构成了计算机图形学的主要研究内容。 具体包括:图形硬件、图形标准、图形交互技术、光栅图形生成算法、曲线曲面造型、实体造型、真实感图形计算与显示算法,以及科学计算可视化、计算机动画、自然景物仿真、虚拟现实等。
一、环境配置
这里使用的是Visual Studio 2019版的,网上很多配置Opengl环境时可能会需要下载不同的头文件和库文件放到VS中,其实不用那么麻烦。
1.下载VS2019
如果有同志以前没有用过这个工具的话,可以在网上看一下关于VS的使用教程。
官方下载链接为:https://visualstudio.microsoft.com/zh-hans/vs/
2.创建新项目
创建控制台空项目
3.添加NuGet包
在左上方的项目栏下,点击管理NuGet程序包,添加glfw和nupengl这两个程序。
4.测试代码
#include <GL/glut.h>
#include <math.h>
#include <cstdio>
void init() //初始化函数
{
glClearColor(1.0, 1.0, 1.0, 0.0); //设置背景颜色
}
void Bresenhamline() //Bresenham画线算法
{
int x0 = 0, y0 = 0, x1 = 5, y1 = 2;
int x, y, dx, dy, e;
dx = x1 - x0, dy = y1 - y0, e = -dx;
x = x0, y = y0;
glClear(GL_COLOR_BUFFER_BIT); //清除颜色
glColor3f(0.0, 0.0, 1.0); //线条颜色
glPointSize(2); //线条大小
glBegin(GL_POINTS);
for (int i = 0; i <= dx; i++)
{
//由于坐标系原因将图像缩小0.1倍
GLfloat xx = x * 0.1;
GLfloat yy = y * 0.1;
glVertex2f(xx, yy); //开始画点
x++, e = e + 2 * dy;
if (e > 0)
{
y++, e = e - 2 * dx;
}
}
glEnd();
glFlush();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(400, 400);
glutCreateWindow("Bresenham画线算法 ");
glutDisplayFunc(&Bresenhamline);
init();
glutMainLoop();
return 0;
}
5.测试结果
结果如下图所示就配置成功了,可以进行下一步的学习了。
二、数学基础知识
1.点
点对应着物体的位置,通常在坐标系中显示。常见的有直线坐标系,平面直角坐标系。在参照系中,为确定空间一点的位置,按规定方法选取的有次序的一组数据,这就叫做“坐标”。在某一问题中规定坐标的方法,就是该问题所用的坐标系。
坐标系的种类很多,常用的坐标系有:笛卡尔直角坐标系、平面极坐标系、柱面坐标系(或称柱坐标系)和球面坐标系(或称球坐标系)等。中学物理学中常用的坐标系,为直角坐标系,或称为正交坐标系。
3维坐标系
2.向量
标量(scalar),只具有数值大小,而没有方向,部分有正负之分。物理学中,标量(或作纯量)指在坐标变换下保持不变的物理量。用通俗的说法,标量是只有大小,没有方向的量。向量等于标量加上一个方向。例如,汽车的行驶速度就是一个向量,它具有一个数值表示大小(叫做速率),同时它还有一个方向用于表示行驶的方向。
3.矩阵
使用m×n来定义矩阵的尺寸,它表示该矩阵m行和n列。根据行和列的值引用矩阵中指定位置的元素值,通常用第i行第j列来表示。
对于3×3矩阵M,可以表示如下:
4.变换
在图形程序的开发过程中,我们经常要对物体进行一些几何变换操作,也就是说,将一个几何图形按照一定的规则或规律变换为另一个新的几何图形。在一定程度上可以说,图形的几何变换是计算机图形学系统的核心内容。其中最基本的三种:平移、旋转和缩放。
5.投影
为了在显示器上显示场景中的三维物体,需要把三维物体投影到二维观察平面上。这种将三维物体变为二维图形的变换称为投影变换。
在3D图形学中,需要涉及的有两种基本的投影方式:平行投影和透视投影。
在平行投影中,三维物体的坐标沿平行线投影到观察平面上,它保持物体的有关比例不变,在三维空间平行的直线经平行投影后依然保持平行;
在透视投影中,三维物体的坐标沿会聚线投影到观察平面上, 基于物体相对于投影平面的距离来确定其投影的大小,它能够生成真实感视图,但是不保持相关的比例。
三、OpenGL图形接口
1.入门程序
在一个黑色的窗口中央画一个白色的矩形。
#include <GL/glut.h>
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);//清除颜色
glRectf(-0.5f, -0.5f, 0.5f, 0.5f);//画一个矩形,四个变量为坐标
glFlush();//保证前面的命令立即执行
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);//初始化,相当于模板
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);//设置显示方式
glutInitWindowPosition(500, 250);//设置窗口在屏幕中的位置
glutInitWindowSize(400, 400);//设置窗口的大小
glutCreateWindow("第一个OpenGL程序");//设置窗口的名称
glutDisplayFunc(&myDisplay);//调用前面的函数
glutMainLoop();//消息循环
return 0;
}
2.点、线
如何指定一个点呢?OpenGL提供了一系列函数。它们都以glVertex开头,后面跟一个数字和1~2个字母。例如:
glVertex2d
glVertex2f
glVertex3f
glVertex3fv
数字表示参数的个数,2表示有两个参数,3表示三个,4表示四个。字母表示参数的类型,s表示16位整数,i表示32位整数,f表示32位浮点数 ,d表示64位浮点数,v表示传递的几个参数将使用指针的方式。
绘制任意两点
void Points(void)//绘制点
{
glClear(GL_COLOR_BUFFER_BIT);//清空
glPointSize(5.0f);//点的大小
glBegin(GL_POINTS);//开始绘制
glVertex2f(0.0f, 0.0f);//点的位置
glVertex2f(0.5f, 0.0f);//点的位置
glEnd();//结束绘制
glFlush();
}
线条的绘制和点有着密不可分的关系,只需要把上述代码第5行改成 glBegin(GL_LINE_LOOP)就可以了。
绘制虚线和改变线的大小
void Lines(void)//绘制虚线
{
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_LINE_STIPPLE);//启动虚线模式
glLineStipple(2, 0x0F0F);//设置虚线的样式
glLineWidth(10.0f);//设置线的大小
glBegin(GL_LINES);//开始
glVertex2f(0.0f, 0.0f);//起始点
glVertex2f(0.5f, 0.5f);//结束点
glEnd();//结束
glFlush();
}
3.示例
五角星
const GLfloat Pi = 3.1415926536f;//圆周率
void star(void)//绘制五角星
{
GLfloat a = 1 / (2 - 2 * cos(72 * Pi / 180));
GLfloat bx = a * cos(18 * Pi / 180);
GLfloat by = a * sin(18 * Pi / 180);
GLfloat cy = -a * cos(18 * Pi / 180);
GLfloat
PointA[2] = { 0, a },
PointB[2] = { bx, by },
PointC[2] = { 0.5, cy },
PointD[2] = { -0.5, cy },
PointE[2] = { -bx, by };//数组存放坐标
glClear(GL_COLOR_BUFFER_BIT);
// 按照A->C->E->B->D->A的顺序,可以一笔将五角星画出
glLineWidth(5.0f);//线的大小
//glEnable(GL_LINE_STIPPLE);
//绘制虚线和关闭glDisable(GL_LINE_STIPPLE)
//glLineStipple(2, 0x0F0F);//虚线的样式
glBegin(GL_LINE_LOOP);
glVertex2fv(PointA);
glVertex2fv(PointC);
glVertex2fv(PointE);
glVertex2fv(PointB);
glVertex2fv(PointD);
glEnd();
glFlush();
}
圆
const int n = 100;//用点来画圆,点越多,圆越光滑
const GLfloat R = 0.5f;
const GLfloat Pi = 3.1415926536f;
void round(void)
{
int i;
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_POLYGON);
for (i = 0;i < n; ++i)
glVertex2f(R * cos(2 * Pi / n * i), R * sin(2 * Pi / n * i));//现在看不懂的可以留到后面
glEnd();
glFlush();
}
四、色彩的应用
1.RGBA颜色
上面所绘制的图形大多都是黑白的,现在终于要进入彩色的世界,在RGBA模式中,每一个像素会保存以下数据:R值(红色分量)、G值(绿色分量)、B值(蓝色分量)和A值(alpha分量)。其中红、绿、蓝三种颜色相组合,就可以得到我们所需要的各种颜色,而alpha不直接影响颜色,它将留待以后介绍。
下面就是一些常见的函数:
void glColor3f(GLfloat red, GLfloat green, GLfloat blue);
void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
大多数通常使用第一种,比如
glColor3f(1.0f, 0.0f, 0.0f); 表示不使用绿、蓝色,而将红色使用最多,于是得到最纯净的红色。
glColor3f(0.0f, 1.0f, 1.0f); 表示使用绿、蓝色到最多,而不使用红色。混合的效果就是浅蓝色。
glColor3f(0.5f, 0.5f, 0.5f); 表示各种颜色使用一半,效果为灰色。
对于想要改变背景颜色可以用清除屏幕颜色glClearColor,和上面差不多,就不做赘述。
如果有兴趣的同学可以把前面学的第一个程序用不同的颜色实现一下,代码和结果图放在下面了。
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);//清除颜色
glColor3f(1.0f,0.0f,0.0f);//红色
glRectf(-0.5f, -0.5f, 0.5f, 0.5f);//画一个矩形,四个变量为坐标
glColor3f(0.0f, 1.0f, 1.0f);//蓝色
glRectf(0.0f, -0.5f, 1.0f, 0.5f);//画一个矩形,四个变量为坐标
glColor3f(0.5f, 0.5f, 0.5f);//灰色
glRectf(0.5f, -0.5f, 1.5f, 0.5f);//画一个矩形,四个变量为坐标
glFlush();//保证前面的命令立即执行
}
注意:glColor系列函数,在参数类型不同时,表示“最大”颜色的值也不同。
采用f和d做后缀的函数,以1.0表示最大的使用。
采用b做后缀的函数,以127表示最大的使用。
采用ub做后缀的函数,以255表示最大的使用。
采用s做后缀的函数,以32767表示最大的使用。
采用us做后缀的函数,以65535表示最大的使用。
这个链接页面提供了常见RGB表数值:各种常见颜色的RGB数值|RGB数值,rgb颜色表,金色rgb,rgb颜色,rgb颜色代码,各种颜色的rgb,常用颜色rgb,金属颜色rgb,rgb颜色对照表
2.索引和选择颜色
索引颜色:如果原图像中的某种颜色没有出现在该表中,则程序将选取现有颜色中最接近的一种,或使用现有颜色模拟该颜色。简单说,程序中没有,选取最相近的颜色。
选择颜色:在颜色表中选择颜色,其中最常用的可能是glIndexi,它的参数是一个整形,
void glIndexi(GLint c);
3.颜色表
后面补充
五、光栅图形学
图形的光栅化:就是在像素点阵中确定最佳逼近于理想图形的像素点集的过程。
1.直线
(1)DDA算法
DDA是数字微分分析式的缩写,下面用一个例子说明:
代码实现:
void DDALine() //DDA画线算法
{
int x0 = 0, y0 = 0, x1 = 5, y1 = 2;//设置坐标点
int x;
float dx, dy, y, k;
dx = x1 - x0, dy = y1 - y0;
k = dy / dx, y = y0;
glClear(GL_COLOR_BUFFER_BIT); //清除颜色
glColor3f(1.0f, 0.0f, 1.0f); //设置线条颜色
glPointSize(3.0f); //设置线条大小
glBegin(GL_POINTS);//开始画点
for (x = x0; x <= x1; x++)
{
glVertex2f(x * 0.1,(int)(y + 0.5) * 0.1); //开始画点
y = y + k;
}
glEnd();
glFlush();
}
(2)Bresenham算法