OpenGL-普通着色和Shader类(代码及使用方法)

目录

普通着色

RGBA颜色

渐变三角形

单色矩形

着色器类

下载地址

使用方法

举个栗子

函数列表

GLuint glCreateShader

void glShaderSource

void glCompileShader

GLuint glCreateProgram

void glAttachShader

void glLinkProgram

void glDeleteShader

void glUseProgram


参考:LearnOpenGL

普通着色

这里就介绍一个RGBA吧,还是要学Shader的。

RGBA颜色

RGBA模式中,每一个像素会保存以下数据:

R值(红色分量)、G值(绿色分量)、B值(蓝色分量)和A值(alpha分量)。其中红、绿、蓝三种颜色相组合,就可以得到我们所需要的各种颜色,而alpha值不直接影响颜色。
glColor*系列函数可以用于设置颜色。其中三个参数的版本可以指定R、G、B的值,而A值采用默认;四个参数的版本可以分别指定R、G、B、A的值。

例如:
void glColor3f(GLfloat red, GLfloat green, GLfloat blue);
void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
将浮点数作为参数,其中0.0表示不使用该种颜色,而1.0表示将该颜色用的最多。

例如:
glColor3f(1.0f, 0.0f, 0.0f);    表示不使用绿、蓝色,而将红色使用最多,于是得到最纯净的红色。
glColor3f(0.0f, 1.0f, 1.0f);    表示使用绿、蓝色到最多,而不使用红色。混合的效果就是浅蓝色。
glColor3f(0.5f, 0.5f, 0.5f);    表示各种颜色使用一半,效果为灰色。
注意:浮点数可以精确到小数点后若干位,这并不表示计算机就可以显示如此多种颜色。实际上,如果OpenGL找不到精确的颜色,会进行类似“四舍五入”的处理。


注意:glColor系列函数,在参数类型不同时,表示“最大”颜色的值也不同。
采用f和d做后缀的函数,以1.0表示最大的使用。
采用b做后缀的函数,以127表示最大的使用。
采用ub做后缀的函数,以255表示最大的使用。
采用s做后缀的函数,以32767表示最大的使用。
采用us做后缀的函数,以65535表示最大的使用。

图片来源:百度百科

RGB空间

渐变三角形

main.cpp

#include <GL/glut.h>
//显示函数
void myDisplay(void)

{
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_TRIANGLES);
	glColor3f(0.0f, 1.0f, 1.0f);//0 255 255
	glVertex3f(-0.5f, -0.5f, 0.0f);
	glColor3f(1.0f, 1.0f, 0.0f);//255 255 0
	glVertex3f(0.5f, -0.5f, 0.0f);
	glColor3f(1.0f, 0.0f, 1.0f);// 255 0 255
	glVertex3f(0.0f, 0.5f, 0.0f);
	glEnd();
	glFlush();
}
//主函数
int main(int argc, char *argv[])

{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(800, 600);
	glutCreateWindow("渐变三角形");
	glutDisplayFunc(&myDisplay);
	glutMainLoop();
	return 0;
}

结果截图

渐变三角形截图

单色矩形

main.cpp

#include <GL/glut.h>
//显示矩形函数
void myDisplay(void)
{
	glClear(GL_COLOR_BUFFER_BIT);//清除屏幕
	glColor3f(1.0f,0.0f,1.0f);   //255 0 255
	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(100, 100);//设置位置和窗口大小
	glutInitWindowSize(400, 400);
	glutCreateWindow("有颜色的矩形");
	glutDisplayFunc(&myDisplay);//传递函数
	glutMainLoop();
	return 0;
}

结果截图

单色矩形截图

 

着色器类

为什么使用着色器类?你不感觉之前的代码很乱吗?用字符串去定义真的很low,把着色器从代码中拿出去,方便进行着色的修改,当然,自己写着色器类真的很麻烦。

大神的代码不多,我拜读了一下,讲一些我对着色器类的理解,不想听原理的直接看下面的使用方法吧。

着色器类工作原理

我们不想在main函数中做那么多东西,着色器部分直接在里面用字符串的形式写死也很不适合,那我们就写一个类,从文件读好了。

我们直接把GLSL写的顶点着色器文件和片段着色器文件直接读入cpu吧,,,如果你这像就大错特错

