天天看点

第六章:漫反射光照

原文链接:

http://www.rastertek.com/gl40tut06.html

Tutorial 6: Diffuse Lighting 第六章:漫反射光照

In this tutorial I will cover how to light 3D objects using diffuse lighting and OpenGL 4.0. We will start with the code from the previous tutorial and modify it. 本章将介绍如何使用OpenGL 4.0和漫反射光照对3D物体进行照明。我们将在前面教程的代码基础上进行改进。

The type of diffuse lighting we will be implementing is called directional lighting. Directional lighting is similar to how the Sun illuminates the Earth. It is a light source that is a great distance away and based on the direction it is sending light you can determine the amount of light on any object. However unlike ambient lighting (another lighting model we will cover soon) it will not light up surfaces it does not directly touch. 使用漫反射光照我们将实现平行光。平行光类似于太阳光。他是一个有足够远的光源可以照射所有物体的光照。但是与环境光(之后会介绍)照不同,它不会照射到背向它的表面。

I picked directional lighting to start with because it is very easy to debug visually. Also since it only requires a direction the formula is simpler than the other types of diffuse lighting such as spot lights and point lights. 我们先从平行光照开始,因为它非常简单并且好调试。对比与聚光光源和点光源,它只需要一个方向。

The implementation of diffuse lighting in OpenGL 4.0 is done with both vertex and pixel shaders. Diffuse lighting requires just the direction and a normal vector for any polygons that we want to light. The direction is a single vector that you define, and you can calculate the normal for any polygon by using the three vertices that compose the polygon. In this tutorial we will also implement the color of the diffuse light in the lighting equation. OpenGL 4.0已经在顶点和像素着色器中实现了漫反射光照。漫反射光照只需要光照方向和需要照明的多边形的法向量。光照方向使用自己定义的向量即可。多边形面的法向量可以通过面的3个顶点确定。本章也实现了带颜色的漫反射光照。

Frame Work 框架

For this tutorial we will create a new class called LightClass which will represent the light sources in the scenes. LightClass won't actually do anything other than hold the direction and color of the light. We will also remove the TextureShaderClass and replace it with LightShaderClass which handles the light shading on the model. With the addition of the new classes the frame work now looks like the following: 本章新加了名叫LightClass的类,用来表示场景中的光源。LightClass只负责确定光的方向和颜色。我们使用LightShaderClass来替换TextureShaderClass,这个类用来处理光在模型上着色。现在,框架的结构如下:

第六章:漫反射光照

We will start the code section by looking at the GLSL light shader. You will notice that the light shader is just an updated version of the texture shader from the previous tutorial. I will cover the changes that have been made. 下面从GLSL光照着色器开始。光照着色器只是在前面教程纹理着色器上的修改。下面会指出涉及修改的部分。

Light.vs

// Filename: light.vs

#version 400
           

There is a new input and output variable which is a 3 float normal vector. The normal vector is used for calculating the amount of light by using the angle between the direction of the normal and the direction of the light. 这里新加了一个变量用来保存法向量。这里使用法向量和光的夹角计算光的反射量。

/
// 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)
{
 // Calculate the position of the vertex against the world, view, and projection matrices.
 gl_Position = worldMatrix * vec4(inputPosition, 1.0f);
 gl_Position = viewMatrix * gl_Position;
 gl_Position = projectionMatrix * gl_Position;

 // Store the texture coordinates for the pixel shader.
 texCoord = inputTexCoord;
           

The normal vector for this vertex is calculated in world space and then normalized before being sent as input into the pixel shader. Note that sometimes these need to be re-normalized inside the pixel shader due to the interpolation that occurs. 计算世界空间中顶点的法向量,归一化后作为输入变量传递到像素着色器。注意,有时候需要在像素着色器里再次进行归一化处理,以防止进行差值计算时改变了它的值。

// Calculate the normal vector against the world matrix only.
 normal = mat3(worldMatrix) * inputNormal;

 // Normalize the normal vector.
 normal = normalize(normal);
}
           

