1
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-10-04

はじめに

OpenGLで複数の画像を一つの頂点情報から表示します。
行列を使って位置/大きさ/回転は自由に変更可能です。

OneVertex.PNG

開発環境

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 soil

追加のインクルードディレクトリ

$(SolutionDir)packages\soil.1.16.0\build\native\include;$(SolutionDir)packages\freeglut.2.8.1.15\build\native\include\GL;$(SolutionDir)packages\glm.0.9.9.600\build\native\include;$(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\soil.1.16.0\build\native\lib\Win32\Debug;$(SolutionDir)packages\freeglut.2.8.1.15\build\native\lib\v110\Win32\Debug\static;$(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;%(AdditionalLibraryDirectories)

追加の依存ファイル

glew.lib;glfw3.lib;soil.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 <iostream>
# include <soil.h>
# include <string>
# include <vector>
# include <cassert>
# include <glm/glm.hpp>
# include <glm/gtc/matrix_transform.hpp>
# include <glm/gtx/quaternion.hpp>
# include <glm/gtc/type_ptr.hpp>
# include <glm/gtx/string_cast.hpp>
# include <glm/ext.hpp>
# include "gli.h"

struct Transform {
	glm::vec3 position;
	glm::vec3 scale;
	glm::quat rotation;

	Transform() : position(), scale(1, 1, 1), rotation() {
	}

	void reset() {
		this->position = glm::vec3();
		this->scale = glm::vec3(1, 1, 1);
		this->rotation = glm::quat();
	}
};

static 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;
	(*outWindow) = window;
	return 0;
}

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);
}

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;
}

glm::mat4 compute_transform(glm::vec3 position, glm::vec3 scale, glm::quat rotation) {
	glm::mat4 t = glm::translate(glm::mat4(1.0f), position);
	glm::mat4 r = glm::toMat4(rotation);
	glm::mat4 s = glm::scale(glm::mat4(1.0f), scale);
	return t * r * s;
}