当然没有那么简单,至少要经过内存吧。不要忘记CPU的速度太快了,学过计算机组成原理的同学应该知道需要协调它们之间的速度。

在c++(OpenGL是API或者叫状态机,其他语言也可以使用)中,用ifstream可以作为缓存,就相当于图中的FileBuffer。

stringstream就相当于StringBuffer了。现在我们从文件流转到了字符串流,是不是再用一下字符串流的str()函数就得到字符串,可以用了呢?原则上来说就可以了,但是OpenGL的函数接收的是const char *,所以还要用c_str()函数转一下。经过千辛万苦,终于把GLSL编写的着色器代码读入了,当然,你的GLSL代码可能出错,还需要检查等,自己写这个类真的会很累。

没错,有大神已经帮你做了,包含

  • 读点着色器及片段着色器文件到ifstream流
  • 从ifstream流转成string流(做缓冲)
  • 从string流转到string
  • 再从string转换为char *
  • 编译、链接着色器
  • 着色器使用方法
  • 抛出一些可能出现的异常,如文件无法打开,点着色器代码编译错误

真的是想的很全面,感谢大神随手的恩赐。

下载地址

先给出我加注释的 Shader.h, 基本上没有改,除了调试时加了两行输出点着色器及片段着色器资源文件到控制台

/*
 Date:2019-04-12
 Author:Frank yu(Translate)
*/
#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>// 包含glad来获取所有的必须OpenGL头文件
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader
{
public:
	// 着色器程序ID
	unsigned int ID;

	//构造函数 读取点着色器和片段着色器
	Shader(const char* vertexPath, const char* fragmentPath)
	{
		// 1. 从文件路径打开文件
		std::string vertexCode;
		std::string fragmentCode;
		std::ifstream vShaderFile;
		std::ifstream fShaderFile;
		// 确保ifstream流能够抛出异常
		vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
		fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
		try
		{
			// 打开文件
			vShaderFile.open(vertexPath);
			fShaderFile.open(fragmentPath);
			std::stringstream vShaderStream, fShaderStream;
			// 从文件流中读入到buffer中
			vShaderStream << vShaderFile.rdbuf();
			fShaderStream << fShaderFile.rdbuf();
			// 关闭文件流
			vShaderFile.close();
			fShaderFile.close();
			// 把流转到string里面
			vertexCode = vShaderStream.str();
			fragmentCode = fShaderStream.str();
			//自己加的,用于测试,打印到控制台
			//std::cout << vertexCode << std::endl;
			//std::cout << fragmentCode << std::endl;
		}
		catch (std::ifstream::failure e)
		{
			std::cout << "错误:Shader读取文件失败" << std::endl;
		}
		//转换成参数要求char *类型,
		const char* vShaderCode = vertexCode.c_str();
		const char * fShaderCode = fragmentCode.c_str();
		// 2. 编译着色器
		unsigned int vertex, fragment;
		// 点着色器
		vertex = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(vertex, 1, &vShaderCode, NULL);
		glCompileShader(vertex);
		checkCompileErrors(vertex, "VERTEX");
		// 片段着色器
		fragment = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragment, 1, &fShaderCode, NULL);
		glCompileShader(fragment);
		checkCompileErrors(fragment, "FRAGMENT");
		// 着色器程序
		ID = glCreateProgram();
		glAttachShader(ID, vertex);
		glAttachShader(ID, fragment);
		glLinkProgram(ID);
		checkCompileErrors(ID, "PROGRAM");
		// 删除着色器,因为它们已经链接到我们的程序中了,已经不再需要了
		glDeleteShader(vertex);
		glDeleteShader(fragment);
	}
	// 激活着色器类
	// ------------------------------------------------------------------------
	//使用着色器类
	void use()
	{
		glUseProgram(ID);
	}
	// 比较实用的统一函数
	// ------------------------------------------------------------------------
	void setBool(const std::string &name, bool value) const
	{
		glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
	}
	// ------------------------------------------------------------------------
	void setInt(const std::string &name, int value) const
	{
		glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
	}
	// ------------------------------------------------------------------------
	void setFloat(const std::string &name, float value) const
	{
		glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
	}