Light.ps

// Filename: light.ps

#version 400
           

The input normal vector variable is added here in the pixel shader. 这里增加了像素着色器用的输入法向量。

/
// INPUT VARIABLES //
/
in vec2 texCoord;
in vec3 normal;

//
// OUTPUT VARIABLES //
//
out vec4 outputColor;
           

We have two new uniform variables. These variables are used for sending as input the diffuse color and direction of the light. These two variables will be set from values in the new LightClass object that are then sent in through the LightShaderClass. 这里新加了两个一致变量。用来传入光的方向和颜色。这两个变量将在LightClass对象里进行设置并通过LightShaderClass发送到像素着色器。

///
// UNIFORM VARIABLES //
///
uniform sampler2D shaderTexture;
uniform vec3 lightDirection;
uniform vec4 diffuseLightColor;


// Pixel Shader

void main(void)
{
 vec4 textureColor;
 vec3 lightDir;
 float lightIntensity;

 // Sample the pixel color from the texture using the sampler at this texture coordinate location.
 textureColor = texture(shaderTexture, texCoord);
           

This is where the lighting equation that was discussed earlier is now implemented. The light intensity value is calculated as the dot product between the normal vector of triangle and the light direction vector. The clamp function is surrounding the equation is used to keep it in the 0.0f to 1.0f range. We also combine the diffuse light color after the intensity has been calculated by multiplying the two values together and once again clamping them into the 0.0f to 1.0f range. 下面来实现之前讨论的光照公式。通过法向量点乘光的方向向量来计算光的强度。clamp方法用来限制最后的结果在0.0f与1.0f之间。然后我们通过乘法将光的颜色融合到强度里,同意也要限制最后的结果在0.0f与1.0f之间。

// Invert the light direction for calculations.
 lightDir = -lightDirection;

 // Calculate the amount of light on this pixel.
 lightIntensity = clamp(dot(normal, lightDir), 0.0f, 1.0f);

 // Determine the final amount of diffuse color based on the diffuse color combined with the light intensity.
 outputColor =  clamp((diffuseLightColor * lightIntensity), 0.0f, 1.0f);
           

And finally the diffuse value of the light is combined with the texture pixel value to produce the color result. 最后,漫反射光照再与纹理像素进行融合得到最终的颜色。

// Multiply the texture pixel and the final diffuse color to get the final pixel color result.
 outputColor = outputColor * textureColor;
}
           

Lightshaderclass.h

The new LightShaderClass is just the TextureShaderClass from the previous tutorials re-written slightly to incorporate lighting. 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;
           

The new light.vs and light.ps GLSL shader files are used as input to initialize the light shader. 新的light.vs和light.psGLSL着色器文件作为输入参数用来初始化光照着色器。

// Initialize the vertex and pixel shaders.
 result = InitializeShader("../Engine/light.vs", "../Engine/light.ps", OpenGL, hwnd);
 if(!result)
 {
  return false;
 }

 return true;
}

void LightShaderClass::Shutdown(OpenGLClass* OpenGL)
{
 // Shutdown the vertex and pixel shaders as well as the related objects.
 ShutdownShader(OpenGL);

 return;
}

void LightShaderClass::SetShader(OpenGLClass* OpenGL)
{
 // Install the shader program as part of the current rendering state.
 OpenGL->glUseProgram(m_shaderProgram);
 
 return;
}

