当前位置: 首页 > article >正文

OpenGL给矩形贴上纹理

        前面几次,我们在创建的窗口上绘制了一个三角形,并且可以通过颜色罗盘去改变它的颜色,不过这还是有一些太单调了,笔者在这里告诉大家如何给绘制的几何图形贴上纹理。这里主要是2D平面纹理,至于3D的纹理,笔者还没有去试过,有兴趣朋友可以自己去研究一下,接下来我们看一下需要哪些步骤。

绘制一个矩形

2D的纹理一般都是矩形的,因为这些纹理的来源基本上都是图片和照片,为了能够保证纹理的完整性,我们需要先画出一个矩形。这里简单介绍一下,OpenGL是没有直接绘制矩形的图元,所以矩形需要被分成两个三角形进行绘制,总共四个顶点,两个三角形如下图所示

四个顶点的下标分别为:0,1,2,3。我们需要画两个三角形,下标的顺序是0,1,2;2,3,0;总共六个下标,三角形的绕行方向最好保持一致,OpenGL到底有没有背面剔除,这个笔者不是很清楚。这里我们需要索引缓冲区,它的声明和数组缓冲区是一样的,代码如下:

unsigned int vertexIndex[] = {
		0,1,2,
		2,3,0
};

GLuint indexBuffer = 0;

glCreateBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertexIndex), vertexIndex, GL_STATIC_DRAW);

绘制的顶点也要增加到四个,代码如下,并且声明数据具体的分布情况,具体代码如下

float positions[] = {
		-0.5f,	-0.5f,	0.0f,0.0f,
		0.5f,	-0.5f,	1.0f,0.0f,
		0.5f,	0.5f,	1.0f,1.0f,
		-0.5f,	0.5f,	0.0f,1.0f
	};

GLuint buffer = 0;

glCreateBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), NULL);

glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const void*)(2*sizeof(float)));

并且使用glDrawElements函数进行绘制

glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_INT, NULL);

我们就得到了一个矩形,结果如下

将照片转换为纹理

将照片转换为纹理,然后进行一些采样,让片元着色器可以使用。这里需要使用一个三方库当中的文件stb_image.h github官方链接如下:stb_image.h这个头文件有8000行的代码,这里我就不给大家贴在这里了,开个加速器上上官网复制粘贴一下。如果说你不知道怎么开加速器,这里还有一个网站同样也有这个文件,是笔者自己的一个项目:stb_image.h。有了头文件过后,我们需要对头文件当中的内容进行编译,创建一个stb_image.cpp文件,具体代码如下

#define STB_IMAGE_IMPLEMENTATION
#include"stb_image.h"

这样我们就可以使用头文件当中的功能了。

在创建一个Texture类,方便我们使用纹理。

Texture.h

#pragma once

class Texture {
public:
	Texture(const std::string& path);
	~Texture();

	void Bind(unsigned int slot = 0) const;
	void UBind() const;

	uint32_t GetWidth() const  { return m_Width; }
	uint32_t GetHeight() const  { return m_Height; }
	uint32_t GetRendererID() const  { return m_RendererID; }

private:
	std::string m_Path;
	bool m_IsLoaded = false;
	uint32_t m_Width, m_Height;
	uint32_t m_RendererID;
	GLenum m_InternalFormat, m_DataFormat;
};

Texture.cpp

#include<glad/glad.h>

#include<string>
#include<iostream>

#include"stb_image.h"

#include"Texture.h"

Texture::Texture(const std::string& path):m_Path(path){
	stbi_set_flip_vertically_on_load(1);
	int width, height, channels;
	stbi_set_flip_vertically_on_load(1);
	stbi_uc* data = nullptr;
	
	data = stbi_load(path.c_str(), &width, &height, &channels, 4);
	
	if (data) {
		m_IsLoaded = true;

		m_Width = width;
		m_Height = height;

		m_InternalFormat = GL_RGBA8;
		m_DataFormat = GL_RGBA;

		glCreateTextures(GL_TEXTURE_2D, 1, &m_RendererID);
		glTextureStorage2D(m_RendererID, 1, GL_RGBA8, m_Width, m_Height);

		glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_REPEAT);

		glTextureSubImage2D(m_RendererID, 0, 0, 0, m_Width, m_Height, GL_RGBA, GL_UNSIGNED_BYTE, data);

		stbi_image_free(data);
	}
}

