opengl回顾(二)[2]

接上篇,我们进一步得解释和分离我们的代码

首先我们回顾一下,绘制三角的步骤

1、写出组成三角的顶点数据,三组顶点每组均由3个float分别代表x,y,z坐标

2、创建顶点缓冲对象

3、创建顶点数组对象

3、绑定缓冲对象与数组对象

4、告知opengl如何解释顶点,解释的是第几组顶点,并激活这组顶点

5、编写实际的顶点着色器与片段着色器,着色器处理的是一个顶点/一个像素(着色器代码存在其他的文件中)

6.、创建着色器对象

7、创建文件流读取着色器代码,并把着色器代码附加到着色器对象上

8、附加着色器对象

9、创建着色器程序对象

10、链接着色器对象并删除着色器对象

11、渲染循环中绑定顶点数组对象,启用着色器,调用绘制函数

 

 

那么这里有哪些代码可以分离呢?

首先,所有的创建步骤其实都可以分离。啊是的首先你想到了,把VAO和VBO的创建分离,很遗憾,这是一部分你无法分出的代码,当然你可以尝试,但是VAO,VBO要呆在main函数中创建,总之我们忽略他们,把他们的代码丑陋得留着。

那么我们还能做啥?是的,我们可以分离shader的创建。我们可以创建一个自己的着色器类,来减少每次创建着色器的重复代码。

好的,怎么做呢?提示一下,我们先决定要提供什么。

1、代码文件地址

2、没了

没错,除了代码地址,咱们啥都不需要,因为着色器总共只有三个已知类型,目前我们只用了两个,那么着色器数量的问题也可以通过重载或默认参数来解决。

那么接口呢?我们目前来看,只需要一个use(),即激活着色器。好的那么公有部分就确定了,一个构造函数,一个use接口。

接着是私有函数,首先是文件读取readFile,我们仅需要文件地址。

然后是着色器对象的创建,我们需要着色器的类型、源码来创建一个着色器,别的都一样,也就是需要一个createShader。

然后是着色器程序对象,我们需要链接函数linkShader,还需要一个可以保持的着色器程序对象,也就是一个数据成员。

啊,好的,回过头去看一眼,我们的着色器对象是一个一个创建,故创建着色器对象的函数调用readFile来获取自己的代码也很合理喽。而着色器程序对象是要创建了全部的着色器对象之后才能运作,所以它是独立的;这儿出现一个问题了“着色器程序对象到底有几个着色器对象需要链接?”,出于这个问题,我们会需要std::initializer_list<GLuint>来作为参数,用循环来链接/删除着色器对象。

好的,看一下基于这几点写出的代码。

mShader.h或者shader/h反正随你喜欢

#pragma once
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

class shader {
public:
	shader(const std::string &vSource, const std::string &fSource, 
		const std::string &gSource = std::string())//几何着色器赋予默认值,因为几何着色器为可选着色器
	{
		GLuint vertex, fragment;
		createShader(GL_VERTEX_SHADER, vertex, vSource);
		createShader(GL_FRAGMENT_SHADER, fragment, fSource);
		if (!gSource.empty()) {
			GLuint geometry;
			createShader(GL_GEOMETRY_SHADER, geometry, gSource);
			linkShader({ &vertex,&fragment,&geometry });
		}//检测几何着色器需不需要创建
		else linkShader({ &vertex,&fragment });
	}
	void use() { glUseProgram(program); }

private:
	GLuint program;

	std::string readFile(const std::string &source) const;//接受地址读取文件代码
	//需要文件地址来调用readFile
	void createShader(GLenum type, GLuint &mshader,const std::string &source) const;
	void linkShader(std::initializer_list<GLuint*> shaders);//读入数目不一定的着色器
};

std::string shader::readFile(const std::string &source) const
{
	std::ifstream file;
	//保证ifstream对象可以抛出异常:
	file.exceptions(std::ifstream::failbit | std::ifstream::badbit);

	try {
		file.open(source);
		std::stringstream code;
		code << file.rdbuf();
		return code.str();
		//返回字符串,注意不要用引用,因为不能返回局部对象的引用(code内的string随着code析构释放)
	}
	catch (std::iostream::failure &e)
	{
		std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ\n" << e.what() << std::endl;
	}
	catch (std::exception &e)
	{
		std::cout << "ERROR::SHADER::ERROR_APPEARED_WHEN_IO\n" << e.what() << std::endl;
	}
}

