openGL系列文章目录

文章目录

  • openGL系列文章目录
  • 前言
  • 一、具体实现思路
  • 二、代码
    • 主程序c++
    • 着色器程序
  • 效果
  • 源码下载

前言

凹凸贴图的一种替代方法是使用查找表来替换法向量。这样我们就可以在不依赖数学函
数的情况下,对凸起进行构造,例如月球上的陨石坑所对应的凸起。一种使用查找表的常
见方法叫作法线贴图。
为了理解法线贴图的工作原理,我们首先注意,向量通过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 4float 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 shaderglobalAmbLoc = 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 shaderglProgramUniform4fv(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 430layout (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 430in 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);
}

效果

源码下载

源码下载地址

openGL增强表面细节----法线贴图相关推荐

  1. openGL增强表面细节--凹凸贴图

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.凹凸贴图 二.代码 1.主程序c++ 总结 前言 假设我们想要对不规则表面的物体进行建模,例如橘子凹凸的表皮.葡萄干褶皱的表面 ...

  2. openGL增强表面细节--凹凸贴图具体实现

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.凹凸贴图 二.代码 1.主程序c++ 2.着色器程序 运行效果 源码下载 前言 假设我们想要对不规则表面的物体进行建模,例如橘子 ...

  3. openGL增强表面细节----高度贴图

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.代码 主程序c++ 效果 前言 现在我们扩展法线贴图的概念--从纹理图像用于扰动法向量到扰乱顶点位置本身.实 际上,以这种方式修 ...

  4. OpenGL shader normals法线贴图的实例

    OpenGL shader normals法线贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #in ...

  5. OpenGL生成的法线贴图并增加光照

    这一篇将由OpenGL生成法线贴图的基础上再增加光照效果. 思路如下: 准备一张墙壁图片A. 通过A自动生成法线贴图. 设计一个平行光,指定平行光的光照颜色和光照方向. 使用漫反射光照公式,法线贴图和 ...

  6. openGL法线贴图和纹理贴图结合使用,以增强三维物体表面细节

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.法线贴图? 二.代码 1.主程序 2.着色器程序 运行效果 源码下载 前言 凹凸贴图的一种替代方法是使用查找表来替换法向量.这样 ...

  7. OpenGL 法线贴图Normal Mapping

    OpenGL法线贴图Normal Mapping 法线贴图Normal Mapping简介 法线贴图 切线空间 手工计算切线和副切线 切线空间法线贴图 复杂物体 最后一件事 法线贴图Normal Ma ...

  8. Learn OpenGL 笔记6.5 Normal Mapping(法线贴图)

    我们通过在这些平面三角形上包裹 2D 纹理来增强真实感,隐藏多边形只是很小的平面三角形的事实. 从照明技术的角度来看,确定对象形状的唯一方法是通过其垂直法向量. 这种使用每片段法线与每表面法线相比的技 ...

  9. opengl高级光照之法线贴图

    法线贴图 opengl官方文档 核心修改的就是片段着色器中的normal值 uniform sampler2D normalMap; void main() { // 从法线贴图范围[0,1]获取法线 ...

  10. OpenGL.Shader:9-学习光照-法线贴图(计算TBN矩阵)

    OpenGL.Shader:9-学习光照-法线贴图(计算TBN矩阵) 这次文章学习法线贴图,法线贴图在游戏开发和GIS系统开发当中尤为广泛,其表现力特别的强,绘制的效果特别接近真实.更重要的一点就是, ...

最新文章

  1. Java开发微信之菜单不显示
  2. Android4.4 ContentResolver查询图片无效 及 图库删除 添加图片后,ContentResolver不更新的问题解决...
  3. JavaOne 2012:JavaOne技术主题演讲
  4. 【Python 必会技巧】使用 join() 方法将序列中的元素拼接成字符串
  5. JS中URL中的特殊字符问题:escape,encodeURI,encodeURIComponent(转)
  6. 暴力测试也疯狂——论Python代码优化
  7. 安全小白必看的Windows渗透基础命令大全
  8. 如何下载矢量建筑轮廓
  9. 软件测试用例优先级,软件测试用例的优先级划分方法
  10. 应用时间序列分析——有季节效应的非平稳序列分析-ARIMA加法模型-R语言
  11. python的语言是开源的_python语音识别 开源_DaCiDian是一个开源的中文普通话词汇,用于自动语音识别(ASR)....
  12. python聊天室设计_如何使用 Python 开发一个聊天室?
  13. win10删除提示找不到该项目
  14. ceph osd为down的情况
  15. 颜色直方图, HSV直方图, histogram bins
  16. js 色卡 (javascript 色卡 hsv 色卡 hsb 色卡)
  17. 图像处理课程大设计--汽车牌照自动识别
  18. 更改磁盘名称,D盘变为E盘
  19. 中英文会计科目对照表
  20. 为什么docker容器启动不了?

热门文章

  1. 轻量应用服务器和ecs云服务器哪个好
  2. 深入理解多线程(四)— Moniter的实现原理
  3. 外卖小程序源码java后台_扫码点餐系统小程序源码搭建开发
  4. c# 计算一年有多少周
  5. 详解电路设计中的RC电路
  6. 小米无线路由器服务器用户名和密码忘了,小米路由器密码忘记了怎么办 小米路由器密码忘记解决办法【详解】...
  7. 2019年12月份统考计算机应用基础题库,2019年12月网络教育统考《计算机应用基础》模拟题...
  8. 零基础无实物一步一步学PLCS7-1200仿真(一)--电动机的点动控制
  9. TSP_旅行商问题 - 遗传算法(四)
  10. EXCEL的COUNTIF和COUNTIFS函数的区别和联系