private:
	// 用于检查着色器编译 / 链接错误的实用程序函数。
	// ------------------------------------------------------------------------
	void checkCompileErrors(unsigned int shader, std::string type)
	{
		int success;
		char infoLog[1024]; //存储信息日志
		if (type != "PROGRAM")
		{
			glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
			if (!success)
			{
				glGetShaderInfoLog(shader, 1024, NULL, infoLog);
				std::cout << "错误:Shader编译错误: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
			}
		}
		else
		{
			glGetProgramiv(shader, GL_LINK_STATUS, &success);
			if (!success)
			{
				glGetProgramInfoLog(shader, 1024, NULL, infoLog);
				std::cout << "错误:Shader链接错误: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
			}
		}
	}
};
#endif

你可以从这里下载:Shader.h

使用方法

  • 下载好的头文件加入到项目中
  • 在那你的 .cpp文件中加入 #include "Shader.h"
  • 然后就是利用构造函数读入你的着色器文件,例如 Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");
  • 最后直接用就好 myShader->use();

是不是很强大?是的 当然,应该还有很多强大的着色器类,我也是小白,跟着教程走的,大神请绕道。

举个栗子

新建项目,项目结构如下:

项目结构

Shade.h就是上面的, glad.c就是之前文章中下载的,如果你不知道就翻翻前面的文章。

main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"

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

// 设置窗体宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

//主函数
int main()
{
	
	// 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); // uncomment this statement to fix compilation on OS X
#endif

	 // glfw 窗体生成
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "firstTriangle", 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: 加载所有的OpenGL功能指针
	// ---------------------------------------
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}
	// 建立并编译着色器
	// ------------------------------------
	Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");

	// 设置点数据 (还有缓冲) 配置点的属性(包含点坐标等) 这里设置了4个,将以索引的方式选择点来画三角形
	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,1.0f,0.0f,1.0f, //左上
	};
	unsigned int indices[] = {  // note that we start from 0!
		0, 1, 3,  // 第一个三角形选择索引为 0 1 3的三个点
	    1, 2, 3,   // 第一个三角形选择索引为 1 2 3的三个点
	};
	unsigned int VBO, VAO, EBO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);  //注意,这里使用EBO作为缓冲对象
							// 绑定顶点数组, 然后绑定并设置缓冲, 最后配置顶点属性.
	glBindVertexArray(VAO);

	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//修改属性
	glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(6);
	glVertexAttribPointer(7, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3*sizeof(float)));
	glEnableVertexAttribArray(7);

	//注意这是允许的,对glVertexAttribPointer的调用将VBO注册为顶点属性的绑定顶点缓冲区对象,所以之后我们可以安全地解除绑定
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// 记住:当VAO处于活动状态时,不要取消绑定EBO,因为绑定元素缓冲对象IS存储在VAO中; 保持EBO的约束力。
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	// 您可以在之后取消绑定VAO,以便其他VAO调用不会意外地修改此VAO,但这种情况很少发生。无论如何, 
	// 修改其他VAO需要调用glBindVertexArray,因此我们通常不会在不直接需要时解除VAO(VBO同样)的绑定。
	glBindVertexArray(0);

	// 取消注释此调用会绘制线框多边形。
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// render loop
	// -----------
	while (!glfwWindowShouldClose(window))
	{
		// 输入
		// -----
		processInput(window);

		// render
		// ------
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// 画第一个三角形
		//glUseProgram(shaderProgram);
		myShader->use();
		glBindVertexArray(VAO); //可以知道我们只有一个三角形VAO,没必要每次都绑定它,但是我们这么做会让代码有一点组织性
								//glDrawArrays(GL_TRIANGLES, 0, 6);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		// glBindVertexArray(0); //没必要每次都解绑 

		// 交换buffers和poll的IO事件 (按键按下/释放,鼠标移动等.)
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// optional: 一旦他们超出已有的资源,就取消所有资源的分配:
	// ------------------------------------------------------------------------
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);

	// glfw:终止,清空之前所有的GLFW的预分配资源
	// ------------------------------------------------------------------
	glfwTerminate();
	return 0;
}

// process all input:查询GLFW相关按键是否被按下/释放,根据情况作出反应
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

