[video(video-83qMkG9M-1628401078616)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=762197327)(image-https://img-blog.csdnimg.cn/img_convert/1607e0b671b2d2ac8251654fa64b5633.png)(title-【opengl】高斯模糊)]
-
高斯模糊原理
高斯模糊(高斯滤波)的原理与算法
-
OpenGL环境搭建
glut+glew+linux开发环境搭建
-
实验效果:
原图:
6+6次水平垂直高斯模糊效果:
12+12次水平垂直高斯模糊效果:
50+50次水平垂直高斯模糊效果:
- gles中的算法实现:
#version 130
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D image;
uniform bool horizontal;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{
//模糊步长,最精细为像素级,补偿越小颗粒感越低,同时每次模糊的效果也越低
vec2 tex_offset = 2.0 / textureSize(image, 0); // gets size of single texel
vec3 result = texture(image, TexCoords).rgb * weight[0]; // current fragment's contribution
if(horizontal)
{
for(int i = 1; i < 5; ++i)
{
result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
}
}
else
{
for(int i = 1; i < 5; ++i)
{
result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
}
}
FragColor = vec4(result, 1.0);
}
片段着色器中只是对所有像素乘以高斯核,完成一次水平或者垂直的模糊,要想控制模糊程度需要CPU代码进行多次模糊。
对应的顶点着色器程序:
#version 130
in vec3 aPos; // 位置变量的属性位置值为0
in vec3 aColor;
in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoords = vec2(aTexCoord.x, aTexCoord.y);
}
-
OpenGL乒乓离线帧缓存:
参考:
LearnOpenGL CN专栏 作者:JoeyDeVries
基本思想是:申请两块帧缓存,第一次对原图纹理做一次水平高斯模糊,将结果放入其中一块帧缓存对应的纹理内存。第二次对上一步完成渲染纹理再做垂直高斯模糊,结果放入另一块纹理内存。如此交替进行,类似乒乓球过程,整个过程不进行显示双缓冲的交换,不会显示到屏幕,因此也叫离屏渲染。
生成乒乓帧缓存片段:
glGenFramebuffers(2, pingpongFBO);
glGenTextures(2, pingpongBuffers);
for (unsigned int i = 0; i < 2; i++)
{
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
glBindTexture(GL_TEXTURE_2D, pingpongBuffers[i]);
//大坑!!!这里的纹理尺寸需要与屏幕(视口)像素尺寸一致,当图片尺寸大于屏幕尺寸时,会导致纹理内存填不满
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffers[i], 0);
// also check if framebuffers are complete (no need for depth buffer)
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Framebuffer not complete!" << std::endl;
}
进行乒乓渲染片段:
void blur()
{
GLboolean horizontal = true;
GLboolean first_iteration = true;
GLuint amount = 100;
mshader.use();
for (GLuint i = 0; i < amount; i++)
{
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
glUniform1i(glGetUniformLocation(mshader.ID, "horizontal"), horizontal);
glBindTexture(
GL_TEXTURE_2D, first_iteration ? texture1 : pingpongBuffers[!horizontal]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
///* 测试单步效果
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glutSwapBuffers();
usleep(500 * 1000 );
//*/
horizontal = !horizontal;
if (first_iteration)
first_iteration = false;
}
//可以实现GPU渲染完成再返回,类似linux的barrier机制
glFinish();
}
完整代码:
main.cpp
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <shader.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include<stdio.h>
// settings
const unsigned int SCR_WIDTH = 960;
const unsigned int SCR_HEIGHT = 540;
static Shader mshader;
unsigned int texture1;
unsigned int VBO, VAO, EBO;
GLboolean horizontal = true;
GLuint pingpongFBO[2];
GLuint pingpongBuffers[2];
void reshape(int w, int h);
void display();
void resourceready();
void blur();
int main(int argc, char **argv)
{
#define MILLION 1000000
struct timeval tv1,tv2;
long sec,usec;
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(SCR_WIDTH, SCR_HEIGHT);
glutCreateWindow("SHADER_RECTANGLE");
glewInit();
const GLubyte* name = glGetString(GL_VENDOR); //返回负责当前OpenGL实现厂商的名字
const GLubyte* biaoshifu = glGetString(GL_RENDERER); //返回一个渲染器标识符,通常是个硬件平台
const GLubyte* OpenGLVersion =glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
const GLubyte* GLESVersion =glGetString(GL_SHADING_LANGUAGE_VERSION); //返回当前OpenGL实现的版本号
printf("OpenGL实现厂商的名字:%s\n", name);
printf("渲染器标识符:%s\n", biaoshifu);
printf("OpenGL实现的版本号:%s\n",OpenGLVersion );
printf("GLES语法的版本号:%s\n",GLESVersion );
mshader= Shader("shader.vs", "shader.fs");
resourceready();
gettimeofday(&tv1, NULL);
blur();
gettimeofday(&tv2, NULL);
sec = (tv2.tv_sec - tv1.tv_sec);
usec = (tv2.tv_usec - tv1.tv_usec);
std::cout << "---------tmie cost---------------" << sec <<"(s) "<< usec<<"(us) " << std::endl;
glutReshapeFunc(&reshape);
glutDisplayFunc(&display);
glutMainLoop();
// optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
//glDeleteVertexArrays(1, &VAO);
//glDeleteBuffers(1, &VBO);
//glDeleteBuffers(1, &EBO);
return 0;
}
void reshape(int w, int h)
{
glViewport(0, 0,(GLsizei)w, (GLsizei)h);
}
void resourceready()
{
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float vertices[] = {
// positions // colors // texture coords
1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right
1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left
-1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// texture coord attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
// load and create a texture
// -------------------------
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object
// set the texture wrapping parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); // set texture wrapping to GL_REPEAT (default wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
// set texture filtering parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// load image, create texture and generate mipmaps
int width, height, nrChannels;
stbi_set_flip_vertically_on_load(true);
unsigned char *data = stbi_load("1080.jpeg", &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);
std::cout << "texture1 width " <<width << " hight " << height <<" nrChannels " << nrChannels<< std::endl;
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
glGenFramebuffers(2, pingpongFBO);
glGenTextures(2, pingpongBuffers);
for (unsigned int i = 0; i < 2; i++)
{
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
glBindTexture(GL_TEXTURE_2D, pingpongBuffers[i]);
//大坑!!!这里的纹理尺寸需要与屏幕(视口)像素尺寸一致,当图片尺寸大于屏幕尺寸时,会导致纹理内存填不满
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffers[i], 0);
// also check if framebuffers are complete (no need for depth buffer)
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Framebuffer not complete!" << std::endl;
}
}
void blur()
{
GLboolean first_iteration = true;
GLuint amount = 100;
mshader.use();
for (GLuint i = 0; i < amount; i++)
{
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
glUniform1i(glGetUniformLocation(mshader.ID, "horizontal"), horizontal);
glBindTexture(
GL_TEXTURE_2D, first_iteration ? texture1 : pingpongBuffers[!horizontal]);
//std::cout << "---------------------------loop:" << i << std::endl;
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
///* 测试单步效果
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glutSwapBuffers();
usleep(500 * 1000 );
//*/
horizontal = !horizontal;
if (first_iteration)
first_iteration = false;
}
glFinish();
}
void display()
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// render
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// bind Texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, pingpongBuffers[!horizontal]);
// render container
mshader.use();
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//glDisableVertexAttribArray(0);
glutSwapBuffers();
}
依赖库:
实验步骤:
-
编译
g++ main.cpp -o test -lGL -lglut -lGLEW -I. - 对应目录放图片,命名1080.jpeg
- ./test
- 耗时情况:
OpenGL实现厂商的名字:VMware, Inc.
渲染器标识符:SVGA3D; build: RELEASE; LLVM;
OpenGL实现的版本号:3.0 Mesa 18.0.5
GLES语法的版本号:1.30
texture1 width 1920 hight 1080 nrChannels 3
---------time cost---------------0(s) 72273(us)
版权声明:本文为ludashei2原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。