第六章:漫反射光照

原文链接:

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

时间: 2024-10-19 14:43:42

第六章:漫反射光照的相关文章

Diffuse Shading——漫反射光照改善技巧

转:http://www.narkii.com/club/thread-355113-1.html 我们会列出两种方法:使用Half Lambert lighting model(半兰伯特光照模型)和使用一个ramp texture来控制diffuse shading. 准备工作 同样,我们需要你已经做好了上一篇文章中的内容,并得到了如下shader: Shader “Custom/BasicDiffuse” { Properties { _EmissiveColor (“Emissive Co

(转载)虚幻引擎3--第六章 –函数

第六章 –函数 6.1概述 指南 6.1环境生物, 第一部分:基类声明 指南 6.2 环境生物, 第二部分:类的变量声明 指南 6.3 环境生物,第三部分:渲染及光照组件 指南 6.4 环境生物, 第四部分:碰撞及物理属性 6.2 函数声明 指南 6.5 环境生物, 第五部分: SETRANDDEST() 函数 6.3函数修饰符 Static Native Final Singular NoExport Exec Latent Iterator Simulated Server Client R

数据库系统实现 第六章 查询执行

第六章 查询执行 查询执行也就是操作数据库的算法 一次查询的过程: 查询-->查询编译(第七章)-->查询执行(第六章)-->数据 查询编译预览 查询编译可以分为三个步骤: a)分析:构造分析树,用来表达查询和它的结构 b)查询重写,分析树被转化为初始查询计划,通常是代数表达式,之后初始的查询计划会被优化为一个时间更小的计划 c)物理计划生成,将查询计划转化成物理的计划, 为了选择更好的查询计划,需要判断 1)查询哪一个代数的等价形式是最有效的 2)对选中形式的每一个操作,所使用的算法选

第六章:异常机制

第六章:异常机制 异常的定义 异常:在程序运行过程中出现的意外事件,导致程序中断执行. 异常处理 try...catch 语法:try{ //可能出现异常的代码}catch(异常类型 异常对象名){ //处理异常的代码:}执行过程:当try中的代码异常发生时抛出一个异常对象,该异常对象与catch中异常类型进行匹配,匹配成功进入catch块,否则不执行catch中代码(相当于异常未被处理).程序只有当异常处理成功后才能继续执行. try...catch...catch 语法:try{ //可能出

2017上半年软考 第六章 重要知识点

第六章 项目整体管理 []项目整体管理概述 [][]项目整体管理的含义.作用和过程 项目整体管理6个过程?p264 项目整体管理包括什么? 项目管理的核心是什么? 项目整体管理涉及哪几个方面?p265 [][]项目经理是整合者 项目经理作为整合者要做什么?p265 [][]整体管理的地位 []项目整体管理实现过程 [][]制定项目章程概述 项目章程的意义是什么? 项目章程包括什么? [][]制定项目章程 项目章程的作用? 项目章程的输入? 制定项目章程的工具和技术?p267 项目章程的输出?p2

ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第六章:管理产品图片:多对多关系(上)

这章介绍了怎样创建一个新的实体来管理图片,怎样使用HTML窗体来上传图片文件和使用多对多关系来使它们与产品相关,并且怎样来保存图片到文件系统中.这章也介绍了更多复杂的错误处理增加客户端错误到模型中为了把它们显示回给用户.在这章中播种数据库使用的产品图片可能在在第六章的从Apress网页站点下载代码中. 注意:如果你想遵从这章的代码,你必须完成第五章的代码或者从www.apress.com下载第五章的源代码作为一个起点. 创建实体保存图片文件名 这个项目,我们正要使用文件系统在Web项目中存储图片

Linux与云计算——第二阶段Linux服务器架设 第六章:目录Directory服务器架设—FreeIPA

Linux与云计算--第二阶段Linux服务器架设 第六章:目录Directory服务器架设-FreeIPA 1 FreeIPA 配置FreeIPA服务器 Configure IPA Server to share users' account in your local network. [1] Install FreeIPA. [[email protected] ~]# yum -y install ipa-server ipa-server-dns bind bind-dyndb-lda

APUE读书笔记-第六章 系统数据文件和信息

昨天看完了,今天来看看第六章.感觉第六章的内容不是非常重要.简单看看吧 6.2 口令文件 口令文件其实就是/etc文件夹下的passwd文件,但处于安全性的考虑,我们无法直接读取它.就是通过直接限制权限的方式对其进行保护,passwd文件具体权限如下: -rw-r--r-- 1 root root 可以看到只有root用户具有读写权限,与root同组的用户与其他用户仅具有读权限. 不过为了解决以上问题,Linux中给出了一系列数据结构与函数帮助我们操纵口令文件,首先是关键数据结构,定义位于/in

CSS3秘笈:第六章

第六章  文本格式化 1.font-family 属性设置字体.除了指定想要的字体之外还要使用备用字体.例如: p{ font-family:Arial ,Helvetica ,sans-serif; } 如果字体的名称中包含多个单词,则必须用双引号(””)将它们括起来. 2.·serif字体,适合冗长的文字信息. ·sans-serif字体看起来干净而整洁因此经常被放在标题上. ·monospaced字体经常用于显示计算机代码.字体中的每个字母都是等宽的. ·其他常用字体:Arial Blac