Texture::~Texture() {
	glDeleteTextures(1, &m_RendererID);
}

void Texture::Bind(unsigned int slot) const {
	glBindTextureUnit(slot, m_RendererID);
}

void Texture::UBind() const {
	glBindTexture(GL_TEXTURE_2D, 0);
}

有了将普通照片转换为纹理的类以后,我们还需要修改我们的着色器,代码如下

#type vertex
#version 450 core
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texCoord;

layout(location = 1) out vec2 v_TexCoord;

void main()
{
	gl_Position = position;	
	v_TexCoord = texCoord;
};

#type fragment
#version 450 core

layout(location = 0) out vec4 color;

layout(location = 1) in vec2 v_TexCoord;

uniform vec4 u_Color;
uniform sampler2D u_Texture;

void main()
{
	vec4 texColor = texture(u_Texture, v_TexCoord);
	color = texColor;
};

如果说读者不清楚怎么去编译自己的着色器,请阅读一笔者的OpenGL编译用户着色器这一篇文章。

绑定纹理,渲染至几何图形上

1、纹理的下标

float positions[] = {
		-0.5f,	-0.5f,	0.0f,0.0f,
		0.5f,	-0.5f,	1.0f,0.0f,
		0.5f,	0.5f,	1.0f,1.0f,
		-0.5f,	0.5f,	0.0f,1.0f
	};

GLuint buffer = 0;

glCreateBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), NULL);
//纹理坐标在buffer缓冲区中的分布情况
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const void*)(2*sizeof(float)));

有人可能有疑问了,这不是顶点的分布?这里给大家说明一下,OpenGL的一个顶点当中可以包含多个数据,顶点位置, 顶点颜色,法线,纹理坐标,等等。只要能够正确声明数据的分布,写多少个都行(硬件允许的情况下)。至于这个坐标值有什么讲究了,其实也很简单,就是下图的对应关系

2、创建纹理

Texture *pTexture = new Texture("assets/Textures/5_r.jpg");

3、着色器修改,用户参数

Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");
pShader->Bind();
pShader->UploadUniform1i("u_Texture", 0);

这里解释一下,为什么修改的值是0,我们回看一下Texture类中的Bind函数的定义

void Bind(unsigned int slot = 0) const;
void Texture::Bind(unsigned int slot) const {
	glBindTextureUnit(slot, m_RendererID);
}

没错这个纹理默认绑定在下标为0的纹理插槽上,所以在修改着色器用户变量是赋值为0

4、绑定纹理然后使用

while (!glfwWindowShouldClose(window)) {
		pFrameBuffer->Bind();
		pShader->Bind();
		pTexture->Bind(0);
		glClear(GL_COLOR_BUFFER_BIT);
		glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_INT, NULL);
		pFrameBuffer->UBind();

得到结果如下

 Jerry就出现了。

存在的一些疑问

可能有人要问,为什么这么发杂的纹理,只需要四个顶点的纹理坐标就可以了。其实片元着色器进行了一些插值处理,再[0,1]这个范围插入了非常多的值,基本上每个像素点都进行了计算,还有一些方法我们可以让片元着色器不要进行插值计算,这个就是后话了。

最后依然附上主函数的整体代码,希望能帮助到大家

#include<glad/glad.h>
#include<GLFW/glfw3.h>

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

#include<iostream>

#include"FrameBuffer.h"
#include"Shader.h"
#include"Texture.h"