bool LightShaderClass::InitializeShader(char* vsFilename, char* fsFilename, OpenGLClass* OpenGL, HWND hwnd)
{
 const char* vertexShaderBuffer;
 const char* fragmentShaderBuffer;
 int status;

 // Load the vertex shader source file into a text buffer.
 vertexShaderBuffer = LoadShaderSourceFile(vsFilename);
 if(!vertexShaderBuffer)
 {
  return false;
 }

 // Load the fragment shader source file into a text buffer.
 fragmentShaderBuffer = LoadShaderSourceFile(fsFilename);
 if(!fragmentShaderBuffer)
 {
  return false;
 }

 // Create a vertex and fragment shader object.
 m_vertexShader = OpenGL->glCreateShader(GL_VERTEX_SHADER);
 m_fragmentShader = OpenGL->glCreateShader(GL_FRAGMENT_SHADER);

 // Copy the shader source code strings into the vertex and fragment shader objects.
 OpenGL->glShaderSource(m_vertexShader, 1, &vertexShaderBuffer, NULL);
 OpenGL->glShaderSource(m_fragmentShader, 1, &fragmentShaderBuffer, NULL);

 // Release the vertex and fragment shader buffers.
 delete [] vertexShaderBuffer;
 vertexShaderBuffer = 0;
 
 delete [] fragmentShaderBuffer;
 fragmentShaderBuffer = 0;

 // Compile the shaders.
 OpenGL->glCompileShader(m_vertexShader);
 OpenGL->glCompileShader(m_fragmentShader);

 // Check to see if the vertex shader compiled successfully.
 OpenGL->glGetShaderiv(m_vertexShader, GL_COMPILE_STATUS, &status);
 if(status != 1)
 {
  // If it did not compile then write the syntax error message out to a text file for review.
  OutputShaderErrorMessage(OpenGL, hwnd, m_vertexShader, vsFilename);
  return false;
 }

 // Check to see if the fragment shader compiled successfully.
 OpenGL->glGetShaderiv(m_fragmentShader, GL_COMPILE_STATUS, &status);
 if(status != 1)
 {
  // If it did not compile then write the syntax error message out to a text file for review.
  OutputShaderErrorMessage(OpenGL, hwnd, m_fragmentShader, fsFilename);
  return false;
 }

 // Create a shader program object.
 m_shaderProgram = OpenGL->glCreateProgram();

 // Attach the vertex and fragment shader to the program object.
 OpenGL->glAttachShader(m_shaderProgram, m_vertexShader);
 OpenGL->glAttachShader(m_shaderProgram, m_fragmentShader);
           

We now add a third attribute for the normal vector that will be used for lighting in the GLSL light vertex shader. 下面增加了GLSL光照顶点着色器使用的第三个属性,法向量。

// Bind the shader input variables.
 OpenGL->glBindAttribLocation(m_shaderProgram, 0, "inputPosition");
 OpenGL->glBindAttribLocation(m_shaderProgram, 1, "inputTexCoord");
 OpenGL->glBindAttribLocation(m_shaderProgram, 2, "inputNormal");

 // Link the shader program.
 OpenGL->glLinkProgram(m_shaderProgram);

 // Check the status of the link.
 OpenGL->glGetProgramiv(m_shaderProgram, GL_LINK_STATUS, &status);
 if(status != 1)
 {
  // If it did not link then write the syntax error message out to a text file for review.
  OutputLinkerErrorMessage(OpenGL, hwnd, m_shaderProgram);
  return false;
 }

 return true;
}

