openGL系列文章目录
前言
凹凸贴图的一种替代方法是使用查找表来替换法向量。这样我们就可以在不依赖数学函
数的情况下,对凸起进行构造,例如月球上的陨石坑所对应的凸起。一种使用查找表的常
见方法叫作法线贴图。
为了理解法线贴图的工作原理,我们首先注意,向量通过3 字节存储,X、Y 和Z 分量
各占1 字节,就可以达到合理的精度。这样,我们就可以将法向量存储在彩色图像文件中,
其中R、G 和B 分量分别对应于X、Y 和Z。图像中的RGB 值以字节存储,通常被解释为[0…1]
范围内的值,但是向量可以有正负值分量。如果我们将法向量分量限制在[−1…+1]范围内,
那么在图像文件中将法向量N 存储为像素的简单转换是:
法线贴图使用一个图像文件(称为法线贴图),该图像文件包含在光照下所期望表面外
观的法向量。在法线贴图中,向量相对于任意平面XY 表示,其X 和Y 分量表示与“垂直”
的偏差,其Z 分量设置为1,严格垂直于XY 平面的向量(即没有偏差)将表示为(0, 0, 1),
而不垂直的向量将具有非零的X 和/或Y 分量。我们需要使用上面的公式将值转换至RGB 空
间;例如,(0, 0, 1)将存储为(0.5, 0.5, 1),因为实际偏移的范围为[−1…+1],而RGB 值的
范围为[0…1]。
我们可以通过纹理单元的另一种妙用来生成这样一幅法线贴图:我们在纹理单元中存储所需的法向量而非颜色。然后,在给定片段中,我们就可以使用采样器从法线贴图中查找值,接下来,我们将所得的值作为法向量,而非输出像素颜色(在纹理贴图中我们是这么做的)。图10.3 展示了一个法线贴图图像文件的例子,通过将GIMP 法线贴图插件[GI16]应用于Luna [LU16]纹理而生成。法线贴图图像文件并不
适合作为图像查看,我们展示这幅图就是为了指明这一点,法线贴图最终看起来基本都是蓝色的。这是因为图像文件中每个像素的B 值(蓝色值)都是1(最大蓝色值),这会让它在作
为图像时看起来是“蓝色的”。
图1 展示了两个不同的法线贴图图像文件(它们都由Luna [LU16]的纹理构建)以及在
Blinn-Phong 光照模型下将它们应用于球体的结果。从法线贴图查找到的法向量不能直接使用,因为它们是相对于上述的任意XY 平面定义
的,并没有考虑它们在物体上的位置以及在相机空间中的方向。这个问题的解决策略是建
立一个转换矩阵,用于将法向量转换为相机空间,如下所示。
在对象的每个顶点处,我们考虑与对象相切的平面。顶点处的物体的法向量垂直于该切
面。我们在该切面中定义两个相互垂直的向量,同时也垂直于法向量,称为切向量和副切
向量(有时称为副法向量)。构造我们期望的变换矩阵要求我们的模型包括每个顶点的切向
量(可以通过计算切向量和法向量的叉积来构建副切向量)。如果模型中没有定义切向量,
则需要通过计算得到它们。在球体的情况下,可以通过计算得到精确的切向量。以下是对
程序的修改:
图1
图2
一、具体实现思路
对于那些表面不可导以至于无法精确求解切向量的模型,其切向量可以通过近似得到,
例如在构造(或加载)模型时,将每个顶点指向下一个顶点的向量作为切向量。请注意,
这种近似可能会导致切向量与顶点法向量不严格垂直。因此,如果要实现适用于各种模型
的法线贴图,需要考虑这种可能性(我们的解决方案中对此进行了处理)。
切向量与顶点、纹理坐标以及法向量一样,是从缓冲区(VBO)传递到顶点着色器中的
顶点属性。然后,顶点着色器通过应用MV 矩阵的逆转置并将结果沿着流水线转发以由光
栅器进行插值并最终进入片段着色器,从而对正常向量进行处理。逆转置的应用将法向量
和切向量转换为相机空间,之后我们使用叉积构造副切向量。
一旦我们在相机空间中得到法向量、切向量和副切向量,就可以使用它们来构造矩阵(依
其分量命名为“TBN”矩阵),该矩阵用于将从法线贴图中检索到的法向量转换为在相机空
间中相对于物体表面的法向量。
在片段着色器中,新法向量的计算在calcNewNormal()函数中完成。函数的第三行[包
含dot(tangent,normal)]的计算确保切向量垂直于法向量。新的切向量和法向量的叉积就
是副切向量。
然后,我们创建一个类型为mat3 的3×3 矩阵,作为TBN。mat3 构造函数接收3 个向量
作为参数,生成一个矩阵,其中顶行是第一个向量,中间行是第二个向量,底行是第三个
向量(类似于从摄像机位置构建视图矩阵,见图3.13)。
着色器使用片段的纹理坐标来提取与当前片段对应的法线贴图单元。着色器在提取时使
用采样器变量“normMap”,并被绑定到纹理单元0(注意:因此在C++ / OpenGL 应用程序
中必须将法线贴图图像附加到纹理单元0)。因为需要将颜色分量从纹理中存储范围[0…1]
转换为其原始范围[−1 … + 1],我们将其乘以2.0 再减去1.0。然后将TBN 矩阵应用于所得法向量以产
生当前像素的最终法向量。着色器的其余部分与用于Phong 光照的片段着色器相同。片段着色器代码基于Etay Meiri [ME11]的版本,如程序10.2 所示。制作法线贴图图像可以使用各种各样的工具。有的图像编辑工具就有制作法线贴图的功能,例如GIMP [GI16]和Photoshop [PH16]。它们通过分析图像中的边缘,推断凸起和凹陷,并产生相应的法线贴图。图3 显示了由Hastings-Trew [HT16]基于
NASA 卫星数据创建的月面纹理图。其相应的法线贴图由GIMP 法线贴图插件[GP16],通过处理由Hastings-Trew 创建的黑白版本月面纹理图生成。
图3
二、代码
主程序c++
#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Sphere.h"
#include "Utils.h"
using namespace std;
float toRadians(float degrees) { return (degrees * 2.0f * 3.14159f) / 360.0f; }
#define numVAOs 1
#define numVBOs 4
float cameraX, cameraY, cameraZ;
float sphLocX, sphLocY, sphLocZ;
float lightLocX, lightLocY, lightLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
// variable allocation for display
GLuint mvLoc, projLoc, nLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat, invTrMat;
GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc, mambLoc, mdiffLoc, mspecLoc, mshiLoc;
glm::vec3 currentLightPos;
float lightPos[3];
float rotAmt = 0.0f;
Sphere mySphere(48);
int numSphereVertices;
GLuint roofTexture;
// white light
float globalAmbient[4] = { 0.7f, 0.7f, 0.7f, 1.0f };
float lightAmbient[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
float lightDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float lightSpecular[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
// silver material
float* matAmb = Utils::silverAmbient();
float* matDif = Utils::silverDiffuse();
float* matSpe = Utils::silverSpecular();
float matShi = Utils::silverShininess();
void setupVertices(void) {
numSphereVertices = mySphere.getNumIndices();
std::vector<int> ind = mySphere.getIndices();
std::vector<glm::vec3> vert = mySphere.getVertices();
std::vector<glm::vec2> tex = mySphere.getTexCoords();
std::vector<glm::vec3> norm = mySphere.getNormals();
std::vector<glm::vec3> tang = mySphere.getTangents();
std::vector<float> pvalues;
std::vector<float> tvalues;
std::vector<float> nvalues;
std::vector<float> tanvalues;
for (int i = 0; i < mySphere.getNumIndices(); i++) {
pvalues.push_back((vert[ind[i]]).x);
pvalues.push_back((vert[ind[i]]).y);
pvalues.push_back((vert[ind[i]]).z);
tvalues.push_back((tex[ind[i]]).s);
tvalues.push_back((tex[ind[i]]).t);
nvalues.push_back((norm[ind[i]]).x);
nvalues.push_back((norm[ind[i]]).y);
nvalues.push_back((norm[ind[i]]).z);
tanvalues.push_back((tang[ind[i]]).x);
tanvalues.push_back((tang[ind[i]]).y);
tanvalues.push_back((tang[ind[i]]).z);
}
glGenVertexArrays(1, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glBufferData(GL_ARRAY_BUFFER, nvalues.size() * 4, &nvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
glBufferData(GL_ARRAY_BUFFER, tanvalues.size() * 4, &tanvalues[0], GL_STATIC_DRAW);
}
void installLights(glm::mat4 vMatrix) {
glm::vec3 transformed = glm::vec3(vMatrix * glm::vec4(currentLightPos, 1.0));
lightPos[0] = transformed.x;
lightPos[1] = transformed.y;
lightPos[2] = transformed.z;
// get the locations of the light and material fields in the shader
globalAmbLoc = glGetUniformLocation(renderingProgram, "globalAmbient");
ambLoc = glGetUniformLocation(renderingProgram, "light.ambient");
diffLoc = glGetUniformLocation(renderingProgram, "light.diffuse");
specLoc = glGetUniformLocation(renderingProgram, "light.specular");
posLoc = glGetUniformLocation(renderingProgram, "light.position");
mambLoc = glGetUniformLocation(renderingProgram, "material.ambient");
mdiffLoc = glGetUniformLocation(renderingProgram, "material.diffuse");
mspecLoc = glGetUniformLocation(renderingProgram, "material.specular");
mshiLoc = glGetUniformLocation(renderingProgram, "material.shininess");
// set the uniform light and material values in the shader
glProgramUniform4fv(renderingProgram, globalAmbLoc, 1, globalAmbient);
glProgramUniform4fv(renderingProgram, ambLoc, 1, lightAmbient);
glProgramUniform4fv(renderingProgram, diffLoc, 1, lightDiffuse);
glProgramUniform4fv(renderingProgram, specLoc, 1, lightSpecular);
glProgramUniform3fv(renderingProgram, posLoc, 1, lightPos);
glProgramUniform4fv(renderingProgram, mambLoc, 1, matAmb);
glProgramUniform4fv(renderingProgram, mdiffLoc, 1, matDif);
glProgramUniform4fv(renderingProgram, mspecLoc, 1, matSpe);
glProgramUniform1f(renderingProgram, mshiLoc, matShi);
}
void init(GLFWwindow* window) {
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
cameraX = 0.0f; cameraY = 0.0f; cameraZ = 2.0f;
sphLocX = 0.0f; sphLocY = 0.0f; sphLocZ = -1.0f;
lightLocX = -5.0f; lightLocY = 2.0f; lightLocZ = 5.0f;
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
setupVertices();
roofTexture = Utils::loadTexture("castleroofNORMAL.jpg");
}
void display(GLFWwindow* window, double currentTime) {
glClear(GL_DEPTH_BUFFER_BIT);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(renderingProgram);
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
nLoc = glGetUniformLocation(renderingProgram, "norm_matrix");
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(sphLocX, sphLocY, sphLocZ));
mMat = glm::rotate(mMat, toRadians(20.0f), glm::vec3(1.0f, 0.0f, 0.0f));
mMat = glm::rotate(mMat, rotAmt, glm::vec3(0.0f, 1.0f, 0.0f));
rotAmt += 0.002f;
mvMat = vMat * mMat;
invTrMat = glm::transpose(glm::inverse(mvMat));
currentLightPos = glm::vec3(lightLocX, lightLocY, lightLocZ);
installLights(vMat);
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glUniformMatrix4fv(nLoc, 1, GL_FALSE, glm::value_ptr(invTrMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(3);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, roofTexture);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glDrawArrays(GL_TRIANGLES, 0, numSphereVertices);
}
void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
aspect = (float)newWidth / (float)newHeight;
glViewport(0, 0, newWidth, newHeight);
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}
int main(void) {
if (!glfwInit()) { exit(EXIT_FAILURE); }
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GLFWwindow* window = glfwCreateWindow(800, 800, "Chapter10 - program2", NULL, NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
glfwSwapInterval(1);
glfwSetWindowSizeCallback(window, window_size_callback);
init(window);
while (!glfwWindowShouldClose(window)) {
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
着色器程序
1.顶点着色器
#version 430
layout (location = 0) in vec3 vertPos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 vertNormal;
layout (location = 3) in vec3 vertTangent;
out vec3 varyingLightDir;
out vec3 varyingVertPos;
out vec3 varyingNormal;
out vec3 varyingTangent;
out vec3 originalVertex;
out vec2 tc;
out vec3 varyingHalfVector;
layout (binding=0) uniform sampler2D s;
struct PositionalLight
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
vec3 position;
};
struct Material
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
};
uniform vec4 globalAmbient;
uniform PositionalLight light;
uniform Material material;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
void main(void)
{ varyingVertPos = (mv_matrix * vec4(vertPos,1.0)).xyz;
varyingLightDir = light.position - varyingVertPos;
tc = texCoord;
originalVertex = vertPos;
varyingNormal = (norm_matrix * vec4(vertNormal,1.0)).xyz;
varyingTangent = (norm_matrix * vec4(vertTangent,1.0)).xyz;
varyingHalfVector =
normalize(normalize(varyingLightDir)
+ normalize(-varyingVertPos)).xyz;
gl_Position = proj_matrix * mv_matrix * vec4(vertPos,1.0);
}
2.片元着色器
#version 430
in vec3 varyingLightDir;
in vec3 varyingVertPos;
in vec3 varyingNormal;
in vec3 varyingTangent;
in vec3 originalVertex;
in vec2 tc;
in vec3 varyingHalfVector;
out vec4 fragColor;
layout (binding=0) uniform sampler2D normMap;
struct PositionalLight
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
vec3 position;
};
struct Material
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
};
uniform vec4 globalAmbient;
uniform PositionalLight light;
uniform Material material;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
vec3 calcNewNormal()
{
vec3 normal = normalize(varyingNormal);
vec3 tangent = normalize(varyingTangent);
tangent = normalize(tangent - dot(tangent, normal) * normal);
vec3 bitangent = cross(tangent, normal);
mat3 tbn = mat3(tangent, bitangent, normal);
vec3 retrievedNormal = texture(normMap,tc).xyz;
retrievedNormal = retrievedNormal * 2.0 - 1.0;
vec3 newNormal = tbn * retrievedNormal;
newNormal = normalize(newNormal);
return newNormal;
}
void main(void)
{ // normalize the light, normal, and view vectors:
vec3 L = normalize(varyingLightDir);
vec3 V = normalize(-varyingVertPos);
vec3 N = calcNewNormal();
// get the angle between the light and surface normal:
float cosTheta = dot(L,N);
// compute light reflection vector, with respect N:
//vec3 R = normalize(reflect(-L, N));
vec3 H = normalize(varyingHalfVector);
// angle between the view vector and reflected light:
float cosPhi = dot(H,N);
// compute ADS contributions (per pixel):
fragColor = globalAmbient * material.ambient
+ light.ambient * material.ambient
+ light.diffuse * material.diffuse * max(cosTheta,0.0)
+ light.specular * material.specular
* pow(max(cosPhi,0.0), material.shininess*3.0);
}