好的,又是因为要面试,这个面试需要复习opengl、图像算法、机器学习、算法题。所以会写相应的系列文章。。。
首先opengl是一个做显示的的东西。首先对他的一个基本流程有一个初步的概念。
常见的工作流程:
opengl的搭建可以借助vs2017,在nuget安装nupengl
安装之后可以试一下跑一个最简单的函数
//初始化 glut
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(SCR_WIDTH, 70);
glutInitWindowSize(SCR_WIDTH, SCR_HEIGHT);
glutCreateWindow("test");
// 初始化 glew(功能与glad类似)
glewInit();
// 程序中需要的初始化,比如加载shader、加载文字、加载素材等
init();
glutTimerFunc(20, timer, 0);
// 设置主函数
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutMouseFunc(MouseEvent);
glutMotionFunc(MotionMove);
// 主函数循环
glutMainLoop();
我们能看到的三维效果其实也是一系列的变化后的二维图片,这个过程就是opengl图像渲染管线要干的事。其实随着技术的迭代跟新,已经由传统的顶点着色器和片段着色器,衍生了很多别的部分,如下图所示
针对顶点着色器:
顶点包括x、y和z方向,需要注意,要将数据归一化到 [-1,1] 这个区间。
比如这是一组顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
绘制出来的的三角形如下
在高级一点的版本里有顶点缓冲对象(VBO),它主要做缓存处理,如果不使用,顶点会一个个发送到显卡内存区,比较占CPU。
下面是一个三角形的顶点着色器
#version 330 core
// 输入数据 aPos输入是一个(x,y)的向量
// aColor是一个4维向量
attribute vec2 aPos;
attribute vec4 aColor;
// 输出至片段着色器的数据
out vec4 Color;
void main()
{
// color在顶点着色器里不用,直接输出
Color=aColor;
// gl_Position是内置函数,这里是4个参数,x,y,z还有alpha值,暂时alpha值为1
gl_Position = vec4(aPos, 0.0, 1.0);
}
下面是一个三角形的顶点着色器
#version 330 core
// 从顶点着色器的输入
in vec4 Color;
void main()
{
// gl_FragColor 是内置函数,这里是4个参数
gl_FragColor = Color;
}
最简单的串接步骤如下所示:
因为这个步骤比较程式化,所以写好一个shader读取函数基本就是通用变了。
我这里贴出我自己的一个框架:
static char* readShaderSource(const char *fileName)
{
FILE *fp;
char *content = NULL;
int count = 0;
if (fileName != NULL)
{
fp = fopen(fileName, "rt");
if (fp != NULL)
{
fseek(fp, 0, SEEK_END);
count = ftell(fp);
rewind(fp);
if (count > 0)
{
content = (char *)malloc(sizeof(char) * (count + 1));
count = fread(content, sizeof(char), count, fp);
content[count] = NULL;
}
fclose(fp);
}
}
return content;
}
static GLuint genShader(GLenum type, const char* filename, char*& log)
{
GLuint shader = glCreateShader(type);
char* shaderSource = readShaderSource(filename);
if (!shaderSource)
{
printf("Error:\tCan not read shader file--%s ,please confirm the file name is right!\n",filename);
pause();
}
const char* ptrShaderSource = shaderSource;
glShaderSource(shader, 1, &ptrShaderSource, NULL);
free(shaderSource);
glCompileShader(shader);
GLint status = 0;
//查看编译状态
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (!status)
{
GLint length;
//读取日志信息的长度
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
log = (GLchar*)malloc(length);
//读取日志信息
glGetShaderInfoLog(shader, length, &length, log);
#if 1
printf("Error in generating shaders!\n");
printf("\tError:%s\n", log);
#endif
//删除着色器对象
glDeleteShader(shader);
pause();
}
return shader;
}
static GLuint linkProgram(GLuint* shader, int shaderNum, char*& log)
{
//创建着色器程序
GLuint program = glCreateProgram();
int i;
//往着色器程序中加入着色器对象
for (i = 0; i < shaderNum; i++)
glAttachShader(program, shader[i]);
//链接着色器程序
glLinkProgram(program);
GLint status;
//查看链接状态
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (!status)
{
GLint length;
//读取日志信息的长度
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
log = (GLchar*)malloc(length);
//读取日志信息
glGetProgramInfoLog(program, length, &length, log);
#if 1
printf("Error in linking shaders!\n");
printf("\tError:%s\n", log);
#endif
//删除着色器对象
glDeleteProgram(program);
pause();
}
return program;
}
static GLuint Shader(const char *vsfileName, const char *fsfileName, const char* geometryPath = nullptr)
{
char* log = NULL;
GLuint tvertexShader = genShader(GL_VERTEX_SHADER, vsfileName, log);
if (!tvertexShader)
{
free(log);
return false;
}
GLuint tfragmentShader = genShader(GL_FRAGMENT_SHADER, fsfileName, log);
if (!tfragmentShader)
{
free(log);
return false;
}
GLuint tgeometryShader;
if (geometryPath != nullptr)
{
tgeometryShader = genShader(GL_GEOMETRY_SHADER, geometryPath, log);
if (!tgeometryShader)
{
free(log);
return false;
}
}
GLuint program;
if (geometryPath == nullptr)
{
GLuint shader[2] = { tvertexShader, tfragmentShader };
program = linkProgram(shader, 2, log);
}
else if (geometryPath != nullptr)
{
GLuint shader[3] = { tvertexShader, tfragmentShader, tgeometryShader };
program = linkProgram(shader, 3, log);
}
if (!program)
{
glDeleteShader(tvertexShader);
glDeleteShader(tfragmentShader);
free(log);
return false;
}
return program;
}
void LoadShader(int id)
{
shader_id = Shader("shader/prog_2D_color.vs", "shader/prog_2D_color.fs");
Prgm_2D_color_ATTR_aPos = glGetAttribLocation(shader_id, "aPos");
Prgm_2D_color_ATTR_aCol = glGetAttribLocation(shader_id, "aColor");
}
上面的这个我没借助VAO,用的 glVertexAttribPointer ,和上面的glGetAttribLocation对应起来用。
glVertexAttribPointer(Prgm_3D_road_ATTR_aPos, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), road_vertice);
在通过下面的代码绘制
// 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. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
如果使用VAO,其实更像是一个指针管理。如下图所示
这部分我没使用,完整代码opengl绘制矩形、opengl绘制三角形
前面我们能绘制矩形,我们希望这个矩形不只是有颜色,还有纹理。
效果如下图所示
首先这个纹理需要制定对应坐标。
比如上图对应的坐标为
float texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 上中
};
接着要指定环绕方式
GL_REPEAT 对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT 和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。
具体效果如下图所示
注意选择合理的效果!!!!我就曾经在这踩过坑,建议用最后一个啊
对应的代码
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
//我们还需要指定一个边缘的颜色
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
总体而言,加载纹理的代码主要如下(这个也是不怎么需要改的,直接用就好了)
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
纹理的数据怎么传输?
这里含有顶点数据,纹理数据,颜色(主要是透明度)数据
大概数据结构长这样
float vertices[] = {
// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
};
通过设置读取的步长也就是以下函数,告诉顶点着色器怎么接受这些数据。
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
完整的代码:https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/4.1.textures/textures.cpp
这个代码是绘制一个矩形box