void shader::createShader(GLenum type, GLuint &mshader,const std::string &source) const
{
	std::string code_string = readFile(source);
	const char *code = code_string.c_str();//读取源码

	mshader = glCreateShader(type);
	glShaderSource(mshader, 1, &code, nullptr);
	glCompileShader(mshader);

	int success;
	char infoLog[1024];
	glGetShaderiv(mshader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(mshader, 1024, nullptr, infoLog);
		std::cout  << " SHADER FAILED TO COMPILE\n" << infoLog << std::endl;
	}
}

void shader::linkShader(std::initializer_list<GLuint*> shaders)
{
	{
		program = glCreateProgram();
		int num = sizeof(shaders);
		for (GLuint *s : shaders)
		{
			glAttachShader(program, *s);
		}
		glLinkProgram(program);//先链接再删除
		for (GLuint *s : shaders)
		{
			glDeleteShader(*s);
		}

		int success;
		char infoLog[1024];
		glGetProgramiv(program, GL_LINK_STATUS, &success);
		if (!success)
		{
			glGetProgramInfoLog(program, 1024, nullptr, infoLog);
			std::cout << "ERROR::FAILED TO LINK PROGRAM!\n" << infoLog << std::endl;
		}
	}
}

并没有什么新的东西,所以接下来提一下新的东西。

 

 

 

新的顶点相关对象:索引缓冲对象 EBO

啊哈,我们的xxO家族中又多了一员,EBO(element buffer object)。还记得我们的顶点数据吧,3个一组,一共三组,绘制一个三角,如果是两个三角呢?我们需要glDrawArrays(GL_TRAINGLES, 0, 6);对不对,因为两个三角有6个顶点。那如果两个三角只有四个顶点呢?我们用顶点1/2/3组成一个三角,再用2/3/4组成第二个,非常合理,符合我们绘图的方法。然而VAO只会要求你把2/3两个顶点重复写入到顶点数组里,那么EBO就是让我们不需要增加顶点数据的东西,它标记你使用的顶点,也就是顶点的索引。

当然,它并不能让你只写四个顶点就完成两个三角的绘制,但是它可以让你使用4个顶点加上两组索引完成两个三角的绘制。

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = { // 注意索引从0开始! 
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

就像这样,第二组indices就是顶点的索引数据,目前看起来并没有什么明显的节省,但是如果你要画几百(万)个三角呢?可以想象我们省去的顶点数据。那么接下来我们创建一个EBO。

 

EBO使用

好的,我们使出我们的三板斧,glGenBuffers、glBufferData、glBindBuffer!没错,完全和VBO一样的函数调用,不同的只是参数(还有顺序)。

    GLuint VBO, VAO, EBO;
	glGenBuffers(1, &VBO);
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &EBO);//创建,第一斧
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindVertexArray(VAO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//第二斧,绑定,注意一定在VAO,VBO均绑定后
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    //第三斧,传递索引数据
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(0));
	glEnableVertexAttribArray(0);

然后修改我们的绘制函数

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
//告知opengl绘制图元的方式(绘制背面及 正面,启用线框模式)
mShader.use();
glBindVertexArray(VAO);
//glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//图形,顶点数(索引数),索引类型,偏移

启用线框模式来让绘制轨迹更加清楚(请忽略这个函数的细节),这里注意这个6,是你要绘制的顶点个数,或者说你索引的数量、索引数组的大小。

 

 

 

 

 

新东西之二,拓展你的着色器

1、首先,让顶点着色器向片段着色器发送数据

回想之前的着色器代码

#version 330 core
//使用3.3版本核心模式
layout (location = 0) in vec3 aPos;
//接收位置在0   这个顶点属性是接收进来的(in)  vec3类型的变量    之后使用名字为aPos

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    //处理这个顶点的数据 = vec4(x坐标,y坐标,z坐标, 透视变形值);
}


//-------------------------------------------------

#version 330 core
//同顶点着色器
out vec4 FragColor;
//声明一个要输出(out)的变量    类型为vec4   名字为FragColor

void main()
{
    FragColor = vec4(1.0f, 0.8f, 0.2f, 1.0f);
 //输出的颜色 = vec4类型  red1.0f  green0.8f  blue0.2f   alpha1.0f(最高1.0即不透明)混合的颜色
}

