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