代码放在github上
根据教程:ogldev一步步从零开始,记录学习历程
本节的纹理学习还参考了教程: learnopengl和《OpenGL 编程指南》
我们之前的OpenGL学习一直停留在绘制一个彩色的四面体:
而现实世界物体的表面一般都呈现丰富特殊的颜色,并且在很小的图形上呈现多彩的变化,如果我们还是用计算机计算出一个个微小像素上的颜色再拼成特定的“图案”是非常辛苦的工作。
纹理映射就是找到一张图片,然后把它“粘”到物体表面上,就像贴墙纸一样。
OpenGL支持不同类型的纹理:1D(一维),2D(二维),3D(三维),CUBE(立方体)等等,我们使用的是2D纹理
所以我们要指定图片的“某一块”,这个时候就需要指定纹理坐标
上图显示了纹理坐标怎么定义,纹理坐标定义在单位化的正方形内,并且以左下角为(0,0)
比如图片像素是480*320,纹理坐标是(0.5,0.5),则在图形中的坐标即为(240,160)
使用纹理映射,需要遵循以下3个步骤
glGenTextures(1, &m_textureObj);
glBindTexture(m_textureTarget, m_textureObj);
正如之前我们学到的创建顶点缓冲区和索引缓冲区用到的GLUT的函数glGenBuffers(),我们创建一个纹理对象需呀用到函数glGenTextures(),函数返回一个没有用到的整数(对象编号)赋值到m_textureObj
并且我们要绑定当前的纹理对象,就像使用glBindBuffer()绑定顶点缓冲对象和索引缓冲对象一样,在对纹理对象进行操作需要先绑定当前的纹理对象
然而我们的贴图在硬盘中存放,我们OpenGL应用程序无法直接从硬盘中加载纹理数据,所以需要我们先把硬盘中的贴图读取到内存里,再从内存中加载纹理数据,示意图如下:
unsigned char *data = stbi_load(m_fileName.c_str(),&width, &height, &nrChannels, 0);
...
glTexImage2D(m_textureTarget, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
这里我们使用github上一个开源库——stb_image.h来读取存在硬盘里的图像数据
glTexImage2D()是根据指定的参数生成一个2D纹理
glTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels);
我们在主程序main.cpp中为顶点数据增加纹理坐标,首先自定义一个数据结构,用来存放三维位置信息(一个长度为3的float类型数组)和二维纹理坐标(长度为2的数组):
struct Vertex
{
Vector3f m_pos;
Vector2f m_tex;
Vertex() {}
Vertex(Vector3f pos, Vector2f tex)
{
CopyVector3(m_pos, pos);
CopyVector2(m_tex, tex);
}
};
随后我们创建顶点缓冲器时不仅包含我们的位置信息,还要包含纹理坐标信息
static void CreateVertexBuffer()
{
Vector3f Vertices3f[4];
Vector2f Vertices2f[4];
LoadVector3(Vertices3f[0], -1.0f, -1.0f, 0.5773f); LoadVector2(Vertices2f[0], 0.0f, 0.0f);
LoadVector3(Vertices3f[1], 0.0f, -1.0f, -1.15475f); LoadVector2(Vertices2f[1], 0.5f, 0.0f);
LoadVector3(Vertices3f[2], 1.0f, -1.0f, 0.5773f); LoadVector2(Vertices2f[2], 1.0f, 0.0f);
LoadVector3(Vertices3f[3], 0.0f, 1.0f, 0.0f); LoadVector2(Vertices2f[3], 0.5f, 1.0f);
Vertex Vertices[4] = { Vertex(Vertices3f[0],Vertices2f[0]),
Vertex(Vertices3f[1],Vertices2f[1]),
Vertex(Vertices3f[2],Vertices2f[2]),
Vertex(Vertices3f[3],Vertices2f[3]) };
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}
在渲染函数Render()中完成纹理坐标到着色器的传送
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)sizeof(Vector3f));
启动一个顶点属性1用于纹理坐标,并且在glVertexAttribPointer()函数的参数设置偏移量为Vector3f,因为每个顶点坐标之后存放纹理坐标存,偏移量即为顶点坐标
shader.vs:
#version 330
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
uniform mat4 gWVP;
out vec2 TexCoord0;
void main()
{
gl_Position= gWVP * vec4(Position,1.0);
TexCoord0=TexCoord;
}
可以看到同获取位置坐标一样,这里layout参数修饰符里用location=1来获取位置坐标到TexCoord,这里跟刚才glEnableVertexAttribArray(1)对应
shader.fs:
#version 330
in vec2 TexCoord0;
out vec4 FragColor;
uniform sampler2D gSampler;
void main()
{
FragColor=texture2D(gSampler,TexCoord0.st);
}
glUniform1i(gSampler, 0);
我们创建了一个Texture类,用来封装对的纹理操作。
opengl_texture.h:
#ifndef __OPENGL_TEXTURE_H
#define __OPENGL_TEXTURE_H
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
class Texture
{
public:
Texture(GLenum TextureTarget, const std::string& FileName)
{
m_textureTarget = TextureTarget;
m_fileName = FileName;
}
bool Load()
{
int width, height, nrChannels;
unsigned char *data = stbi_load(m_fileName.c_str(),&width, &height, &nrChannels, 0);
glGenTextures(1, &m_textureObj);
glBindTexture(m_textureTarget, m_textureObj);
glTexImage2D(m_textureTarget, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(m_textureTarget, 0);
return true;
}
void Bind(GLenum TextureUnit)
{
glActiveTexture(TextureUnit);
glBindTexture(m_textureTarget, m_textureObj);
}
private:
std::string m_fileName;
GLenum m_textureTarget;
GLuint m_textureObj;
};
#endif
glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
这两个句用来设置纹理滤波方式。
由于纹理坐标不依赖于分辨率,且可以是任何浮点数,而纹理贴图中纹素的坐标以整数定义,如果纹理坐标映射到纹素上坐标为(152.34,745.14),就需要用到滤波取一个颜色。
纹理滤波方式一般有两种:GL_NEAREST(邻近滤波,默认滤波方式)和GL_LINEAR(线性滤波)
邻近滤波相当于舍去小数,把(152.34,745.14)变为(152,745),线性滤波选择周围纹素2x2的4个坐标 ( (152,745), (153,745), (152,744) 和 (153,744) ) 并根据他们的颜色做线性插值
纹理过滤可以指定放大和缩小操作,我们这里设置放大和缩小操作都是线性滤波方式
glActiveTexture(TextureUnit);
glActiveTexture()函数用来激活纹理单元,这里参数使用纹理单元的枚举(GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2 等等)
比如我们在渲染主循环函数Render()中使用了
pTexture->Bind(GL_TEXTURE0)
我们在向着色器传递采样器Uniform变量时就传递的是0
glUniform1i(gSampler, 0);
opengl_math.h, opengl_camera.h,opengl_pipeline.h, opengl_camera.cpp, opengl_pipeline.cpp都没有改变,因为代码量太大就不贴出来了,可以到github上下载来看
代码放在github上
main.cpp:
#include
#include
#include ]
#include
#include
#include
#include "opengl_math.h"
#include "opengl_pipeline.h"
#include "opengl_texture.h"
#define Window_Width 1024
#define Window_Height 768
using namespace std;
GLuint VBO,IBO;
GLuint gWVPLocation;
GLuint gSampler;
Camera* GameCamera = NULL;
Texture* pTexture = NULL;
PersProjInfo gPersProjInfo;
const char* pVSFileName = "shader.vs";
const char* pFSFileName = "shader.fs";
struct Vertex
{
Vector3f m_pos;
Vector2f m_tex;
Vertex() {}
Vertex(Vector3f pos, Vector2f tex)
{
CopyVector3(m_pos, pos);
CopyVector2(m_tex, tex);
}
};
static void Render()
{
GameCamera->OnRender();
glClear(GL_COLOR_BUFFER_BIT);
static float Scale = 0.0f;
Scale += 0.1f;
Pipeline p;
p.Rotate(0.0f, Scale, 0.0f);
p.WorldPos(0.0f, 0.0f, 3.0f);
p.SetCamera(*GameCamera);
p.SetPerspectiveProj(gPersProjInfo);
glUniformMatrix4fv(gWVPLocation, 1, GL_FALSE, (const GLfloat*)p.GetWVPTrans());
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)sizeof(Vector3f));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
pTexture->Bind(GL_TEXTURE0);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glutSwapBuffers();
}
static void Keyboard(int key, int x, int y)
{
GameCamera->OnKeyboard(key);
}
static void Mouse(int x, int y)
{
GameCamera->OnMouse(x, y);
}
static void InitializeCallbacks()
{
glutDisplayFunc(Render);
glutIdleFunc(Render);
glutPassiveMotionFunc(Mouse);
glutSpecialFunc(Keyboard);
}
static void CreateVertexBuffer()
{
Vector3f Vertices3f[4];
Vector2f Vertices2f[4];
LoadVector3(Vertices3f[0], -1.0f, -1.0f, 0.5773f); LoadVector2(Vertices2f[0], 0.0f, 0.0f);
LoadVector3(Vertices3f[1], 0.0f, -1.0f, -1.15475f); LoadVector2(Vertices2f[1], 0.5f, 0.0f);
LoadVector3(Vertices3f[2], 1.0f, -1.0f, 0.5773f); LoadVector2(Vertices2f[2], 1.0f, 0.0f);
LoadVector3(Vertices3f[3], 0.0f, 1.0f, 0.0f); LoadVector2(Vertices2f[3], 0.5f, 1.0f);
Vertex Vertices[4] = { Vertex(Vertices3f[0],Vertices2f[0]),
Vertex(Vertices3f[1],Vertices2f[1]),
Vertex(Vertices3f[2],Vertices2f[2]),
Vertex(Vertices3f[3],Vertices2f[3]) };
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}
static void CreateIndexBuffer()
{
unsigned int Indices[] =
{
0,3,1,
1,3,2,
2,3,0,
0,1,2
};
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}
bool ReadFile(const char* FileName, string &outFile)
{
ifstream f(FileName);
bool ret = FALSE;
if (f.is_open()) {
string line;
while (getline(f, line)) {
outFile.append(line);
outFile.append("\n");
}
f.close();
ret = TRUE;
}
else {
fprintf(stderr, "%s : %d unable to read file: %s", __FILE__, __LINE__, FileName);
system("pause");
exit(1);
}
return ret;
}
static void Addshader(GLuint ShaderProgram,const char* ShaderText,GLenum ShaderType)
{
GLuint ShaderObj = glCreateShader(ShaderType);
if (!ShaderObj) {
fprintf(stderr, "Error creating shder object");
system("pause");
exit(1);
}
const GLchar* p[1];
p[0] = ShaderText;
GLint Length[1];
Length[0] = strlen(ShaderText);
glShaderSource(ShaderObj, 1, p, Length);
glCompileShader(ShaderObj);
GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar InfoLog[1024];
glGetShaderInfoLog(ShaderObj,sizeof(InfoLog),NULL,InfoLog);
fprintf(stderr, "Error compiling shader object: '%s'", InfoLog);
system("pause");
exit(1);
}
glAttachShader(ShaderProgram, ShaderObj);
}
static void CompileShader()
{
GLuint ShaderProgram = glCreateProgram();
if (!ShaderProgram) {
fprintf(stderr, "Error creating shader program");
system("pause");
exit(1);
}
string vs, fs;
if (!ReadFile(pVSFileName, vs)) {
exit(1);
}
if (!ReadFile(pFSFileName, fs)) {
exit(1);
}
Addshader(ShaderProgram,vs.c_str(),GL_VERTEX_SHADER);
Addshader(ShaderProgram,fs.c_str(),GL_FRAGMENT_SHADER);
GLchar ErrorLog[1024] = { 0 };
GLint Success;
glLinkProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (!Success) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error linking shader program: '%s'", ErrorLog);
system("pause");
exit(1);
}
glValidateProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL_VALIDATE_STATUS, &Success);
if (!Success) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Valied to shader program:'%s'", ErrorLog);
system("pause");
exit(1);
}
glUseProgram(ShaderProgram);
gWVPLocation = glGetUniformLocation(ShaderProgram, "gWVP");
assert(gWVPLocation != 0xFFFFFFFF);
gSampler = glGetUniformLocation(ShaderProgram, "gSampler");
assert(gSampler != 0xFFFFFFFF);
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(10, 10);
glutInitWindowSize(Window_Width, Window_Height);
glutCreateWindow("Texture");
glutGameModeString("1280x1024@32");
InitializeCallbacks();
GLenum res = glewInit();
if (res != GLEW_OK) {
fprintf(stderr, "unable to init glew");
system("pause");
exit(1);
}
printf("GL version : '%s'", glGetString(GL_VERSION));
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
CreateVertexBuffer();
CreateIndexBuffer();
CompileShader();
glUniform1i(gSampler, 0);
pTexture = new Texture(GL_TEXTURE_2D, "test.png");
if (!pTexture->Load()) {
return 1;
}
gPersProjInfo.FOV = 60.0f;
gPersProjInfo.Height = Window_Height;
gPersProjInfo.Width = Window_Width;
gPersProjInfo.zNear = 1.0f;
gPersProjInfo.zFar = 100.0f;
Vector3f CameraPos, CameraTarget, CameraUp;
LoadVector3(CameraPos, 0.0f, 0.0f, -4.0f);
LoadVector3(CameraTarget, 0.0f, 0.0f, 1.0f);
LoadVector3(CameraUp, 0.0f, 1.0f, 0.0f);
GameCamera = new Camera(Window_Width, Window_Height, CameraPos, CameraTarget, CameraUp);
glutMainLoop();
return 0;
}
这里的相较于上一节的改变,大部分已经在上面讲到了,有几处还没提到:
pTexture = new Texture(GL_TEXTURE_2D, "test.png");
if (!pTexture->Load()) {
return 1;
}
这里创建纹理对象,并加载我们在同一工程目录下的test.png贴图文件
glFrontFace(GL_CW); glCullFace(GL_BACK); glEnable(GL_CULL_FACE);
glFrontFace()用来设置顺时针多边形为正面,GL_CW(顺时针为正面),GL_CCW(逆时针为正面)
索引里三角形顺序都是顺时针所以需要设置顺时针为正面,不然默认逆时针为正面