在本教程中,我将介绍如何使用漫射照明和OpenGL 4.0照明3D对象。我们将从上一教程中的代码开始并进行修改。
我们将要实现的漫射照明的类型称为定向照明。定向照明类似于太阳照亮地球的方式。它是一个距离很远的光源,根据它发出光的方向,您可以确定任何物体上的光量。但是,与环境照明(我们将很快介绍的另一种照明模型)不同,它不会照亮不会直接接触的表面。
我之所以选择定向照明是因为它很容易在视觉上进行调试。同样,由于只需要一个方向,因此该公式比其他类型的漫射照明(如聚光灯和点光源)更简单。
OpenGL 4.0中的漫反射照明的实现是通过顶点着色器和像素着色器完成的。漫反射照明只需要我们要照明的任何多边形的方向和法线向量。方向是您定义的单个矢量,并且可以使用组成多边形的三个顶点来计算任何多边形的法线。在本教程中,我们还将在照明方程式中实现漫射光的颜色。
框架
在本教程中,我们将创建一个名为LightClass的新类,它将表示场景中的光源。除了保持灯光的方向和颜色外,LightClass实际上不会做任何其他事情。我们还将删除TextureShaderClass并将其替换为处理模型上阴影的LightShaderClass。添加了新的类后,框架的工作现在看起来像以下内容:
我们将通过查看GLSL灯光着色器来开始代码部分。您会注意到,灯光着色器只是先前教程中纹理着色器的更新版本。我将介绍所做的更改。
light.vs
// Filename: light.vs
#version 400
//有一个新的输入和输出变量,它是一个3浮点法线向量。
//法线向量用于通过使用法线方向和光方向之间的角度来计算光量。
/
// INPUT VARIABLES //
/
in vec3 inputPosition;
in vec2 inputTexCoord;
in vec3 inputNormal;
//
// OUTPUT VARIABLES //
//
out vec2 texCoord;
out vec3 normal;
///
// 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;
//在世界空间中计算此顶点的法线向量,然后对其进行归一化,然后再作为输入发送到像素着色器中。
//请注意,有时由于发生插值,需要在像素着色器内部将它们重新标准化。
// 仅针对世界矩阵计算法线向量.
normal = mat3(worldMatrix) * inputNormal;
// 归一化法线向量
normal = normalize(normal);
}
light.ps
// Filename: light.ps
#version 400
//输入法线向量变量添加到此处的像素着色器中。.
/
// INPUT VARIABLES //
/
in vec2 texCoord;
in vec3 normal;
//
// OUTPUT VARIABLES //
//
out vec4 outputColor;
//我们有两个新的统一变量。这些变量用于发送光的漫反射颜色和方向作为输入。
//这两个变量将从新的LightClass对象中的值设置,然后通过LightShaderClass发送。
///
// UNIFORM VARIABLES //
///
uniform sampler2D shaderTexture;
uniform vec3 lightDirection;
uniform vec4 diffuseLightColor;
// Pixel Shader
void main(void)
{
vec4 textureColor;
vec3 lightDir;
float lightIntensity;
// 在此纹理坐标位置使用采样器从纹理中采样像素颜色。
textureColor = texture(shaderTexture, texCoord);
//现在在这里实现了前面讨论的照明方程式。将光强度值计算为三角形的法线向量和光方向向量之间的点积。
//该方程式周围的钳位函数用于将其保持在0.0f至1.0f的范围内。
//通过将两个值相乘并再次将它们限制在0.0f至1.0f的范围内,我们还可以在计算强度后合并漫射光的颜色。
// 反转光的方向进行计算
lightDir = -lightDirection;
// 计算此像素上的光强.
lightIntensity = clamp(dot(normal, lightDir), 0.0f, 1.0f);
// 根据漫反射颜色和光强度确定漫反射颜色的最终数量.
outputColor = clamp((diffuseLightColor * lightIntensity), 0.0f, 1.0f);
//最后,将光的漫射值与纹理像素值组合在一起以产生颜色结果。
// 将纹理像素和最终的漫反射颜色相乘,以获得最终的像素颜色结果。
outputColor = outputColor * textureColor;
}
Lightshaderclass.h
新的LightShaderClass只是以前教程中的TextureShaderClass,略作了重新编写以合并照明。
// Filename: lightshaderclass.h
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_
//
// INCLUDES //
//
#include <fstream>
using namespace std;
///
// MY CLASS INCLUDES //
///
#include "openglclass.h"
// Class name: LightShaderClass
class LightShaderClass
{
public:
LightShaderClass();
LightShaderClass(const LightShaderClass&);
~LightShaderClass();
bool Initialize(OpenGLClass*, HWND);
void Shutdown(OpenGLClass*);
void SetShader(OpenGLClass*);
bool SetShaderParameters(OpenGLClass*, float*, float*, float*, int, float*, float*);
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
Lightshaderclass.cpp
// Filename: lightshaderclass.cpp
#include "lightshaderclass.h"
LightShaderClass::LightShaderClass()
{
}
LightShaderClass::LightShaderClass(const LightShaderClass& other)
{
}
LightShaderClass::~LightShaderClass()
{
}
bool LightShaderClass::Initialize(OpenGLClass* OpenGL, HWND hwnd)
{
bool result;
//新的light.vs和light.ps GLSL着色器文件用作初始化光照着色器的输入。
// 初始化顶点和像素着色器
result = InitializeShader("../Engine/light.vs", "../Engine/light.ps", OpenGL, hwnd);
if (!result)
{
return false;
}
return true;
}
void LightShaderClass::Shutdown(OpenGLClass* OpenGL)
{
// 关闭顶点和像素着色器以及相关对象
ShutdownShader(OpenGL);
return;
}
void LightShaderClass::SetShader(OpenGLClass* OpenGL)
{
// 将着色器程序安装为当前渲染状态的一部分.
OpenGL->glUseProgram(m_shaderProgram);
return;
}
bool LightShaderClass::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);
//为法向矢量添加第三个属性,该属性将用于GLSL光源顶点着色器中的照明
// 绑定着色器输入变量.
OpenGL->glBindAttribLocation(m_shaderProgram, 0, "inputPosition");
OpenGL->glBindAttribLocation(m_shaderProgram, 1, "inputTexCoord");
OpenGL->glBindAttribLocation(m_shaderProgram, 2, "inputNormal");
//链接着色器程序.
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* LightShaderClass::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 LightShaderClass::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 LightShaderClass::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 LightShaderClass::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函数将lightDirection和diffuseLightColor作为输入
bool LightShaderClass::SetShaderParameters(OpenGLClass* OpenGL, float* worldMatrix, float* viewMatrix, float* projectionMatrix, int textureUnit,
float* lightDirection, float* diffuseLightColor)
{
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);
// 在像素着色器中设置纹理以使用第一个纹理单元中的数据
location = OpenGL->glGetUniformLocation(m_shaderProgram, "shaderTexture");
if (location == -1)
{
return false;
}
OpenGL->glUniform1i(location, textureUnit);
//此处在像素着色器中设置光的方向和漫射光的颜色。请注意,lightDirection是3个浮点向量,因此我们使用glUniform3fv进行设置。
//而且diffuseLightColor是一个4浮点数组,因此我们使用glUniform4fv进行设置。
// 在像素着色器中设置光的方向.
location = OpenGL->glGetUniformLocation(m_shaderProgram, "lightDirection");
if (location == -1)
{
return false;
}
OpenGL->glUniform3fv(location, 1, lightDirection);
// 在像素着色器中设置光的方向.
location = OpenGL->glGetUniformLocation(m_shaderProgram, "diffuseLightColor");
if (location == -1)
{
return false;
}
OpenGL->glUniform4fv(location, 1, diffuseLightColor);
return true;
}
modelclass.h
对ModelClass进行了稍微修改,以处理照明组件。
现在,VertexType结构具有可容纳照明的法线向量。
// Filename: modelclass.h
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
//如前所述,ModelClass负责封装3D模型的几何图形。
///
// MY CLASS INCLUDES //
///
//#include "openglclass.h"
#include "textureclass.h"
// Class name: ModelClass
class ModelClass
{
private:
struct VertexType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;//可容纳照明的法线向量
};
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
InitializeBuffers函数的主要更改在此处是顶点设置。现在,每个顶点都有与之关联的法线用于照明计算。法线是与多边形的面垂直的线,因此可以计算出该面所指向的确切方向。为简单起见,我通过将每个Z分量设置为-1.0f来设置沿Z轴的每个顶点的法线,这使法线指向观察者。
下一个主要更改是启用普通顶点属性数组。
我们还指定法线向量的位置是缓冲区中的第6、7和8个浮点数。
// 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 = 6;
// 设置索引数组中的索引数。
m_indexCount = 6;
//创建顶点数组.
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[0].nx = 0.0f; // 法向.
vertices[0].ny = 0.0f;
vertices[0].nz = -1.0f;
// 左上角.
vertices[1].x = -1.0f; // 位置.
vertices[1].y = 1.0f;
vertices[1].z = 0.0f;
vertices[1].tu = 0.0f; // 纹理坐标.
vertices[1].tv = 1.0f;
vertices[1].nx = 0.0f; // 法向.
vertices[1].ny = 0.0f;
vertices[1].nz = -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;
vertices[2].nx = 0.0f; // 法向.
vertices[2].ny = 0.0f;
vertices[2].nz = -1.0f;
// 左上角.
vertices[3].x = -1.0f; // 位置.
vertices[3].y = 1.0f;
vertices[3].z = 0.0f;
vertices[3].tu = 0.0f; // 纹理坐标.
vertices[3].tv = 1.0f;
vertices[3].nx = 0.0f; // 法向.
vertices[3].ny = 0.0f;
vertices[3].nz = -1.0f;
// 右上角.
vertices[4].x = 1.0f; // 位置.
vertices[4].y = 1.0f;
vertices[4].z = 0.0f;
vertices[4].tu = 1.0f; // 纹理坐标.
vertices[4].tv = 1.0f;
vertices[4].nx = 0.0f; // 法向.
vertices[4].ny = 0.0f;
vertices[4].nz = -1.0f;
// 右下角.
vertices[5].x = 1.0f; // 位置.
vertices[5].y = -1.0f;
vertices[5].z = 0.0f;
vertices[5].tu = 1.0f; // 纹理坐标.
vertices[5].tv = 0.0f;
vertices[5].nx = 0.0f; // 法向.
vertices[5].ny = 0.0f;
vertices[5].nz = -1.0f;
//用数据加载索引数组.
indices[0] = 0; // 左下方.
indices[1] = 1; // 左上方.
indices[2] = 2; // 右下角.
indices[3] = 3; // 右下角.
indices[4] = 4; // 左上角.
indices[5] = 5; // 右上角.
// 分配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->glEnableVertexAttribArray(2); // 法线.
// 指定顶点缓冲区的位置部分的位置和格式.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
OpenGL->glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(VertexType), 0);
//glVertexAttribPointer(参数)1.指定要修改的顶点属性的索引值;
//2.每个顶点属性的组件数量,坐标就是xyz是3,颜色就是rgba是4;
//3.每个组件的数据类型
//4.固定点数据值是否应该被归一化;
//5.指定连续顶点属性之间的偏移量。如果为0那么顶点属性是紧密排列在一起的。初始值0。
//6.指定第一个组件在数组的第一个顶点属性中的偏移量。该数组与GL_ARRAY_BUFFER绑定,储存于缓冲区中。初始值为0;
//当我们指定顶点缓冲区的纹理坐标部分的布局时,我们需要将第二个参数更改为2,因为现在只有两个浮点数,而不是三个。
//否则,其余输入保持不变。
// 指定顶点缓冲区的纹理坐标部分的位置和格式.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
OpenGL->glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (3 * sizeof(float)));
//指定法线向量的位置是缓冲区中的第6、7和8个浮点数
//指定顶点缓冲区法线向量部分的位置和格式
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
OpenGL->glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (5 * 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_TRIANGLE_STRIP, m_indexCount, GL_UNSIGNED_INT, 0);//GL_TRIANGLE_STRIP,绘制一组相连的三角形,对于奇数n,顶点n、n+1和n+2定义了第n个三角形;对于偶数n,顶点n+1、n和n+2定义了第n个三角形,总共绘制N-2个三角形
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;
}
lightclass.h
现在我们来看一下非常简单的新光源类。其目的仅是保持灯光的方向和颜色。
// Filename: lightclass.h
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_
//非常简单的新光源类。其目的仅是保持灯光的方向和颜色
// Class name: LightClass
class LightClass
{
public:
LightClass();
LightClass(const LightClass&);
~LightClass();
void SetDiffuseColor(float, float, float, float);
void SetDirection(float, float, float);
void GetDiffuseColor(float*);
void GetDirection(float*);
private:
float m_diffuseColor[4];
float m_direction[3];
};
#endif
Lightclass.cpp
// Filename: lightclass.cpp
#include "lightclass.h"
LightClass::LightClass()
{
}
LightClass::LightClass(const LightClass& other)
{
}
LightClass::~LightClass()
{
}
void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
m_diffuseColor[0] = red;
m_diffuseColor[1] = green;
m_diffuseColor[2] = blue;
m_diffuseColor[3] = alpha;
return;
}
void LightClass::SetDirection(float x, float y, float z)
{
m_direction[0] = x;
m_direction[1] = y;
m_direction[2] = z;
return;
}
void LightClass::GetDiffuseColor(float* color)
{
color[0] = m_diffuseColor[0];
color[1] = m_diffuseColor[1];
color[2] = m_diffuseColor[2];
color[3] = m_diffuseColor[3];
return;
}
void LightClass::GetDirection(float* direction)
{
direction[0] = m_direction[0];
direction[1] = m_direction[1];
direction[2] = m_direction[2];
return;
}
Graphicsclass.h
GraphicsClass现在为LightShaderClass和LightClass提供了两个新的包含。
#include“ lightshaderclass.h”
#include“ lightclass.h”
现在,渲染器将浮点值作为输入。
bool Render(float);
灯光着色器和灯光对象有两个新的私有变量。
LightShaderClass * m_LightShader;
LightClass * m_Light;
// Filename: graphicsclass.h
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
///
// MY CLASS INCLUDES //
///
#include "openglclass.h"
#include "cameraclass.h"
#include "modelclass.h"
//#include "textureshaderclass.h"
#include "lightshaderclass.h"
#include "lightclass.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(float);
private:
//OpenGL类的对象
OpenGLClass* m_OpenGL;
CameraClass* m_Camera;
ModelClass* m_Model;
//TextureShaderClass* m_TextureShader;
LightShaderClass* m_LightShader;
LightClass* m_Light;
};
#endif
graphicsclass.cpp
在类构造函数中,将灯光着色器和灯光对象设置为null
我们使用更好的纹理作为模型对象的输入,从而使浅色的效果更加明显
在此创建并初始化新的灯光着色器对象。
在此创建并初始化新的灯光对象。灯的颜色设置为黄色,并且光的方向设置为指向Z轴正下方指向屏幕。
关闭功能现在释放新的灯光和灯光着色器对象。
frame()中我们添加一个新的静态变量,以在每帧中保存更新的旋转值,该值将传递到Render函数中。
我们在这里获得光的方向和扩散颜色。
在这里,我们通过旋转值旋转世界矩阵,以便在使用此更新的世界矩阵渲染三角形时,它将以旋转量旋转三角形。
调用了灯光着色器以渲染三角形。我们将漫射光的颜色和光的方向发送到SetShaderParameters函数中,以便着色器可以访问这些值。
// Filename: graphicsclass.cpp
#include "graphicsclass.h"
GraphicsClass::GraphicsClass()
{
m_OpenGL = 0;
m_Camera = 0;
m_Model = 0;
//m_TextureShader = 0;
m_LightShader = 0;
m_Light = 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/stone.bmp", 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;
}
*/
// 创建灯光着色器对象.
m_LightShader = new LightShaderClass;
if (!m_LightShader)
{
return false;
}
// 初始化灯光着色器对象.
result = m_LightShader->Initialize(m_OpenGL, hwnd);
if (!result)
{
MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
return false;
}
//在此创建并初始化新的灯光对象。灯的颜色设置为黄色,并且光的方向设置为指向Z轴正下方指向屏幕。
//创建灯光对象.
m_Light = new LightClass;
if (!m_Light)
{
return false;
}
// 初始化灯光对象
m_Light->SetDiffuseColor(1.0f, 1.0f, 0.0f, 1.0f);
m_Light->SetDirection(0.0f, 0.0f, 1.0f);
return true;
}
//关机
void GraphicsClass::Shutdown()
{
/*
//释放纹理着色器对象
if (m_TextureShader)
{
m_TextureShader->Shutdown(m_OpenGL);
delete m_TextureShader;
m_TextureShader = 0;
}
*/
// 释放灯光对象.
if (m_Light)
{
delete m_Light;
m_Light = 0;
}
// 释放灯光着色器对象.
if (m_LightShader)
{
m_LightShader->Shutdown(m_OpenGL);
delete m_LightShader;
m_LightShader = 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;
//添加一个新的静态变量,以在每帧中保存更新的旋转值,该值将传递到Render函数中
static float rotation = 0.0f;
// 每帧更新旋转变量.
rotation += 0.0174532925f * 2.0f;
if (rotation > 360.0f)
{
rotation -= 360.0f;
}
// 渲染图形场景.
result = Render(rotation);
if (!result)
{
return false;
}
return true;
}
//“渲染”功能仍然从清除场景开始,清除为黑色。
//之后,它会调用相机对象的Render函数以根据在Initialize函数中设置的相机位置创建视图矩阵。
//创建视图矩阵后,我们会从相机类中获取它的副本。我们还从OpenGLClass对象获取世界和投影矩阵的副本。
//接下来,将颜色GLSL着色器设置为当前渲染程序,以便绘制的任何内容都将使用这些顶点和像素着色器。
//然后,我们调用ModelClass :: Render函数绘制绿色三角形模型的几何形状。
//现在将绿色三角形绘制到后台缓冲区。这样,场景就完成了,我们调用EndScene将其显示在屏幕上。
bool GraphicsClass::Render(float rotation)
{
float worldMatrix[16];
float viewMatrix[16];
float projectionMatrix[16];
float lightDirection[3];
float diffuseLightColor[4];
// 清除缓冲区,开始场景
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_Light->GetDirection(lightDirection);
m_Light->GetDiffuseColor(diffuseLightColor);
//通过旋转值旋转世界矩阵,以便在使用此更新的世界矩阵渲染三角形时,它将以旋转量旋转三角形
// 通过旋转值旋转世界矩阵,以便三角形旋转
m_OpenGL->MatrixRotationY(worldMatrix, rotation);
//调用了灯光着色器以渲染三角形。
//我们将漫射光的颜色和光的方向发送到SetShaderParameters函数中,以便着色器可以访问这些值
// 将灯光着色器设置为当前的着色器程序,并设置它将用于渲染的矩阵
m_LightShader->SetShader(m_OpenGL);
m_LightShader->SetShaderParameters(m_OpenGL, worldMatrix, viewMatrix, projectionMatrix, 0, lightDirection, diffuseLightColor);
// 使用着色器渲染模型
m_Model->Render(m_OpenGL);
// 将渲染的场景呈现到屏幕上.
m_OpenGL->EndScene();
return true;
}
概要
通过对代码进行一些更改,我们就可以实现一些基本的定向照明。确保您了解法向矢量的工作原理以及为什么它们对于计算多边形面上的光照很重要。请注意,由于我们在OpenGLClass中启用了背面剔除,所以旋转三角形的背面不会点亮。
练习
1.重新编译项目,并确保您得到一个旋转的带纹理的两三角形,三角形被黄光照亮。按Escape键退出。
2.将灯的颜色更改为绿色。
3.更改光的方向,使其沿X轴的正负方向下降。您可能还想更改旋转速度。