计算机图形学 实验一 OpenGL基本绘制【OpenGL】


受到同届大佬的启发,来开新坑写自己学计图的笔记了( 快进到挖矿不填
包括了实验1.1 1.2和实验一以及对应代码的解析,接下来会包含计图全部实验的解析和答案( 希望能有全部

实验1.1的配置

配置详细见配置环境的文件,我这里顺便上传一下好了
最终能绘制出下图的三角形即可(不排除有人会第一次配置好了情况结果后面还是出问题的情况比如说我
在这里插入图片描述

理论背景

OpenGL的含义
OpenGL是一种应用程序编程接口(Application Programming Interface,API),它是一种可以对图形硬件设备特性进行访问的软件库。OpenGL最新的4.3版本包含了超过500个不同的命令,可以用于设置所需的对象、图像和操作,以便开发出交互式的三维计算机图形应用程序。

OpenGL被设计为一个现代化的、硬件无关的接口,因此可以在不考虑计算机操作系统或窗口系统的前提下,在多种不同的图形硬件系统上,或者完全通过软件的方式实现OpenGL的接口。OpenGL自身并不包括任何执行窗口任务或者处理用户输入的函数,OpenGL也没有提供任何用于表达三维物体模型,或者读取图像文件(例如JPEG文件)的操作。这时,需要通过一系列的几何图元(geometric primitive)(包括点、线、三角形)来创建三维空间的物体。

一个用来渲染图像的OpenGL需要执行的主要操作如下所示:
• 从OpenGL的几何图元中设置数据,用于构建形状。
• 使用不同的着色器(shader)对输入的图元数据执行计算操作,判断它们的位置、颜色,以及其他渲染属性。
• 将输入图元的数学描述转换为与屏幕位置对应的像素片元(fragment)。这一步也成为光栅化(rasterization)。
• 最后针对光栅化过程产生的每个片元,执行片元着色器(fragment shader),从而决定这个片元的最终颜色和位置。
• 如果有必要,还需要对每个片元执行一些额外的操作,例如判断片元对应的对象是否可见,或者将片元的颜色与当前屏幕位置的颜色进行融合。
OpenGL另一个最本质的概念叫做着色器,它是图形硬件设备所执行的一类特殊函数。理解着色器最好的方法是把它看作专为图形处理单元(通常称作GPU)编译的一种小型程序。OpenGL在其内部包含了所有的编译器工具,可以直接从着色器源代码创建GPU所需的编译代码并执行。在OpenGL中,会用到四种不同的着色阶段(shader stage)。其中最常用的包括顶点着色器(vertex shader)以及片元着色器(fragment shader),前者用于处理顶点数据,后者用于处理光栅化后的片元数据。所有的OpenGL程序都需要用到这两类着色器。
最终生成的图像包含了屏幕上绘制的所有像素点。像素(pixel)是显示器上最小显示单位。计算机系统将所有的像素保存到帧缓存(frame buffer)。

在这里插入图片描述
OpenGL 实现了通常所说的渲染管线(rendering pipeline),它是一系列数据处理过程,并且将应用程序的数据转换到最终渲染的图像。上图为 OpenGL 4.3 版本的管线。自从 OpenGL 诞生以来,它的渲染管线已经发生了非常大的变化。
OpenGL 首先接收用户提供的几何数据(顶点和几何图元),并且将它输入到一系列着色器阶段中进行处理,包括:顶点着色、细分着色(它本身包含两个着色器),以及最后的几何着色,然后它将被送入光栅化单元(rasterizer)。光栅化单元负责对所有剪切区域(clipping region)内的图元生成片元数据,然后对每个生成的片元都执行一个片元着色器。
对于 OpenGL 应用程序而言着色器扮演了一个最重要的角色。你可完全控制自己需要用到的着色器来实现自己所需的功能。实际上,不需要用到所有的着色阶段,只有顶点着色器和片元着色器是必需的。细分和几何着色器是可选的步骤。

着色器与OpenGL

现代 OpenGL 渲染管线严重依赖着色器来处理传入的数据。在OpenGL 3.0 版本及以前,或者如果你用到了兼容模式(compatibility profile)环境,OpenGL 还包含一个固定功能管线(fixed-function pipeline),它可以在不使用着色器的情况下处理几何与像素数据。从 3.1 版本开始,固定功能管线从核心模式中去除,因此必须使用着色器来完成工作。
对于 OpenGL 而言,会使用 GLSL去编写着色器,也就是 OpenGL Shading Language。虽然 GLSL 是一种专门为图形开发设计的编程语言,但它与“C”语言非常类似,当然还有一点 C++ 的影子。

着色器的编译

OpenGL 着色器程序的编写与 C 语言等基于编译器的语言非常类似,也就是使用编译器来解析程序,检查是否存在错误,然后将它翻译为目标代码。然后在链接过程中将一系列目标文件合并,并产生最终的可执行程序。下图给出了创建 GLSL 着色器对象并通过链接来生成可执行着色器程序的过程。
在这里插入图片描述

对于每个着色器程序,都需要在应用程序中通过下面的步骤进行设置:

  1. 创建一个着色器对象。
  2. 将着色器源代码编译为对象。
  3. 验证着色器的编译是否成功。

然后需要将所有着色器对象链接为一个着色器程序,包括:

  1. 创建一个着色器程序。
  2. 将着色器对象关联到着色器程序。
  3. 链接着色器程序。
  4. 判断着色器的链接过程是否成功完成。
  5. 使用着色器来处理顶点和片元

通常,创建多个着色器对象是因为有可能在不同的程序中复用同一个函数,而 GLSL 程序也是同样的道理。

OpenGL 应用程序接口
FreeGLUT 应用程序接口
GLEW 基本使用

绘制简单图形的代码解析

本次实验内容主要是利用C++和OpenGL着色语言建立一个显示简单二维彩色图形的程序。

  1. 创建OpenGL工程项目
    (1) 新建一个SimpleShapes工程项目,并参照实验1.1的内容,复制本次实验用到的Common文件夹、include文件夹、main.cpp、vshader .glsl 、fshader .glsl复制到项目的子文件夹中SimpleShapes中(在本示例中为D:\VS2015CODE\SimpleShapes\SimpleShapes)。下图为该目录中包含的各个文件。
    (2)对项目属性 “附加包含目录”的配置中添加include文件夹,在项目的“源文件”中添加Common中的Initshader.cpp到源文件。
  • include文件夹包含了一些基础工具类头文件,其中Angel.h主要包含了FreeGLUTGLEW的头文件和简单的宏定义,CheckError.h定义了输出错误信息的函数,mat.h定义了多种矩阵的数据结构和相关的数据操作vec.h定义多种容器的数据结构和相关的数据操作
  • Common文件夹中的Initshader.cpp实现了InitShader()函数,是为着色器进入GPU的操作专门实现的函数。
  • main.cpp是项目中的主要逻辑实现文件,包含初始化、绘制、响应控制等功能实现。
  • vshader.glsl与fshader.glsl分别是用GLSL编写的顶点着色器和片元着色器,在程序中通过InitShader()函数加载
  1. 编写main.cpp核心代码文件
    一个OpenGL程序通常会在起始部分,包含必要的头文件,并声明一些全局变量和其他有用的程序结构。程序主体由**init(),display(),main()**这三个函数组成。

init()函数负责设置程序中需要用到的数据。

在本实验中,init负责设置渲染图元时用到的顶点位置信息。然后指定了程序中使用的顶点和片元着色器。最后将应用程序的数据与着色器程序的变量关联起来。

display()函数真正执行了渲染的工作。

display负责调用OpenGL函数并渲染需要的内容,几乎所有的display()函数都要完成清除窗口内容、调用OpenGL命令来渲染对象、将最终图像输出到屏幕这三个步骤。

main()函数执行了创建窗口、调用init()以及最终进入时间循环体系(main函数的执行与普通的c程序不同,并不是执行一次就结束,而是会不断重复执行,后文会详细讲到)的一系列繁重工作。

main函数里使用到一些以gl开头的函数,这些会是来自第三方库GLUT和GLEW的函数,这些函数的作用是快速完成一些简单的功能,并保证OpenGL程序可以运行在不同的操作系统上。

(1) 深入main()主函数

int main(int argc, char **argv)
{
	glutInit(&argc, argv);//初始化GLUT库。
	glutInitDisplayMode(GLUT_RGBA);//设置了程序所使用的窗口类型
	glutInitWindowSize(500, 500);//窗口大小
	glutCreateWindow("Simple 2D Shapes");//创建一个窗口以及为窗口名字赋值

	glewExperimental = GL_TRUE;
	glewInit();//该函数属于另一个辅助库GLEW的初始化函数

	init();//调用init函数

	glutDisplayFunc(display);//GLUT在每次更新窗口内容的时候回自动调用的例程

	glutMainLoop();//无限执行的循环,它会负责一直处理窗口和操作系统的用户输入等操作
	return 0;
}

  • glutInit()初始化GLUT库。glutInit()必须是应用程序调用的第一个GLUT函数,它会负责设置其他GLUT例程所必需的数据结构。
  • glutDisplayMode()设置了程序所使用的窗口类型。在本例中只需要设置窗口使用RGBA颜色空间。
  • glutInitWindowSize()设置所需的窗口大小。
  • glutCreateWindow()的功能与其名字一致。只有创建窗口后才可使用openGL相关的函数。
  • glewInit()函数属于另一个辅助库GLEW的初始化函数。GLEW可以简化获取函数地址的过程,并且包含了可以跨平台使用的其他一些OpenGL编程方法。
  • 完成了使用OpenGL之前的全部设置工作后,init ()将初始化OpenGL相关的所有数据,以便完成后面的渲染工作。
  • glutDisplayFunc(),它设置了显示回调,即GLUT在每次更新窗口内容的时候回自动调用的例程。
  • glutMainLoop()函数是一个无限执行的循环,它会负责一直处理窗口和操作系统的用户输入等操作。

(2) OpenGL的初始化过程
下面将分析讨论init()函数。

void init()
{
	vec2 vertices[TOTAL_NUM_POINTS];//二维图形顶点只有xy分量
	vec3 colors[TOTAL_NUM_POINTS];//颜色是RGB模型因此是三维向量

	generateTrianglePoints(vertices, colors, 0);
	//generate用来生成形状上顶点的位置信息和颜色信息。
	generateSquarePoints(vertices, colors, SQUARE_NUM, TRIANGLE_NUM_POINTS);//用三角形来绘制正方形,因此传入的是三角形的点的个数
	

	GLuint vao[1];//详见下方
	glGenVertexArrays(1, vao);//用来分配顶点数组对象。详见下方
	glBindVertexArray(vao[0]);//详见下方

	GLuint buffer;
	glGenBuffers(1, &buffer);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);

	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);

	GLuint program = InitShader("vshader.glsl", "fshader.glsl");
	glUseProgram(program);

	GLuint pLocation = glGetAttribLocation(program, "vPosition");
	glEnableVertexAttribArray(pLocation);
	glVertexAttribPointer(pLocation, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
	GLuint cLocation = glGetAttribLocation(program, "vColor");
	glEnableVertexAttribArray(cLocation);
	glVertexAttribPointer(cLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(vertices)));

		glClearColor(0.0, 0.0, 0.0, 1.0);//设置背景颜色
}

GLuint:
“GLuint为OpenGL的数据类型。因为OpenGL是一个跨平台的API,数据类型的大小会随使用的编程语言以及处理器(64位,32位,16位)等的不同而不同,所以OpenGL定义了自己的数据类型。
当传递数据到OpenGL时,你应该坚持使用这些OpenGL的数据类型,从而保证传递数据的尺寸和精度正确。不这样做的后果是可能会导致无法预料的结果或由于运行时的数据转换造成效率低下。不论平台或语言实现的OpenGL都采用这种方式定义数据类型以保证在各平台上数据的尺寸一致,并使平台间OpenGL代码移植更为容易。”
摘抄自
openGL的数据类型的详细说明

以下这三个函数必须掌握用法:

void glDrawArrays (GLenum mode, Glint first Glsizei count);
使用当前绑定的顶点数组元素来建立一系列的几何图元,起始位置为first,数量为count。mode设置了构建图元的类型。

void generateTrianglePoints(vec2 vertices[], vec3 colors[], int startVertexIndex)
从startVertexIndex的索引位置开始,在vertices数组存入三角形顶点的坐标信息,在colors数组中存入颜色信息。

void generateSquarePoints(vec2 vertices[], vec3 colors[], int squareNumber, int startVertexIndex)
从startVertexIndex的索引位置开始,在vertices数组存入正方形顶点的坐标信息,在colors数组中存入颜色信息,squareNumber代表正方形的数量。
其实与其他函数不同,这两个函数并不是系统自带的,而是需要自己去书写,如何书写见下面

以下这两个需做了解

void glGenVertexArrays(GLsizei n, GLuint *arrays);
返回n个未使用的对象名到数组arrays中,用作顶点数组对象。返回的名字可以用来分配更多的缓存对象,并且它们已经使用未初始化的顶点数组集合的默认状态进行了数值的初始化。

void glBindVertexArray(GLuint array);(往往和上面那个函数配合使用)

glBindVertexArray()完成了三项工作。如果输入的变量array非0,并且是glGenVertexArrays()所返回的,那么它将创建一个新的顶点数组对象并且与其名称关联起来。
(拓展:如果绑定到一个已经创建的顶点数组对象中,那么会激活这个顶点数组对象,并且直接影响对象中所保存的顶点数组状态。如果输入的变量array为0,那么OpenGL将不再使用程序所分配的任何顶点数组对象,并且将渲染状态重设为顶点数组的默认状态。)
简洁概述:用来创建并绑定了一个顶点数组对象。

拓展的参考函数(即便不能完全理解,也能做出来实验题):
但是恐怕做大作业就有问题了,因为大作业需要将很多东西融合在一起,而像这些代码由于我们在实验中基本从来都不用自己写而是有框架的,但是由于对框架的代码不熟悉所以你可能不能很好的融合在一起,建议刚开学就好好搞懂,不然像我一样到时候还得回头钻研

void glGenBuffers(GLsizei n, GLuint *buffers);
用来创建顶点缓存对象的名称,在这里分配了1个对象到数组buffer当中。

void glBindBuffer(GLenum target, GLuint buffer);
指定了激活当前的buffer数组缓存对象。
初始化顶点缓存对象后,使用glBufferData()函数把顶点数据从对象传输到缓存对象当中。

void glBufferData (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
在内存中分配size个存储单元,用于存储数据或者索引。对于顶点的属性数据,target为GL_ARRAY_BUFFER,size表示存储数据的总数量。data是为了初始化缓存对象时内存中的指针,不初始化则为NULL。usage用于设置分配数据之后的读取和写入方式。
sizeof(colors)指定了内存分配的大小,由于运行时不做修改所以最后的usage参数为GL_STATIC_DRAW。

void glBufferSubData (GLenum target, GLintptr offset, GLsizei size, const void *data);
函数是为顶点位置数组vertices和顶点颜色数组colors分别分配内存。

void glVertexAttribPointer (GLuint index, Glint size, GLsizei size, GLenum type, GLboolean normalized, GLsizei stride const GLvoid *pointer);
设置index位置对应的数据值。pointer表示缓存对象中从起始位置开始计算的数组数据的偏移值。size表示每个顶点需要更新的分量数目。type指定了数组中每个元素的数据类型。normalized设置顶点数据在存储前是否需要进行归一化。stride是数组中每两个元素之间的大小偏移,为0则是紧密地封装在一起。

void glEnableVertexAttribArray (GLuint index);
设置启用与index索引相关联的顶点数组。

在使用高于3.1版本的OpenGL,都需要至少指定顶点着色器和片元着色器,这里通过辅助函数InitShader()实现。
在init()函数的剩余部分指定了顶点着色器与片元着色器变量与存储在缓存对象中数据的关系。

关于如何使用这些函数的运用和解析可以查看B站教程:
https://www.bilibili.com/video/BV1px41197A5?p=4

(3) 使用OpenGL进行渲染
下面将分析讨论display()函数。

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);///在渲染前需要利用glClear()函数清除帧缓存的数据。
	
	glDrawArrays(GL_TRIANGLES, 0, TRIANGLE_NUM_POINTS);// 绘制三角形

	for (int i = 0; i < SQUARE_NUM; ++i) {
		glDrawArrays(GL_TRIANGLE_FAN, TRIANGLE_NUM_POINTS + (i * 4), 4);	// 绘制多个正方形
	}
	glFlush();
}

