OpenGL(二)—— Hello World

目录

 

一、前言

二、渲染

三、GLSL

3.1 数据类型

3.2 编程步骤

四、渲染窗口

五、渲染三角形

5.1 顶点输入

 5.2 顶点着色器

5.3 片段着色器

5.4 着色器链接

5.5 顶点属性

5.6 VAO管理顶点属性

5.7 画图

六、渲染四边形

6.1 元素缓冲对象EBO

6.2 运行


一、前言

GLSL用来编写着色器Shader程序的语言,本篇介绍了渲染常用的方法以及GLSL语言数据类型,最后写一个窗口渲染示例(与GLSL无关的)。

二、渲染

3D模型:它是以文件的形式提供给开发人员,开发人员需要学习如何读取3D模型的信息并进行计算与重新组合从而绘制出3D模型。静态的3D模型一般是游戏中固定的景物或道具;动态的3D模型需要计算并存储每一帧模型信息,然后按照顺序逐帧绘制模型,当速度足够快就显示出动画效果。

 3D地形:3D地形可以呈现不同的环境与地貌,如高山,雪地,树林等。3D地形通过一个或多个地形网格所组成,配合不同的高度值就可以设置高低起伏的地形变化,配合不同纹理图可以生成不同地形环境。开发人员通过程序生成一张(地形网格)就是一个个大小相同而位置不同的连续的三角形/正方形。

 纹理:要为每个地形子网络计算出正确的纹理坐标,这样纹理才可以被正确地映射到地形上面。纹理是一张二维图片(色彩信息),原图只是均匀的地形子网络。

 3D地形平滑处理:地形中网格高度值差距较大时,显得地形比较粗糙,因此需要对地形进行平滑处理。

3D地形块衔接: 当开发人员根据高度信息创建了许多地形块,下一步将要将地形块进行拼接,形成一个更大的地形。(图片拼接)当地形移动时,需要注意远景和近景表现效果。

斜坡地形光照:

光照与法线用于实现3D图片的明暗对比,较高的物体会遮挡较矮的物体形成阴影区域。实现这一效果使用基于斜坡的光照,定义光源的位置和方向。

 地形子纹理

制作一张很大的纹理图需要不同的小的子纹理图构建,当需要改动时,只需要修改其中一个子纹理即可,具备灵活性。下图时WarCraft地形子纹理图定义。

 纹理的镂空与重叠:两张纹理图重叠时,上层纹理图透明部分可以看到下层纹理图。下图中黑色部分看到下一层纹理图叫做镂空。其他非镂空部分直接是上层覆盖下层。

 子纹理组合:不同的子纹理组合出一个完整的图形。这种做法让地形变化更灵活,通过有限的纹理图,拼接出多种不同形状的地形纹理,同时可以拼接出一个无限大地形纹理。

  光照与法线

光照是发光物体所发射出来的光线,如太阳、灯。

法线是垂直平面的向量,代表平面的方向。平面正对光源比较明亮,背对光源黑暗。

 当我们计算出每个平面(子网格)法线后,根据法线和光照的方向进行计算,每个平面法线不一样导致亮度不一样,所以仍能看到一个一个方格,需要在边界处进一步进行过渡处理。

 法线平均值:对各个平面之间进行平滑过渡处理,从而避免了不同平面之间出现忽明忽暗的情况

 光照转动:当我们不停的改变光照方向时就可以改变每个平面上的亮度,从而模拟出白天和黑夜情况。

动态阴影:阴影是物体在光照下背向光照时所投下的影子。动态阴影是影子会随着(模型动画的变化)或(光照位置的变化)而变化。模型阴影需要通过一个着色器去结合光源位置与模型顶点位置计算绘制出影子。

三、GLSL

3.1 数据类型

(1)向量

向量类型描述为2维、3维、4维向量,向量中的数据可以是(整数、浮点、布尔数据)3种类型。在GLSL种向量数据用于描述颜色、位置、纹理坐标等。

浮点:vec2,vec3,vec4

整型:ivec2,ivec3,ivec4

布尔类型:bvec2,bvec3,bvec4

向量初始化

vec3 a;

a= vec3(1.0,2.0,3.0); 

vec4 b = vec4(1.0,2.0,3.0,4.0);

vec4 p[10];//定义了vec4类型的数组,大小10个

