通过前一章的绘制三角形,我们可以看到,通过多顶点添加颜色,可以绘制出丰富的图像,但如果要绘制更真实复杂的图像,那么就需要足够多的顶点,并且指定足够多的颜色,这无疑会增加额外的开销。这时候,引入了纹理这一概念。纹理就是一张2D(3D)的图片,我们可以为图片增加更多的细节处理,将处理好的图片加载进来就可以的到想要的图像。
一、指定三角形的纹理坐标
为了纹理映射到三角形中,我们需要指定三角形的每个顶点对应纹理的什么位置取样。纹理也有一个纹理坐标,纹理坐标的x轴和y轴的取值范围是[0,1],即纹理的左下角是(0,0),右上角是(1,1),指定三角形的左下角对纹理坐标的左下角(0,0)取样,三角形的右下角对纹理坐标的右下角(1,0)取样,三角形的顶点对纹理坐标的中上(0.5,1)取样,三角形顶点数据如下:
float vertices[] =
{
//顶点位置 颜色 纹理坐标
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f
};
二、链接纹理属性
链接纹理属性和链接顶点、颜色属性方法相同,但需要重新设置步长和起始位置偏移量,同时指定纹理位置为2
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
三、加载和创建纹理
我们设置完带纹理坐标的纹理属性后,需要把纹理加载进来,我们可以通过写一个图片加载器加载纹理,但太过于麻烦,stb_image是一个比较流行的图像加载库,它可以支持各种格式的图片,我们使用这个库来加载图片,std_image.h可以从这里下载,在引用这个库时,需要添加这个宏定义,让其只包含相关函数定义的源码
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
接下来就是加载图片
int width, height, nchannels;
unsigned char* data = stbi_load("./image/wall.jpg", &width, &height, &nchannels, 0);
stbi_load();是stb_imag库提供的图片加载函数,第一个参数是图片资源路径,第二、三个参数是图片的宽高,第三个参数是图片的通道数(如果是rgb的话,就是三个通道)
创建并绑定纹理对象
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
设置纹理环绕和过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri();设置指定轴的纹理环绕和过滤方式,环绕和过滤后续文章中会描述。
第一个参数,指定纹理目标
第二个参数,指定环绕轴过滤对象,GL_TEXTURE_WRAP_S为s(x)轴,GL_TEXTURE_WRAP_T为t(y)轴,GL_TEXTURE_MIN_FILTER表示缩放是的过滤方式,GL_TEXTURE_MAG_FILTER表示放大时的过滤方式
第三个参数是环绕方式
生成纹理
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 << "Load Texture Failed!" << std::endl;
return;
}
glTexImage2D();将图片加载到纹理
第一个参数是目标纹理
第二个参数是设置多级渐远纹理为基本等级(后续描述)
第三个参数告诉OpenGL将图片加载为RGB的方式
第四和第五个参数是纹理的宽高
第七个参数是原图的格式为RGB格式
第八个参数是原图的数据类型
第九个参数是图片数据
glGenerateMipmap();生成多级渐远的纹理
纹理生成后,不要忘记释放掉图片资源
stbi_image_free(data);
四、编写顶点和片段着色器
顶点着色器
#version 330 core
layout (location = 0) in vec3 apos;
layout (location = 1) in vec3 acolor;
layout (location = 2) in vec2 atexcoord;
out vec3 ourcolor;
out vec2 texcoord;
void main()
{
gl_Position = vec4(apos.x, apos.y, apos.z, 1.0);
ourcolor = acolor;
texcoord = atexcoord;
};
atexcoord 是在设置顶点属性时,传进来的纹理坐标,location为2
texcoord = atexcoord;将纹理坐标输出到片段着色器中
片段着色器
#version 330 core
out vec4 FragColor;
//uniform vec4 ourcolor;
in vec3 ourcolor;
in vec2 texcoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, texcoord);
};
uniform sampler2D ourTexture;定义纹理采样器,从外界输入进来的纹理图片,需要在外界绑定纹理图片
使用texture函数对纹理进行采样,第一个参数是纹理采样器,第二个是纹理坐标
五、绑定纹理采样器
最后一步就是绑定问你采样器,这样,图片就绘制出来了
glBindTexture(GL_TEXTURE_2D, mTexture);
绘制的结果如图
源码如下
glsl.h
#pragma once
#include "Glad\glad.h"
#include "GLFW\glfw3.h"
#include <iostream>
#include <fstream>
#include <sstream>
class CShader
{
public:
//创建编译shader
CShader(const char* vShaderPath, const char* fShaderPath);
//激活着色程序
void useProgram();
//绘制图形
void Draw();
//处理顶点数据
void dealVetPoint();
//删除着色器和着色程序
void deleteShaderAndProgram();
//加载纹理
void loadTexture();
//uniform工具函数
void setUniform4f();
~CShader();
public:
unsigned int mProgramID; //程序ID
unsigned int VAO; //顶点数组对象
unsigned int VBO; //顶点缓冲对象
unsigned int EBO; //元素缓冲对象
unsigned int mTexture; //纹理对象
};
glsl.cpp
#include "glsl.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
CShader::CShader(const char* vShaderPath, const char* fShaderPath)
{
//从文件中读取着色器
std::ifstream vShaderFile;
std::ifstream fShaderFile;
vShaderFile.open(vShaderPath);
if (vShaderFile.fail())
{
std::cout << "Open vShader Failed!" << std::endl;
return;
}
fShaderFile.open(fShaderPath);
if (fShaderFile.fail())
{
std::cout << "Open fShader Failed!" << std::endl;
return;
}
//将文件中的缓冲内容读取到数据流中
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
vShaderFile.close();
fShaderFile.close();
//将数据流转换到string中
std::string vertexCode, fragmentCode;
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
//创建编译着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vShaderCode, NULL);
glCompileShader(vertexShader);
int success;
char logInfo[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, logInfo);
std::cout << "Compile vertShader Error" << logInfo << std::endl;
}
unsigned int fragmenShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmenShader, 1, &fShaderCode, NULL);
glCompileShader(fragmenShader);
glGetShaderiv(fragmenShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmenShader, 512, NULL, logInfo);
std::cout << "Compile FragShader Error" << logInfo << std::endl;
}
//创建链接着色器程序
mProgramID = glCreateProgram();
glAttachShader(mProgramID, vertexShader);
glAttachShader(mProgramID, fragmenShader);
glLinkProgram(mProgramID);
glGetProgramiv(mProgramID, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(mProgramID, 512, NULL, logInfo);
std::cout << "Link Shader Error" << logInfo << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmenShader);
}
void CShader::loadTexture()
{
int width, height, nchannels;
//加载图片
unsigned char* data = stbi_load("./image/wall.jpg", &width, &height, &nchannels, 0);
//创建并绑定纹理对象
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
//设置纹理环绕和过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//生成纹理
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 << "Load Texture Failed!" << std::endl;
return;
}
stbi_image_free(data);
}
void CShader::useProgram()
{
glUseProgram(mProgramID);
}
void CShader::Draw()
{
glBindTexture(GL_TEXTURE_2D, mTexture);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
void CShader::dealVetPoint()
{
//顶点数据处理
//定义顶点数组
float vertices[] =
{
//顶点位置 颜色 纹理坐标
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f
};
//创建并绑定顶点数组对象,用于存储顶点数据的配置
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//生成一个顶点缓冲对象VBO
glGenBuffers(1, &VBO);
//将生成的VBO绑定到目标上
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//将顶点数据复制到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//链接顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
//启用顶点属性
glEnableVertexAttribArray(0);
//链接颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
//启用颜色属性
glEnableVertexAttribArray(1);
//链接纹理属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
//启用纹理属性
glEnableVertexAttribArray(2);
//解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void CShader::setUniform4f()
{
int uniforLoc = glGetUniformLocation(mProgramID, "ourcolor");
glUniform4f(uniforLoc, 1.0f, 0.0f, 0.0f, 1.0f);
}
void CShader::deleteShaderAndProgram()
{
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(mProgramID);
}
CShader::~CShader()
{
}
int main(int argc, char *argv[])
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (NULL == window)
{
std::cout << "Create Windows Failed" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Load Failed" << std::endl;
return -1;
}
CShader shader("./shader/shader.vs", "./shader/shader.fs");
shader.dealVetPoint();
shader.loadTexture();
glViewport(0, 0, 800, 600);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
shader.useProgram();
//shader.setUniform4f();
shader.Draw();
glfwSwapBuffers(window);
glfwPollEvents();
}
shader.deleteShaderAndProgram();
//查看支持多少个attribute
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Max Num is: " << nrAttributes << std::endl;
glfwTerminate();
system("pause");
return 0;
}