char* LightShaderClass::LoadShaderSourceFile(char* filename)
{
 ifstream fin;
 int fileSize;
 char input;
 char* buffer;

 // Open the shader source file.
 fin.open(filename);

 // If it could not open the file then exit.
 if(fin.fail())
 {
  return 0;
 }

 // Initialize the size of the file.
 fileSize = 0;

 // Read the first element of the file.
 fin.get(input);

 // Count the number of elements in the text file.
 while(!fin.eof())
 {
  fileSize++;
  fin.get(input);
 }

 // Close the file for now.
 fin.close();

 // Initialize the buffer to read the shader source file into.
 buffer = new char[fileSize+1];
 if(!buffer)
 {
  return 0;
 }

 // Open the shader source file again.
 fin.open(filename);

 // Read the shader text file into the buffer as a block.
 fin.read(buffer, fileSize);

 // Close the file.
 fin.close();

 // Null terminate the buffer.
 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;

 // Get the size of the string containing the information log for the failed shader compilation message.
 OpenGL->glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logSize);

 // Increment the size by one to handle also the null terminator.
 logSize++;

 // Create a char buffer to hold the info log.
 infoLog = new char[logSize];
 if(!infoLog)
 {
  return;
 }

 // Now retrieve the info log.
 OpenGL->glGetShaderInfoLog(shaderId, logSize, NULL, infoLog);

 // Open a file to write the error message to.
 fout.open("shader-error.txt");

 // Write out the error message.
 for(i=0; i<logSize; i++)
 {
  fout << infoLog[i];
 }

 // Close the file.
 fout.close();

 // Convert the shader filename to a wide character string.
 error = mbstowcs_s(&convertedChars, newString, 128, shaderFilename, 128);
 if(error != 0)
 {
  return;
 }

 // Pop a message up on the screen to notify the user to check the text file for compile errors.
 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;

 // Get the size of the string containing the information log for the failed shader compilation message.
 OpenGL->glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logSize);

 // Increment the size by one to handle also the null terminator.
 logSize++;

 // Create a char buffer to hold the info log.
 infoLog = new char[logSize];
 if(!infoLog)
 {
  return;
 }

 // Now retrieve the info log.
 OpenGL->glGetProgramInfoLog(programId, logSize, NULL, infoLog);

 // Open a file to write the error message to.
 fout.open("linker-error.txt");

 // Write out the error message.
 for(i=0; i<logSize; i++)
 {
  fout << infoLog[i];
 }

 // Close the file.
 fout.close();

 // Pop a message up on the screen to notify the user to check the text file for linker errors.
 MessageBox(hwnd, L"Error compiling linker.  Check linker-error.txt for message.", L"Linker Error", MB_OK);

 return;
}

void LightShaderClass::ShutdownShader(OpenGLClass* OpenGL)
{
 // Detach the vertex and fragment shaders from the program.
 OpenGL->glDetachShader(m_shaderProgram, m_vertexShader);
 OpenGL->glDetachShader(m_shaderProgram, m_fragmentShader);

 // Delete the vertex and fragment shaders.
 OpenGL->glDeleteShader(m_vertexShader);
 OpenGL->glDeleteShader(m_fragmentShader);

 // Delete the shader program.
 OpenGL->glDeleteProgram(m_shaderProgram);

 return;
}
           

The SetShaderParameters function now takes in lightDirection and diffuseLightColor as inputs. SetShaderParameters方法增加了lightDirection和diffuseLightColor作为输入参数。

bool LightShaderClass::SetShaderParameters(OpenGLClass* OpenGL, float* worldMatrix, float* viewMatrix, float* projectionMatrix, int textureUnit,
        float* lightDirection, float* diffuseLightColor)
{
 unsigned int location;

 // Set the world matrix in the vertex shader.
 location = OpenGL->glGetUniformLocation(m_shaderProgram, "worldMatrix");
 if(location == -1)
 {
  return false;
 }
 OpenGL->glUniformMatrix4fv(location, 1, false, worldMatrix);

 // Set the view matrix in the vertex shader.
 location = OpenGL->glGetUniformLocation(m_shaderProgram, "viewMatrix");
 if(location == -1)
 {
  return false;
 }
 OpenGL->glUniformMatrix4fv(location, 1, false, viewMatrix);

 // Set the projection matrix in the vertex shader.
 location = OpenGL->glGetUniformLocation(m_shaderProgram, "projectionMatrix");
 if(location == -1)
 {
  return false;
 }
 OpenGL->glUniformMatrix4fv(location, 1, false, projectionMatrix);

 // Set the texture in the pixel shader to use the data from the first texture unit.
 location = OpenGL->glGetUniformLocation(m_shaderProgram, "shaderTexture");
 if(location == -1)
 {
  return false;
 }
 OpenGL->glUniform1i(location, textureUnit);
           

The light direction and diffuse light color are set here in the pixel shader. Note that lightDirection is a 3 float vector so we use glUniform3fv to set it. And diffuseLightColor is a 4 float array so we use glUniform4fv to set it. 下面设置顶点着色器用的光照方向和漫反射光照颜色。注意,光照方向是3个浮点数组成的向量,所有这里使用glUniform3fv方法来设置。diffuseLightColor是由4个浮点数组成的向量,所以使用glUniform4fv方法进行设置。

// Set the light direction in the pixel shader.
 location = OpenGL->glGetUniformLocation(m_shaderProgram, "lightDirection");
 if(location == -1)
 {
  return false;
 }
 OpenGL->glUniform3fv(location, 1, lightDirection);

 // Set the light direction in the pixel shader.
 location = OpenGL->glGetUniformLocation(m_shaderProgram, "diffuseLightColor");
 if(location == -1)
 {
  return false;
 }
 OpenGL->glUniform4fv(location, 1, diffuseLightColor);

 return true;
}
           