描述位置或者方向:x,y,z,w获取分量

描述颜色:r,g,b,a获取分量

描述纹理坐标:s,t,p,q获取分量

访问向量:以数组的形式访问 a[0]

(2)矩阵:

矩阵是按照 列顺序 存放的,取出矩阵数据可以使用一维数组或二维数组形式。

mat2描述2x2浮点矩阵;mat3描述3x3浮点矩阵;mat4描述4x4浮点矩阵;

mat2 m = mat2(1.0,2.0,3.0,4.0);   m[1][0] = 3;//第2列,第1行, 第一个表示列

m[1]获取第2列元素(3,4)返回一个2维向量 vec2(3,4)

 (3)采样器类型:

采样器类型描述纹理数据,纹理其实是图片,所以在着色器中我们可以对图片中数据进行获取并处理。

sampler1D访问一维纹理;sampler2D访问二维纹理;sampler3D访问三维纹理;

samplerCube访问一个立方贴图纹理;

sampler1DShadow访问带对比的1维深度纹理;sampler2DShadow访问带对比的2维深度纹理

sampler2D a;//定义采样器变量a;
sampler2D b[2]; //定义采样器数组b,有两个数组元素即2张纹理,b[0] b[1]

(4)限定符 in

说明它所指定的变量在着色器中作为输入数据,同时指定不能被改变或赋值。

被in所指定的变量会作为一个入口,让数据可以通过该变量输入到GLSL所编写的着色器程序中使用;数据来源包括设定的顶点数据、纹理坐标、向量等,也可以是上一阶段着色器输出的结果。

in vec4 P;//通过in限定符指定了变量p作为一个数据入口,同时变量类型维vec4

(5)限定符 out

用于说明它所指定的变量在着色器中作为数据输出。可以在顶点着色器把out指定的变量中数据,输出给片元着色器。

out vec4 p;//指定变量p作为数据输出

out vec2 t = vec2(2.0,3.0);//指定变量t作为数据输出,并赋值

p = vec4(1,2,3,4);//对变量p赋值

(6)限定符uniform

所指定的变量在着色器中作为数据输入,所指定变量在着色器不能被改变。被uniform 所指定的变量会让数据通过该变量输入搞着色器程序使用。类型包括矩阵、向量、浮点、整型、纹理。

uniform 类型 变量名

uniform变量

uniform变量是application传递给shader的变量,在application外部赋值,在shader内部,不能被修改。如果uniform变量在vertex和fragment两者之间声明方式一样,则可以在vertex和fragment共享使用。

uniform一般用来表示:材质,参数,变换矩阵和公用颜色等信息。

uniform vec3 lightPosition;//光源位置

uniform mat4 viewMatrix;//视图矩阵

attribute变量

attribute变量只能在vertex shader中使用的变量,不能在fragment shader中声明attribute变量,也不能在fragment shader中使用。

一般用于表示一些顶点数据,如:发现,纹理坐标,顶点坐标,顶点颜色等等

varying变量

varying变量用于vertex和fragment shader之间做数据传递使用。一般vertex shader修改varying,fragment shader使用该varying值。因此varying在vertex和fragment声明需要一致

3.2 编程步骤

GLSL(OpenGL Shading Language)是一种专门用于编写图形着色器的着色语言。GLSL编写流程一般包括以下几个步骤:

  1. 创建GLSL程序:使用OpenGL的函数来创建GLSL程序对象,并绑定着色器代码。
  2. 编写着色器代码:使用GLSL语言编写顶点着色器和片段着色器代码。
  3. 编译着色器:使用OpenGL的函数来编译顶点着色器和片段着色器代码。
  4. 链接着色器:使用OpenGL的函数来将编译后的顶点着色器和片段着色器代码链接成一个GLSL程序。
  5. 使用GLSL程序:在绘制图形之前,使用OpenGL的函数来激活GLSL程序,在绘制图形时使用GLSL程序进行着色。
  6. 删除GLSL程序:使用OpenGL的函数来删除GLSL程序,释放资源。
     

四、渲染窗口