glDrawArrays(int mode, int first,int count)
参数1:有三种取值
1.GL_TRIANGLES:每三个顶之间绘制三角形,之间不连接
2.GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式绘制三角形
3.GL_TRIANGLE_STRIP:顺序在每三个顶点之间均绘制三角形。这个方法可以保证从相同的方向上所有三角形均被绘制。以V0V1V2,V1V2V3,V2V3V4……的形式绘制三角形
参数2:从数组缓存中的哪一位开始绘制,一般都定义为0
参数3:顶点的数量
glDrawArrays(),使用当前绑定的顶点数组元素来建立一系列的几何图元,实现顶点数据向OpenGL管线的传输。在这里用此函数绘制了三角形和正方形。

在display()函数最后调用glFlush (),强制所有进行中的OpenGL命令立即完成并传输到OpenGL服务端处理

(4) 生成二维图形上的点
在实验中,三角形和正方形各个顶点坐标都是以角度参数化的方式获得。如正方形的某个顶点坐标就可用**x = cos(angle), y = sin(angle)**的方式表示。三角形和正方形的顶点数据在generateTrianglePoints()函数和generateSquarePoints()函数中分别存入,其中包括了顶点位置和颜色信息。

  1. 编写顶点与片元着色器
    着色器就是使用OpenGL着色语言(GLSL)编写的一个小型函数。程序可以以字符串的形式传输GLSL着色器到OpenGL,不过为了更容易地使用着色器去进行开发,所有实验都将着色器字符串的内容保存到文件中,并且使用Initshader()读取文件和创建OpenGL着色器程序。下面将深入了解SimpleShapes项目中的顶点着色器与片元着色器。

