はじめに
OpenGLのフレームバッファー、レンダーバッファーを使ってポストエフェクトを実現します。
開発環境
OpenGL
Vendor: Intel
Version: 4.5.0 - Build 25.20.100.6373
GLSL: 4.50 - Build 25.20.100.6373
Windows10
VisualStudio2017
使用ライブラリ
今回使うライブラリは全てnugetでインストールしています。
VisualStudioのパッケージマネージャコンソールを使う場合は、
以下ですべてインストールできます。
Install-Package glfw
Install-Package glew
Install-Package freeglut
Install-Package glm
Install-Package libpng
追加のインクルードディレクトリ
$(SolutionDir)packages\libpng.1.6.28.1\build\native\include;$(SolutionDir)packages\glm.0.9.9.600\build\native\include;$(SolutionDir)packages\freeglut.2.8.1.15\build\native\include\GL;$(SolutionDir)packages\glew.1.9.0.1\build\native\include\GL;$(SolutionDir)packages\glfw.3.3.0.1\build\native\include\GLFW;%(AdditionalIncludeDirectories)
追加のライブラリディレクトリ
$(SolutionDir)packages\zlib.v140.windesktop.msvcstl.dyn.rt-dyn.1.2.8.8\lib\native\v140\windesktop\msvcstl\dyn\rt-dyn\Win32\Debug;$(SolutionDir)packages\libpng.1.6.28.1\build\native\lib\Win32\v140\dynamic\Debug;$(SolutionDir)packages\glfw.3.3.0.1\build\native\lib\static\v142\win32;$(SolutionDir)packages\glew.1.9.0.1\build\native\lib\v110\Win32\Debug\static;$(SolutionDir)packages\freeglut.2.8.1.15\build\native\lib\v110\Win32\Debug\static;%(AdditionalLibraryDirectories)
追加の依存ファイル
freeglut.lib;glew.lib;glfw3.lib;libpng16.lib;zlibd.lib;%(AdditionalDependencies)
ソースコード
できるだけシンプルなサンプルにしたかったので以下のふたつのファイルで実装しました。
実際にはクラスなどにまとめたほうが便利だと思います。
gli.h
# pragma once
# ifndef GLI_HPP
# define GLI_HPP
// GLEW
# define GLEW_STATIC
# include <gl/glew.h>
// GLUT
# include <glut.h>
// GLFW
# include <GLFW/glfw3.h>
// GLM
# include <glm/glm.hpp>
# endif
main.cpp
# include "gli.h"
# include <string>
# include <iostream>
# include <png.h>
# include <vector>
# include <fstream>
# include <glm/ext/matrix_clip_space.hpp>
# include <glm/ext/matrix_transform.hpp>
# include <glm/gtc/type_ptr.hpp>
# include <glm/gtx/raw_data.hpp>
# pragma warning(disable: 4996)
struct Texture {
GLuint width;
GLuint height;
GLuint id;
unsigned char* data;
};
struct ScreenBuffer {
GLuint fbo;
GLuint rbo;
GLuint vao;
GLuint vertex;
GLuint uv;
GLuint texture;
GLuint shader;
};
void gl_on_error(GLenum source, GLenum type, GLuint eid,
GLenum severity, GLsizei length,
const GLchar* message, const void* user_param) {
std::cout << message << std::endl;
}
int gl_init(int argc, char* argv[], const char* title, int width, int height, bool fullScreen, GLFWwindow** outWindow) {
(*outWindow) = NULL;
if (!glfwInit()) return -1;
// create window
GLFWwindow* window = NULL;
if (fullScreen) {
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
window = glfwCreateWindow(mode->width, mode->height, title, monitor, NULL);
} else {
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
window = glfwCreateWindow(width, height, title, NULL, NULL);
}
if (!window) {
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK) return -1;
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(reinterpret_cast<GLDEBUGPROC>(gl_on_error), nullptr);
glfwSetTime(0.0);
glfwSwapInterval(1);
(*outWindow) = window;
// OpenGLのバージョン情報を表示
std::cout << "OpenGL" << std::endl;
std::cout << " Vendor: " << glGetString(GL_VENDOR) << std::endl;
std::cout << " Version: " << glGetString(GL_VERSION) << std::endl;
std::cout << " GLSL: " << glGetString(GL_SHADING_LANGUAGE_VERSION)
<< std::endl;
return 0;
}
// Utility
template<typename T>
void push_vec2(std::vector<T>& dest, T a, T b) {
dest.push_back(a);
dest.push_back(b);
}
template<typename T>
void push_vec3(std::vector<T>& dest, T a, T b, T c) {
push_vec2(dest, a, b);
dest.push_back(c);
}
template<typename T>
void push_vec4(std::vector<T>& dest, T a, T b, T c, T d) {
push_vec3(dest, a, b, c);
dest.push_back(d);
}
// VAO, VBO
void gen_rect(GLuint* vao, GLuint* vertex, GLuint* uv, glm::vec2 pos, glm::vec2 size) {
glGenVertexArrays(1, vao);
// 頂点情報を作成
std::vector<float> vertexData;
push_vec4<float>(vertexData, pos.x + 0, pos.y + size.y, 1, 1);
push_vec4<float>(vertexData, pos.x + size.x, pos.y + 0, 1, 1);
push_vec4<float>(vertexData, pos.x + 0, pos.y + 0, 1, 1);
push_vec4<float>(vertexData, pos.x + 0, pos.y + size.y, 1, 1);
push_vec4<float>(vertexData, pos.x + size.x, pos.y + size.y, 1, 1);
push_vec4<float>(vertexData, pos.x + size.x, pos.y + 0, 1, 1);
glGenBuffers(1, vertex);
glBindBuffer(GL_ARRAY_BUFFER, *vertex);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertexData.size(), vertexData.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// UV情報を作成
std::vector<float> uvData;
push_vec2<float>(uvData, 0, 1);
push_vec2<float>(uvData,1, 0);
push_vec2<float>(uvData, 0, 0);
push_vec2<float>(uvData, 0, 1);
push_vec2<float>(uvData, 1, 1);
push_vec2<float>(uvData, 1, 0);
glGenBuffers(1, uv);
glBindBuffer(GL_ARRAY_BUFFER, *uv);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * uvData.size(), uvData.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void gen_screen_rect(GLuint* vao, GLuint* vertex, GLuint* uv) {
glGenVertexArrays(1, vao);
// 頂点情報を作成
std::vector<float> vertexData;
push_vec4<float>(vertexData, -1, 1, 0, 1);
push_vec4<float>(vertexData, -1, -1, 0, 1);
push_vec4<float>(vertexData, 1, -1, 0, 1);
push_vec4<float>(vertexData, -1, 1, 0, 1);
push_vec4<float>(vertexData, 1, -1, 0, 1);
push_vec4<float>(vertexData, 1, 1, 0, 1);
glGenBuffers(1, vertex);
glBindBuffer(GL_ARRAY_BUFFER, *vertex);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertexData.size(), vertexData.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// UV情報を作成
std::vector<float> uvData;
push_vec2<float>(uvData, 0, 1);
push_vec2<float>(uvData, 0, 0);
push_vec2<float>(uvData, 1, 0);
push_vec2<float>(uvData, 0, 1);
push_vec2<float>(uvData, 1, 0);
push_vec2<float>(uvData, 1, 1);
glGenBuffers(1, uv);
glBindBuffer(GL_ARRAY_BUFFER, *uv);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * uvData.size(), uvData.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// Png
Texture load_png(const std::string& path) {
// https://yatt.hatenablog.jp/entry/20090817/1250508825
Texture ret;
FILE* fp = fopen(path.c_str(), "rb");
int depth, colorType, interlaceType;
// ファイルが存在するか確認する
if (fp == NULL) {
throw std::logic_error(path + " is not found");
}
png_structp pPng =
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop pInfo = png_create_info_struct(pPng);
assert(pPng != NULL);
assert(pInfo != NULL);
png_init_io(pPng, fp);
png_read_info(pPng, pInfo);
png_get_IHDR(pPng, pInfo, &(ret.width), &(ret.height),
&depth, &colorType, &interlaceType,
NULL, NULL);
// 画像サイズの確認
if (ret.width == 0 || ret.height == 0) {
throw std::logic_error("invalid bounds");
}
// RGBAのみサポート
if (colorType != PNG_COLOR_TYPE_RGBA) {
throw std::logic_error("Not supported color type");
}
// インターレースを確認
if (interlaceType != PNG_INTERLACE_NONE) {
throw std::logic_error("Not supported interlace type");
}
int rowSize = png_get_rowbytes(pPng, pInfo);
int imgSize = rowSize * ret.height;
ret.data = (unsigned char*)std::malloc(imgSize);
assert(ret.data != NULL);
// 色情報を読み込む
for (int i = 0; i < static_cast<int>(ret.height); i++) {
png_read_row(pPng, &(ret.data[i * rowSize]), NULL);
}
png_read_end(pPng, pInfo);
png_destroy_info_struct(pPng, &pInfo);
png_destroy_read_struct(&pPng, NULL, NULL);
fclose(fp);
// 画像をOpenGLのシステムへ登録する
glGenTextures(1, &(ret.id));
glBindTexture(GL_TEXTURE_2D, ret.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glEnable(GL_TEXTURE_2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ret.width, ret.height, 0, GL_RGBA, GL_UNSIGNED_BYTE,ret. data);
glGenerateMipmap(GL_TEXTURE_2D);
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
return ret;
}
void free_texture(const Texture texture) {
glDeleteTextures(1, &(texture.id));
std::free(texture.data);
}
// Shader
void check_shader_state(GLuint shader, const std::string& baseStr) {
GLsizei logSize;
GLsizei bufSize = 2048;
char buf[2048] = { 0 };
glGetShaderInfoLog(shader, bufSize, &logSize, buf);
std::string str = baseStr + '\n' + buf;
std::cerr << str << std::endl;
throw std::logic_error(str);
}
std::string load_text(const std::string& file) {
// https://bi.biopapyrus.jp/cpp/syntax/file.html
std::ifstream ifs(file);
std::string str;
if (ifs.fail()) {
throw std::logic_error("Failed to open file.");
}
std::string buf;
while (getline(ifs, str)) {
buf += str + '\n';
}
return buf;
}
GLuint load_shader(const std::string& vert, const std::string& frag) {
// 頂点シェーダ作成
GLuint vertShId = glCreateShader(GL_VERTEX_SHADER);
if (vertShId == 0) {
check_shader_state(vertShId, "failed a create vertex shader");
}
const char* vertShCStr = vert.c_str();
glShaderSource(vertShId, 1, &vertShCStr, NULL);
glCompileShader(vertShId);
// エラーを確認
GLint compileErr;
glGetShaderiv(vertShId, GL_COMPILE_STATUS, &compileErr);
if (GL_FALSE == compileErr) {
check_shader_state(vertShId, "failed a compile vertex shader");
}
// フラグメントシェーダ作成
GLuint fragShId = glCreateShader(GL_FRAGMENT_SHADER);
if (fragShId == 0) {
check_shader_state(fragShId, "failed a create fragment shader");
}
const char* fragShCStr = frag.c_str();
glShaderSource(fragShId, 1, &fragShCStr, NULL);
glCompileShader(fragShId);
// エラーを確認
glGetShaderiv(fragShId, GL_COMPILE_STATUS, &compileErr);
if (GL_FALSE == compileErr) {
check_shader_state(fragShId, "failed a compile fragment shader");
}
// プログラムの作成, リンク
GLuint program = glCreateProgram();
if (program == 0) {
throw std::logic_error("failed a create program");
}
glAttachShader(program, vertShId);
glAttachShader(program, fragShId);
glLinkProgram(program);
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (GL_FALSE == status) {
throw std::logic_error("failed a link program");
}
glDeleteShader(vertShId);
glDeleteShader(fragShId);
return program;
}
GLuint load_shader_from_file(const std::string& vertFile, const std::string& fragFile) {
std::string vert = load_text(vertFile);
std::string frag = load_text(fragFile);
return load_shader(vert, frag);
}
void configure_screen_shader(GLuint shader, GLuint vao, GLuint vertex, GLuint uv) {
GLuint vertexAttrib = glGetAttribLocation(shader, "aVertex");
GLuint uvAttrib = glGetAttribLocation(shader, "aUV");
GLuint textureUniform = glGetUniformLocation(shader, "uTexture");
glUseProgram(shader);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vertex);
glVertexAttribPointer(vertexAttrib, 4, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(vertexAttrib);
glBindBuffer(GL_ARRAY_BUFFER, uv);
glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(uvAttrib);
glUniform1i(textureUniform, 0);
glBindVertexArray(0);
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void configure_2dtexture_shader(GLuint shader, GLuint vao, GLuint vertex, GLuint uv, glm::vec2 screenSize) {
configure_screen_shader(shader, vao, vertex, uv);
glm::mat4 mvpMatrix = glm::ortho(0.0f, screenSize.x, screenSize.y, 0.0f, -1.0f, 1.0f);
GLuint alphaUniform = glGetUniformLocation(shader, "uAlpha");
GLuint mvpUniform = glGetUniformLocation(shader, "uMVPMatrix");
glUseProgram(shader);
glUniform1f(alphaUniform, 1);
glUniformMatrix4fv(mvpUniform, 1, GL_FALSE, glm::value_ptr(mvpMatrix));
glUseProgram(0);
}
// RBO, FBO
ScreenBuffer gen_screen_buffer(GLuint shader, glm::vec2 screenSize) {
float width = screenSize.x;
float height = screenSize.y;
ScreenBuffer ret;
ret.shader = shader;
glGenTextures(1, &(ret.texture));
glBindTexture(GL_TEXTURE_2D, ret.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast<int>(width), static_cast<int>(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
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_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glGenFramebuffers(1, &(ret.fbo));
glBindFramebuffer(GL_FRAMEBUFFER, ret.fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ret.texture, 0);
glGenRenderbuffers(1, &(ret.rbo));
glBindRenderbuffer(GL_RENDERBUFFER, ret.rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, static_cast<int>(width), static_cast<int>(height));
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, ret.rbo);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
throw std::logic_error("invalid state");
}
gen_screen_rect(&(ret.vao), &(ret.vertex), &(ret.uv));
configure_screen_shader(ret.shader, ret.vao, ret.vertex, ret.uv);
return ret;
}
void bind_screen_buffer(const ScreenBuffer screenBuffer) {
glBindFramebuffer(GL_FRAMEBUFFER, screenBuffer.fbo);
glEnable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void unbind_screen_buffer(const ScreenBuffer screenBuffer) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void render_screen_buffer(const ScreenBuffer screenBuffer) {
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(screenBuffer.shader);
glBindTexture(GL_TEXTURE_2D, screenBuffer.texture);
glBindVertexArray(screenBuffer.vao);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glUseProgram(0);
glEnable(GL_DEPTH_TEST);
}
void free_screen_buffer(const ScreenBuffer screenBuffer) {
glDeleteRenderbuffers(1, &(screenBuffer.rbo));
glDeleteFramebuffers(1, &(screenBuffer.fbo));
glDeleteTextures(1, &(screenBuffer.texture));
glDeleteProgram(screenBuffer.shader);
}
int main(int argc, char* argv[]) {
GLFWwindow* window;
glm::vec2 screenSize = glm::vec2(1280, 720);
int status = gl_init(argc, argv, "Sample", static_cast<int>(screenSize.x), static_cast<int>(screenSize.y), false, &window);
if (status != 0) {
return status;
}
Texture texture = load_png("TestImage.png");
GLuint vao, vertex, uv;
gen_rect(&vao, &vertex, &uv, (screenSize - glm::vec2(texture.width, texture.height)) / 2.0f, glm::vec2(texture.width, texture.height));
// シェーダを作成して、頂点, UV を設定する
GLuint shader = load_shader_from_file("Texture2D.vert", "Texture2D.frag");
configure_2dtexture_shader(shader, vao, vertex, uv, screenSize);
// ポストエフェクトのためのシェーダ, スクリーンバッファを作成
GLuint screenShader = load_shader_from_file("Noise.vert", "Noise.frag");
ScreenBuffer screenBuffer = gen_screen_buffer(screenShader, screenSize);
// アルファブレンドを有効にする
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glActiveTexture(GL_TEXTURE0);
float gameTime = 0;
float deltaTime = 0;
GLuint timeUniform = glGetUniformLocation(screenShader, "uTime");
while (!glfwWindowShouldClose(window)) {
glUseProgram(screenShader);
glUniform1f(timeUniform, gameTime);
glUseProgram(0);
double oldTime = glfwGetTime();
bind_screen_buffer(screenBuffer);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glUseProgram(shader);
glBindTexture(GL_TEXTURE_2D, texture.id);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glDisable(GL_TEXTURE_2D);
unbind_screen_buffer(screenBuffer);
render_screen_buffer(screenBuffer);
glfwSwapBuffers(window);
glfwPollEvents();
double newTime = glfwGetTime();
deltaTime = (float)(newTime - oldTime);
gameTime += deltaTime;
}
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vertex);
glDeleteBuffers(1, &uv);
glDeleteProgram(shader);
free_texture(texture);
free_screen_buffer(screenBuffer);
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
シェーダ
Texture2D.vert
# version 410
# extension GL_ARB_explicit_attrib_location : require
# extension GL_ARB_explicit_uniform_location : require
// texture2D.vert
uniform mat4 uMVPMatrix;
in vec4 aVertex;
in vec2 aUV;
out vec2 uv;
void main(void) {
uv = aUV;
gl_Position = uMVPMatrix * aVertex;
}
Texture2D.frag
# version 410
# extension GL_ARB_explicit_attrib_location : require
# extension GL_ARB_explicit_uniform_location : require
// texture2D.frag
out vec4 outputC;
uniform sampler2D uTexture;
uniform float uAlpha;
in vec2 uv;
void main (void) {
vec4 color = texture(uTexture, uv);
color.a = color.a * uAlpha;
outputC = color;
}
Noise.vert
# version 410
# extension GL_ARB_explicit_attrib_location : require
# extension GL_ARB_explicit_uniform_location : require
// screen.vert
in vec4 aVertex;
in vec2 aUV;
out vec2 uv;
void main(void) {
uv = aUV;
gl_Position = aVertex;
}
Noise.frag
# version 410
# extension GL_ARB_explicit_attrib_location : require
# extension GL_ARB_explicit_uniform_location : require
// screen.frag
out vec4 outputC;
uniform sampler2D uTexture;
uniform float uTime;
in vec2 uv;
float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
void main()
{
vec4 colorA = texture(uTexture, uv);
vec3 color = colorA.xyz;
vec3 raw = color;
// Random number.
vec2 pos = uv;
pos *= sin(uTime);
float r = rand(pos);
// Noise color using random number.
vec3 noise = vec3(r);
float noise_intensity = 0.5;
// Combined colors.
color = mix(color, noise, noise_intensity);
outputC = vec4(color, 1.0);
}
※Noise.vert Noise.fragは下記サイトを参考に少し改変しています。
https://clemz.io/article-retro-shaders-webgl.html
画像
画像はなんでもいいです。
最初の .gif ではこの画像を使っています。
解説
configure_screen_shader
Texture2D, Noiseシェーダの両方に共通して必要な変数を定義しています。
void configure_screen_shader(GLuint shader, GLuint vao, GLuint vertex, GLuint uv) {
GLuint vertexAttrib = glGetAttribLocation(shader, "aVertex");
GLuint uvAttrib = glGetAttribLocation(shader, "aUV");
GLuint textureUniform = glGetUniformLocation(shader, "uTexture");
glUseProgram(shader);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vertex);
glVertexAttribPointer(vertexAttrib, 4, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(vertexAttrib);
glBindBuffer(GL_ARRAY_BUFFER, uv);
glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(uvAttrib);
glUniform1i(textureUniform, 0);
glBindVertexArray(0);
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
configure_2dtexture_shader
こちらはTexture2Dシェーダにのみ必要な変数を定義しています。
void configure_2dtexture_shader(GLuint shader, GLuint vao, GLuint vertex, GLuint uv, glm::vec2 screenSize) {
configure_screen_shader(shader, vao, vertex, uv);
glm::mat4 mvpMatrix = glm::ortho(0.0f, screenSize.x, screenSize.y, 0.0f, -1.0f, 1.0f);
GLuint alphaUniform = glGetUniformLocation(shader, "uAlpha");
GLuint mvpUniform = glGetUniformLocation(shader, "uMVPMatrix");
glUseProgram(shader);
glUniform1f(alphaUniform, 1);
glUniformMatrix4fv(mvpUniform, 1, GL_FALSE, glm::value_ptr(mvpMatrix));
glUseProgram(0);
}
ScreenBuffer
ScreenBufferというのはOpenGLの用語ではなく、
勝手にこのプログラムのなかでそのように名付けて使っているだけです。
このオブジェクトが
- フレームバッファー(fbo)
- レンダーバッファー(rbo)
- ウィンドウを表す頂点情報(vao, vertex, uv)
を持っています。
struct ScreenBuffer {
GLuint fbo;
GLuint rbo;
GLuint vao;
GLuint vertex;
GLuint uv;
GLuint texture;
GLuint shader;
};
...中略...
// RBO, FBO
ScreenBuffer gen_screen_buffer(GLuint shader, glm::vec2 screenSize) {
float width = screenSize.x;
float height = screenSize.y;
ScreenBuffer ret;
ret.shader = shader;
glGenTextures(1, &(ret.texture));
glBindTexture(GL_TEXTURE_2D, ret.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast<int>(width), static_cast<int>(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
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_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glGenFramebuffers(1, &(ret.fbo));
glBindFramebuffer(GL_FRAMEBUFFER, ret.fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ret.texture, 0);
glGenRenderbuffers(1, &(ret.rbo));
glBindRenderbuffer(GL_RENDERBUFFER, ret.rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, static_cast<int>(width), static_cast<int>(height));
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, ret.rbo);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
throw std::logic_error("invalid state");
}
gen_screen_rect(&(ret.vao), &(ret.vertex), &(ret.uv));
configure_screen_shader(ret.shader, ret.vao, ret.vertex, ret.uv);
return ret;
}
void bind_screen_buffer(const ScreenBuffer screenBuffer) {
glBindFramebuffer(GL_FRAMEBUFFER, screenBuffer.fbo);
glEnable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void unbind_screen_buffer(const ScreenBuffer screenBuffer) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void render_screen_buffer(const ScreenBuffer screenBuffer) {
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(screenBuffer.shader);
glBindTexture(GL_TEXTURE_2D, screenBuffer.texture);
glBindVertexArray(screenBuffer.vao);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glUseProgram(0);
glEnable(GL_DEPTH_TEST);
}
void free_screen_buffer(const ScreenBuffer screenBuffer) {
glDeleteRenderbuffers(1, &(screenBuffer.rbo));
glDeleteFramebuffers(1, &(screenBuffer.fbo));
glDeleteTextures(1, &(screenBuffer.texture));
glDeleteProgram(screenBuffer.shader);
}
gen_screen_bufferでシェーダを受け取っていますが、
これを例えば別のシェーダに入れ替えると、また別の効果を得られます。
(必要に応じて変数を渡す部分を書き換える必要があります。)
例えば...
CRT.vert
# version 410
# extension GL_ARB_explicit_attrib_location : require
# extension GL_ARB_explicit_uniform_location : require
// screen.vert
in vec4 aVertex;
in vec2 aUV;
out vec2 uv;
void main(void) {
uv = aUV;
gl_Position = aVertex;
}
CRT.frag
# version 410
# extension GL_ARB_explicit_attrib_location : require
# extension GL_ARB_explicit_uniform_location : require
// screen.frag
out vec4 outputC;
uniform sampler2D uTexture;
uniform float uTime;
//uniform int enabled;
in vec2 uv;
void main()
{
vec3 color = texture2D(uTexture, uv).rgb;
color -= abs(sin(uv.y * 100.0 + uTime * 5.0)) * 0.08; // (1)
color -= abs(sin(uv.y * 300.0 - uTime * 10.0)) * 0.05; // (2)
outputC = vec4(color, 1.0).rgba;
}
※CRT.vert CRT.fragも下記サイトを参考に少し改変しています。
https://clemz.io/article-retro-shaders-webgl.html
main.cpp
...中略...
// ポストエフェクトのためのシェーダ, スクリーンバッファを作成
GLuint screenShader = load_shader_from_file("CRT.vert", "CRT.frag");
...中略...