#include "glad.h"
#include <GL/glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
	// glfw: initialize and configure,设置GLFW窗口基本属性
	// ------------------------------
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

	// glfw window creation创建窗口
	// --------------------
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);//通知GLFW将窗口的上下文设置为当前线程的主上下文
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//设置窗口大小改变回调函数

	// glad: load all OpenGL function pointers加载系统相关的OPENGL函数指针地址函数
	// ---------------------------------------
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}
	float Red = 0.2;
	float Green = 0.3;
	float Blue = 0.3;
	float* ThisColor = &Red;
	int colorChangeCnt = 0;
	// render loop 主动关闭之前不断绘制图像并接受用户输入,这一步也叫做渲染循环
	// -----------
	while (!glfwWindowShouldClose(window))//每次循环检查GLFW是否被强制要求退出
	{
		// input
		// -----
		processInput(window);//检查是否按下esc按键,退出
		//渲染指令处理
		colorChangeCnt++;
		if (colorChangeCnt >= 1000)
		{
			*ThisColor += 0.1;
			colorChangeCnt = 0;
		}
		if (Red > 1.0)
		{
			Red = 0;
			ThisColor = &Green;
		}
		if (Green > 1.0)
		{
			Green = 0;
			ThisColor = &Blue;
		}
		if (Blue > 1.0)
		{
			Blue = 0;
			ThisColor = &Red;
		}
		//自定义一个颜色清屏
		glClearColor(Red, Green, Blue, 1.0);//设置清空屏幕所用的颜色
		glClear(GL_COLOR_BUFFER_BIT);//接受缓冲位指定要清空的缓冲,GL_DEPTH_BUFFER_BIT,GL_STENCIL_BUFFER_BIT,调用该函数后,颜色缓冲区为glClearColor设置的颜色

		// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
		// -------------------------------------------------------------------------------
		glfwSwapBuffers(window);//双缓冲功能,即解决单缓冲闪烁问题。前缓冲保持最终输出图像,后缓冲执行渲染指令,执行结束后,交换前后缓冲区
		glfwPollEvents();//检查有没有触发什么事件(键盘输入、鼠标移动等)、更新窗口状态,调用回调函数
	}
	// glfw: terminate, clearing all previously allocated GLFW resources.
	// ------------------------------------------------------------------
	glfwTerminate();//正确释放、删除之前分配的资源
	return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// make sure the viewport matches the new window dimensions; note that width and 
	// height will be significantly larger than specified on retina displays.
	glViewport(0, 0, width, height);//视口比窗口小
	std::cout << "我变了" << std::endl;
}

五、渲染三角形

顶点数组对象vertex Array Object, VAO;顶点缓冲对象 vertex Buffer Object, VBO;元素缓冲对象 Element Buffer Object EBO;索引缓冲对象 Index Buffer Object IBO;

3D坐标转为2D坐标的处理过程叫做OpenGL 图形渲染管线Graphics pipeline。

OpenGL是一个3D图形库,指定的坐标默认都是3D坐标。

图形管线分为两部分:

  1. 把3D坐标转化为2D坐标;
  2. 把2D坐标转变为实际有颜色的像素。

2D坐标和像素不同,前者说2D空间的具体位置,2D像素是2D坐标的近似值,2D像素受到屏幕窗口分辨率限制。

着色器Shader:图形渲染管线可以划分为几个阶段,每个阶段把前一阶段输出作为输入。这些阶段是高度专门化的,很容易并行执行,当今显卡有成千上万个小处理器核心,它们在GPU为每一个渲染管线阶段运行各自小程序,从而可以快速处理你的数据,这些小程序叫着色器。

 上图蓝色部分代表是我们可以注入自定义的着色器部分。图形管线从转换顶点到最终像素过程。

顶点着色器:把单独顶点作为输入,3D坐标转为另一种3D坐标,处理顶点属性

图元装配:顶点着色器作为输入(GL_POINTS,一个顶点),指定所有点装配成的形状

几何着色器:把图元形式的一系列顶点集合作为输入,可以通过产生新顶点构造新的图元来生成其他形状。该着色器是可选的,通常使用默认的着色器即可。

光栅化:把图元映射到最终屏幕上相应像素,生成供片段着色器使用的片段。剪切掉视图以外所有像素来提高运行效率。OpenGL一个片段是渲染一个像素所需的所有数据。

片段着色器:计算像素最终颜色,包含3D场景数据(光照、阴影、光的颜色等)

测试与混合:深度测试,模板合并,透明度混合

必须定义至少一个顶点着色器与片段着色器。