(1)顶点着色器vshader.glsl

#version 330 core

in vec3 vPosition;
in vec3 vColor;
out vec3 color;//这里将color给out出去,会传给片元着色器中的同名变量

void main()
{
    gl_Position = vec4(vPosition, 1.0);
    color = vColor;
}

第一行#version 330 core指定了所用的OpenGL着色语言版本,330 core代表了使用OpenGL 3.3 对应的GLSL语言,core代表使用OpenGL核心模式。每个着色器的第一行都应该设置#version,否则系统会使用110版本。

下一步是分配着色器变量。对于in vec3 vPosition,in字段指定了数据进入着色器的流向,而vPosition变量是一个GLSL中四维浮点数向量。接下来的vColor与vPosition是相似的输入变量。对于out vec3 color,out的限定符代表在顶点着色器中,会把color对应的数值输出。
着色器的main()函数实现其主体部分,在OpenGL的所有着色器中,都会有一个main()函数。在这里实现的就是将输入的三维顶点位置转换为四维,最后一位用1.0补齐,并复制到顶点着色器的指定输出位置gl_Positition中。

顶点着色器中out的同名变量会传给片元着色器中in的同名变量。

(2)片元着色器fshader.glsl
在OpenGL中,还需要一个片元着色器来配合顶点着色器的工作。下面就是片元着色器的内容。

