OpenGL纹理学习记录

纹理(Texture)

纹理我们可以理解成是一张2D图片,给定一组纹理坐标,告诉GPU需要将图片放在哪个位置。同时如果位置上有像素点,则会覆盖上去,目前阶段,纹理就是无缝贴合在你给定的位置中。

对于一些精细的物体,需要对不同位置进行不同的贴图,从而到达模型精细真实的效果。

地板图片
我们将用这张地板贴图,进行简单纹理演示。

纹理映射(Map)

我们需要给出一定的纹理坐标,让GPU知道纹理贴图应该贴在哪里,因此我们需要在顶点坐标数组中,再添加一组纹理坐标:

//position array
    float positions[] = {
        // positions          // colors           // texture coords
         0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
         0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0, 0.0f, // bottom right
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // bottom left
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // top left 
    };

其中,一般来说窗口左下角为纹理坐标的 (0,0),右上角为(1,1)。而不同类型的图片格式坐标体系是不同的,例如 png 格式的图片,是由上往下扫描,因此对于这类的图片格式,我们需要在使用前进行filp反转,这个后文会提及。

纹理环绕

通常,纹理坐标范围是在零到一之间,而OpenGL是允许超过区间的。现实中的情况也是如此,往往物体和贴图不能很完美的契合,因此我们还需要采用纹理环绕的方式,即扩充图片:

环绕参数作用
GL_REPEAT重复图像
GL_MIRRORED_REPEAT重复镜像图像
GL_CLAMP_TO_EDGE重复纹理图像边缘像素点
GL_CLAMP_TO_BORDER超出范围用指定颜色填充

在创建纹理对象时,这些参数都需要单独为每个坐标轴设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

第一个参数,设置为2D纹理图像;第二个参数为设置S轴环绕方式(S,T,R分别代表常见坐标轴中的xyz);第三个参数确定环绕方式为镜像重复。
如果第三个参数设为 GL_CLAMP_TO_BORDER,则需要在后面紧跟以下代码,告诉GPU填充颜色:

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

纹理过滤(Filtering)

在实际应用中,图像或物体得到分辨率往往不是统一的,对应不同场景,同一图像物体的分辨率都可能发生变化,因此当分辨率不匹配时,我们需要采用纹理过滤的方式:

  • GL_NEAREST 邻近过滤。其实就是常见处理图像中的临近插值算法,OpenGL会选择该像素点最近的纹理坐标的像素点颜色,作为填充。也因此,直接填充,导致图像会有较为明显的割裂感,往往在纹理缩小时使用。
    在这里插入图片描述
  • GL_LINEAR 线性过滤。即常见处理图像中的线性插值算法,会根据临近纹理坐标几种颜色的占比,进行计算,从而得出一个较为平均的颜色,因此在图像上较为平滑,适合在纹理被放大时使用。
  • 在这里插入图片描述

加载图片

纹理的基础用法介绍完了,现在需要将纹理贴图载入进程序中,一般使用 stb_image.h 库,它可以加载当今流行的大部分图片格式,并且直接下载放在程序中即可使用。

Do this:
      #define STB_IMAGE_IMPLEMENTATION
   before you include this file in *one* C or C++ file to create the implementation.

上述为文件中提及,我们需要创建一个cpp文件,然后先定义 STB_IMAGE_IMPLIEMENTATTION 宏,再包含该头文件,即可使用。

#define STB_IMAGE_IMPLEMENTATION

#include"stb_image.h"

当需要载入png图像时,前文提过,坐标系的规定是不一样的,因此我们需要先反转,后调用load函数载入图像即可,(参数后文会提及):

stbi_set_flip_vertically_on_load(1);
	m_LocalBuffer = stbi_load(m_FilePath.c_str(), &m_Width, &m_Height, &m_BPP, 4);

自定义纹理类

在使用着色器或是纹理时,我比较喜欢封装成类,然后再对类进行功能扩充和调整。首先定义一个Texture类:

class Texture {
private:
	unsigned int m_Render_ID;  //纹理ID
	std::string m_FilePath;   //纹理贴图路径
	unsigned char* m_LocalBuffer;   //纹理缓冲区
	int m_Width, m_Height, m_BPP;   //宽,高,通道数
public:
	Texture(const std::string& file_path);
	~Texture();

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

	void SetParameteri(unsigned int target, unsigned int pname, int param);
	
	inline int GetWidth() const { return m_Width; };
	inline int GetHeight() const { return m_Height; };
	inline unsigned int GetTextureID() const { return m_Render_ID; };
};

最关键的构造函数定义如下:

Texture::Texture(const std::string& file_path)
	: m_Render_ID(0), m_FilePath(file_path), m_LocalBuffer(nullptr), 
	m_Width(0), m_Height(0), m_BPP(0)
{
	stbi_set_flip_vertically_on_load(1);
	m_LocalBuffer = stbi_load(m_FilePath.c_str(), &m_Width, &m_Height, &m_BPP, 4);


	glGenTextures(1, &m_Render_ID);
	glBindTexture(GL_TEXTURE_2D, m_Render_ID);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

	if (m_LocalBuffer) {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_Width, m_Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_LocalBuffer);
		glBindTexture(GL_TEXTURE_2D, 0);
	}
	else {
		std::cout << "Failed to load texture" << std::endl;
	}

	stbi_image_free(m_LocalBuffer);
}

首先,我们构造类对象时,获取文件路径,确定反转图像同时载入图像。载入图像 stbi_load() 需要文件路径参数,纹理图像宽高文件通道和纹理贴图通道(此时贴图为RGBA4通道)。

与着色器一样,我们先生成纹理区,再绑定纹理区。并分别设置好参数:纹理放大缩小的处理方式以及S轴 T轴的环绕方式。
最后将载入的图像,传入纹理缓冲区中。

应用纹理

  float positions[] = {
        // positions          // colors           // texture coords
         0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
         0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0, 0.0f, // bottom right
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // bottom left
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // top left 
    };

顶点坐标如上,我们需要告诉GPU每个数组元素的意义:

//set mode in position
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    //set mode in color
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    
    //set mode in texture
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * (sizeof(float)), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

对于位置信息,是前三个;对于颜色信息,从第四个开始计算,有三个元素;对于纹理信息,从第七个开始计算,有两个元素。

在这我载入两个纹理贴图,生成两个纹理对象,分别对应0和1:

// texture
    Texture texture1("res\\texture\\img\\wall.png");
    Texture texture2("res\\texture\\img\\awesomeface.png");

    glUniform1i(shader.GetUniformLoaction("texture1"), 0);
    glUniform1i(shader.GetUniformLoaction("texture2"), 1);

在主题循环中,我们需要将纹理缓冲区绑定给着色器,并输出出去:

shader.BindTexture(GL_TEXTURE_2D, texture1.GetTextureID());
shader.BindTexture(GL_TEXTURE_2D, texture2.GetTextureID());
shader.UseProgram();

texture1.Bind(0);
        texture2.Bind(1);
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

最终呈现结果:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值