异构计算GLSL学习笔记(1)
原文地址:http://blog.csdn.net/hjimce/article/details/51475644
作者:hjimce
最近开始学习深度学习的一些gpu编程,大体学了cuda后,感觉要在手机上跑深度学习,没有nvidia显卡,不能加速。所以只能老老实实的学习opengl的shader编程,进行gpu通用计算加速,总的感觉shader编程比cuda编程难,还好自己之前研究生的时候,经常用opengl的一些API函数,对opengl比较了解。对于每一种语言,最简单的学习程序就是:hello world,下面记录一下shader编程最简单的例子,利用gpu加速实现彩色图像转灰度图像。大体包含五个步骤:
1、初始化环境、创建shader程序等;
2、根据需求,编写片元着色器的纹理图像处理代码;
3、从cpu传入数据(利用uniform类型变量,传数据到shader中,如果是图片可以直接采用纹理传入);
4、设置渲染模型、纹理坐标,绘制矩形,进行渲染计算;
5、取回图片处理后的数据;
一、opengl与shader环境初始化、编译链接
(1)初始化环境
- //1、初始化环境
- glutInit(&argc, argv);
- //glutInitWindowSize(512,512);
- //glutInitWindowPosition(100,100);
- glutCreateWindow("GLEW Test");
- glewExperimental = GL_TRUE;
- glewInit();
- if (!glewIsSupported("GL_VERSION_4_0"))
- {
- std::cerr << "Failed to initialize GLEW with OpenGL 4.0!" << std::endl;
- return EXIT_FAILURE;
- }
(2)创建链接、编译shader程序
- //2、读取、创建shader程序,编译连接等
- auto program_id = ShaderProgram("shader/gl_texture.vert", "shader/gl_texture.frag");
- glUseProgram(program_id);
ShaerProgram函数的代码如下:
- GLuint ShaderProgram(const std::string &vertex_shader_file, const std::string &fragment_shader_file) {
- // 创建shader程序
- auto vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
- auto fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
- auto result = GL_FALSE;
- auto info_length = 0;
- // 读取shader源码
- std::ifstream vertex_shader_stream(vertex_shader_file);
- std::string vertex_shader_code((std::istreambuf_iterator<char>(vertex_shader_stream)), std::istreambuf_iterator<char>());
- std::ifstream fragment_shader_stream(fragment_shader_file);
- std::string fragment_shader_code((std::istreambuf_iterator<char>(fragment_shader_stream)), std::istreambuf_iterator<char>());
- // 编译顶点shader代码
- std::cout << "Compiling Vertex Shader ..." << std::endl;
- auto vertex_shader_code_ptr = vertex_shader_code.c_str();
- glShaderSource(vertex_shader_id, 1, &vertex_shader_code_ptr, NULL);
- glCompileShader(vertex_shader_id);
- // Check vertex shader log
- glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result);
- if (result == GL_FALSE) {
- glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_length);
- std::string vertex_shader_log((unsigned int)info_length, ' ');
- glGetShaderInfoLog(vertex_shader_id, info_length, NULL, &vertex_shader_log[0]);
- std::cout << vertex_shader_log << std::endl;
- }
- // 编译片元着色器代码
- std::cout << "Compiling Fragment Shader ..." << std::endl;
- auto fragment_shader_code_ptr = fragment_shader_code.c_str();
- glShaderSource(fragment_shader_id, 1, &fragment_shader_code_ptr, NULL);
- glCompileShader(fragment_shader_id);
- // Check fragment shader log
- glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result);
- if (result == GL_FALSE) {
- glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_length);
- std::string fragment_shader_log((unsigned long)info_length, ' ');
- glGetShaderInfoLog(fragment_shader_id, info_length, NULL, &fragment_shader_log[0]);
- std::cout << fragment_shader_log << std::endl;
- }
- // 创建链接程序
- std::cout << "Linking Shader Program ..." << std::endl;
- auto program_id = glCreateProgram();
- glAttachShader(program_id, vertex_shader_id);
- glAttachShader(program_id, fragment_shader_id);
- glBindFragDataLocation(program_id, 0, "FragmentColor");
- glLinkProgram(program_id);
- //打印编译信息,如果编译错误,就可以看见错误信息了
- glGetProgramiv(program_id, GL_LINK_STATUS, &result);
- if (result == GL_FALSE) {
- glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_length);
- std::string program_log((unsigned long)info_length, ' ');
- glGetProgramInfoLog(program_id, info_length, NULL, &program_log[0]);
- std::cout << program_log << std::endl;
- }
- glDeleteShader(vertex_shader_id);
- glDeleteShader(fragment_shader_id);
- return program_id;
- }
二、编写shader程序
上面在创建shader程序的时候,需要shader源码,对于图像处理来说,顶点着色器我们一般不需要用到,主要是利用片元着色器进行图像处理的相关计算。
(1)顶点着色器源码文件gl_texture.vert的代码如下:
- #version 400
- //顶点着色器输入
- in vec2 Position;
- in vec2 TexCoord;
- //顶点着色器的输出,这个变量会进入片元着色器
- out vec2 fragTexCoord;
- void main() {
- // Copy the input to the fragment shader
- fragTexCoord =TexCoord.xy;
- // Calculate the final position on screen
- // Note the visible portion of the screen is in <-1,1> range for x and y coordinates
- gl_Position = vec4(Position.x,Position.y, 0.0, 1.0);
- }
- #version 400
- // 需要注意opengl纹理输入
- uniform sampler2D Texture;
- // 来自顶点着色器
- in vec2 fragTexCoord;
- //输出
- out vec4 FragmentColor;
- void main() {
- // Lookup the color in Texture on coordinates given by fragTexCoord
- vec3 pColor = texture(Texture, fragTexCoord.xy).rgb;
- //转换成灰度图像
- float gray=pColor.r*0.2126+ 0.7152* pColor.g + 0.0722*pColor.b;
- FragmentColor =vec4(gray, gray,gray, 1.0);
- }
三、opencv读取图片、输入纹理、启用纹理等
- //3、设置纹理相关参数、或者输入shader计算所需要的数据
- auto texture_id = LoadImage("2.jpg", height_width, height_width);//读入一张图片,转成纹理格式,把并把图片数据拷贝到opengl纹理单元。
- auto texture_attrib = glGetUniformLocation(program_id, "Texture");//找到shader程序中,变量名为Texture,类型为uniform的变量索引
- glUniform1i(texture_attrib,0);
- glActiveTexture(GL_TEXTURE0 + 0);//启用第一个纹理,并绑定纹理数据
- glBindTexture(GL_TEXTURE_2D, texture_id);
LoadImage函数代码如下:
- GLuint LoadImage(const std::string &image_file, unsigned int width, unsigned int height)
- {
- // Create new texture object
- GLuint texture_id;
- glGenTextures(1, &texture_id);
- glBindTexture(GL_TEXTURE_2D, texture_id);
- // Set mipmaps
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- cv::Mat texture_cv=cv::imread(image_file);
- if (texture_cv.empty())
- {
- return -1;
- }
- else
- {
- glTexImage2D(GL_TEXTURE_2D, 0, 3, texture_cv.cols, texture_cv.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, texture_cv.data);
- return texture_id;
- }
- }
四、绘制渲染、计算
通过绘制一个矩形,该矩形的纹理刚好就是我们的图片,这样就可以实现gpu图片处理了:
- //4、设置渲染相关参数(矩形4个顶点、及其对应的纹理坐标)
- InitializeGeometry(program_id);
- //5、绘制、渲染
- glClearColor(0.f,0.f,0.f,1.f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
InitializeGeometry函数:
- void InitializeGeometry(GLuint program_id)
- {
- // Generate a vertex array object
- GLuint vao;
- glGenVertexArrays(1, &vao);
- glBindVertexArray(vao);
- // 顶点缓存
- GLfloat tempv[12] = {1.0f,1.0f,-1.0f,1.0f,1.0f,-1.0f,-1.0f, -1.0f}; //二维顶点坐标,分别为矩形的四个顶点坐标
- std::vector<GLfloat> vertex_buffer(tempv , tempv+12);
- // Generate a vertex buffer object
- GLuint vbo;
- glGenBuffers(1, &vbo);
- glBindBuffer(GL_ARRAY_BUFFER, vbo);
- glBufferData(GL_ARRAY_BUFFER, vertex_buffer.size() * sizeof(GLfloat), vertex_buffer.data(), GL_STATIC_DRAW);
- // Setup vertex array lookup
- auto position_attrib = glGetAttribLocation(program_id, "Position");
- glVertexAttribPointer(position_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
- glEnableVertexAttribArray(position_attrib);
- // Generate another vertex buffer object for texture coordinates
- GLfloat temptex[8] = {1.0f,0.0f,0.0f,0.0f,1.0f,1.0f,0.0f, 1.0f};
- std::vector<GLfloat> texcoord_buffer(temptex,temptex+8);
- GLuint tbo;
- glGenBuffers(1, &tbo);
- glBindBuffer(GL_ARRAY_BUFFER, tbo);
- glBufferData(GL_ARRAY_BUFFER, texcoord_buffer.size() * sizeof(GLfloat), texcoord_buffer.data(), GL_STATIC_DRAW);
- auto texcoord_attrib = glGetAttribLocation(program_id, "TexCoord");
- glVertexAttribPointer(texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
- glEnableVertexAttribArray(texcoord_attrib);
- glBindVertexArray(vao);
- }
这个需要设置好顶点坐标和纹理坐标。
五、取出数据到opencv中
渲染完毕后,我们就要把图像处理的结果保存回opencv的cv::Mat上,然后看看处理结果
- void show(int height=height_width,int width=height_width)
- {
- cv::Mat img=cv::Mat::zeros(height, width, CV_8UC3);
- //use fast 4-byte alignment (default anyway) if possible
- //glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);
- //set length of one complete row in destination data (doesn't need to equal img.cols)
- //glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
- glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);//opencv存储为BGR顺序
- cv::flip(img, img, 0);//需要翻转
- cv::imshow("result",img);
- cv::waitKey(0);
- }
最后贴一下主程序流程的完整代码:
- #include "stdafx.h"
- #include <stdlib.h>
- #include "include/glew.h"
- #include "include/GLUT.H"
- #include <opencv2/opencv.hpp>
- #include "shader.h"
- #define height_width 512
- void show(int height=height_width,int width=height_width)
- {
- cv::Mat img=cv::Mat::zeros(height, width, CV_8UC3);
- //use fast 4-byte alignment (default anyway) if possible
- //glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);
- //set length of one complete row in destination data (doesn't need to equal img.cols)
- //glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
- glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);//opencv存储为BGR顺序
- cv::flip(img, img, 0);//需要翻转
- cv::imshow("result",img);
- cv::waitKey(0);
- }
- int main( int argc, char** argv )
- {
- //1、初始化环境
- glutInit(&argc, argv);
- //glutInitWindowSize(512,512);
- //glutInitWindowPosition(100,100);
- glutCreateWindow("GLEW Test");
- glewExperimental = GL_TRUE;
- glewInit();
- if (!glewIsSupported("GL_VERSION_4_0"))
- {
- std::cerr << "Failed to initialize GLEW with OpenGL 4.0!" << std::endl;
- return EXIT_FAILURE;
- }
- //2、读取、创建shader程序,编译连接等
- auto program_id = ShaderProgram("shader/gl_texture.vert", "shader/gl_texture.frag");
- glUseProgram(program_id);
- //3、设置纹理相关参数、或者输入shader计算所需要的数据
- auto texture_id = LoadImage("2.jpg", height_width, height_width);//读入一张图片,转成纹理格式,把并把图片数据拷贝到opengl纹理单元。
- auto texture_attrib = glGetUniformLocation(program_id, "Texture");//找到shader程序中,变量名为Texture,类型为uniform的变量索引
- glUniform1i(texture_attrib,0);
- glActiveTexture(GL_TEXTURE0 + 0);//启用第一个纹理,并绑定纹理数据
- glBindTexture(GL_TEXTURE_2D, texture_id);
- //4、设置渲染相关参数(矩形4个顶点、及其对应的纹理坐标)
- InitializeGeometry(program_id);
- //5、绘制、渲染
- glClearColor(0.f,0.f,0.f,1.f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- //6、取回数据到opencv,并显示结果
- show();//
- return 0;
- }
结果:
原图 处理结果