学习目标
创建和编译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之间传递数据等等。