顶点:3D坐标在3个轴上是(-1,1)才进行处理。这个范围内的坐标叫做标准化设备坐标NDC,此范围内的坐标最终显示在屏幕上。

Z坐标:代表一个像素在空间中和你的距离,如果离你远就可能被别的像素遮挡,你就看不到它了,它会被丢弃,以节省资源。

glViewPort数据进行视口变换;标准化设备坐标会变换为屏幕空间坐标。所得的屏幕空间坐标又会被转换为片段输入到片段着色器中。

5.1 顶点输入

//定义顶点数据
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};
//改缓冲有一个独一无二的ID
unsigned int VBO;
//生成一个缓冲ID VBO对象
glGenBuffers(1,&VBO);
//绑定缓冲对象类型
glBindBuffer(GL_ARRAY_BUFFER,&VBO);//OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型
/*从现在开始任何GL_ARRAY_BUFFER目标缓冲调用都会用来配置当前绑定的VBO缓冲*/
//把之前用户定义的顶点数据复制到缓冲的内存
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

//
1.glGenBuffers
n: Specifies the number of buffer object names to be generated.个数
buffers: Specifies an array in which the generated buffer object names are stored.对象名
void glGenBuffers(	GLsizei	n, GLuint *	buffers);
2.glBindBuffer
target:Specifies the target to which the buffer object is bound, which must be one of the buffer binding targets in table;缓冲类型
buffer: Specifies the name of a buffer object.对象名字
void glBindBuffer(	GLenum target,GLuint buffer);

3.glBufferData
target:
Specifies the target to which the buffer object is bound for glBufferData, which must be one of the buffer binding targets in the following table.指定缓冲区类型
size:
Specifies the size in bytes of the buffer object's new data store.字节大小
data:
Specifies a pointer to data that will be copied into the data store for initialization, or NULL if no data is to be copied.输入发送的实际数据
usage:Specifies the expected usage pattern of the data store.数据存储用法样式 
GL_STATIC_DRAW :数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变。
void glBufferData(	GLenum target,
 	GLsizeiptr size,
 	const GLvoid * data,
 	GLenum usage);

 5.2 顶点着色器

1)编写

#version 330 core    版本声明
//in 声明一个输入顶点属性
layout (location = 0) in vec3 aPos;//layout 标识符把position位置属性设置为0


void main()
{
    //向量数据类型,包含1到4个float分量x,y,z,w=1.0f
    //定义顶点着色器输出为gl_position,把vec3转化为4分量vec4
    gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0);
}

在真实程序中输入数据通常不是标准化设备坐标,所以首先必须转换为OpenGL可视区域内

(2)编译

运行时动态编译它的源代码:

//暂时将顶点着色器的源代码 硬编码 在代码文件 顶部 的C风格 字符串 中
const char *vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";
//创建一个着色器对象,ID引用
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

//将着色器源码附加到着色器对象中,然后编译它
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
glCompileShader(vertexShader);

//检查是否编译成功
int success;
char infoLog[512];
glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);//检查是否编译成功
if(!success)//失败
{
    glGetShaderInfoLog(vertexShader,512,NULL,infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}


1.创建着色器
GLuint glCreateShader(	GLenum shaderType);  //返回一个着色器对象
shaderType:指定创建着色器的类型
Specifies the type of shader to be created. Must be one of GL_COMPUTE_SHADER, GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_GEOMETRY_SHADER, or GL_FRAGMENT_SHADER.

2.添加源码
void glShaderSource(	GLuint shader,
 	GLsizei count,
 	const GLchar **string,
 	const GLint *length);

shader着色器对象
Specifies the handle of the shader object whose source code is to be replaced.

count指定传递的源码字符串数量
Specifies the number of elements in the string and length arrays.

string:源码字符串指针
Specifies an array of pointers to strings containing the source code to be loaded into the shader.

length:长度
Specifies an array of string lengths.

3.编译着色器
void glCompileShader(	GLuint shader);
shader:指定着色器对象
Specifies the shader object to be compiled.

4.编译结果
void glGetShaderiv(	GLuint shader,
 	GLenum pname,
 	GLint *params);

shader:指定着色器
Specifies the shader object to be queried.

pname:指定状态
Specifies the object parameter. Accepted symbolic names are GL_SHADER_TYPE, GL_DELETE_STATUS, GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH, GL_SHADER_SOURCE_LENGTH.

params:状态返回结果
Returns the requested object parameter.

5.获取编译信息
void glGetShaderInfoLog(	GLuint shader,
 	GLsizei maxLength,
 	GLsizei *length,
 	GLchar *infoLog);

shader着色器
Specifies the shader object whose information log is to be queried.

maxLength log信息最大长度
Specifies the size of the character buffer for storing the returned information log.

length:返回log的字节长度
Returns the length of the string returned in infoLog (excluding the null terminator).

infoLog:返回信息
Specifies an array of characters that is used to return the information log.


5.3 片段着色器

(1) 编写

片段着色器Fragment Shader 所做的是计算像素最后的颜色输出。

片段着色器只需要一个输出变量, 4分量向量,表示最终输出的颜色,我们应当计算出来。

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);//橘黄色
}