#version 330 core

in vec3 color;	//来自vshader的color
out vec4 fColor;

void main()
{
    fColor = vec4(color, 1.0);
}

虽然片元着色器与顶点着色器属于两个完全不同类型的着色器,但大部分的代码都很类似。这里的in vec3 color代表了将顶点着色器中输出的color作为该片元着色器输入数据,这样便把两个不同着色阶段的数据连接了起来。最终片元着色器把fColor对应的数值输出,在这里也就是片元所对应的颜色值。

在OpenGL中的颜色是通过RGBA空间表示,因此最后用1.0的完全不透明alpha值,将RGB颜色向量转换为四维RGBA向量。

在片元着色器中重点的内容就是设定片元的颜色,而在这里便决定了图元的最终颜色。

实验 1.2

接下来运行实例代码:

vshader:

#version 330 core

in vec3 vPosition;
in vec3 vColor;
out vec3 color;

void main()
{
    gl_Position = vec4(vPosition, 1.0);
    color = vColor;
}


fshader:

#version 330 core

in vec3 color;
out vec4 fColor;

void main()
{
    fColor = vec4(color, 1.0);
}

main函数(此处为完整函数,部分关键函数解析见下面)
因为怕与原代码中的注释混淆,自己的注释并没有加入//符号,复制运行时记得加上//


