openGL增强表面细节----法线贴图
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增强表面细节----法线贴图相关推荐
- openGL增强表面细节--凹凸贴图
openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.凹凸贴图 二.代码 1.主程序c++ 总结 前言 假设我们想要对不规则表面的物体进行建模,例如橘子凹凸的表皮.葡萄干褶皱的表面 ...
- openGL增强表面细节--凹凸贴图具体实现
openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.凹凸贴图 二.代码 1.主程序c++ 2.着色器程序 运行效果 源码下载 前言 假设我们想要对不规则表面的物体进行建模,例如橘子 ...
- openGL增强表面细节----高度贴图
openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.代码 主程序c++ 效果 前言 现在我们扩展法线贴图的概念--从纹理图像用于扰动法向量到扰乱顶点位置本身.实 际上,以这种方式修 ...
- OpenGL shader normals法线贴图的实例
OpenGL shader normals法线贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #in ...
- OpenGL生成的法线贴图并增加光照
这一篇将由OpenGL生成法线贴图的基础上再增加光照效果. 思路如下: 准备一张墙壁图片A. 通过A自动生成法线贴图. 设计一个平行光,指定平行光的光照颜色和光照方向. 使用漫反射光照公式,法线贴图和 ...
- openGL法线贴图和纹理贴图结合使用,以增强三维物体表面细节
openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.法线贴图? 二.代码 1.主程序 2.着色器程序 运行效果 源码下载 前言 凹凸贴图的一种替代方法是使用查找表来替换法向量.这样 ...
- OpenGL 法线贴图Normal Mapping
OpenGL法线贴图Normal Mapping 法线贴图Normal Mapping简介 法线贴图 切线空间 手工计算切线和副切线 切线空间法线贴图 复杂物体 最后一件事 法线贴图Normal Ma ...
- Learn OpenGL 笔记6.5 Normal Mapping(法线贴图)
我们通过在这些平面三角形上包裹 2D 纹理来增强真实感,隐藏多边形只是很小的平面三角形的事实. 从照明技术的角度来看,确定对象形状的唯一方法是通过其垂直法向量. 这种使用每片段法线与每表面法线相比的技 ...
- opengl高级光照之法线贴图
法线贴图 opengl官方文档 核心修改的就是片段着色器中的normal值 uniform sampler2D normalMap; void main() { // 从法线贴图范围[0,1]获取法线 ...
- OpenGL.Shader:9-学习光照-法线贴图(计算TBN矩阵)
OpenGL.Shader:9-学习光照-法线贴图(计算TBN矩阵) 这次文章学习法线贴图,法线贴图在游戏开发和GIS系统开发当中尤为广泛,其表现力特别的强,绘制的效果特别接近真实.更重要的一点就是, ...
最新文章
- Java开发微信之菜单不显示
- Android4.4 ContentResolver查询图片无效 及 图库删除 添加图片后,ContentResolver不更新的问题解决...
- JavaOne 2012:JavaOne技术主题演讲
- 【Python 必会技巧】使用 join() 方法将序列中的元素拼接成字符串
- JS中URL中的特殊字符问题:escape,encodeURI,encodeURIComponent(转)
- 暴力测试也疯狂——论Python代码优化
- 安全小白必看的Windows渗透基础命令大全
- 如何下载矢量建筑轮廓
- 软件测试用例优先级,软件测试用例的优先级划分方法
- 应用时间序列分析——有季节效应的非平稳序列分析-ARIMA加法模型-R语言
- python的语言是开源的_python语音识别 开源_DaCiDian是一个开源的中文普通话词汇,用于自动语音识别(ASR)....
- python聊天室设计_如何使用 Python 开发一个聊天室?
- win10删除提示找不到该项目
- ceph osd为down的情况
- 颜色直方图, HSV直方图, histogram bins
- js 色卡 (javascript 色卡 hsv 色卡 hsb 色卡)
- 图像处理课程大设计--汽车牌照自动识别
- 更改磁盘名称,D盘变为E盘
- 中英文会计科目对照表
- 为什么docker容器启动不了?
热门文章
- 轻量应用服务器和ecs云服务器哪个好
- 深入理解多线程(四)— Moniter的实现原理
- 外卖小程序源码java后台_扫码点餐系统小程序源码搭建开发
- c# 计算一年有多少周
- 详解电路设计中的RC电路
- 小米无线路由器服务器用户名和密码忘了,小米路由器密码忘记了怎么办 小米路由器密码忘记解决办法【详解】...
- 2019年12月份统考计算机应用基础题库,2019年12月网络教育统考《计算机应用基础》模拟题...
- 零基础无实物一步一步学PLCS7-1200仿真(一)--电动机的点动控制
- TSP_旅行商问题 - 遗传算法(四)
- EXCEL的COUNTIF和COUNTIFS函数的区别和联系