如果你还不知道什么是纹理,可以先看一下LearnOpenGL-CN中的纹理章节。
简单的思路就是用OpenGL画一个矩形,然后将一张图片作为纹理贴到这个正方形上。为了能够将纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分,这样的话每个顶点都会关联一个纹理坐标。
首先,需要定义好矩形(两个三角形)的顶点和纹理坐标的对应关系:
在OpenGL中,所有的坐标都是3D坐标(x, y, z),OpenGL会把所有的3D坐标转换为所谓的标准化设备坐标,坐标在范围为[-1.0, 1.0],只有落在这个范围内的坐标最终才会呈现在屏幕上。
标准化设备坐标的坐标系: 原点在屏幕中间,y轴正方向向上,x轴正方向向右,z轴正方向垂直屏幕指向屏幕前的你。 所以,如果我们要顶一个矩形的4个顶点,使它撑满视窗的话,坐标定义如下:
然后定义每个顶点对应的纹理坐标: 我们使用的是2D纹理图像,纹理坐标在x轴和y轴,范围在[0,1],纹理坐标起始于(0, 0),也就是纹理图像的左下角。坐标定义如下所示:
于是,矩形的坐标和纹理的坐标的对应关系就找到了。定义为数组的形式如下:
GLfloat vertices[] = {
// Positions // Texture Coords
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // Top Right
1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // Bottom Right
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // Bottom Left
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f // Top Left
};
然后要解决的是如何将cvMat转换为OpenGL中的纹理:
首先,定义一个函数用来创建一个纹理,返回该纹理对应的ID:
static GLuint createTexture()
{
GLuint textureID;
// 创建一个纹理对象,并返回一个独一无二的标识保存在textureID中
glGenTextures(1, &textureID);
// 绑定textureID标识的纹理,之后的所有操作都是相对于该纹理的
glBindTexture(GL_TEXTURE_2D, textureID);
// 设置纹理环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
// 设置纹理过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 解绑
glBindTexture(GL_TEXTURE_2D, 0);
return textureID;
}
然后用下面的函数设置纹理的内容:
static void cvmatToTexture(GLuint& textureId, const cv::Mat& mat)
{
// 绑定textureID标识的纹理,之后的所有操作都是相对于该纹理的
glBindTexture(GL_TEXTURE_2D, textureId);
// 注意OpenCV中图像通道的顺序是BGR
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, mat.cols, mat.rows, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, mat.data);
glGenerateMipmap(GL_TEXTURE_2D);
// 解绑
glBindTexture(GL_TEXTURE_2D, 0);
}
剩下的就是常规的渲染正方形的操作了。参考LearnOpenGL-CN的代码即可。
下面基于上面的内容,编写一个小的工程,用OpenCV从USB摄像头获取图像,然后用OpenGL显示。
获取摄像头图像线程函数定义如下:
void captureImage(int deviceId)
{
cv::VideoCapture cap(deviceId);
if (!cap.isOpened()) {
printf("Open camera failed!!!\n");
exit(-1);
}
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
if (prepared)
continue;
cv::Mat frame;
cap >> frame;
if (!frame.data)
continue;
imageBuf[bufId] = frame;
prepared = true;
}
}
主线程定义如下:
int main() {
std::thread t(captureImage, 0);
t.detach();
// 初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 创建一个OpenGL上下文
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Camera", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
// 初始化glew
glewExperimental = GL_TRUE;
glewInit();
glViewport(0, 0, WIDTH, HEIGHT);
// 创建并编译shader程序
Shader ourShader("./vertex_shader.vs", "./fragment_shader.frag");
// 定义顶点属性
GLfloat vertices[] = {
// Positions // Texture Coords
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // Top Right
1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // Bottom Right
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // Bottom Left
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f // Top Left
};
// 一个正方形是由两个三角形组成的
GLuint indices[] = {
0, 1, 3, // First Triangle
1, 2, 3 // Second Triangle
};
GLuint VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO); // 绑定VAO
// 给VBO指定缓冲目标,并设置缓冲数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 给EBO指定缓冲目标,并设置缓冲数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 指定顶点数据格式
glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * sizeof(GL_FLOAT), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
glBindVertexArray(0); // 解绑
// 创建一个纹理
GLuint texture = createTexture();
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// 清空颜色缓冲区
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (prepared) {
cvmatToTexture(texture, imageBuf[bufId]);
bufId = (bufId + 1) % 2;
prepared = false;
// 激活纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(glGetUniformLocation(ourShader.program, "ourTexture1"), 0);
ourShader.Use();
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
运行结果:
完整工程代码可以参考OpenGL_show_camera。