Modelclass.h

The ModelClass has been slightly modified to handle lighting components. ModelClass增加了光照组件。

// Filename: modelclass.h

#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

///
// MY CLASS INCLUDES //
///
#include "textureclass.h"


// Class name: ModelClass

class ModelClass
{
private:
           

The VertexType structure now has a normal vector to accommodate lighting. VertexType结构为了计算光照增加了发向量。

struct VertexType
 {
  float x, y, z;
  float tu, tv;
  float nx, ny, nz;
 };

public:
 ModelClass();
 ModelClass(const ModelClass&);
 ~ModelClass();

 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:
 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()
{
}

bool ModelClass::Initialize(OpenGLClass* OpenGL, char* textureFilename, unsigned int textureUnit, bool wrap)
{
 bool result;

 // Initialize the vertex and index buffer that hold the geometry for the triangle.
 result = InitializeBuffers(OpenGL);
 if(!result)
 {
  return false;
 }

 // Load the texture for this model.
 result = LoadTexture(OpenGL, textureFilename, textureUnit, wrap);
 if(!result)
 {
  return false;
 }

 return true;
}

void ModelClass::Shutdown(OpenGLClass* OpenGL)
{
 // Release the texture used for this model.
 ReleaseTexture();

 // Release the vertex and index buffers.
 ShutdownBuffers(OpenGL);

 return;
}

void ModelClass::Render(OpenGLClass* OpenGL)
{
 // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
 RenderBuffers(OpenGL);

 return;
}

bool ModelClass::InitializeBuffers(OpenGLClass* OpenGL)
{
 VertexType* vertices;
 unsigned int* indices;

 // Set the number of vertices in the vertex array.
 m_vertexCount = 3;

 // Set the number of indices in the index array.
 m_indexCount = 3;

 // Create the vertex array.
 vertices = new VertexType[m_vertexCount];
 if(!vertices)
 {
  return false;
 }

 // Create the index array.
 indices = new unsigned int[m_indexCount];
 if(!indices)
 {
  return false;
 }
           

The primary change to the InitializeBuffers function is here in the vertex setup. Each vertex now has normals associated with it for lighting calculations. The normal is a line that is perpendicular to the face of the polygon so that the exact direction the face is pointing can be calculated. For simplicity purposes I set the normal for each vertex along the Z axis by setting each Z component to -1.0f which makes the normal point towards the viewer. 下面是InitializeBuffers方法改动最大的地方。为了光照计算每个顶点都关联一个法线。法线垂直于多边形的面,可以确定面的方向。本章为了简单,设置每个顶点的反向为Z轴负方向,也就是朝向读者。

// Load the vertex array with data.

 // Bottom left.
 vertices[0].x  = -1.0f;  // Position.
 vertices[0].y  = -1.0f;
 vertices[0].z  =  0.0f;
 vertices[0].tu =  0.0f;  // Texture coordinates.
 vertices[0].tv =  0.0f;
 vertices[0].nx =  0.0f;  // Normals.
 vertices[0].ny =  0.0f;
 vertices[0].nz = -1.0f;
 
 // Top middle.
 vertices[1].x  =  0.0f;  // Position.
 vertices[1].y  =  1.0f;
 vertices[1].z  =  0.0f;
 vertices[1].tu =  0.5f;  // Texture coordinates.
 vertices[1].tv =  1.0f;
 vertices[1].nx =  0.0f;  // Normals.
 vertices[1].ny =  0.0f;
 vertices[1].nz = -1.0f;

 // Bottom right.
 vertices[2].x  =  1.0f;  // Position.
 vertices[2].y  = -1.0f;
 vertices[2].z  =  0.0f;
 vertices[2].tu =  1.0f;  // Texture coordinates.
 vertices[2].tv =  0.0f;
 vertices[2].nx =  0.0f;  // Normals.
 vertices[2].ny =  0.0f;
 vertices[2].nz = -1.0f;

 // Load the index array with data.
 indices[0] = 0;  // Bottom left.
 indices[1] = 1;  // Top middle.
 indices[2] = 2;  // Bottom right.

 // Allocate an OpenGL vertex array object.
 OpenGL->glGenVertexArrays(1, &m_vertexArrayId);

 // Bind the vertex array object to store all the buffers and vertex attributes we create here.
 OpenGL->glBindVertexArray(m_vertexArrayId);

 // Generate an ID for the vertex buffer.
 OpenGL->glGenBuffers(1, &m_vertexBufferId);

 // Bind the vertex buffer and load the vertex (position, texture, and normal) data into the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glBufferData(GL_ARRAY_BUFFER, m_vertexCount * sizeof(VertexType), vertices, GL_STATIC_DRAW);
The next major change is that we enable the normal vertex attribute array.

 // Enable the three vertex array attributes.
 OpenGL->glEnableVertexAttribArray(0);  // Vertex position.
 OpenGL->glEnableVertexAttribArray(1);  // Texture coordinates.
 OpenGL->glEnableVertexAttribArray(2);  // Normals.

 // Specify the location and format of the position portion of the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(VertexType), 0);

 // Specify the location and format of the texture coordinate portion of the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (3 * sizeof(float)));
We also specify that the position of the normal vector is the 6th, 7th, and 8th float in the buffer.

 // Specify the location and format of the normal vector portion of the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (5 * sizeof(float)));

 // Generate an ID for the index buffer.
 OpenGL->glGenBuffers(1, &m_indexBufferId);

 // Bind the index buffer and load the index data into it.
 OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId);
 OpenGL->glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount* sizeof(unsigned int), indices, GL_STATIC_DRAW);
 
 // Now that the buffers have been loaded we can release the array data.
 delete [] vertices;
 vertices = 0;

 delete [] indices;
 indices = 0;

 return true;
}