可以看到我们使用in与out关键字来接收/输出数据,那么顶点着色器是在片段着色器之前的着色器,很简单得能想到,我们在顶点着色器中使用out关键字,然后就可以在片段着色器中使用in读取到这些数据。

代码:

#version 330 core
layout(location = 0)in vec3 aPos;

out vec4 beginColor;//发出这个vec4

void main()
{
	//gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f);
	gl_Position = vec4(aPos, 1.0f);     //注意这里,也就是说vec4( vec3,float )的构造是存在的
	beginColor = vec4(0.2f, 0.0f, 0.3f, 1.0f);
}



//-----------------------------以下为片段着色器



#version 330 core
out vec4 FragColor;

in vec4 beginColor;//接收同名的vec4

void main()
{
	FragColor = beginColor;//改变输出的颜色为从顶点着色器得到的变量
}

很简单的使用,顶点着色器中 out 关键字创建一个vec4变量,片段着色器中使用 in 关键字创建同名的变量即可读取这个其他着色器发来的vec4,然后再把读来的vec4赋给自己要输出的FragColor,结果我们的三角就变成了紫色。这实现的是gpu到gpu的通信,因为着色器是在gpu上运行的,当然,我们也可以让cpu以顶点属性之外的方式发送数据给gpu。

 

2、然后,让cpu给gpu发送数据

由cpu发送到gpu也就意味着从你的cpp发送到你的着色器,那么我们需要借助的是uniform变量,还有一个,可能你猜得到,也就是我们的着色器程序对象。

 

首先我们要修改我们的顶点数据,我们需要一组新的属性来传送每个顶点颜色数据

//顶点数据
	float vertices[] = {
		// 位置              // 颜色
		0.5f, 0.5f, 0.0f,    1.0f, 0.0f, 0.0f,   
		0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   
		-0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f,   
		-0.5f, 0.5f, 0.0f,   0.0f, 0.0f, 1.0f
	};
	unsigned int indices[] = { // 注意索引从0开始! 
		0, 1, 3, // 第一个三角形
		1, 2, 3  // 第二个三角形
	};

啊,你可能会奇怪,顶点怎么有颜色呢?一个点如何有颜色?是的,对一个点上色你也很难看出来,然而opengl采用的是片段插值来决定片段的颜色。简单得说,这个顶点上的片段的颜色是你传递的颜色,如果一个片段在顶点1和顶点2直接,它与1的距离为总距离30%,与2的距离就会是(1-30%) = 70%的总距离,那么它的颜色将会是70%顶点1的颜色30%的顶点2的颜色的混合值。也就是这个片段离哪个顶点近,就受到哪个顶点的颜色影响较大。

 

那么我们修改了定的数组,自然也要修改顶点的解析方式,这里我们的顶点数据变成了每隔6个float才能读取到下一个数据,而且对于颜色数据来说,他得先忽略开始的三个顶点数据,也就是颜色数据的偏移量为3 * sizeof(float)。

代码:

//顶点缓冲对象
	GLuint VBO, VAO, EBO;
	glGenBuffers(1, &VBO);
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindVertexArray(VAO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(0));
	glEnableVertexAttribArray(0);//第一组属性步长改变
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);//给出第二组属性,修改步长和偏移值

实际上没有什么新的东西,只是加了两句代码,修改了原来的步长。

 

那么此时编译你会看到一个彩色的三角,算是对之前的回顾把,接下来我们让这个彩色的三角变色。

首先我们要提出新的东西

uniform变量

uniform变量是定义在着色器中的变量,但它的实际值由你在cpp中发送,我们需要三角变色,所以我们会需要一个时刻变换的数字,比如时间,用float表示的时间。

那么我们先读出我们的时间,并且把它限制在(0.0f,1.0f)的范围

float time = sin(glfwGetTime() / 2.0f) + 0.5f;//把时间限定到(0f, 1.0f)的范围

这里使用glfw提供的函数和数学函数,你当然也可以用c++标准函数,随便啦。

然后把这个数值发送到着色器,我们会需要着色器对象。哦豁,问题出现了,我们的着色器对象是私有的,所以我们需要一个接口,一个不能更改着色器对象的接口。

const GLuint& program() const{ return _program; }

private:
	GLuint _program;

如上。

 

然后我们需要获取uniform变量的位置,再发送到着色器。

