Chapter 2. Our First OpenGL Program

学习目标

创建和编译Shader代码
通过OpenGL画图
利用这本书的应用程序架构初始化程序并自行clean up

准备工作(转自配置环境&第一个程序-CSDN博客)

下载安装CMake -> download -> Binary distributions -> Windows x64 Installer
下载sb7code,运行CMake编译(第三步Configure选择win32)

然后关闭Visual Studio,打开文件夹。
把sb7code-master\extern\glfw-3.0.4\build\src\Debug文件夹下的glfw3.lib复制到sb7code-master\lib,并改名为glfw3_d.lib
把sb7code-master\extern\glfw-3.0.4\build\src\Release文件夹下的glfw3.lib复制到sb7code-master\lib,这次不改名
再次运行CMake,这次是生成蓝宝书的示例程序。

成功打开项目后,编译整个工程(直接编译,不用改东西),Debug和Release都要。
这时准备工作已经做好了。
可以开始新建自己的工程了
打开Visual Studio,新建一个新解决方案,复制文件夹

新建项目,配置以下属性
C/C++附加包含目录 – $(SolutionDir)include
附加库目录 – $(SolutionDir)lib
附加依赖项 – sb7_d.lib glfw3_d.lib opengl32.lib glu32.lib,
预处理器 – WIN32 _WINDOWS,
系统-子系统 – 窗口 (/SUBSYSTEM:WINDOWS)

大功告成!复制书上代码至新建的源文件,成功!

第一个程序

// Include the "sb7.h" header file
#include "sb7.h"
// Derive my_application from sb7::application
class my_application : public sb7::application
{
public:
	// Our rendering function
	void render(double currentTime)
	{
		// Simply clear the window with red
		static const GLfloat red[] = { 1.0f, 0.0f, 0.0f, 1.0f };
		glClearBufferfv(GL_COLOR, 0, red);
	}
};
// Our one and only instance of DECLARE_MAIN
DECLARE_MAIN(my_application);

函数命名规则

所有OpenGL函数都以gl开头,并遵循许多命名约定,例如把参数类型加入尾缀,上述的glClearBufferfv中的f是float,v是vector,组合起来意思就是这个函数使用浮点型的向量。
glClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat * value)这个函数用于清除缓冲区为value值,buffer指定缓冲区,drawbuffer表示你要清除的缓冲区的索引。

第二个程序

void render(double currentTime)
{
	const GLfloat color[] = { (float)sin(currentTime) * 0.5f + 0.5f,
	(float)cos(currentTime) * 0.5f + 0.5f, 0.0f, 1.0f };
	glClearBufferfv(GL_COLOR, 0, color);
}

使用Shader

如果想要在屏幕显示出像素,最少使用一个vertex shader和一个fragment shader

一个简单的vertex shader

#version 450 core
void main(void)
{
      gl_Position = vec4(0.0, 0.0, 0.5, 1.0);
}

#version 450 core 表面我们使用4.5的core profile版本的OpenGL。
在顶点着色器中,gl_Position表示顶点的输出位置。

一个简单的fragment shader

#version 450 core
out vec4 color;
void main(void)
{
     color = vec4(0.0, 0.8, 1.0, 1.0);
}

out vec4 color中out关键字声明输出变量,输出的变量将被输出到窗口或屏幕上

编译链接shader

GLuint compile_shaders(void)
{
	GLuint vertex_shader;
	GLuint fragment_shader;
	GLuint program;
	// Source code for vertex shader
	static const GLchar* vertex_shader_source[] =
	{
	"#version 450 core \n"
	" \n"
	"void main(void) \n"
	"{ \n"
	" gl_Position = vec4(0.0, 0.0, 0.5, 1.0); \n"
	"} \n"
	};
	// Source code for fragment shader
	static const GLchar* fragment_shader_source[] =
	{
	"#version 450 core \n"
	" \n"
	"out vec4 color; \n"
	" \n"
	"void main(void) \n"
	"{ \n"
	" color = vec4(0.0, 0.8, 1.0, 1.0); \n"
	"} \n"
	};
	// Create and compile vertex shader
	vertex_shader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex_shader, 1, vertex_shader_source, NULL);
	glCompileShader(vertex_shader);
	// Create and compile fragment shader
	fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment_shader, 1, fragment_shader_source,
		NULL);
	glCompileShader(fragment_shader);
	// Create program, attach shaders to it, and link it
	program = glCreateProgram();
	glAttachShader(program, vertex_shader);
	glAttachShader(program, fragment_shader);
	glLinkProgram(program);
	// Delete the shaders as the program has them now
	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);
	return program;
}