#include "Angel.h"

#pragma comment(lib, "glew32.lib")

const vec3 WHITE(1.0, 1.0, 1.0);
const vec3 BLACK(0.0, 0.0, 0.0);
const vec3 RED(1.0, 0.0, 0.0);
const vec3 GREEN(0.0, 1.0, 0.0);
const vec3 BLUE(0.0, 0.0, 1.0);

const int TRIANGLE_NUM_POINTS = 3;
const int SQUARE_NUM = 1;
const int SQUARE_NUM_POINTS = 4 * SQUARE_NUM;
const int TOTAL_NUM_POINTS = TRIANGLE_NUM_POINTS + SQUARE_NUM_POINTS;

// 获得三角形的每个角度
double getTriangleAngle(int point)
{
    return 2 * M_PI / 3 * point;//
    采用的是弧度制 这里M_PI就是派	
}

// 获得正方形的每个角度
double getSquareAngle(int point)
{
    return M_PI / 4 + (M_PI / 2 * point);
}

// 生成三角形上的每个点
void generateTrianglePoints(vec2 vertices[], vec3 colors[], int startVertexIndex)
{
    /*在此函数中修改三角形的顶点位置*/

    double scale = 0.25;
    vec2 center(0.0, 0.70);

    for (int i = 0; i < 3; ++i) {
        // 当前顶点对应的角度
        double currentAngle = getTriangleAngle(i);
        vertices[startVertexIndex + i] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
        采用单位圆上的点的计算方法来实现三角形在三个点的变化,给其乘以一个规模再加上中心的偏移量即可。
    }

    colors[startVertexIndex] = RED;//这个颜色将会传给顶点着色器和片元着色器
    colors[startVertexIndex + 1] = GREEN;
    colors[startVertexIndex + 2] = BLUE;
}

// 生成正方形上的每个点
void generateSquarePoints(vec2 vertices[], vec3 colors[], int squareNumber, int startVertexIndex)
{
    /*在此函数中修改,生成多个嵌套正方形*/

    double scale = 0.90;
    vec2 center(0.0, -0.25);
    int vertexIndex = startVertexIndex;

    vec3 currentColor = WHITE;
    for (int j = 0; j < 4; ++j) {
        // 当前顶点对应的角度
        double currentAngle = getSquareAngle(j);
        vertices[vertexIndex] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
        colors[vertexIndex] = currentColor;
        vertexIndex++;
    }
}

// 负责设置程序中用到的数据
void init()
{
    vec2 vertices[TOTAL_NUM_POINTS];
    vec3 colors[TOTAL_NUM_POINTS];

    // 调用生成形状顶点位置的函数
    generateTrianglePoints(vertices, colors, 0);//调用上面的函数将顶点和颜色数组的值设定好
    generateSquarePoints(vertices, colors, SQUARE_NUM, TRIANGLE_NUM_POINTS);
    注意!!!
    最后一个参数是前面三角形的点的个数,由于传给顶点着色器时,是把一个顶点数组传进去,因此这里把三角形和正方形放在一个顶点数组里绘制。也就是说这个顶点数组包含三角形和正方形的顶点的信息
------------------------------------------------------------------
线内区域目前不是很懂具体的含义,想明白详细解析的可以见main函数下方的链接,但是貌似也不影响后续完成作业?
    // 创建顶点数组对象
    GLuint vao[1];
    // 分配1个顶点数组对象
    glGenVertexArrays(1, vao);
    // 绑定顶点数组对象
    glBindVertexArray(vao[0]);

    // 创建顶点缓存对象
    GLuint buffer;
    // 分配1个顶点数组对象
    glGenBuffers(1, &buffer);
    // 绑定顶点缓存对象
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    // 分配数据所需的存储空间,将数据拷贝到OpenGL服务端内存
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);

    // 分别读取数据
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);

------------------------------------------------------------------
    // 读取着色器并使用
    GLuint program = InitShader("vshader.glsl", "fshader.glsl");
    生成着色器对象,传入两个着色器的名字
    glUseProgram(program);
    代表当前使用的是这个着色器对象

    // 从顶点着色器中初始化顶点的位置
    GLuint pLocation = glGetAttribLocation(program, "vPosition");
    这里的详细解释见下方
    // 启用顶点属性数组
    glEnableVertexAttribArray(pLocation);
    // 关联到顶点属性数组 (index, size, type, normalized, stride, *pointer)
    glVertexAttribPointer(pLocation, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    // 从片元着色器中初始化顶点的颜色
    GLuint cLocation = glGetAttribLocation(program, "vColor");
    glEnableVertexAttribArray(cLocation);
    glVertexAttribPointer(cLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(vertices)));

    // 设置了当前使用的清除颜色值,这里设置为黑色背景
    glClearColor(0.0, 0.0, 0.0, 1.0);
}