void ModelClass::ShutdownBuffers(OpenGLClass* OpenGL)
{
 // Disable the two vertex array attributes.
 OpenGL->glDisableVertexAttribArray(0);
 OpenGL->glDisableVertexAttribArray(1);
 
 // Release the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, 0);
 OpenGL->glDeleteBuffers(1, &m_vertexBufferId);

 // Release the index buffer.
 OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
 OpenGL->glDeleteBuffers(1, &m_indexBufferId);

 // Release the vertex array object.
 OpenGL->glBindVertexArray(0);
 OpenGL->glDeleteVertexArrays(1, &m_vertexArrayId);

 return;
}

void ModelClass::RenderBuffers(OpenGLClass* OpenGL)
{
 // Bind the vertex array object that stored all the information about the vertex and index buffers.
 OpenGL->glBindVertexArray(m_vertexArrayId);

 // Render the vertex buffer using the index buffer.
 glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, 0);

 return;
}

bool ModelClass::LoadTexture(OpenGLClass* OpenGL, char* textureFilename, unsigned int textureUnit, bool wrap)
{
 bool result;

 // Create the texture object.
 m_Texture = new TextureClass;
 if(!m_Texture)
 {
  return false;
 }

 // Initialize the texture object.
 result = m_Texture->Initialize(OpenGL, textureFilename, textureUnit, wrap);
 if(!result)
 {
  return false;
 }

 return true;
}

