OpenGL学習記録(5)

参考文献:
https://learnopengl.com/
https://learnopengl-cn.github.io/

今回実装したいのはTransform変形機能です。OpenGL には行列やベクトルの知識は含まれていないため、GLM などの既製の数学ライブラリを使用できます。GLM ライブラリはここからダウンロードできます。使用する場合は、次のヘッダー ファイルをインポートする必要があります。

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

画像に対して変位、回転、拡大縮小などの操作を行うには行列が切り離せないため、頂点シェーダーのコード内で4次元の行列変数を定義し、それにgl_Positionを乗算することで変換関数を実現します。

//顶点着色器编码
#version 330 core										
layout(location = 0) in vec3 aPos;						
layout(location = 1) in vec3 aColor;		
layout(location = 2) in vec2 aTexCoord;	

uniform mat4 transform;

out vec4 vertexColor;						  
out vec2 TexCoord;

void main() {
    
    											
	gl_Position = transform * vec4(aPos.x, aPos.y, aPos.z, 1.0);    
	vertexColor = vec4(aColor, 1.0f);	
	TexCoord = aTexCoord;
}

main 関数のループ描画フェーズでは、glUniformMatrix4fv 関数を使用してマトリックス データをシェーダーに送信できます。最初のパラメーターはユニフォームの位置値です。glGetUniformLocation を使用して、対応するシェーダーで必要なユニフォーム変数の位置値を取得します。ユニフォーム mat4 変換を定義するため、変換変数の位置値が取得されます。2 番目のパラメータは、送信する行列の数を OpenGL に伝えます。ここでは 1 です。3 番目のパラメーターは、行列を転置するかどうか、つまり行列の行と列を交換するかどうかを尋ねます。OpenGL 開発者は通常、列優先順序付けレイアウトと呼ばれる内部マトリックス レイアウトを使用します。GLM のデフォルトのレイアウトは列優先であるため、行列を転置する必要がないため、GL_FALSE を入力します。最後のパラメータは実数の行列データですが、GLM は OpenGL が受け入れることを期待しているような行列を保存しないため、最初に GLM 独自の関数 value_ptr を使用してこれらのデータを変換する必要があります。

glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans)); //把矩阵数据发送给着色器

次に、画像の変換方法を決定できる実際の変換行列であるトランス行列を定義するだけです。glm::mat4 を使用して 4 次元行列を宣言します。glm::translate は、4 列目の最初の 3 行の対応する要素の値である変位変換行列に変換できます。そのパラメーターには、変換する行列と 3 次元の変位変換ベクトルを入力する必要があります。変位行列は次のとおりです。

ここに画像の説明を挿入

glm::rotate はそれを回転変換行列に変換し、変換する行列と変換する角度 (実際に必要なのはラジアン値です。角度をラジアンに変換するには glm::radians を使用します) を入力して、どの角度にするかを決定します。回転する軸 の 3 次元回転変数。x、y、z 軸を中心とした回転の変換行列は次のとおりです。

ここに画像の説明を挿入

glm::scale は、変換する行列と 3 次元のスケーリング ベクトル (つまり、行列の最初の 3 行と最初の 3 列の対角線上の要素の値) を渡して、スケーリング行列に変換します。 )。スケーリング マトリックスは次のとおりです。

ここに画像の説明を挿入

行列の乗算は可換ではない、つまり順序​​が重要であることは注目に値します。たとえば、モデルを回転してから変位すると、回転後にモデルの正面の向きが変化するため、モデルは新しい方向に変位しますが、これは当初の予想と一致しません。変換をより直観的にするには、最初にスケーリング操作を実行し、次に回転操作を実行し、最後に移動操作を実行する必要があります。行列は左乗算の形でベクトルと演算されるため、複数の行列を乗算する場合、最初に「右端」の行列がベクトルに影響するため、Mt * Mr * Ms * v のようなディスプレイスメントで変換する必要があります。行列、回転変換行列、スケーリング演算行列が順に乗算されて最終的な変換行列となります。したがって、変換行列のコードは次のようになります。

//变换矩阵
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(0.3f, 0.3f, 0.2f)); //位移
trans = glm::rotate(trans, glm::radians(45.0f), glm::vec3(0, 0, 1.0f)); //旋转
trans = glm::scale(trans, glm::vec3(0.5f, 0.5f, 0.5f)); //缩放

このように、画像に対するトランス マトリクスの効果は次のとおりです。まず、xyz の 3 次元をオリジナルの 0.5 倍にスケールし、次に Z 軸を中心に 45 度回転し、次に画像を x 方向に 0.3、0.3 ずつ移動します。 y 方向に 、z 方向に 0.2 移動します。メインプログラムファイルのコードと実行後の結果は次のとおりです。

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

//顶点数据
float vertices[] = {
    
    
	//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
		 0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
		 0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
		-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};

//顶点索引
unsigned int indices[] = {
    
    
	0, 1, 2,   //第一个三角形使用的顶点
	2, 3, 0    //第二个三角形使用的顶点
};


//检查输入函数
void processInput(GLFWwindow* window)
{
    
    
	//按下ESC键时,将WindowShouldClose设为true,循环绘制将停止
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
	{
    
    
		glfwSetWindowShouldClose(window, true);
	}
}

//视口改变时的回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    
    
	glViewport(0, 0, width, height); //OpenGL渲染窗口的尺寸大小
}