float time = sin(glfwGetTime() / 2.0f) + 0.5f;//把时间限定到(0f, 1.0f)的范围
int aColorLocation = glGetUniformLocation(mShader.program(), "time");
//寻找uniform变量位置(程序对象,变量名)
if (aColorLocation == -1)//找不到变量的时候返回-1
{
	cout << "ERROR::CAN'T FIND UNIFORM" << endl;
}
mShader.use();//激活着色器后才能传递
glUniform1f(aColorLocation, time);//传递变量

接着在着色器接收即可

#version 330 core
out vec4 FragColor;

in vec4 beginColor;//接收同名的vec4
uniform float time;//接受cpu发来的实时变化时间

void main()
{
	FragColor = beginColor * time;//改变输出的颜色为从顶点着色器得到的变量 * 时间变化
}

注意这里我们使用的是glUniform1f来传递一个float变量,1f代表一个float,还有其他不同格式的uniform传递函数。

后缀含义
f函数需要一个float作为它的值
i函数需要一个int作为它的值
ui函数需要一个unsigned int作为它的值
3f函数需要3个float作为它的值
fv函数需要一个float向量/数组作为它的值

 这个时候我们又可以进一步拓展我们的着色器类,因为unifrom变量总是和着色器绑定在一起的,所以我们把所有的传递uniform变量的函数添加到着色器类中去。

完整代码如下:

shader.h

#pragma once
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

class shader {
public:
	shader(const std::string &vSource, const std::string &fSource, 
		const std::string &gSource = std::string())//几何着色器赋予默认值,因为几何着色器为可选着色器
	{
		GLuint vertex, fragment;
		createShader(GL_VERTEX_SHADER, vertex, vSource);
		createShader(GL_FRAGMENT_SHADER, fragment, fSource);
		if (!gSource.empty()) {
			GLuint geometry;
			createShader(GL_GEOMETRY_SHADER, geometry, gSource);
			linkShader({ &vertex,&fragment,&geometry });
		}//检测几何着色器需不需要创建
		else linkShader({ &vertex,&fragment });
	}
	void use() { glUseProgram(_program); }
	const GLuint& program() const{ return _program; }

	//uniform 工具函数
	void setBool(const std::string &name, bool value) const
	{
		glUniform1i(glGetUniformLocation(_program, name.c_str()), (int)value);
	}
	// ------------------------------------------------------------------------
	void setInt(const std::string &name, int value) const
	{
		glUniform1i(glGetUniformLocation(_program, name.c_str()), value);
	}
	// ------------------------------------------------------------------------
	void setFloat(const std::string &name, float value) const
	{
		glUniform1f(glGetUniformLocation(_program, name.c_str()), value);
	}
	// ------------------------------------------------------------------------
	void setVec2(const std::string &name, const glm::vec2 &value) const
	{
		glUniform2fv(glGetUniformLocation(_program, name.c_str()), 1, &value[0]);
	}
	void setVec2(const std::string &name, float x, float y) const
	{
		glUniform2f(glGetUniformLocation(_program, name.c_str()), x, y);
	}
	// ------------------------------------------------------------------------
	void setVec3(const std::string &name, const glm::vec3 &value) const
	{
		glUniform3fv(glGetUniformLocation(_program, name.c_str()), 1, &value[0]);
	}
	void setVec3(const std::string &name, float x, float y, float z) const
	{
		glUniform3f(glGetUniformLocation(_program, name.c_str()), x, y, z);
	}
	// ------------------------------------------------------------------------
	void setVec4(const std::string &name, const glm::vec4 &value) const
	{
		glUniform4fv(glGetUniformLocation(_program, name.c_str()), 1, &value[0]);
	}
	void setVec4(const std::string &name, float x, float y, float z, float w)
	{
		glUniform4f(glGetUniformLocation(_program, name.c_str()), x, y, z, w);
	}
	// ------------------------------------------------------------------------
	void setMat2(const std::string &name, const glm::mat2 &mat) const
	{
		glUniformMatrix2fv(glGetUniformLocation(_program, name.c_str()), 1, GL_FALSE, &mat[0][0]);
	}
	// ------------------------------------------------------------------------
	void setMat3(const std::string &name, const glm::mat3 &mat) const
	{
		glUniformMatrix3fv(glGetUniformLocation(_program, name.c_str()), 1, GL_FALSE, &mat[0][0]);
	}
	// ------------------------------------------------------------------------
	void setMat4(const std::string &name, const glm::mat4 &mat) const
	{
		glUniformMatrix4fv(glGetUniformLocation(_program, name.c_str()), 1, GL_FALSE, &mat[0][0]);
	}

private:
	GLuint _program;