void ModelClass::ReleaseTexture()
{
 // Release the texture object.
 if(m_Texture)
 {
  m_Texture->Shutdown();
  delete m_Texture;
  m_Texture = 0;
 }

 return;
}
           

Lightclass.h

Now we will look at the new light class which is very simple. Its purpose is only to maintain the direction and color of lights. 光照类非常简单。只用了记录光照方向和颜色。

// 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

// Filename: graphicsclass.h

#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

///
// MY CLASS INCLUDES //
///
#include "openglclass.h"
#include "cameraclass.h"
#include "modelclass.h"
           

The GraphicsClass now has two new includes for the LightShaderClass and the LightClass. GraphicsClass新增了两个头文件。

#include "lightshaderclass.h"
#include "lightclass.h"

/
// GLOBALS //
/
const bool FULL_SCREEN = true;
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:
Render now takes a float value as input.

 bool Render(float);

private:
 OpenGLClass* m_OpenGL;
 CameraClass* m_Camera;
 ModelClass* m_Model;
           

There are two new private variables for the light shader and the light object. 这里新增了两个私有变量用来保存光照着色器和光照对象。

LightShaderClass* m_LightShader;
 LightClass* m_Light;
};

#endif
           

Graphicsclass.cpp

// Filename: graphicsclass.cpp

#include "graphicsclass.h"