// glfw:无论窗口大小何时改变(由操作系统或用户自己)这个回调函数将会被执行
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	//确定viewport与新的窗口尺寸匹配; 请注意,宽度和高度将明显大于显示器上指定的宽度和高度。
	glViewport(0, 0, width, height);
}

vertexSource.txt

#version 330 core
layout (location = 6) in vec3 aPos;
layout (location = 7) in vec3 aColor;
out vec4 vertexColor;
void main()
{
   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
   vertexColor = vec4(aColor.x, aColor.y, aColor.z, 1.0);
}

fragmentSource.txt

#version 330 core
in vec4 vertexColor;
out vec4 FragColor;
void main()
{
   FragColor = vertexColor;
}								

结果截图

彩色矩形

函数列表

GLuint glCreateShader

参数:GL_VERTEX_SHADER、GL_FRAGMENT_SHADER、GL_TESS_CONTROL_SHADER、GL_TESS_EVALUATION_SHADER、GL_GEOMETRY_SHADER、GL_COMPUTE_SHADER中的一个

作用:返回非0整数,作为着色器对象的ID

void glShaderSource

参数:着色器ID、count、着色器代码、长度

作用:着色器代码与着色器对象关联

void glCompileShader

参数::着色器ID

作用:编译着色器代码

GLuint glCreateProgram

参数:无

作用:创建空的着色器程序,返回非0整数,作为着色器程序的ID

void glAttachShader

参数:着色器程序ID、着色器对象ID

作用:将着色器对象关联到着色器程序上

void glLinkProgram

参数:着色器程序ID

作用:处理所有关联的着色器对象生成一个完整的着色器程序

void glDeleteShader

参数:着色器对象ID

作用:删除着色器对象

void glUseProgram

参数:着色器程序ID

作用:使用链接过的着色器程序

更多OpenGL知识:现代OpenGL入门教程

有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。

  • 10
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
OpenGL是一种图形编程接口,被广泛用于计算机图形学和游戏开发领域。下面是一个简单的OpenGL代码实例,用于绘制一个彩色三角形: #include <GL/glut.h> // 定义绘制三角形的方法 void drawTriangle() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 设置清屏颜色为黑色 glClear(GL_COLOR_BUFFER_BIT); // 清屏 glBegin(GL_TRIANGLES); // 开始绘制三角形 glColor3f(1.0f, 0.0f, 0.0f); // 设置顶点颜色为红色 glVertex3f(0.0f, 0.5f, 0.0f); // 设置第一个顶点 glColor3f(0.0f, 1.0f, 0.0f); // 设置顶点颜色为绿色 glVertex3f(-0.5f, -0.5f, 0.0f); // 设置第二个顶点 glColor3f(0.0f, 0.0f, 1.0f); // 设置顶点颜色为蓝色 glVertex3f(0.5f, -0.5f, 0.0f); // 设置第三个顶点 glEnd(); // 结束绘制三角形 glFlush(); // 渲染所有待处理的OpenGL命令 } int main(int argc, char** argv) { glutInit(&argc, argv); // 初始化GLUT库 glutCreateWindow("OpenGL Triangle"); // 创建窗口并设置标题 glutDisplayFunc(drawTriangle); // 注册绘制回调函数 glutMainLoop(); // 进入主循环 return 0; } 在这个例子中,我们使用了GLUT库来简化OpenGL的初始化和窗口管理。通过定义drawTriangle函数,我们实现了三角形的绘制逻辑。在drawTriangle函数内部,我们首先设置了清屏颜色为黑色,然后进行了清屏操作。接着,我们使用glBegin函数开始绘制三角形,并通过glColor3f函数设置了各个顶点的颜色。在glVertex3f函数中,我们定义了三角形的三个顶点的坐标。最后,我们使用glEnd函数结束绘制,并调用glFlush函数来渲染所有待处理的OpenGL命令。 在main函数中,我们首先调用glutInit函数来初始化GLUT库。接着,我们创建了一个窗口并设置了标题。然后,我们注册了绘制回调函数drawTriangle。最后,通过调用glutMainLoop函数进入主循环,不断处理与窗口相关的事件,直到程序结束。 总的来说,这个OpenGL代码实例展示了如何使用OpenGL和GLUT库来绘制一个简单的彩色三角形。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lady_killer9

感谢您的打赏,我会加倍努力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值