int main()
{
    
    
	glfwInit(); //初始化GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //告诉GLFW要使用OpenGL的版本号
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //主版本号、次版本号都为3,即3.3版本
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //告诉GLFW使用核心模式(Core-profile)

	//打开 GLFW Window
	GLFWwindow* window = glfwCreateWindow(1600, 1200, "My OpenGL Game", nullptr, nullptr);
	if (window == nullptr) //若窗口创建失败,打印错误信息,终止GLFW并return -1
	{
    
    
		printf("Open window failed.");
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window); //创建OpenGL上下文
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //用户改变窗口大小的时候,视口调用回调函数

	//初始化GLEW
	glewExperimental = true;
	if (glewInit() != GLEW_OK) //若GLEW初始化失败,打印错误信息并终止GLFW窗口
	{
    
    
		printf("Init GLEW failed.");
		glfwTerminate();
		return -1;
	}


	Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");

	//创建VAO(顶点数组对象)
	unsigned int VAO;
	glGenVertexArrays(1, &VAO); //生成一个顶点数组对象
	glBindVertexArray(VAO); //绑定VAO

	//创建VBO(顶点缓冲对象)
	unsigned int VBO;
	glGenBuffers(1, &VBO); //生成缓冲区对象。第一个参数是要生成的缓冲对象的数量,第二个是要输入用来存储缓冲对象名称的数组,由于只需创建一个VBO,因此不需要用数组形式
	glBindBuffer(GL_ARRAY_BUFFER, VBO); //把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上,GL_ARRAY_BUFFER是一种顶点缓冲对象的缓冲类型。OpenGL允许同时绑定多个缓冲,只要它们是不同的缓冲类型。
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //把定义的数据复制到当前绑定缓冲的函数。参数1:目标缓冲类型,参数2:指定传输数据的大小(以字节为单位),参数3:我们希望发送的实际数据,参数4:指定显卡如何管理给定的数据,GL_STATIC_DRAW表示数据不会或几乎不会改变。

	//创建EBO(元素缓冲对象/索引缓冲对象)
	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);


	//位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); //告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)。参数1:要配置的顶点属性(layout(location = 0),故0),参数2:指定顶点属性的大小(vec3,故3),参数3:指定数据的类型,参数4:是否希望数据被标准化,参数5:在连续的顶点属性组之间的间隔,参数6:表示位置数据在缓冲中起始位置的偏移量(Offset)。
	glEnableVertexAttribArray(0); //以顶点属性位置值作为参数,启用顶点属性,由于前面声明了layout(location = 0),故为0

	//颜色属性
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);

	//UV属性
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);

	//使用纹理单元0绑定TexBufferA
	unsigned int TexBufferA;
	glGenTextures(1, &TexBufferA);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TexBufferA);

	int width, height, nrChannel;
	stbi_set_flip_vertically_on_load(true); //翻转图像y轴

	//加载并生成第一张纹理
	unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannel, 0);
	if (data)
	{
    
    
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
    
    
		std::cout << "图片加载失败" << std::endl;
	}
	stbi_image_free(data); //释放

	//使用纹理单元3绑定TexBufferB
	unsigned int TexBufferB;
	glGenTextures(1, &TexBufferB);
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, TexBufferB);

	//加载并生成第二张纹理
	unsigned char* data2 = stbi_load("awesomeface1.png", &width, &height, &nrChannel, 0);
	if (data2)
	{
    
    
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
    
    
		std::cout << "图片加载失败" << std::endl;
	}
	stbi_image_free(data2);

	//变换矩阵
	glm::mat4 trans;
	trans = glm::translate(trans, glm::vec3(0.3f, 0.3f, 0.2f)); //位移
	trans = glm::rotate(trans, glm::radians(45.0f), glm::vec3(0, 0, 1.0f)); //旋转
	trans = glm::scale(trans, glm::vec3(0.5f, 0.5f, 0.5f)); //缩放

	//让程序在手动关闭之前不断绘制图像
	while (!glfwWindowShouldClose(window))
	{
    
    
		//检测输入
		processInput(window);

		//渲染指令
		glClearColor(0.f, 0.5f, 0.5f, 1.0f); //设置清空屏幕所用的颜色
		glClear(GL_COLOR_BUFFER_BIT); //清空屏幕的颜色缓冲区

		//绑定纹理到对应的纹理单元
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, TexBufferA);
		glActiveTexture(GL_TEXTURE3);
		glBindTexture(GL_TEXTURE_2D, TexBufferB);

		glBindVertexArray(VAO); //绘制物体的时候就拿出相应的VAO,绑定它
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //绑定EBO
		

		myShader->use();

		//告诉OpenGL每个着色器采样器属于哪个纹理单元
		glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 0); 
		glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 3);

		glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans)); //把矩阵数据发送给着色器

		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //绘制,参数1:绘制模式(三角形),参数2:绘制顶点数(两个三角形6个顶点),参数3:索引的类型,参数4:指定EBO中的偏移量。


		//检查并调用事件,交换缓冲区
		glfwSwapBuffers(window); //交换颜色缓冲区,前缓冲区保存最终输出的图像,后缓冲区负责绘制渲染指令,当渲染指令执行完毕后,交换前后缓冲区,使完整图像呈现出来,避免逐像素绘制图案时的割裂感
		glfwPollEvents(); //检查触发事件,如键盘输入、鼠标移动等
	}


	glfwTerminate(); //关闭GLFW并退出
	return 0;
}

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weixin_47260762/article/details/128203559