// 负责渲染需要的内容
void display(void)
{
    // 清理指定缓存数据并重设为当前的清除值
    glClear(GL_COLOR_BUFFER_BIT);

    // 使用当前绑定的顶点数据建立几何图元 (mode, first, count)
    glDrawArrays(GL_TRIANGLES, 0, TRIANGLE_NUM_POINTS);

    // 绘制多个正方形
    for (int i = 0; i < SQUARE_NUM; ++i) {
        glDrawArrays(GL_TRIANGLE_FAN, TRIANGLE_NUM_POINTS + (i * 4), 4);
    }

    // 强制所有进行中的OpenGL命令完成
    //glFlush()
    glutSwapBuffers();
}

// 创建窗口、初始化、进入事件循环
int main(int argc, char **argv)
{
    // 初始化GLUT库,必须是应用程序调用的第一个GLUT函数
    glutInit(&argc, argv);

    // 配置窗口的显示特性
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutInitWindowSize(500, 500);
    glutCreateWindow("Simple 2D Shapes");

    glewExperimental = GL_TRUE;
    glewInit();

    init();

    // 指定当前窗口进行重绘时要调用的函数
    glutDisplayFunc(display);

    // 负责一直处理窗口和操作系统的用户输入等操作
    glutMainLoop();
    return 0;
}

部分关键函数解析,摘抄自同届大佬的解析

init函数的下半部分:

// 从顶点着色器中初始化顶点的位置
    GLuint pLocation = glGetAttribLocation(program, "vPosition");
    // 启用顶点属性数组
    glEnableVertexAttribArray(pLocation);
    // 关联到顶点属性数组 (index, size, type, normalized, stride, *pointer)
    glVertexAttribPointer(pLocation, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    // 从片元着色器中初始化顶点的颜色
    GLuint cLocation = glGetAttribLocation(program, "vColor");
    glEnableVertexAttribArray(cLocation);
    glVertexAttribPointer(cLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(vertices)));

在上文中,我们传递了两个 vec3 类型的变量,分别是

vPosition:表示顶点位置坐标
vColor:表示顶点颜色 我们将要利用这两个变量进行三角形的绘制。

我们将 vPosition传递给顶点着色器,同时向片元输出 vColor 变量,以在光栅化阶段生成线性插值的像素颜色,具体的流程如下:
在这里插入图片描述

关于顶点着色器与片元着色器的功能可以理解为顶点着色器是画顶点的,片元着色器是在中间涂色的,详细可以见上方博客链接:在这里插入图片描述

display函数:

“ 这一次我们需要做三件事情:

清空颜色缓存(否则绘制的像素会有滞留)
绘制点及正方形
交换缓冲区”

void display(void)
{
    // 清理指定缓存数据并重设为当前的清除值
    glClear(GL_COLOR_BUFFER_BIT);

    // 使用当前绑定的顶点数据建立几何图元 (mode, first, count)
    glDrawArrays(GL_TRIANGLES, 0, TRIANGLE_NUM_POINTS);

    // 绘制多个正方形
    for (int i = 0; i < SQUARE_NUM; ++i) {
        glDrawArrays(GL_TRIANGLE_FAN, TRIANGLE_NUM_POINTS + (i * 4), 4);
    }

    // 强制所有进行中的OpenGL命令完成
    //glFlush()
    
    glutSwapBuffers();//交换缓冲区


}

glutSwapBuffers()
表示交换缓冲。一般屏幕的缓冲都会有两块,为了保证渲染图形的完整性,我们等到一帧完全渲染结束后在将它显示。然后交换屏幕的前后缓冲:

如果不使用双缓冲,就会出现这一帧绘制在上一帧之上的情况,造成图形错位:(整活)
在这里插入图片描述
运行上述代码后即可得到下图所示的结果:
在这里插入图片描述

实验 1.2习题

通过修改给定代码中生成三角形和生成正方形的函数,得到以下的效果。
void generateTrianglePoints(vec2 vertices[], vec3 colors[], int startVertexIndex)
void generateSquarePoints(vec2 vertices[], vec3 colors[], int squareNumber, int startVertexIndex)

在这里插入图片描述
其实很简单,观察和三角形和正方形有关的存储函数:

double getTriangleAngle(int point)
{
    return 2 * M_PI / 3 * point;//
    采用的是弧度制 这里M_PI就是派	
}

// 获得正方形的每个角度
double getSquareAngle(int point)
{
    return M_PI / 4 + (M_PI / 2 * point);
}

// 生成三角形上的每个点
void generateTrianglePoints(vec2 vertices[], vec3 colors[], int startVertexIndex)
{
    /*在此函数中修改三角形的顶点位置*/

    double scale = 0.25;
    vec2 center(0.0, 0.70);

    for (int i = 0; i < 3; ++i) {
        // 当前顶点对应的角度
        double currentAngle = getTriangleAngle(i);
        vertices[startVertexIndex + i] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
        采用单位圆上的点的计算方法来实现三角形在三个点的变化,给其乘以一个规模再加上中心的偏移量即可。
        初始生成即为最开始那个图的红色顶点
    }

    这个颜色后来将会传给顶点着色器和片元着色器
    colors[startVertexIndex] = RED;//这些颜色是开头设置的全局变量
    colors[startVertexIndex + 1] = GREEN;
    colors[startVertexIndex + 2] = BLUE;
}