int main() {
	glfwInit();

	GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);

	glfwMakeContextCurrent(window);
	glfwSwapInterval(1); // Enable vsync

	// Setup Dear ImGui context
	IMGUI_CHECKVERSION();
	ImGui::CreateContext();
	ImGuiIO& io = ImGui::GetIO(); (void)io;
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
	io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;         // Enable Docking
	io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;       // Enable Multi-Viewport / Platform Windows
	//io.ConfigViewportsNoAutoMerge = true;
	//io.ConfigViewportsNoTaskBarIcon = true;
	
	// Setup Dear ImGui style
	ImGui::StyleColorsDark();
	//ImGui::StyleColorsLight();

	// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
	ImGuiStyle& style = ImGui::GetStyle();
	if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
	{
		style.WindowRounding = 0.0f;
		style.Colors[ImGuiCol_WindowBg].w = 1.0f;
	}

	// Setup Platform/Renderer backends
	ImGui_ImplGlfw_InitForOpenGL(window, true);
	ImGui_ImplOpenGL3_Init("#version 130");

	//需要初始化GLAD
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	float positions[] = {
		-0.5f,	-0.5f,	0.0f,0.0f,
		0.5f,	-0.5f,	1.0f,0.0f,
		0.5f,	0.5f,	1.0f,1.0f,
		-0.5f,	0.5f,	0.0f,1.0f
	};

	unsigned int vertexIndex[] = {
		0,1,2,
		2,3,0
	};

	GLuint buffer = 0;
	GLuint indexBuffer = 0;

	glCreateBuffers(1, &buffer);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), NULL);

	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const void*)(2*sizeof(float)));

	glCreateBuffers(1, &indexBuffer);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertexIndex), vertexIndex, GL_STATIC_DRAW);

	bool show_demo_window = true;
	ImVec2 viewPortSize(640,480);
	float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};

	FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);
	Texture *pTexture = new Texture("assets/Textures/5_r.jpg");
	Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");
	pShader->Bind();
	pShader->UploadUniform1i("u_Texture", 0);
	pShader->UBind();

	while (!glfwWindowShouldClose(window)) {
		pFrameBuffer->Bind();
		pShader->Bind();
		pTexture->Bind(0);
		glClear(GL_COLOR_BUFFER_BIT);
		glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_INT, NULL);
		pFrameBuffer->UBind();

		// Start the Dear ImGui frame
		ImGui_ImplOpenGL3_NewFrame();
		ImGui_ImplGlfw_NewFrame();
		ImGui::NewFrame();

		ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());

		ImGui::Begin("ViewPort");
		viewPortSize = ImGui::GetContentRegionAvail();
		if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {
			pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);
			glViewport(0, 0, viewPortSize.x, viewPortSize.y);
		}
		uint32_t textureID = pFrameBuffer->GetColorAttachment();
		ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });
		ImGui::End();

		ImGui::Begin("ColorEditor");
		ImGui::ColorEdit4("##colorEditor", colorEditor);
		ImGui::End();

		/*if(show_demo_window)
			ImGui::ShowDemoWindow(&show_demo_window);*/

		// Rendering
		ImGui::Render();
		
		ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

		if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
		{
			GLFWwindow* backup_current_context = glfwGetCurrentContext();
			ImGui::UpdatePlatformWindows();
			ImGui::RenderPlatformWindowsDefault();
			glfwMakeContextCurrent(backup_current_context);
		}

		glfwSwapBuffers(window);

		glfwPollEvents();
	}

	// Cleanup
	ImGui_ImplOpenGL3_Shutdown();
	ImGui_ImplGlfw_Shutdown();
	ImGui::DestroyContext();

	delete pFrameBuffer;
	delete pShader;
	delete pTexture;

	glfwDestroyWindow(window);
	glfwTerminate();
}


http://www.kler.cn/a/429721.html

相关文章:

  • 《自动驾驶与机器人中的SLAM技术》ch9:自动驾驶车辆的离线地图构建
  • (12)springMVC文件的上传
  • 贪心算法详细讲解(沉淀中)
  • spring mvc源码学习笔记之十一
  • WINFORM - DevExpress -> DevExpress总结[安装、案例]
  • 目标检测中的Bounding Box(边界框)介绍:定义以及不同表示方式
  • 解决vscode 通过GoInstallUpdate Tools命令安装失败的问题
  • C# 抽奖程序winform示例
  • Keil5的Debug基础使用方式
  • TimeXplusplus——提高时间序列数据的可解释性,避免琐解和分布偏移问题的深度学习可解释性的框架
  • 12.06 深度学习-预训练
  • Linux下网卡实现NAT转发
  • flink-connector-mysql-cdc:03 mysql-cdc常见问题汇总
  • 智能指针中的share_ptr(共享智能指针)
  • 使用ffmpeg将视频与字幕合并为一个文件并将视频拼接
  • 永磁同步电机负载估计--线性扩张状态观测器
  • 分离轴定理检测两个凸多边形是否相交
  • AI驱动的低代码平台:解密背后的算法与架构创新
  • STC单片机I2C驱动例程
  • psmisc移植到ARM Linux环境
  • 【EthIf编译脚本】communication/EthIf/EthIf.mod.mk
  • 夜莺运维指南之自定义告警模板
  • C/C++流星雨
  • 使用php生成、识别二维码
  • ElasticSearch如何做性能优化?
  • Online Monocular Lane Mapping