(2)编译

const char *fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n\0";
unsigned int FragmentShader;
FragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

5.4 着色器链接

(1)  编写链接

链接是指把两个或多个着色器对象链接到一个用来渲染的着色器程序中。着色器程序(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。链接就是把每个着色器的输出链接到下一个着色器作为输入

//创建一个程序对象
unsigned int shaderProgram;
shaderProgram = glCreateProgram();//创建程序,返回创建引用
glAttachShader(shaderProgram, vertexShader); //添加顶点引用
glAttachShader(shaderProgram, fragmentShader);//添加片段引用
glLinkProgram(shaderProgram);//链接

(2)测试链接

glGetProgramiv(shaderProgram,GL_LINK_STATUS,&success);
if(!success)
{
    glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
}

链接成功的话得到一个程序对象,调用glUseProgram函数:

//该函数调用后,每个着色器和渲染调用都会使用该程序对象
glUseProgram(shaderProgram);

//链接后,着色器对象不再需要,需要删除相关对象
glDeleteShader(vetexShader);
glDeleteShader(fragmentShader);

目前已经输入顶点数据给GPU,指示GPU如何在顶点和片段着色器中处理。

OpenGL还不知道如何解释内存中的顶点数据,以及如何将顶点数据链接到顶点着色器属性上。

5.5 顶点属性

顶点着色器允许我们指定任何以顶点属性为形式的输入。开发者必须指定输入数据的哪一部分对应顶点着色器哪一个顶点属性。在渲染前指定OpenGL如何解释顶点数据。

 每个顶点属性都是从一个VBO管理的内存获得数据,调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO

// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定缓冲类型
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//拷贝数据到缓冲区
// 1.设置顶点属性, 告诉内存如何解释顶点输入属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);//启用 位置 顶点属性,默认是禁用的
// 2.当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
//3. 绘制物体
someDraw();

/
void glVertexAttribPointer(	GLuint index,
 	GLint size,
 	GLenum type,
 	GLboolean normalized,
 	GLsizei stride,
 	const GLvoid * pointer);

index: 指定整体顶点属性索引 0 position
Specifies the index of the generic vertex attribute to be modified.

size:指定每个顶点属性几个构成部分
Specifies the number of components per generic vertex attribute. Must be 1, 2, 3, 4. Additionally, the symbolic constant GL_BGRA is accepted by glVertexAttribPointer. The initial value is 4.

type:指定每个部分数据类型
Specifies the data type of each component in the array. The symbolic constants GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, and GL_UNSIGNED_INT are accepted by glVertexAttribPointer and glVertexAttribIPointer. Additionally GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV and GL_UNSIGNED_INT_10F_11F_11F_REV are accepted by glVertexAttribPointer. GL_DOUBLE is also accepted by glVertexAttribLPointer and is the only token accepted by the type parameter for that function. The initial value is GL_FLOAT.

normalized:指定定点数据值是否需要被标准化(true (-1,1)),访问时直接转化为定点值(false)
For glVertexAttribPointer, specifies whether fixed-point data values should be normalized (GL_TRUE) or converted directly as fixed-point values (GL_FALSE) when they are accessed.

stride:指定数据偏移,步长;设置为0,让OpenGL去决定步长多少
Specifies the byte offset between consecutive generic vertex attributes. If stride is 0, the generic vertex attributes are understood to be tightly packed in the array. The initial value is 0.

pointer:表示位置数据在缓冲中起始位置的偏移量(Offset)
Specifies a offset of the first component of the first generic vertex attribute in the array in the data store of the buffer currently bound to the GL_ARRAY_BUFFER target. The initial value is 0.





5.6 VAO管理顶点属性

当存在上百个不同物体时,绑定到正确的缓冲对象以及为每一个物体配置所有顶点属性将很困难。

顶点数组对象(Vertex Array Object , VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。VAO可以把所有状态配置存储在一个对象中,并且可以通过绑定这个对象来恢复状态

当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中

VAO解释VBO缓冲区的内存。一个VAO对应多个VBO,VBO每个描述的属性不同。一个VAO可以绘制或者对应一种渲染形式。

  1. 使用glBindVertexArray绑定VAO
  2. 绑定和配置对应的VBO和属性指针
  3. 解绑VBO,解绑VAO
//初始化运行,设置着色器
unsigned int VAO;
glGenVertexArrays(1, &VAO);
//1.0绑定VAO
glBindVertexArray(VAO)
//2,0绑定缓冲区类型,把顶点数组复制到缓冲区 VBO供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//3.0设置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用位置顶点属性
//4.0解绑
glBindBuffer(GL_ARRAY_BUFFER, 0); 
glBindVertexArray(0); 
//5.0调用 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);


void glGenVertexArrays(	GLsizei n,
 	GLuint *arrays);
n:表示对象数量
Specifies the number of vertex array object names to generate.
arrays:对象指针
Specifies an array in which the generated vertex array object names are stored.

void glDrawArrays(	GLenum mode,
 	GLint first,
 	GLsizei count);
mode: 图形
Specifies what kind of primitives to render. Symbolic constants GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_LINE_STRIP_ADJACENCY, GL_LINES_ADJACENCY, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_TRIANGLE_STRIP_ADJACENCY, GL_TRIANGLES_ADJACENCY and GL_PATCHES are accepted.
first:开始索引
Specifies the starting index in the enabled arrays.
count:渲染点个数
Specifies the number of indices to be rendered.

5.7 画图


#include "glad.h"
#include <GL/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

//将顶点着色器的源码硬编码在C风格字符串中
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    //lesson 2: build and compile shader program
    //1.0顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    //1.1检查编译结果
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    //2.0片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    //2.1检查编译结果
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    //3.0着色器链接
    //3.1创建着色器程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    //3.2检查编译结果
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    //3.3删除对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    //4.0输入顶点数据、绑定顶点数组对象
    float vertices[] = {
    -0.5f, -0.5f, 0.0f, // left  
     0.5f, -0.5f, 0.0f, // right 
     0.0f,  0.5f, 0.0f  // top   
    };
    unsigned int VBO,VAO;
    //创建顶点数组对象
    glGenVertexArrays(1, &VAO);//对象数量,对象地址
    //创建缓冲区对象
    glGenBuffers(1, &VBO);
    //绑定顶点数组对象
    glBindVertexArray(VAO);
    //在VAO下,绑定缓冲对象
    glBindBuffer(GL_ARRAY_BUFFER,VBO);
    //在VBO下,传递缓冲数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices),vertices, GL_STATIC_DRAW);
    //告知VAO,该如何解释VBO的顶点信息
    /*
    index: 指定整体顶点属性索引 0 position
    size:指定每个顶点属性几个构成部分
    type:指定每个部分数据类型
    normalized:指定定点数据值是否需要被标准化(true (-1,1)),访问时直接转化为定点值(false)
    stride:指定数据偏移,步长;设置为0,让OpenGL去决定步长多少
    pointer:表示位置数据在缓冲中起始位置的偏移量(Offset)
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //启动顶点 位置 属性
    glEnableVertexAttribArray(0);
    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER,0);
    //解绑VAO
    glBindVertexArray(0);
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);
        // render
        //背景颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        //画三角形
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0,3);
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    //释放资源
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);
    glDeleteProgram(shaderProgram);
    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

六、渲染四边形

6.1 元素缓冲对象EBO

元素缓冲对象(Element Buffer Object , EBO)也叫索引缓冲对象(index Buffer Object, IBO )。EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引,索引绘制可以去除重复的顶点。

三者之间的关系

glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取其索引。这意味着我们每次想要使用索引渲染对象时都必须绑定相应的EBO,这又有点麻烦。碰巧顶点数组对象也跟踪元素缓冲区对象绑定。在绑定VAO时,绑定的最后一个元素缓冲区对象存储为VAO的元素缓冲区对象。然后,绑定到VAO也会自动绑定该EBO

举例说明: 

  • VBO相当于上述杂乱无章的字符串

  • VAO就相当于GPU能看懂的obj格式

  • EBO就相当于obj格式中的f规定哪些点规定一个三角形,可以避免顶点的重复设置(比如一个正方形若不引用EBO,则需要六个顶点构成两个三角形,若引入EBO则只需要四个顶点就可以构成两三角形。)

//创建元素缓冲对象
unsigned int EBO;
glGenBuffers(1, &EBO);
//绑定EBO,glBufferData把索引复制到缓冲,类型设置为GL_ELEMENT_ARRAY_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

//绘图,使用当前绑定的索引缓冲对象中的索引进行绘制
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

/
从数组数据渲染基本图形
void glDrawElements(	GLenum mode,
 	GLsizei count,
 	GLenum type,
 	const GLvoid * indices);

mode:绘制模式,图形类型
Specifies what kind of primitives to render. Symbolic constants GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_LINE_STRIP_ADJACENCY, GL_LINES_ADJACENCY, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_TRIANGLE_STRIP_ADJACENCY, GL_TRIANGLES_ADJACENCY and GL_PATCHES are accepted.

count:指定元素数量,绘制顶点个数
Specifies the number of elements to be rendered.

type:索引值类型
Specifies the type of the values in indices. Must be one of GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT.

indices:指定索引起始偏移量
Specifies a pointer to the location where the indices are stored.

6.2 运行

#include "glad.h"
#include <GL/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

//将顶点着色器的源码硬编码在C风格字符串中
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    //lesson 2: build and compile shader program
    //1.0顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    //1.1检查编译结果
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    //2.0片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    //2.1检查编译结果
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    //3.0着色器链接
    //3.1创建着色器程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    //3.2检查编译结果
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    //3.3删除对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    //4.0输入顶点数据、绑定顶点数组对象
    float vertices[] = {
         0.5f,  0.5f, 0.0f,  // top right
         0.5f, -0.5f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  // bottom left
        -0.5f,  0.5f, 0.0f   // top left 
    };
    unsigned int indices[] = {  // note that we start from 0!
        0, 1, 3,  // first Triangle
        1, 2, 3   // second Triangle
    };
    unsigned int VBO,VAO,EBO;
    //创建顶点数组对象
    glGenVertexArrays(1, &VAO);//对象数量,对象地址
    //创建缓冲区对象
    glGenBuffers(1, &VBO);
    //创建元素缓冲对象
    glGenBuffers(1, &EBO);
    //绑定顶点数组对象
    glBindVertexArray(VAO);
    //在VAO下,绑定缓冲对象
    glBindBuffer(GL_ARRAY_BUFFER,VBO);
    //在VBO下,传递缓冲数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices),vertices, GL_STATIC_DRAW);
    //在EBO下,传递索引数据
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    //告知VAO,该如何解释VBO的顶点信息
    /*
    index: 指定整体顶点属性索引 0 position
    size:指定每个顶点属性几个构成部分
    type:指定每个部分数据类型
    normalized:指定定点数据值是否需要被标准化(true (-1,1)),访问时直接转化为定点值(false)
    stride:指定数据偏移,步长;设置为0,让OpenGL去决定步长多少
    pointer:表示位置数据在缓冲中起始位置的偏移量(Offset)
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //启动顶点 位置 属性
    glEnableVertexAttribArray(0);
    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER,0);
    //解绑VAO
    glBindVertexArray(0);
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);
        // render
        //背景颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        //画三角形
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    //释放资源
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glDeleteProgram(shaderProgram);
    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

  参考:

图像学纹理:图形学:纹理概述 - 知乎 (zhihu.com)

OpenGL 绑定顶点数组对象 | AI学习网 (aixuexiwang.com)

LearnOpenGL CN (learnopengl-cn.github.io)

glsl编写流程_Msura的博客-CSDN博客

VAO与ABO关系: 【图像】【OpenGL】VAO和VBO的关系_vao vbo_西西敏的博客-CSDN博客

VAO、VBO、EBO关系OpenGL基础概念理解(1) VAO/VB0/EBO - zxddesk - 博客园 (cnblogs.com)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值