0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

OpenGLでポストエフェクトを実装する

Last updated at Posted at 2019-09-20

はじめに

OpenGLのフレームバッファー、レンダーバッファーを使ってポストエフェクトを実現します。
ezgif.com-video-to-gif.gif

開発環境

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 ではこの画像を使っています。
TestImage.png

解説

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");
...中略...

のように変更するとモニター風に
ezgif.com-video-to-gif (1).gif

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?