// 生成正方形上的每个点
void generateSquarePoints(vec2 vertices[], vec3 colors[], int squareNumber, int startVertexIndex)
{
    /*在此函数中修改,生成多个嵌套正方形*/

    double scale = 0.90;
    vec2 center(0.0, -0.25);
    int vertexIndex = startVertexIndex;

    vec3 currentColor = WHITE;
    for (int j = 0; j < 4; ++j) {
        // 当前顶点对应的角度
        double currentAngle = getSquareAngle(j);
        vertices[vertexIndex] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
        colors[vertexIndex] = currentColor;
        vertexIndex++;
    }
}

仔细观察上述代码,为了得到结果修改成如下即可:
开头中修改成这样:

const int TRIANGLE_NUM_POINTS = 3;
const int SQUARE_NUM = 6;
const int SQUARE_NUM_POINTS = 4 * SQUARE_NUM;
const int TOTAL_NUM_POINTS = TRIANGLE_NUM_POINTS + SQUARE_NUM_POINTS;

void函数修改成这样:

void generateTrianglePoints(vec2 vertices[], vec3 colors[], int startVertexIndex)
{
    /*在此函数中修改三角形的顶点位置*/

    double scale = 0.25;
    vec2 center(0.0, 0.70);

    for (int i = 0; i < 3; ++i) {
        // 当前顶点对应的角度
        double currentAngle = getTriangleAngle(i)+M_PI/2;
        vertices[startVertexIndex + i] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
    }

    colors[startVertexIndex] = RED;
    colors[startVertexIndex + 1] = BLUE;
    colors[startVertexIndex + 2] = GREEN;
}

// 生成正方形上的每个点
void generateSquarePoints(vec2 vertices[], vec3 colors[], int squareNumber, int startVertexIndex)
{
    /*在此函数中修改,生成多个嵌套正方形*/

    double scale = 0.90;
    vec2 center(0.0, -0.25);
    int vertexIndex = startVertexIndex;

	vec3 currentColor;
	for (int i = 0; i < squareNumber; ++i)
	{
		if (i % 2 == 0)currentColor =  WHITE;
		else currentColor =BLACK;
		for (int j = 0; j < 4; ++j) {
			// 当前顶点对应的角度
			double currentAngle = getSquareAngle(j);
			vertices[vertexIndex] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
			colors[vertexIndex] = currentColor;
			vertexIndex++;
		}
		scale -= 0.15;
	}


}

为了方便复制粘贴,完整代码:

#include "Angel.h"

#pragma comment(lib, "glew32.lib")

const vec3 WHITE(1.0, 1.0, 1.0);
const vec3 BLACK(0.0, 0.0, 0.0);
const vec3 RED(1.0, 0.0, 0.0);
const vec3 GREEN(0.0, 1.0, 0.0);
const vec3 BLUE(0.0, 0.0, 1.0);

const int TRIANGLE_NUM_POINTS = 3;
const int SQUARE_NUM = 6;
const int SQUARE_NUM_POINTS = 4 * SQUARE_NUM;
const int TOTAL_NUM_POINTS = TRIANGLE_NUM_POINTS + SQUARE_NUM_POINTS;

// 获得三角形的每个角度
double getTriangleAngle(int point)
{
    return 2 * M_PI / 3 * point;
}

// 获得正方形的每个角度
double getSquareAngle(int point)
{
    return M_PI / 4 + (M_PI / 2 * point);
}

// 生成三角形上的每个点
void generateTrianglePoints(vec2 vertices[], vec3 colors[], int startVertexIndex)
{
    /*在此函数中修改三角形的顶点位置*/

    double scale = 0.25;
    vec2 center(0.0, 0.70);

    for (int i = 0; i < 3; ++i) {
        // 当前顶点对应的角度
        double currentAngle = getTriangleAngle(i)+M_PI/2;
        vertices[startVertexIndex + i] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
    }

    colors[startVertexIndex] = RED;
    colors[startVertexIndex + 1] = BLUE;
    colors[startVertexIndex + 2] = GREEN;
}

// 生成正方形上的每个点
void generateSquarePoints(vec2 vertices[], vec3 colors[], int squareNumber, int startVertexIndex)
{
    /*在此函数中修改,生成多个嵌套正方形*/

    double scale = 0.90;
    vec2 center(0.0, -0.25);
    int vertexIndex = startVertexIndex;

	vec3 currentColor;
	for (int i = 0; i < squareNumber; ++i)
	{
		if (i % 2 == 0)currentColor =  WHITE;
		else currentColor =BLACK;
		for (int j = 0; j < 4; ++j) {
			// 当前顶点对应的角度
			double currentAngle = getSquareAngle(j);
			vertices[vertexIndex] = vec2(cos(currentAngle), sin(currentAngle)) * scale + center;
			colors[vertexIndex] = currentColor;
			vertexIndex++;
		}
		scale -= 0.15;
	}


}