int main(int argc, char* argv[]) {
	const int WIDTH = 1280;
	const int HEIGHT = 720;
	GLFWwindow* window;
	gl_init(argc, argv, "Sample", WIDTH, HEIGHT, false, &window);

	// シェーダを作成する
	GLuint program = load_shader(
		// バーテックスシェーダ
R"(
		#version 410
		#extension GL_ARB_explicit_attrib_location : require
		#extension GL_ARB_explicit_uniform_location : require
		
		uniform mat4 uWorldTransform;
		uniform mat4 uViewProj;
		
		in vec3 aVertex;
		in vec2 aUV;
		
		out vec2 uv;
		
		void main() {
			vec4 pos = vec4(aVertex, 1.0);
			gl_Position =  uViewProj * uWorldTransform * pos;
			uv = aUV;
		}
)",
// フラグメントシェーダ
R"(
		#version 410
		#extension GL_ARB_explicit_attrib_location : require
		#extension GL_ARB_explicit_uniform_location : require
		
		in vec2 uv;
		out vec4 outColor;
		uniform sampler2D uTexture;
		
		void main() {
		    outColor = texture(uTexture, uv);
		}
)");

	// 画像を読み込む
	int texW, texH, texCh;
	unsigned char* data = SOIL_load_image("TestImage.png", &texW, &texH, &texCh, SOIL_LOAD_RGBA);
	assert(data != nullptr);
	assert(texCh == 4);
	GLuint texture;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glEnable(GL_TEXTURE_2D);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texW, texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
	glGenerateMipmap(GL_TEXTURE_2D);
	glDisable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	// 画像のための頂点を用意する
	const float vertex[] = {
		-0.5f, 0.5f,  0,
		0.5f, 0.5f,   0,
		0.5f, -0.5f,  0,
		-0.5f, -0.5f, 0,
	};
	const float uv[] = {
		0.f, 0.f,
		1.f, 0.f,
		1.f, 1.f,
		0.f, 1.f
	};
	const GLushort index[] = { 0, 1, 2, 2, 3, 0 };

	GLuint vao, vVBO, uvVBO, ibo;
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	glGenBuffers(1, &vVBO);
	glBindBuffer(GL_ARRAY_BUFFER, vVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 12, vertex, GL_STATIC_DRAW);

	glGenBuffers(1, &uvVBO);
	glBindBuffer(GL_ARRAY_BUFFER, uvVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 8, uv, GL_STATIC_DRAW);

	glGenBuffers(1, &ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * 6, index, GL_STATIC_DRAW);

	// シェーダに値を渡す
	glUseProgram(program);
	GLuint vertexAttrib = glGetAttribLocation(program, "aVertex");
	GLuint uvAttrib = glGetAttribLocation(program, "aUV");
	GLuint textureUniform = glGetUniformLocation(program, "uTexture");
	GLuint viewProjUniform = glGetUniformLocation(program, "uViewProj");
	GLuint worldTransformUniform = glGetUniformLocation(program, "uWorldTransform");

	glBindBuffer(GL_ARRAY_BUFFER, vVBO);
	glVertexAttribPointer(vertexAttrib, 3, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(vertexAttrib);

	glBindBuffer(GL_ARRAY_BUFFER, uvVBO);
	glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(uvAttrib);
	glUniform1i(textureUniform, 0);

	// SimpleViewProjection行列を計算する
	// 参考:「ゲームプログラミングC++」第五章
	glm::mat4 svpScale = glm::scale(
		glm::mat4(1.0f),
		glm::vec3(2.0f / static_cast<float>(WIDTH), 2.0f / static_cast<float>(HEIGHT), 1)
	);
	glm::mat4 svpTranslate = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 1));
	glm::mat4 simpleViewProjection = svpTranslate * svpScale;
	std::cout << glm::to_string(simpleViewProjection) << std::endl;
	glUniformMatrix4fv(viewProjUniform, 1, GL_FALSE, glm::value_ptr(simpleViewProjection));

	// 全てのバインドを解除
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glUseProgram(0);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	// 描画するオブジェクトの形状
	std::vector<Transform> transformVec;
	const float LAYOUT_MARGIN = 300;
	// 中央:
	Transform temp;
	transformVec.emplace_back(temp);
	temp.reset();

	// 右:
	temp.position.x += LAYOUT_MARGIN;
	temp.scale.y = 2;
	transformVec.emplace_back(temp);
	temp.reset();

	// 左:
	temp.position.x -= LAYOUT_MARGIN;
	temp.rotation = glm::angleAxis(45.0f * (3.14f / 180.0f), glm::vec3(0, 0, 1));
	transformVec.emplace_back(temp);
	temp.reset();

	// 下:
	temp.position.y -= LAYOUT_MARGIN;
	temp.rotation = glm::angleAxis(120.0f * (3.14f / 180.0f), glm::vec3(0, 0, 1));
	transformVec.emplace_back(temp);
	temp.reset();

	// 上:
	temp.position.y += LAYOUT_MARGIN;
	transformVec.emplace_back(temp);
	temp.reset();

	while (!glfwWindowShouldClose(window)) {
		glClearColor(0, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glUseProgram(program);
		glBindVertexArray(vao);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

		for (Transform transform : transformVec) {
			glm::mat4 world = compute_transform(
				transform.position,
				transform.scale,
				transform.rotation
			);
			world = glm::scale(world, glm::vec3(texW, texH, 1));

			glUniformMatrix4fv(worldTransformUniform, 1, GL_FALSE, glm::value_ptr(world));
			glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
		}
		glBindVertexArray(0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glUseProgram(0);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwDestroyWindow(window);
	glfwTerminate();
	return 0;
}

画像

画像はなんでもいいです。
最初の画像ではこの画像を使っています。(200x200)
TestImage.png

解説

main

頂点情報を一つしか使っていない点が重要です。
Transformの中身を書き換えるだけでオブジェクトを動かしたり変形したりできます。
オブジェクトの数だけ頂点情報を作ってしまうことも出来ますが、既に存在するVBOの変更はちょっと手間がかかります。
複雑な座標変換を行うなら尚更です。

main.cpp

while (!glfwWindowShouldClose(window)) {
		glClearColor(0, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glUseProgram(program);
		glBindVertexArray(vao);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

		for (Transform transform : transformVec) {
			glm::mat4 world = compute_transform(
				transform.position,
				transform.scale,
				transform.rotation
			);
			world = glm::scale(world, glm::vec3(texW, texH, 1));

			glUniformMatrix4fv(worldTransformUniform, 1, GL_FALSE, glm::value_ptr(world));
			glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
		}
		glBindVertexArray(0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glUseProgram(0);

		glfwSwapBuffers(window);
		glfwPollEvents();
}

比較

比較のためにオブジェクトの移動のたびにVBOを変更する例も載せておきます。
ちょっと面倒だったので上記の例と完全に同じ実行結果になるように実装してません。
もしこの方法で上記と同じ実行結果になるよう実装したらもっと冗長になってしまうと思います。

main.cpp

...

int main(int argc, char* argv[]) {
	const int WIDTH = 1280;
	const int HEIGHT = 720;
	GLFWwindow* window;
	gl_init(argc, argv, "Sample", WIDTH, HEIGHT, false, &window);

	// シェーダを作成する
	GLuint program = load_shader(
		// バーテックスシェーダ
R"(
		#version 410
		#extension GL_ARB_explicit_attrib_location : require
		#extension GL_ARB_explicit_uniform_location : require
		
		uniform mat4 uMVPMatrix;
		
		in vec3 aVertex;
		in vec2 aUV;
		
		out vec2 uv;
		
		void main() {
			vec4 pos = vec4(aVertex, 1.0);
			gl_Position =  uMVPMatrix * pos;
			uv = aUV;
		}
)",
// フラグメントシェーダ
R"(
		#version 410
		#extension GL_ARB_explicit_attrib_location : require
		#extension GL_ARB_explicit_uniform_location : require
		
		in vec2 uv;
		out vec4 outColor;
		uniform sampler2D uTexture;
		
		void main() {
		    outColor = texture(uTexture, uv);
		}
)");

	// 画像を読み込む
	int texW, texH, texCh;
	unsigned char* data = SOIL_load_image("TestImage.png", &texW, &texH, &texCh, SOIL_LOAD_RGBA);
	assert(data != nullptr);
	assert(texCh == 4);
	GLuint texture;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glEnable(GL_TEXTURE_2D);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texW, texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
	glGenerateMipmap(GL_TEXTURE_2D);
	glDisable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	// 画像のための頂点を用意する
	const float vertex[] = {
		0, 0,  0,
		200, 0,   0,
		200, 200,  0,
		0, 200, 0,
	};
	const float uv[] = {
		0.f, 0.f,
		1.f, 0.f,
		1.f, 1.f,
		0.f, 1.f
	};
	const GLushort index[] = { 0, 1, 2, 2, 3, 0 };

	GLuint vao, vVBO, uvVBO, ibo;
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	glGenBuffers(1, &vVBO);
	glBindBuffer(GL_ARRAY_BUFFER, vVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 12, vertex, GL_DYNAMIC_DRAW);

	glGenBuffers(1, &uvVBO);
	glBindBuffer(GL_ARRAY_BUFFER, uvVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 8, uv, GL_STATIC_DRAW);

	glGenBuffers(1, &ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * 6, index, GL_STATIC_DRAW);

	// シェーダに値を渡す
	glUseProgram(program);
	GLuint vertexAttrib = glGetAttribLocation(program, "aVertex");
	GLuint uvAttrib = glGetAttribLocation(program, "aUV");
	GLuint textureUniform = glGetUniformLocation(program, "uTexture");
	GLuint mvpUniform = glGetUniformLocation(program, "uMVPMatrix");
	// GLuint viewProjUniform = glGetUniformLocation(program, "uViewProj");
	// GLuint worldTransformUniform = glGetUniformLocation(program, "uWorldTransform");

	glBindBuffer(GL_ARRAY_BUFFER, vVBO);
	glVertexAttribPointer(vertexAttrib, 3, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(vertexAttrib);

	glBindBuffer(GL_ARRAY_BUFFER, uvVBO);
	glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(uvAttrib);
	glUniform1i(textureUniform, 0);

	// SimpleViewProjection行列を計算する
	// 参考:「ゲームプログラミングC++」第五章
	/*
	glm::mat4 svpScale = glm::scale(
		glm::mat4(1.0f),
		glm::vec3(2.0f / static_cast<float>(WIDTH), 2.0f / static_cast<float>(HEIGHT), 1)
	);
	glm::mat4 svpTranslate = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 1));
	glm::mat4 simpleViewProjection = svpTranslate * svpScale;
	std::cout << glm::to_string(simpleViewProjection) << std::endl;
	glUniformMatrix4fv(viewProjUniform, 1, GL_FALSE, glm::value_ptr(simpleViewProjection));
	*/
	glm::mat4 ortho = glm::ortho(0.0f, static_cast<float>(WIDTH), static_cast<float>(HEIGHT), 0.0f, -1.0f, 1.0f);
	glUniformMatrix4fv(mvpUniform, 1, GL_FALSE, glm::value_ptr(ortho));

	// 全てのバインドを解除
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glUseProgram(0);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	while (!glfwWindowShouldClose(window)) {
		glClearColor(0, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glUseProgram(program);
		glBindVertexArray(vao);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

		glBindBuffer(GL_ARRAY_BUFFER, vVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 12, vertex, GL_DYNAMIC_DRAW);

		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);

		// 移動後の頂点配列を用意する
		const float MOVE_X = 1080;
		const float vertex2[] = {
			0 + MOVE_X, 0,  0,
			200 + MOVE_X, 0,   0,
			200 + MOVE_X, 200,  0,
			0 + MOVE_X, 200, 0,
		};
		glBindBuffer(GL_ARRAY_BUFFER, vVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 12, vertex2, GL_DYNAMIC_DRAW);

		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);

		glBindVertexArray(0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glUseProgram(0);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwDestroyWindow(window);
	glfwTerminate();
	return 0;
}

実行結果

MultipleVertex.PNG

1
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
1
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?