使用OpenGL实现双线性插值和双三次插值C++实现
整理一下最近跑的两个小程序,该程序的目的是利用OpenGL实现图像的缩放,同时比较双线性插值和双三次插值两种算法的最终效果,于是将这两种算法代码以及结果展出,使用的语言为C++,放大倍数为2倍。
先置条件
1.使用的IDE为:visual studio2022
2.软件平台:Windows11
3.OpenGL版本:3
4.确保 GLFW 已正确安装:下载预编译的 GLFW 库: GLFW 官方下载页面链接;解压后,将 include 文件夹中的头文件和 lib 文件夹中的库文件添加到你的项目中。
5.链接 GLFW 库:
6.因为项目要求,输入的是raw图片,需要输入png或jpg图片的自行修改代码
双线性插值
#include <glew.h>
#include <glfw3.h>
#include <iostream>
#include <fstream>
#include <cstdio>
void checkGLError(const char* label) {
GLenum err;
while ((err = glGetError()) != GL_NO_ERROR) {
std::cerr << "OpenGL Error (" << label << "): " << err << std::endl;
}
}
unsigned char* loadRawImage(const char* path, int& width, int& height, int channels) {
FILE* file;
errno_t err = fopen_s(&file, path, "rb");
if (err != 0 || !file) {
fprintf(stderr, "Failed to open raw image file: %s\n", path);
return nullptr;
}
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
rewind(file);
int expectedSize = width * height * channels;
if (fileSize != expectedSize) {
fprintf(stderr, "File size does not match expected size for the given dimensions and channels.\n");
fclose(file);
return nullptr;
}
unsigned char* imageData = new unsigned char[fileSize];
fread(imageData, 1, fileSize, file);
fclose(file);
return imageData;
}
GLuint createTexture(unsigned char* data, int width, int height, int channels) {
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
GLenum internalFormat = (channels == 3) ? GL_RGB : GL_R8;
GLenum format = (channels == 3) ? GL_RGB : GL_RED;
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return textureID;
}
void saveRawImage(unsigned char* data, int width, int height, int channels, const char* filename) {
std::ofstream outFile(filename, std::ios::binary);
if (!outFile) {
fprintf(stderr, "Failed to open output file: %s\n", filename);
return;
}
int dataSize = width * height * channels;
outFile.write(reinterpret_cast<char*>(data), dataSize);
outFile.close();
}
int main(int argc, char* argv[]) {
if (!glfwInit()) {
fprintf(stderr, "Failed to initialize GLFW\n");
return -1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Image Scaling", NULL, NULL);
if (!window) {
fprintf(stderr, "Failed to create GLFW window\n");
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glewExperimental = GL_TRUE;
GLenum status = glewInit();
if (status != GLEW_OK) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(status));
return -1;
}
// Load shader program and set up OpenGL state here...
const char* vertexShaderSource = "#version 330 core\n"
"layout(location = 0) in vec3 aPos;\n"
"layout(location = 1) in vec2 aTexCoord;\n"
"out vec2 TexCoord;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n"
" TexCoord = aTexCoord;\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec2 TexCoord;\n"
"uniform sampler2D ourTexture;\n"
"void main()\n"
"{\n"
" FragColor = texture(ourTexture, TexCoord);\n"
"}\0";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
printf("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n%s\n", infoLog);
};
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
printf("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n%s\n", infoLog);
};
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
printf("ERROR::PROGRAM::LINKING_FAILED\n%s\n", infoLog);
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
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);
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);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindVertexArray(0); // Unbind VAO
int originalWidth = 2480; // Set your image width here
int originalHeight = 3512; // Set your image height here
int channels = 3; // RGB image
unsigned char* imageData = loadRawImage("input.raw", originalWidth, originalHeight, channels);
if (!imageData) {
glfwDestroyWindow(window);
glfwTerminate();
return -1;
}
int newWidth = originalWidth * 2; // Scale up by three times
int newHeight = originalHeight * 2;
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
GLuint textureColorbuffer;
glGenTextures(1, &textureColorbuffer);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, newWidth, newHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GLuint texture = createTexture(imageData, originalWidth, originalHeight, channels);
delete[] imageData;
while (!glfwWindowShouldClose(window)) {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, newWidth, newHeight);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(glGetUniformLocation(shaderProgram, "ourTexture"), 0);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
unsigned char* frameBufferPixels = new unsigned char[newWidth * newHeight * channels];
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glReadPixels(0, 0, newWidth, newHeight, GL_RGB, GL_UNSIGNED_BYTE, frameBufferPixels);
saveRawImage(frameBufferPixels, newWidth, newHeight, channels, "output.raw");
delete[] frameBufferPixels;
break; // Only scale once and then exit
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteTextures(1, &texture);
glDeleteTextures(1, &textureColorbuffer);
glDeleteFramebuffers(1, &framebuffer);
glDeleteProgram(shaderProgram);
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
双三次插值
#include <glew.h>
#include <glfw3.h>
#include <iostream>
#include <fstream>
#include <vector>
// 着色器源码
const char* vertexShaderSource = R"(
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
TexCoords = aTexCoords;
}
)";
const char* fragmentShaderSource = R"(
#version 330 core
in vec2 TexCoords;
out vec4 FragColor;
uniform sampler2D image;
uniform float texelSize;
float cubic(float x) {
float a = -0.5;
if (x < 0.0) x = -x;
if (x < 1.0) return (a + 2.0) * x * x * x - (a + 3.0) * x * x + 1.0;
if (x < 2.0) return a * x * x * x - 5.0 * a * x * x + 8.0 * a * x - 4.0 * a;
return 0.0;
}
vec4 bicubic(sampler2D tex, vec2 uv) {
vec2 texel = vec2(texelSize, texelSize);
vec2 pixel = uv / texel - vec2(0.5, 0.5);
vec2 f = fract(pixel);
pixel = floor(pixel) * texel;
vec4 samples[4];
for (int i = 0; i < 4; ++i) {
float weightY = cubic(f.y - float(i));
vec2 sampleUV = pixel + vec2(0.0, float(i)) * texel;
vec4 rowSamples[4];
for (int j = 0; j < 4; ++j) {
float weightX = cubic(float(j) - f.x);
vec2 offset = vec2(float(j), 0.0) * texel;
rowSamples[j] = texture(tex, sampleUV + offset) * weightX;
}
samples[i] = rowSamples[0] + rowSamples[1] + rowSamples[2] + rowSamples[3];
samples[i] *= weightY;
}
return samples[0] + samples[1] + samples[2] + samples[3];
}
void main() {
FragColor = bicubic(image, TexCoords);
}
)";
// 编译着色器
GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
// 检查编译错误
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
}
return shader;
}
// 创建着色器程序
GLuint createShaderProgram() {
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
// 检查链接错误
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(program, 512, nullptr, infoLog);
std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
// 加载RAW图像数据
std::vector<unsigned char> loadRawImage(const char* filePath, int width, int height) {
std::ifstream file(filePath, std::ios::binary);
if (!file) {
std::cerr << "Failed to open RAW file: " << filePath << std::endl;
return {};
}
std::vector<unsigned char> data(width * height * 3); // 假设是RGB格式
file.read(reinterpret_cast<char*>(data.data()), data.size());
file.close();
return data;
}
// 保存结果图像为RAW格式
void saveRawImage(const char* filePath, const std::vector<unsigned char>& data, int width, int height) {
std::ofstream file(filePath, std::ios::binary);
if (!file) {
std::cerr << "Failed to open output RAW file: " << filePath << std::endl;
return;
}
file.write(reinterpret_cast<const char*>(data.data()), data.size());
file.close();
}
int main() {
// 初始化OpenGL上下文(使用离屏渲染)
glfwInit();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // 不创建窗口
GLFWwindow* window = glfwCreateWindow(1, 1, "", nullptr, nullptr);
glfwMakeContextCurrent(window);
glewInit();
// 创建着色器程序
GLuint shaderProgram = createShaderProgram();
glUseProgram(shaderProgram);
// 加载RAW图像数据
int inputWidth = 2480, inputHeight = 3512; // 输入图像尺寸
auto inputData = loadRawImage("input.raw", inputWidth, inputHeight);
if (inputData.empty()) {
std::cerr << "Failed to load input RAW image." << std::endl;
return -1;
}
// 创建纹理
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, inputWidth, inputHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, inputData.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glUniform1f(glGetUniformLocation(shaderProgram, "texelSize"), 1.0f / inputWidth);
// 设置帧缓冲区和渲染目标
int outputWidth = inputWidth*2, outputHeight = inputHeight*2; // 输出图像尺寸
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
GLuint renderTexture;
glGenTextures(1, &renderTexture);
glBindTexture(GL_TEXTURE_2D, renderTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, outputWidth, outputHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTexture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Framebuffer is not complete!" << std::endl;
return -1;
}
// 设置顶点数据
float vertices[] = {
// 位置 // 纹理坐标
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
// 渲染到帧缓冲区
glViewport(0, 0, outputWidth, outputHeight);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(glGetUniformLocation(shaderProgram, "image"), 0);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
// 读取渲染结果
std::vector<unsigned char> outputData(outputWidth * outputHeight * 3);
glReadPixels(0, 0, outputWidth, outputHeight, GL_RGB, GL_UNSIGNED_BYTE, outputData.data());
// 保存结果图像
saveRawImage("output.raw", outputData, outputWidth, outputHeight);
// 清理资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteTextures(1, &texture);
glDeleteTextures(1, &renderTexture);
glDeleteFramebuffers(1, &framebuffer);
glDeleteProgram(shaderProgram);
glfwTerminate();
return 0;
}
最终结果展示