GraphicsClass::GraphicsClass()
{
 m_OpenGL = 0;
 m_Camera = 0;
 m_Model = 0;
           

The light shader and light object are set to null in the class constructor. 在构造方法里将光照着色器和光照对象设置为null。

m_LightShader = 0;
 m_Light = 0;
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(OpenGLClass* OpenGL, HWND hwnd)
{
 bool result;

 // Store a pointer to the OpenGL class object.
 m_OpenGL = OpenGL;

 // Create the camera object.
 m_Camera = new CameraClass;
 if(!m_Camera)
 {
  return false;
 }

 // Set the initial position of the camera.
 m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

 // Create the model object.
 m_Model = new ModelClass;
 if(!m_Model)
 {
  return false;
 }
           

We use a better texture as input to the model object so that the effect of the light color is more pronounced. 我们为模型换了一张纹理,这样光照的效果更加明显。

// Initialize the model object.
 result = m_Model->Initialize(m_OpenGL, "../Engine/data/stone.tga", 0, true);
 if(!result)
 {
  MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
  return false;
 }
           

The new light shader object is created and initialized here. 这里创建光照着色器并进行初始化。

// Create the light shader object.
 m_LightShader = new LightShaderClass;
 if(!m_LightShader)
 {
  return false;
 }

 // Initialize the light shader object.
 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;
 }
           

The new light object is created and initialized here. The color of the light is set to yellow and the light direction is set to point down the positive Z axis into the screen. 下面创建并初始化光照对象。设置光的颜色为黄色,光照方向为Z轴正方向,就是指向屏幕内的方向。

// Create the light object.
 m_Light = new LightClass;
 if(!m_Light)
 {
  return false;
 }

 // Initialize the light object.
 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()
{
           

The Shutdown function now releases the new light and light shader objects. 关闭方法释放光照和光照着色器对象。

// Release the light object.
 if(m_Light)
 {
  delete m_Light;
  m_Light = 0;
 }

 // Release the light shader object.
 if(m_LightShader)
 {
  m_LightShader->Shutdown(m_OpenGL);
  delete m_LightShader;
  m_LightShader = 0;
 }

 // Release the model object.
 if(m_Model)
 {
  m_Model->Shutdown(m_OpenGL);
  delete m_Model;
  m_Model = 0;
 }

 // Release the camera object.
 if(m_Camera)
 {
  delete m_Camera;
  m_Camera = 0;
 }

 // Release the pointer to the OpenGL class object.
 m_OpenGL = 0;

 return;
}

bool GraphicsClass::Frame()
{
 bool result;
           

We add a new static variable to hold an updated rotation value each frame that will be passed into the Render function. 下面添加了静态变量来保存每一帧的旋转角度,这个值将传递到Render方法。

static float rotation = 0.0f;

 // Update the rotation variable each frame.
 rotation += 0.0174532925f * 2.0f;
 if(rotation > 360.0f)
 {
  rotation -= 360.0f;
 }

 // Render the graphics scene.
 result = Render(rotation);
 if(!result)
 {
  return false;
 }

 return true;
}

bool GraphicsClass::Render(float rotation)
{
 float worldMatrix[16];
 float viewMatrix[16];
 float projectionMatrix[16];
 float lightDirection[3];
 float diffuseLightColor[4];

 // Clear the buffers to begin the scene.
 m_OpenGL->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

 // Generate the view matrix based on the camera's position.
 m_Camera->Render();

 // Get the world, view, and projection matrices from the opengl and camera objects.
 m_OpenGL->GetWorldMatrix(worldMatrix);
 m_Camera->GetViewMatrix(viewMatrix);
 m_OpenGL->GetProjectionMatrix(projectionMatrix);
We obtain the direction and diffuse color of the light here.

 // Get the light properties.
 m_Light->GetDirection(lightDirection);
 m_Light->GetDiffuseColor(diffuseLightColor);
           

Here we rotate the world matrix by the rotation value so that when we render the triangle using this updated world matrix it will spin the triangle by the rotation amount. 我们使用旋转值来旋转世界矩阵,这样当使用世界矩阵渲染三角形时将得到一个旋转的三角形。

// Rotate the world matrix by the rotation value so that the triangle will spin.
 m_OpenGL->MatrixRotationY(worldMatrix, rotation);
           

The light shader is called here to render the triangle. We send the diffuse light color and light direction into the SetShaderParameters function so that the shader has access to those values. 这里调用着色器渲染三角形。我们发送漫反射光照颜色和光照方向到SetShaderParameters方法,这样着色器就可以获取这些值。

// Set the light shader as the current shader program and set the matrices that it will use for rendering.
 m_LightShader->SetShader(m_OpenGL);
 m_LightShader->SetShaderParameters(m_OpenGL, worldMatrix, viewMatrix, projectionMatrix, 0, lightDirection, diffuseLightColor);

 // Render the model using the light shader.
 m_Model->Render(m_OpenGL);
 
 // Present the rendered scene to the screen.
 m_OpenGL->EndScene();

 return true;
}
           

Summary 总结

With a few changes to the code we were able to implement some basic directional lighting. Make sure you understand how normal vectors work and why they are important to calculating lighting on polygon faces. Note that the back of the spinning triangle will not light up since we have back face culling enabled in our OpenGLClass. 通过修改少量的代码我们实现了基本平行光照。请理解法向量的工作原理及在计算多边形表面光照的重要性。注意,如果我们在OpenGLClass里开启背面剔除三角形的背面没有光照。

第六章:漫反射光照

To Do Exercises 练习

1. Recompile the project and ensure you get a spinning textured triangle that is being illuminated by a yellow light. Press escape to quit. 1. 重新编译并运行代码,确保得到一个被黄光照射的旋转的带纹理的三角形。按ESC键退出程序。

2. Change the color of the light to green. 2. 修改光的颜色为绿色。

3. Change the direction of the light to go down the positive and the negative X axis. You might want to change the speed of the rotation also. 3. 修改广德方向为X轴负方向。尝试修改旋转速度。

Source Code 源代码

http://www.rastertek.com/gl40src06.zip

继续阅读