glCreateShader() 创建一个空的着色器对象,准备接受源代码并进行编译。
glShaderSource() 将着色器源代码交给着色器对象,以便它可以保留它的副本。
glCompileShader() 编译着色器对象中包含的任何源代码。
glCreateProgram() 创建一个程序对象,您可以将着色器对象附加到该对象。
glAttachShader() 将着色器对象附加到程序对象。
glLinkProgram() 将附加到程序对象的所有着色器对象链接在一起。
glDeleteShader() 删除着色器对象。将着色器链接到程序对象后,程序将包含二进制代码,不再需要着色器。

shader的使用

class my_application : public sb7::application
{
public:
	// <snip>
	void startup()
	{
		rendering_program = compile_shaders();
		glCreateVertexArrays(1, &vertex_array_object);
		glBindVertexArray(vertex_array_object);
	}
	void shutdown()
	{
		glDeleteVertexArrays(1, &vertex_array_object);
		glDeleteProgram(rendering_program);
		glDeleteVertexArrays(1, &vertex_array_object);
                glPointSize(40.0f);
	}
	// Our rendering function
	void render(double currentTime)
	{
		const GLfloat color[] = { (float)sin(currentTime) * 0.5f + 0.5f,
		(float)cos(currentTime) * 0.5f + 0.5f, 0.0f, 1.0f };
		glClearBufferfv(GL_COLOR, 0, color);
		// Use the program object we created earlier for rendering
		glUseProgram(rendering_program);
		// Draw one point
		glDrawArrays(GL_POINTS, 0, 1);
	}
private:
	GLuint rendering_program;
	GLuint vertex_array_object;
};

由于shader编译好了就不用变了,所以我们只需要在程序一开始的时候(startup)的时候调用这个编译程序并保留编译所得program的句柄即可。
glDrawArrays(GL_POINTS, 0, 1)中,GL_POINTS指定绘制基元(primitive)的类型,0暂时不用管,1是点的数量。其实虽然说是draw array,但其实这里的array我们并没有分配具体的点,因为我们的shader里面,并没有接受这个数据而是写死的,而这里要创建这个vertex array是因为core profile强制必须创建。
顺带一提,glPointSize()用来设置点的像素大小

画第一个三角形

之前我们的shader代码只有一个点,没办法构成线更或是三角形,所以这里我们修改shader代码(居然也是写死的!)

#shader vertex
#version 450 core  

void main(void)
{
 // Declare a hard-coded array of positions
 const vec4 vertices[3] = vec4[3](vec4(0.25, -0.25, 0.5, 1.0), vec4(-0.25, -0.25, 0.5, 1.0), vec4(0.25, 0.25, 0.5, 1.0));
 // Index into our array using gl_VertexID
 gl_Position = vertices[gl_VertexID];
}
void render(double currentTime)
{
    const GLfloat color[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    glClearBufferfv(GL_COLOR, 0, color);
    // Use the program object we created earlier for rendering
    glUseProgram(rendering_program);
    // Draw one triangle
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

这里使用了gl_VertexID,这个变量指的是当前正在渲染第N个点,因为我们的shader是对所有的点进行着色的,这个变量可以标注是顺序中的第几个点,glDrawArrays(GL_TRIANGLES, 0, 3);中,第三个变量3指的是点的个数,就代表着这个绘制会调用shader三次,第一次gl_VertexID为0,第二次为1,第三次为2。

总结

本节我们写了第一个OpenGL程序,但是这一节中,所有的数据都是固定在shader代码里面,并没有从我们的程序中获取数据,后面我们会学习如何从程序传递数据给shader,如何在shader之间传递数据等等。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

大纲

Share the Post:
滚动至顶部