// 负责设置程序中用到的数据
void init()
{
    vec2 vertices[TOTAL_NUM_POINTS];
    vec3 colors[TOTAL_NUM_POINTS];

    // 调用生成形状顶点位置的函数
    generateTrianglePoints(vertices, colors, 0);
    generateSquarePoints(vertices, colors, SQUARE_NUM, TRIANGLE_NUM_POINTS);

    // 创建顶点数组对象
    GLuint vao[1];
    // 分配1个顶点数组对象
    glGenVertexArrays(1, vao);
    // 绑定顶点数组对象
    glBindVertexArray(vao[0]);

    // 创建顶点缓存对象
    GLuint buffer;
    // 分配1个顶点数组对象
    glGenBuffers(1, &buffer);
    // 绑定顶点缓存对象
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    // 分配数据所需的存储空间,将数据拷贝到OpenGL服务端内存
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);

    // 分别读取数据
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);

    // 读取着色器并使用
    GLuint program = InitShader("vshader.glsl", "fshader.glsl");
    glUseProgram(program);

    // 从顶点着色器中初始化顶点的位置
    GLuint pLocation = glGetAttribLocation(program, "vPosition");
    // 启用顶点属性数组
    glEnableVertexAttribArray(pLocation);
    // 关联到顶点属性数组 (index, size, type, normalized, stride, *pointer)
    glVertexAttribPointer(pLocation, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    // 从片元着色器中初始化顶点的颜色
    GLuint cLocation = glGetAttribLocation(program, "vColor");
    glEnableVertexAttribArray(cLocation);
    glVertexAttribPointer(cLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(vertices)));

    // 设置了当前使用的清除颜色值,这里设置为黑色背景
    glClearColor(0.0, 0.0, 0.0, 1.0);
}

// 负责渲染需要的内容
void display(void)
{
    // 清理指定缓存数据并重设为当前的清除值
    glClear(GL_COLOR_BUFFER_BIT);

    // 使用当前绑定的顶点数据建立几何图元 (mode, first, count)
    glDrawArrays(GL_TRIANGLES, 0, TRIANGLE_NUM_POINTS);

    // 绘制多个正方形
    for (int i = 0; i < SQUARE_NUM; ++i) {
        glDrawArrays(GL_TRIANGLE_FAN, TRIANGLE_NUM_POINTS + (i * 4), 4);
    }

    // 强制所有进行中的OpenGL命令完成
    //glFlush()
    glutSwapBuffers();
}

// 创建窗口、初始化、进入事件循环
int main(int argc, char **argv)
{
    // 初始化GLUT库,必须是应用程序调用的第一个GLUT函数
    glutInit(&argc, argv);

    // 配置窗口的显示特性
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutInitWindowSize(500, 500);
    glutCreateWindow("Simple 2D Shapes");

    glewExperimental = GL_TRUE;
    glewInit();

    init();

    // 指定当前窗口进行重绘时要调用的函数
    glutDisplayFunc(display);

    // 负责一直处理窗口和操作系统的用户输入等操作
    glutMainLoop();
    return 0;
}

实验一

在Windows系统下完成OpenGL的环境配置,编译并成功运行你的OpenGL程序。该程序需使用现代OpenGL中的着色器,绘制一个包含各种图元的二维图形,必须有:线、三角形、圆、椭圆、多边形,并且使用的颜色也要多样化。参考下图所示(也可以自行设计房屋、机器人等二维图形,只要包含各种简单图元即可):
在这里插入图片描述
具体内容包括:

  1. OpenGL的环境配置
    参考上机实验1.1的内容,完成Visual Studio 集成开发环境的安装,FreeGLUT库与GLEW库的编译与配置,工程项目的搭建。

  2. 绘制二维图形
    参考实验1.2的内容,在此基础上,以参数化的方式绘制出不同的形状(正方形,三角形,圆形,椭圆),并与参考图中图形的位置和大小有所区别。

参考实验1.2的内容,为形状给出不同的颜色效果,包括渐变等,并与参考中的颜色有所区别。

二、 实验要求

  1. 程序代码:本次实验提供留空代码。程序要求:(1)工程必须为自己新建的包含你学号的任意名称;(2)程序绘图窗口名称不能为原来的“2D Shapes with Color”,改为包含你学号的任意名称;(3)绘制结果为包含各种图元的二维图形,必须有:线、三角形、圆、椭圆、多边形,并且使用的颜色也要多样化;(4)最终提交的代码中与实验内容相关部分,即你修改的部分,必须写上注释。
  2. 实验报告:模板见附件。报告内容完整,实验目的、实验步骤、实验结果、实验心得都要完成,排版要整齐,字体要规范。每一实验内容有相应的文字描述和关键步骤的截图。其中,具体要求:(1)OpenGL的环境配置部分要有文字描述和关键步骤的截图3幅;(2)绘制二维图形部分要有文字描述和关键步骤截图6幅。
  3. 上传格式:按上述要求完成实验,一并提交电子版实验报告和源代码压缩包,文档和压缩包名称为“学号_姓名_实验一”。源代码压缩包中请包含有你学号的sln工程文件。

由于内容比较简单,我就直接放我的结果了
绘制图形部分:
在这里插入图片描述

绘制圆和椭圆部分
在这里插入图片描述

绘制三角形部分:
在这里插入图片描述

绘制正方形:
在这里插入图片描述

生成各种图形的点:
在这里插入图片描述

主函数:
在这里插入图片描述

实验一 最终成品展示:

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值