本教程将说明如何在OpenGL 4.0中使用纹理。通过将纹理上的照片和其他图像应用到多边形,纹理可以使我们在场景中增加真实感。例如,在本教程中,我们将拍摄以下图像:
图片文件在我的下载资源里,大家也可以去网上找一些targa格式的图片。
然后将其应用于上一教程中的多边形以产生以下结果:
我们将使用的纹理格式为.tga文件。这是一种称为targa的32位格式,支持alpha通道,可以使用大多数图像编辑应用程序创建。
下一篇文章里,我会重点讲解32位和24,16,8位深度图片的区别。并且增加纹理格式 .bmp格式图片作为贴图。BMP图片是最常见的图片格式了,可以直接将png,jpg等格式图片重命名为.bmp为后缀的图片即可。
在进入代码之前,我们应该讨论纹理映射的工作原理。要将像素从.tga图像映射到多边形,我们使用所谓的Texel坐标系。该系统将像素的整数值转换为0.0f和1.0f之间的浮点值。例如,如果纹理宽度为256个像素,则第一个像素将映射为0.0f,第256个像素将映射为1.0f,中间的128个像素将映射为0.5f。
在纹理坐标系中,宽度值称为“ U”,高度值称为“ V”。宽度从左侧的0.0变为右侧的1.0。高度从底部的0.0到顶部的1.0。例如,左下将表示为U 0.0,V 0.0,右上将表示为U 1.0,V 1.0。我在下面做了一张图来说明这个系统:
现在,我们对如何将纹理映射到多边形有了基本的了解,我们可以查看本教程的更新框架:
框架
自上一教程以来,框架的更改是ModelClass内部的新TextureClass和替换ColorShaderClass的新TextureShaderClass。我们将通过首先查看新的TextureClass来开始代码部分。
Textureclass.h
TextureClass封装了单个纹理资源的加载,卸载和访问。对于每个需要的纹理,必须实例化此类的对象。
// Filename: textureclass.h
#ifndef _TEXTURECLASS_H_
#define _TEXTURECLASS_H_
//单个纹理资源的加载,卸载和访问
//
// INCLUDES //
//
#include <stdio.h>
///
// MY CLASS INCLUDES //
///
#include "openglclass.h"
// Class name: TextureClass
class TextureClass
{
private:
//我们使用的图像格式称为Targa,并且具有我们需要此结构的唯一标头。
struct TargaHeader
{
unsigned char data1[12];
unsigned short width;
unsigned short height;
unsigned char bpp;
unsigned char data2;
};
public:
TextureClass();
TextureClass(const TextureClass&);
~TextureClass();
//前两个函数,1.从给定的文件名加载纹理,2.并在不再需要时卸载该纹理。
bool Initialize(OpenGLClass*, char*, unsigned int, bool);
void Shutdown();
private:
//LoadTarga函数将targa图像加载到OpenGL纹理中。如果要使用其他格式,例如.bmp,.dds等,则可以在此处放置加载功能。
bool LoadTarga(OpenGLClass*, char*, unsigned int, bool);
private:
//加载的布尔值指示纹理是否已加载到此类对象中。m_textureID是OpenGL看到的纹理的ID号。
bool loaded;
unsigned int m_textureID;
};
#endif
Textureclass.cpp
// Filename: textureclass.cpp
#include "textureclass.h"
//类构造函数会将加载的布尔值初始化为false,以便我们知道尚未加载纹理。
TextureClass::TextureClass()
{
loaded = false;
}
TextureClass::TextureClass(const TextureClass& other)
{
}
TextureClass::~TextureClass()
{
}
//Initialize接受OpenGL指针,纹理的文件名,将纹理加载到其中的纹理单元,
//一个布尔值指示纹理是否应在边缘处包裹或夹持颜色。
//然后,它将targa文件加载到通过调用LoadTarga函数指定的OpenGL纹理单元中。现在可以使用纹理进行渲染。
bool TextureClass::Initialize(OpenGLClass * OpenGL, char* filename, unsigned int textureUnit, bool wrap)
{
bool result;
// 加载targa文件.
result = LoadTarga(OpenGL, filename, textureUnit, wrap);
// 加载bmp文件.
//result = LoadBmp(OpenGL, filename, textureUnit, wrap);
if (!result)
{
return false;
}
return true;
}
//如果已加载纹理资源,则Shutdown函数将释放纹理资源
void TextureClass::Shutdown()
{
// 如果纹理已加载,请确保在关闭时将其释放.
if (loaded)
{
glDeleteTextures(1, &m_textureID);
loaded = false;
}
return;
}
//LoadTarga将.tga图像加载到OpenGL纹理上。它还为纹理设置纹理过滤,纹理包裹和mipmap
bool TextureClass::LoadTarga(OpenGLClass * OpenGL, char* filename, unsigned int textureUnit, bool wrap)
{
int error, width, height, bpp, imageSize;
FILE* filePtr;
unsigned int count;
TargaHeader targaFileHeader;
unsigned char* targaImage;
//开始部分将.tga文件加载到名为targaImage的缓冲区中
// 打开targa文件以二进制形式读取。
error = fopen_s(&filePtr, filename, "rb");
if (error != 0)
{
return false;
}
// 读取文件头.
count = fread(&targaFileHeader, sizeof(TargaHeader), 1, filePtr);
if (count != 1)
{
return false;
}
// 从标题获取重要信息.
width = (int)targaFileHeader.width;
height = (int)targaFileHeader.height;
bpp = (int)targaFileHeader.bpp;
// 检查它是32位而不是24位。.
if (bpp != 32)
{
return false;
}
// 计算32位图像数据的大小.
imageSize = width * height * 4;
// 为targa图像数据分配内存.
targaImage = new unsigned char[imageSize];
if (!targaImage)
{
return false;
}
// 读取targa图像数据.
count = fread(targaImage, 1, imageSize, filePtr);
if (count != imageSize)
{
return false;
}
// 关闭文件.
error = fclose(filePtr);
if (error != 0)
{
return false;
}
//现在,缓冲区包含.tga数据,我们创建了OpenGL纹理对象,并将缓冲区复制到该纹理对象中。
//请注意,.tga的RGB已反转,因此在glTextImage2D中,我们需要将输入格式设置为GL_BGRA,
//以便在加载时会反转红色和蓝色分量。
// 设置用于存储数据的唯一纹理单位
OpenGL->glActiveTexture(GL_TEXTURE0 + textureUnit);
// 生成纹理的ID.
glGenTextures(1, &m_textureID);
// 将纹理绑定为2D纹理.
glBindTexture(GL_TEXTURE_2D, m_textureID);
// 将图像数据加载到纹理单元中.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, targaImage);
//加载纹理后,我们可以设置环绕,过滤并为其生成mipmap
// 将纹理颜色设置为环绕或钳制到边缘.
if (wrap)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
else
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
}
// 设置纹理过滤.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// 生成纹理的mipmap.
OpenGL->glGenerateMipmap(GL_TEXTURE_2D);
// 释放targa图像数据.
delete[] targaImage;
targaImage = 0;
// 设置加载纹理.
loaded = true;
return true;
}
texture.vs
新的GLSL纹理顶点着色器与我们在上一教程中介绍的颜色顶点着色器非常相似。但是,现在有了像素颜色输入和颜色输出,而不是单纯的颜色输入和颜色输出。还要注意,纹理坐标使用vec2类型,因为它仅包含两个用于U和V坐标的浮点数,而颜色输入和输出具有用于R,G和B的三个浮点数。就像上一教程中的颜色一样,将纹理坐标直接传递到像素着色器。否则,顶点着色器将与之前的教程相同。
// Filename: texture.vs
#version 400
/
// INPUT VARIABLES //
/
in vec3 inputPosition;
in vec2 inputTexCoord;
//
// OUTPUT VARIABLES //
//
out vec2 texCoord;
///
// UNIFORM VARIABLES //
///
uniform mat4 worldMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
// Vertex Shader
void main(void)
{
// 根据世界,视图和投影矩阵计算顶点的位置。
gl_Position = worldMatrix * vec4(inputPosition, 1.0f);
gl_Position = viewMatrix * gl_Position;
gl_Position = projectionMatrix * gl_Position;
// 存储像素着色器的纹理坐标.
texCoord = inputTexCoord;
}
Texture.ps
像素着色器具有一个新的统一变量,称为shaderTexture。这是一个纹理采样器,允许我们访问已加载到OpenGL纹理中的targa图像。要访问它,像素着色器使用一个称为“纹理”的新函数,该函数使用来自顶点着色器的输入纹理坐标从shaderTexture采样像素。请注意,OpenGL会插值纹理坐标以与我们在屏幕上绘制的当前像素匹配。一旦使用纹理坐标从纹理中采样了像素,然后将其作为最终输出像素颜色返回。
// Filename: texture.ps
#version 400
/
// INPUT VARIABLES //
/
in vec2 texCoord;
//
// OUTPUT VARIABLES //
//
out vec4 outputColor;
///
// UNIFORM VARIABLES //
///
uniform sampler2D shaderTexture;
// Pixel Shader
void main(void)
{
vec4 textureColor;
//在此纹理坐标位置使用采样器从纹理中采样像素颜色
textureColor = texture(shaderTexture, texCoord);
outputColor = textureColor;
}
Textureshaderclass.h
TextureShaderClass只是ColorShaderClass的更新版本,是从上一教程修改而来的,用于处理纹理坐标而不是颜色分量。
//TextureShaderClass只是ColorShaderClass的更新版本,是从上一教程修改而来的,用于处理纹理坐标而不是颜色分量
// Filename: textureshaderclass.h
#ifndef _TEXTURESHADERCLASS_H_
#define _TEXTURESHADERCLASS_H_
//
// INCLUDES //
//
#include <fstream>
using namespace std;
///
// MY CLASS INCLUDES //
///
#include "openglclass.h"
// Class name: TextureShaderClass
class TextureShaderClass
{
public:
TextureShaderClass();
TextureShaderClass(const TextureShaderClass&);
~TextureShaderClass();
bool Initialize(OpenGLClass*, HWND);
void Shutdown(OpenGLClass*);
void SetShader(OpenGLClass*);
bool SetShaderParameters(OpenGLClass*, float*, float*, float*, int);
private:
bool InitializeShader(char*, char*, OpenGLClass*, HWND);
char* LoadShaderSourceFile(char*);
void OutputShaderErrorMessage(OpenGLClass*, HWND, unsigned int, char*);
void OutputLinkerErrorMessage(OpenGLClass*, HWND, unsigned int);
void ShutdownShader(OpenGLClass*);
private:
unsigned int m_vertexShader;
unsigned int m_fragmentShader;
unsigned int m_shaderProgram;
};
#endif
Textureshaderclass.cpp
我会指出,仅对源文件进行了几处更改(除了将其重命名为TextureShaderClass之外)。
//TextureShaderClass只是ColorShaderClass的更新版本,是从上一教程修改而来的,用于处理纹理坐标而不是颜色分量
// Filename: textureshaderclass.cpp
#include "textureshaderclass.h"
TextureShaderClass::TextureShaderClass()
{
}
TextureShaderClass::TextureShaderClass(const TextureShaderClass& other)
{
}
TextureShaderClass::~TextureShaderClass()
{
}
bool TextureShaderClass::Initialize(OpenGLClass* OpenGL, HWND hwnd)
{
bool result;
//将为该着色器加载新的texture.vs和texture.ps GLSL文件。
// 初始化顶点和像素着色器.
result = InitializeShader("../Engine/texture.vs", "../Engine/texture.ps", OpenGL, hwnd);
if (!result)
{
return false;
}
return true;
}
void TextureShaderClass::Shutdown(OpenGLClass* OpenGL)
{
// 关闭顶点和像素着色器以及相关对象.
ShutdownShader(OpenGL);
return;
}
void TextureShaderClass::SetShader(OpenGLClass* OpenGL)
{
// 安装着色器程序作为当前渲染状态的一部分
OpenGL->glUseProgram(m_shaderProgram);
return;
}
bool TextureShaderClass::InitializeShader(char* vsFilename, char* fsFilename, OpenGLClass* OpenGL, HWND hwnd)
{
const char* vertexShaderBuffer;
const char* fragmentShaderBuffer;
int status;
// 将顶点着色器源文件加载到文本缓冲区中
vertexShaderBuffer = LoadShaderSourceFile(vsFilename);
if (!vertexShaderBuffer)
{
return false;
}
// 将片段着色器源文件加载到文本缓冲区中。
fragmentShaderBuffer = LoadShaderSourceFile(fsFilename);
if (!fragmentShaderBuffer)
{
return false;
}
// 创建一个顶点和片段着色器对象.
m_vertexShader = OpenGL->glCreateShader(GL_VERTEX_SHADER);
m_fragmentShader = OpenGL->glCreateShader(GL_FRAGMENT_SHADER);
// 将着色器源代码字符串复制到顶点和片段着色器对象中
OpenGL->glShaderSource(m_vertexShader, 1, &vertexShaderBuffer, NULL);
OpenGL->glShaderSource(m_fragmentShader, 1, &fragmentShaderBuffer, NULL);
// 释放顶点和片段着色器缓冲区.
delete[] vertexShaderBuffer;
vertexShaderBuffer = 0;
delete[] fragmentShaderBuffer;
fragmentShaderBuffer = 0;
// 编译着色器.
OpenGL->glCompileShader(m_vertexShader);
OpenGL->glCompileShader(m_fragmentShader);
// 检查顶点着色器是否编译成功.
OpenGL->glGetShaderiv(m_vertexShader, GL_COMPILE_STATUS, &status);
if (status != 1)
{
// 如果未编译,则将语法错误消息写出到文本文件中以供检查.
OutputShaderErrorMessage(OpenGL, hwnd, m_vertexShader, vsFilename);
return false;
}
// 检查片段着色器是否成功编译.
OpenGL->glGetShaderiv(m_fragmentShader, GL_COMPILE_STATUS, &status);
if (status != 1)
{
// 如果未编译,则将语法错误消息写出到文本文件中以供检查.
OutputShaderErrorMessage(OpenGL, hwnd, m_fragmentShader, fsFilename);
return false;
}
// 创建一个着色器程序对象.
m_shaderProgram = OpenGL->glCreateProgram();
// 将顶点和片段着色器附加到程序对象.
OpenGL->glAttachShader(m_shaderProgram, m_vertexShader);
OpenGL->glAttachShader(m_shaderProgram, m_fragmentShader);
//已更改第二个着色器输入变量,以匹配inputTexCoord的顶点着色器中的输入。
// 绑定着色器输入变量.
OpenGL->glBindAttribLocation(m_shaderProgram, 0, "inputPosition");
OpenGL->glBindAttribLocation(m_shaderProgram, 1, "inputTexCoord");
// 链接着色器程序
OpenGL->glLinkProgram(m_shaderProgram);
// 检查链接的状态
OpenGL->glGetProgramiv(m_shaderProgram, GL_LINK_STATUS, &status);
if (status != 1)
{
// 如果未链接,则将语法错误消息写出到文本文件中以供检查.
OutputLinkerErrorMessage(OpenGL, hwnd, m_shaderProgram);
return false;
}
return true;
}
char* TextureShaderClass::LoadShaderSourceFile(char* filename)
{
ifstream fin;
int fileSize;
char input;
char* buffer;
// 打开着色器源文件
fin.open(filename);
// 如果无法打开文件,则退出.
if (fin.fail())
{
return 0;
}
// /初始化文件的大小
fileSize = 0;
// 读取文件的第一个元素.
fin.get(input);
// 计算文本文件中的元素数.
while (!fin.eof())
{
fileSize++;
fin.get(input);
}
// 立即关闭文件。
fin.close();
// 初始化缓冲区以将着色器源文件读入其中。
buffer = new char[fileSize + 1];
if (!buffer)
{
return 0;
}
// 再次打开着色器源文件.
fin.open(filename);
// 将着色器文本文件作为一个块读入缓冲区.
fin.read(buffer, fileSize);
// 关闭文件.
fin.close();
// Null终止缓冲区.
buffer[fileSize] = '\0';
return buffer;
}
void TextureShaderClass::OutputShaderErrorMessage(OpenGLClass* OpenGL, HWND hwnd, unsigned int shaderId, char* shaderFilename)
{
int logSize, i;
char* infoLog;
ofstream fout;
wchar_t newString[128];
unsigned int error, convertedChars;
// 获取包含失败的着色器编译消息的信息日志的字符串的大小。
OpenGL->glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logSize);
// 将大小增加一以处理空终止符.
logSize++;
// 创建一个char缓冲区来保存信息日志
infoLog = new char[logSize];
if (!infoLog)
{
return;
}
// 现在检索信息日志。.
OpenGL->glGetShaderInfoLog(shaderId, logSize, NULL, infoLog);
// 打开一个文件,将错误消息写入其中.
fout.open("shader-error.txt");
// 写出错误信息.
for (i = 0; i < logSize; i++)
{
fout << infoLog[i];
}
// 关闭文件.
fout.close();
// 将着色器文件名转换为宽字符串.
error = mbstowcs_s(&convertedChars, newString, 128, shaderFilename, 128);
if (error != 0)
{
return;
}
// 在屏幕上弹出一条消息,通知用户检查文本文件是否存在编译错误。
MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", newString, MB_OK);
return;
}
void TextureShaderClass::OutputLinkerErrorMessage(OpenGLClass* OpenGL, HWND hwnd, unsigned int programId)
{
int logSize, i;
char* infoLog;
ofstream fout;
//获取包含失败的着色器编译消息的信息日志的字符串的大小.
OpenGL->glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logSize);
//将大小增加一以处理空终止符.
logSize++;
// 创建一个char缓冲区来保存信息日志
infoLog = new char[logSize];
if (!infoLog)
{
return;
}
// 现在检索信息日志
OpenGL->glGetProgramInfoLog(programId, logSize, NULL, infoLog);
// 打开一个文件,将错误消息写入其中
fout.open("linker-error.txt");
// 写出错误信息.
for (i = 0; i < logSize; i++)
{
fout << infoLog[i];
}
// 关闭文件.
fout.close();
// 在屏幕上弹出一条消息,通知用户检查文本文件中的链接器错误
MessageBox(hwnd, L"Error compiling linker. Check linker-error.txt for message.", L"Linker Error", MB_OK);
return;
}
void TextureShaderClass::ShutdownShader(OpenGLClass* OpenGL)
{
// 从程序中分离顶点着色器和片段着色器
OpenGL->glDetachShader(m_shaderProgram, m_vertexShader);
OpenGL->glDetachShader(m_shaderProgram, m_fragmentShader);
// 删除顶点和片段着色器.
OpenGL->glDeleteShader(m_vertexShader);
OpenGL->glDeleteShader(m_fragmentShader);
// 删除着色器程序
OpenGL->glDeleteProgram(m_shaderProgram);
return;
}
//现在,SetShaderParameters函数采用一个额外的输入值,称为textureUnit。
//这是我们可以指定要绑定的纹理单元,以便OpenGL知道要在像素着色器中采样的纹理。
bool TextureShaderClass::SetShaderParameters(OpenGLClass* OpenGL, float* worldMatrix, float* viewMatrix, float* projectionMatrix, int textureUnit)
{
unsigned int location;
// 在顶点着色器中设置世界矩阵
location = OpenGL->glGetUniformLocation(m_shaderProgram, "worldMatrix");
if (location == -1)
{
return false;
}
OpenGL->glUniformMatrix4fv(location, 1, false, worldMatrix);
// 在顶点着色器中设置视图矩阵.
location = OpenGL->glGetUniformLocation(m_shaderProgram, "viewMatrix");
if (location == -1)
{
return false;
}
OpenGL->glUniformMatrix4fv(location, 1, false, viewMatrix);
// 在顶点着色器中设置投影矩阵.
location = OpenGL->glGetUniformLocation(m_shaderProgram, "projectionMatrix");
if (location == -1)
{
return false;
}
OpenGL->glUniformMatrix4fv(location, 1, false, projectionMatrix);
//在此处获取shaderTexture变量在像素着色器中的位置,然后设置纹理单位。现在可以在像素着色器中对纹理进行采样。
//在像素着色器中设置纹理以使用第一个纹理单元中的数据
location = OpenGL->glGetUniformLocation(m_shaderProgram, "shaderTexture");
if (location == -1)
{
return false;
}
OpenGL->glUniform1i(location, textureUnit);
return true;
}
modelclass.h
如前所述,ModelClass负责封装3D模型的几何图形。
// Filename: modelclass.h
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
///
// MY CLASS INCLUDES //
///
//#include "openglclass.h"
#include "textureclass.h"
// Class name: ModelClass
class ModelClass
{
private:
struct VertexType
{
float x, y, z;
float tu, tv;
};
public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();
//此处的函数处理模型的顶点和索引缓冲区的初始化和关闭。
//渲染功能将模型几何图形放置在显卡上,并使用GLSL着色器进行绘制。
bool Initialize(OpenGLClass*, char*, unsigned int, bool);
void Shutdown(OpenGLClass*);
void Render(OpenGLClass*);
private:
bool InitializeBuffers(OpenGLClass*);
void ShutdownBuffers(OpenGLClass*);
void RenderBuffers(OpenGLClass*);
bool LoadTexture(OpenGLClass*, char*, unsigned int, bool);
void ReleaseTexture();
private:
//ModelClass中的私有变量是顶点数组对象,顶点缓冲区和索引缓冲区ID。
//另外,还有两个整数可以跟踪顶点和索引缓冲区的大小。
int m_vertexCount, m_indexCount;
unsigned int m_vertexArrayId, m_vertexBufferId, m_indexBufferId;
TextureClass* m_Texture;
};
#endif
modelclass.cpp
// Filename: modelclass.cpp
#include "modelclass.h"
ModelClass::ModelClass()
{
m_Texture = 0;
}
ModelClass::ModelClass(const ModelClass& other)
{
}
ModelClass::~ModelClass()
{
}
//Initialize将模型将使用的.tga纹理的文件名作为输入。它还以将.tga文件加载到的纹理单位作为输入。
//并且它还采用一个布尔值,该值指示颜色是否应该环绕或固定在边缘。
bool ModelClass::Initialize(OpenGLClass* OpenGL, char* textureFilename, unsigned int textureUnit, bool wrap)
{
bool result;
// 初始化保存三角形几何图形的顶点和索引缓冲区
result = InitializeBuffers(OpenGL);
if (!result)
{
return false;
}
//Initialize函数调用一个新的私有函数,该私有函数将加载.tga纹理
// 加载该模型的纹理.
result = LoadTexture(OpenGL, textureFilename, textureUnit, wrap);
if (!result)
{
return false;
}
return true;
}
//关闭功能将为缓冲区和相关数据调用关闭功能。
void ModelClass::Shutdown(OpenGLClass * OpenGL)
{
//释放用于此模型的纹理
ReleaseTexture();
// 释放顶点和索引缓冲区。
ShutdownBuffers(OpenGL);
return;
}
//从GraphicsClass :: Render函数调用Render。
//此函数调用RenderBuffers将顶点和索引缓冲区放在图形管线上,并使用颜色着色器进行渲染。
void ModelClass::Render(OpenGLClass * OpenGL)
{
//将顶点和索引缓冲区放在图形管线上,以准备进行绘制。
RenderBuffers(OpenGL);
return;
}
//InitializeBuffers函数是我们处理创建顶点和索引缓冲区的地方。通常,您会读入一个模型并从该数据文件创建缓冲区。
bool ModelClass::InitializeBuffers(OpenGLClass * OpenGL)
{
VertexType* vertices;
unsigned int* indices;
//首先创建两个临时数组,以保存顶点和索引数据,稍后我们将使用这些数据来填充最终缓冲区。
// 设置顶点数组中的顶点数。
m_vertexCount = 3;
// 设置索引数组中的索引数。
m_indexCount = 3;
//创建顶点数组.
vertices = new VertexType[m_vertexCount];
if (!vertices)
{
return false;
}
//创建索引数组.
indices = new unsigned int[m_indexCount];
if (!indices)
{
return false;
}
// 顶点数组具有纹理坐标分量而不是颜色分量。纹理矢量始终为U优先,然后为V。
// 例如,第一纹理坐标是三角形的左下角,对应于U 0.0,V 0.0。使用此页面顶部的图来确定您的坐标是什么。
// 请注意,您可以更改坐标以将纹理的任何部分映射到多边形面的任何部分。
// 在本教程中,出于简单起见,我只是进行直接映射。
// 用数据加载顶点数组。
// 左下方。.
vertices[0].x = -1.0f; // 位置.
vertices[0].y = -1.0f;
vertices[0].z = 0.0f;
vertices[0].tu = 0.0f; // 纹理坐标.
vertices[0].tv = 0.0f;
// 中间居中.
vertices[1].x = 0.0f; // 位置.
vertices[1].y = 1.0f;
vertices[1].z = 0.0f;
vertices[1].tu = 0.5f; // 纹理坐标.
vertices[1].tv = 1.0f;
// 右下角.
vertices[2].x = 1.0f; // 位置.
vertices[2].y = -1.0f;
vertices[2].z = 0.0f;
vertices[2].tu = 1.0f; // 纹理坐标.
vertices[2].tv = 0.0f;
//用数据加载索引数组.
indices[0] = 0; // 左下方.
indices[1] = 1; // 中间居中.
indices[2] = 2; // 右下角.
// 分配OpenGL顶点数组对象.
OpenGL->glGenVertexArrays(1, &m_vertexArrayId);
// 绑定顶点数组对象VAO以存储我们在此处创建的所有缓冲区和顶点属性
OpenGL->glBindVertexArray(m_vertexArrayId);
// 生成顶点缓冲区的ID。
OpenGL->glGenBuffers(1, &m_vertexBufferId);
// 绑定顶点缓冲区并将顶点(位置和纹理)数据加载到顶点缓冲区中。
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
OpenGL->glBufferData(GL_ARRAY_BUFFER, m_vertexCount * sizeof(VertexType), vertices, GL_STATIC_DRAW);
//尽管启用第二个顶点属性并没有改变,但是它现在意味着不同的东西,因为它现在是纹理坐标,不再是颜色分量。
// 启用两个顶点数组属性.
OpenGL->glEnableVertexAttribArray(0); // 顶点位置.
OpenGL->glEnableVertexAttribArray(1); // 纹理坐标.
// 指定顶点缓冲区的位置部分的位置和格式.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
OpenGL->glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(VertexType), 0);
//当我们指定顶点缓冲区的纹理坐标部分的布局时,我们需要将第二个参数更改为2,因为现在只有两个浮点数,而不是三个。
//否则,其余输入保持不变。
// 指定顶点缓冲区的纹理坐标部分的位置和格式.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
OpenGL->glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (3 * sizeof(float)));
// 生成索引缓冲区的ID.
OpenGL->glGenBuffers(1, &m_indexBufferId);
//绑定索引缓冲区并将索引数据加载到其中
OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId);
OpenGL->glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount * sizeof(unsigned int), indices, GL_STATIC_DRAW);
//创建顶点缓冲区和索引缓冲区后,您可以删除顶点数组和索引数组,因为将数据复制到缓冲区中不再需要它们。
//现在,缓冲区已加载,我们可以释放数组数据了.
delete[] vertices;
vertices = 0;
delete[] indices;
indices = 0;
return true;
}
//ShutdownBuffers函数释放在InitializeBuffers函数中创建的缓冲区和属性。
void ModelClass::ShutdownBuffers(OpenGLClass * OpenGL)
{
// 禁用两个顶点数组属性.
OpenGL->glDisableVertexAttribArray(0);
OpenGL->glDisableVertexAttribArray(1);
// 释放顶点缓冲区.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, 0);
OpenGL->glDeleteBuffers(1, &m_vertexBufferId);
// 释放索引缓冲区。
OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
OpenGL->glDeleteBuffers(1, &m_indexBufferId);
// 释放顶点数组对象.
OpenGL->glBindVertexArray(0);
OpenGL->glDeleteVertexArrays(1, &m_vertexArrayId);
return;
}
//从Render函数调用RenderBuffers。
//此功能的目的是通过绑定OpenGL顶点数组对象,将顶点缓冲区和索引缓冲区设置为在GPU中的输入汇编器上处于活动状态。
//GPU具有活动的顶点缓冲区后,便可以使用当前设置的着色器渲染该缓冲区。
//此函数还定义应如何绘制这些缓冲区,例如三角形,直线,扇形等。
//在本教程中,我们将顶点缓冲区和索引缓冲区在输入汇编器上设置为活动状态,
//并告诉GPU应该使用glDrawElements OpenGL函数将缓冲区绘制为三角形。
//glDrawElements函数还指示我们将使用索引缓冲区进行绘制。
void ModelClass::RenderBuffers(OpenGLClass* OpenGL)
{
// 绑定存储有关顶点和索引缓冲区的所有信息的顶点数组对象。
OpenGL->glBindVertexArray(m_vertexArrayId);
// 使用索引缓冲区渲染顶点缓冲区
glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, 0);
return;
}
//LoadTexture是一个新的私有函数,它将创建纹理对象,然后使用提供的输入文件名对其进行初始化。在初始化期间调用此函数。
bool ModelClass::LoadTexture(OpenGLClass* OpenGL, char* textureFilename, unsigned int textureUnit, bool wrap)
{
bool result;
// 创建纹理对象.
m_Texture = new TextureClass;
if (!m_Texture)
{
return false;
}
// 初始化纹理对象.
result = m_Texture->Initialize(OpenGL, textureFilename, textureUnit, wrap);
if (!result)
{
return false;
}
return true;
}
//ReleaseTexture函数将释放在LoadTexture函数期间创建和加载的纹理对象。
void ModelClass::ReleaseTexture()
{
// 释放纹理对象
if (m_Texture)
{
m_Texture->Shutdown();
delete m_Texture;
m_Texture = 0;
}
return;
}
Graphicsclass.h
// Filename: graphicsclass.h
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
///
// MY CLASS INCLUDES //
///
#include "openglclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "textureshaderclass.h"
/
// GLOBALS //
/
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
// Class name: GraphicsClass
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();
bool Initialize(OpenGLClass*, HWND);
void Shutdown();
bool Frame();
private:
bool Render();
private:
//OpenGL类的对象
OpenGLClass* m_OpenGL;
CameraClass* m_Camera;
ModelClass* m_Model;
//添加了一个新的TextureShaderClass私有对象。
TextureShaderClass* m_TextureShader;
};
#endif
Graphicsclass.cpp
// Filename: graphicsclass.cpp
#include "graphicsclass.h"
GraphicsClass::GraphicsClass()
{
m_OpenGL = 0;
m_Camera = 0;
m_Model = 0;
m_TextureShader = 0;
}
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
GraphicsClass::~GraphicsClass()
{
}
//Initialize函数可以创建和初始化三个新对象。
bool GraphicsClass::Initialize(OpenGLClass* OpenGL, HWND hwnd)
{
bool result;
// 存储指向OpenGL类对象的指针
m_OpenGL = OpenGL;
//创建相机对象。
m_Camera = new CameraClass;
if (!m_Camera)
{
return false;
}
//设置摄像机的初始位置。
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
//创建模型对象。
m_Model = new ModelClass;
if (!m_Model)
{
return false;
}
//ModelClass :: Initialize函数将使用将用于渲染模型的纹理名称以及纹理单元和包装布尔值
//初始化模型对象。
result = m_Model->Initialize(m_OpenGL, "../Engine/data/test1.tga", 0, true);
if (!result)
{
MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
return false;
}
//新的TextureShaderClass对象已创建并初始化。
// 创建纹理着色器对象.
m_TextureShader = new TextureShaderClass;
if (!m_TextureShader)
{
return false;
}
// 初始化纹理着色器对象.
result = m_TextureShader->Initialize(m_OpenGL, hwnd);
if (!result)
{
MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK);
return false;
}
return true;
}
//关机
void GraphicsClass::Shutdown()
{
// Shutdown函数中也会释放TextureShaderClass对象.
//释放纹理着色器对象
if (m_TextureShader)
{
m_TextureShader->Shutdown(m_OpenGL);
delete m_TextureShader;
m_TextureShader = 0;
}
//释放模型对象。
if (m_Model)
{
m_Model->Shutdown(m_OpenGL);
delete m_Model;
m_Model = 0;
}
//释放相机对象。
if (m_Camera)
{
delete m_Camera;
m_Camera = 0;
}
//释放指向OpenGL类对象的指针
m_OpenGL = 0;
return;
}
//帧函数已更新,可以在每个框架上调用渲染函数
bool GraphicsClass::Frame()
{
bool result;
// 渲染图形场景.
result = Render();
if (!result)
{
return false;
}
return true;
}
//“渲染”功能仍然从清除场景开始,清除为黑色。
//之后,它会调用相机对象的Render函数以根据在Initialize函数中设置的相机位置创建视图矩阵。
//创建视图矩阵后,我们会从相机类中获取它的副本。我们还从OpenGLClass对象获取世界和投影矩阵的副本。
//接下来,将颜色GLSL着色器设置为当前渲染程序,以便绘制的任何内容都将使用这些顶点和像素着色器。
//然后,我们调用ModelClass :: Render函数绘制绿色三角形模型的几何形状。
//现在将绿色三角形绘制到后台缓冲区。这样,场景就完成了,我们调用EndScene将其显示在屏幕上。
bool GraphicsClass::Render()
{
float worldMatrix[16];
float viewMatrix[16];
float projectionMatrix[16];
// 清除缓冲区,开始场景
m_OpenGL->BeginScene(1.0f, 1.0f, 0.0f, 1.0f);
// 根据相机的位置生成视图矩阵.
m_Camera->Render();
// 从opengl和camera对象获取世界,视图和投影矩阵.
m_OpenGL->GetWorldMatrix(worldMatrix);
m_Camera->GetViewMatrix(viewMatrix);
m_OpenGL->GetProjectionMatrix(projectionMatrix);
//现在调用纹理着色器而不是颜色着色器来渲染模型。注意,它也将纹理单位作为SetShaderParameters函数的最后一个输入参数。
//零表示OpenGL中的第一个纹理单位。
// 将纹理着色器设置为当前着色器程序,并设置它将用于渲染的矩阵
m_TextureShader->SetShader(m_OpenGL);
m_TextureShader->SetShaderParameters(m_OpenGL, worldMatrix, viewMatrix, projectionMatrix, 0);
// 使用颜色着色器渲染模型
m_Model->Render(m_OpenGL);
// 将渲染的场景呈现到屏幕上.
m_OpenGL->EndScene();
return true;
}
概要
现在,您应该了解加载纹理,将其映射到多边形面然后使用着色器进行渲染的基本知识。
练习
1.重新编译代码,并确保在屏幕上确实出现纹理映射的三角形。完成后,按Escape键退出。
2.创建您自己的tga纹理并将其与test.tga放在同一数据目录中。在GraphicsClass :: Initialize函数内部,将模型初始化更改为具有您的纹理名称,然后重新编译并运行该程序。
3.更改代码以创建两个形成正方形的三角形。将整个纹理映射到该正方形,以便将整个纹理正确显示在屏幕上。
4.将摄像机移到不同的距离,以查看过滤效果。
5.尝试使用其他一些滤镜,并将相机移至不同的距离,以查看不同的结果。