	std::string readFile(const std::string &source) const;//接受地址读取文件代码
	//需要文件地址来调用readFile
	void createShader(GLenum type, GLuint &mshader,const std::string &source) const;
	void linkShader(std::initializer_list<GLuint*> shaders);//读入数目不一定的着色器
};

std::string shader::readFile(const std::string &source) const
{
	std::ifstream file;
	//保证ifstream对象可以抛出异常:
	file.exceptions(std::ifstream::failbit | std::ifstream::badbit);

	try {
		file.open(source);
		std::stringstream code;
		code << file.rdbuf();
		return code.str();
		//返回字符串,注意不要用引用,因为不能返回局部对象的引用(code内的string随着code析构释放)
	}
	catch (std::iostream::failure &e)
	{
		std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ\n" << e.what() << std::endl;
	}
	catch (std::exception &e)
	{
		std::cout << "ERROR::SHADER::ERROR_APPEARED_WHEN_IO\n" << e.what() << std::endl;
	}
}

void shader::createShader(GLenum type, GLuint &mshader,const std::string &source) const
{
	std::string code_string = readFile(source);
	const char *code = code_string.c_str();//读取源码

	mshader = glCreateShader(type);
	glShaderSource(mshader, 1, &code, nullptr);
	glCompileShader(mshader);

	int success;
	char infoLog[1024];
	glGetShaderiv(mshader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(mshader, 1024, nullptr, infoLog);
		std::cout  << " SHADER FAILED TO COMPILE\n" << infoLog << std::endl;
	}
}

void shader::linkShader(std::initializer_list<GLuint*> shaders)
{
	{
		_program = glCreateProgram();
		int num = sizeof(shaders);
		for (GLuint *s : shaders)
		{
			glAttachShader(_program, *s);
		}
		glLinkProgram(_program);//先链接再删除
		for (GLuint *s : shaders)
		{
			glDeleteShader(*s);
		}

		int success;
		char infoLog[1024];
		glGetProgramiv(_program, GL_LINK_STATUS, &success);
		if (!success)
		{
			glGetProgramInfoLog(_program, 1024, nullptr, infoLog);
			std::cout << "ERROR::FAILED TO LINK PROGRAM!\n" << infoLog << std::endl;
		}
	}
}

 

main.cpp

#include"main.h"


int main()
{
	using namespace initialization;
	GLFWwindow *window = init(SCR_WIDTH,SCR_HEIGHT);

	//顶点数据
	float vertices[] = {
		// 位置              // 颜色
		0.5f, 0.5f, 0.0f,    1.0f, 0.0f, 0.0f,   
		0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   
		-0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f,   
		-0.5f, 0.5f, 0.0f,   0.0f, 0.0f, 1.0f
	};
	unsigned int indices[] = { // 注意索引从0开始! 
		0, 1, 3, // 第一个三角形
		1, 2, 3  // 第二个三角形
	};


	//顶点缓冲对象
	GLuint VBO, VAO, EBO;
	glGenBuffers(1, &VBO);
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindVertexArray(VAO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(0));
	glEnableVertexAttribArray(0);//第一组属性
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);//给出第二组属性,修改步长和偏移值



	//着色器对象,特别注意不要把顶点着色器与片段着色器文件读反,或者附加反,或者用了两个顶点/片段
	shader mShader("vertexShader.vs", "fragmentShader.fs");



	// render loop
	// -----------
	while (!glfwWindowShouldClose(window))
	{
		// render
		// ------
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);//清除指定BUFFER


		// input
		// -----
		processInput(window);
		float time = sin(glfwGetTime() / 2.0f) + 0.5f;//把时间限定到(0f, 1.0f)的范围
		int aColorLocation = glGetUniformLocation(mShader.program(), "time");
		if (aColorLocation == -1)
		{
			cout << "ERROR::CAN'T FIND UNIFORM" << endl;
		}
		mShader.use();
		glUniform1f(aColorLocation, time);
		glBindVertexArray(VAO);
		//glDrawArrays(GL_TRIANGLES, 0, 3);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);



		// 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;
} 

main.h

没有变化,除了增加了#include"shader.h"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值