法线贴图

opengl官方文档

  • 核心修改的就是片段着色器中的normal值
uniform sampler2D normalMap;  void main()
{           // 从法线贴图范围[0,1]获取法线normal = texture(normalMap, fs_in.TexCoords).rgb;// 将法线向量转换为范围[-1,1]normal = normalize(normal * 2.0 - 1.0);   [...]// 像往常那样处理光照
}

切线空间

  • 处理两个坐标系不一样的问题
    然而有个问题限制了刚才讲的那种法线贴图的使用。我们使用的那个法线贴图里面的所有法线向量都是指向正z方向的。上面的例子能用,是因为那个平面的表面法线也是指向正z方向的。可是,如果我们在表面法线指向正y方向的平面上使用同一个法线贴图会发生什么?光照看起来完全不对!发生这种情况是平面的表面法线现在指向了y,而采样得到的法线仍然指向的是z。结果就是光照仍然认为表面法线和之前朝向正z方向时一样;这样光照就不对了。下面的图片展示了这个表面上采样的法线的近似情况:

  • 你可以看到所有法线都指向z方向,它们本该朝着表面法线指向y方向的。一个可行方案是为每个表面制作一个单独的法线贴图。如果是一个立方体的话我们就需要6个法线贴图,但是如果模型上有无数的朝向不同方向的表面,这就不可行了(译注:实际上对于复杂模型可以把朝向各个方向的法线储存在同一张贴图上,你可能看到过不只是蓝色的法线贴图,不过用那样的法线贴图有个问题是你必须记住模型的起始朝向,如果模型运动了还要记录模型的变换,这是非常不方便的;此外就像作者所说的,如果把一个diffuse纹理应用在同一个物体的不同表面上,就像立方体那样的,就需要做6个法线贴图,这也不可取)。

    另一个稍微有点难的解决方案是,在一个不同的坐标空间中进行光照,这个坐标空间里,法线贴图向量总是指向这个坐标空间的正z方向;所有的光照向量都相对与这个正z方向进行变换。这样我们就能始终使用同样的法线贴图,不管朝向问题。这个坐标空间叫做切线空间(tangent space)。

代码

  • 贴图:
    unsigned int diffuseMap = loadTexture(FileSystem::getPath("resources/textures/brickwall.jpg").c_str());unsigned int normalMap  = loadTexture(FileSystem::getPath("resources/textures/brickwall_normal.jpg").c_str());
  • 让任何向量乘以一个TBN矩阵(新的基底)转化为模型空间中的值。大家都在一个坐标系里边就行。(TBN矩阵这三个字母分别代表tangent、bitangent和normal向量)
  • 推导过程就在官方文档中。用一个三角形的顶点和纹理坐标(因为纹理坐标和切线向量在同一空间中)计算出切线和副切线。
  • 有两种方法:一种是在片段着色器中转换,一种是基于顶点转换计算之后。后者操作比较小。
  • 一般在mesh里边就有处理法线纹理的东西用来处理复杂模型。
  • 多出来两个变量:计算切线空间 tangent bitangent **(切线和副切线)**计算出来的切线空间法线贴图。
// renders a 1x1 quad in NDC with manually calculated tangent vectors
// ------------------------------------------------------------------
unsigned int quadVAO = 0;
unsigned int quadVBO;
void renderQuad()
{if (quadVAO == 0){// positionsglm::vec3 pos1(-1.0f, 1.0f, 0.0f);glm::vec3 pos2(-1.0f, -1.0f, 0.0f);glm::vec3 pos3(1.0f, -1.0f, 0.0f);glm::vec3 pos4(1.0f, 1.0f, 0.0f);// texture coordinatesglm::vec2 uv1(0.0f, 1.0f);glm::vec2 uv2(0.0f, 0.0f);glm::vec2 uv3(1.0f, 0.0f);glm::vec2 uv4(1.0f, 1.0f);// normal vectorglm::vec3 nm(0.0f, 0.0f, 1.0f);// calculate tangent/bitangent vectors of both trianglesglm::vec3 tangent1, bitangent1;glm::vec3 tangent2, bitangent2;// triangle 1// ----------glm::vec3 edge1 = pos2 - pos1;glm::vec3 edge2 = pos3 - pos1;glm::vec2 deltaUV1 = uv2 - uv1;glm::vec2 deltaUV2 = uv3 - uv1;float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);// triangle 2// ----------edge1 = pos3 - pos1;edge2 = pos4 - pos1;deltaUV1 = uv3 - uv1;deltaUV2 = uv4 - uv1;f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);float quadVertices[] = {//多出来两个变量// positions            // normal         // texcoords  // tangent                          // bitangentpos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z};// configure plane VAOglGenVertexArrays(1, &quadVAO);glGenBuffers(1, &quadVBO);glBindVertexArray(quadVAO);glBindBuffer(GL_ARRAY_BUFFER, quadVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(2);glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(6 * sizeof(float)));glEnableVertexAttribArray(3);glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(8 * sizeof(float)));glEnableVertexAttribArray(4);glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void*)(11 * sizeof(float)));}glBindVertexArray(quadVAO);glDrawArrays(GL_TRIANGLES, 0, 6);glBindVertexArray(0);
}
  • 顶点着色器可以看出来使用的是第二种方法。注意这里计算TBN的方式,用到线性代数。
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;//传出TangentPos说明用的是第二种方式,从把模型坐标系纹理变量转换成纹理坐标系的变量转移到片段着色器
out VS_OUT {vec3 FragPos;vec2 TexCoords;vec3 TangentLightPos;vec3 TangentViewPos;vec3 TangentFragPos;
} vs_out;uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;uniform vec3 lightPos;
uniform vec3 viewPos;void main()
{vs_out.FragPos = vec3(model * vec4(aPos, 1.0));   vs_out.TexCoords = aTexCoords;mat3 normalMatrix = transpose(inverse(mat3(model)));    //转换矩阵vec3 T = normalize(normalMatrix * aTangent);vec3 N = normalize(normalMatrix * aNormal);T = normalize(T - dot(T, N) * N);//线性代数中向量的减法vec3 B = cross(N, T);mat3 TBN = transpose(mat3(T, B, N)); //赋值输出到片段着色器,一般应该直接放在mesh里边   vs_out.TangentLightPos = TBN * lightPos;vs_out.TangentViewPos  = TBN * viewPos;vs_out.TangentFragPos  = TBN * vs_out.FragPos;gl_Position = projection * view * model * vec4(aPos, 1.0);
}
  • 片段着色器
#version 330 core
out vec4 FragColor;in VS_OUT {vec3 FragPos;vec2 TexCoords;vec3 TangentLightPos;vec3 TangentViewPos;vec3 TangentFragPos;
} fs_in;uniform sampler2D diffuseMap;
uniform sampler2D normalMap;uniform vec3 lightPos;
uniform vec3 viewPos;void main()
{           // obtain normal from normal map in range [0,1]vec3 normal = texture(normalMap, fs_in.TexCoords).rgb;  //法线贴图中的值// transform normal vector to range [-1,1]normal = normalize(normal * 2.0 - 1.0);  // 这里的发现是传递过来的纹理贴图的法线// get diffuse colorvec3 color = texture(diffuseMap, fs_in.TexCoords).rgb;// ambientvec3 ambient = 0.1 * color;// diffusevec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos);float diff = max(dot(lightDir, normal), 0.0);vec3 diffuse = diff * color;// specularvec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);vec3 reflectDir = reflect(-lightDir, normal);vec3 halfwayDir = normalize(lightDir + viewDir);  float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);vec3 specular = vec3(0.2) * spec;FragColor = vec4(ambient + diffuse + specular, 1.0);
}

效果

opengl高级光照之法线贴图相关推荐

  1. LearnOpenGL笔记——五、高级光照:“法线贴图”和”视差贴图“

    五.高级光照:"法线贴图"和"视差贴图" 5.4 法线贴图 以光照算法的视角考虑的话,只有一件事决定物体的形状,这就是垂直于它的法线向量 砖块表面只有一个法线向 ...

  2. opengl高级光照之视差贴图(陡峭视差贴图以及视差遮蔽映射)

    视差贴图 视差贴图官方文档 视差贴图 视差贴图(Parallax Mapping)技术和法线贴图差不多,但它有着不同的原则.和法线贴图一样视差贴图能够极大提升表面细节,使之具有深度感.它也是利用了视错 ...

  3. QT+OpenGL高级光照 Blinn-Phong和Gamma校正

    QT+OpenGL高级光照1 本篇完整工程见gitee:QtOpenGL 对应点的tag,由turbolove提供技术支持,您可以关注博主或者私信博主 Blinn-Phong 冯氏光照:视线与反射方向 ...

  4. OpenGL 高级光照Advanced Lighting

    OpenGL高级光照Advanced Lighting 高级光照Advanced Lighting简介 Blinn-Phong 高级光照Advanced Lighting简介 在光照小节中,我们简单地 ...

  5. (多图)Unity 2D 光照---------添加法线贴图

    ~~~~~~~~~~~~~~~~~····RoudLun原创~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 首先需要设置2D灯光: 法线贴图是记录图片高度的,可以使平面的图片更加立体,效果 ...

  6. opengl高级光照之延迟渲染以及光体积

    延迟着色法 延迟着色法官方文档 我们现在一直使用的光照方式叫做正向渲染(Forward Rendering)或者正向着色法(Forward Shading),它是我们渲染物体的一种非常直接的方式,在场 ...

  7. opengl高级光照之gamma校正

    官方文章 gamma校正 gamma校正概念 一个渐变的效果 通过以下网站调整Gamma值可以观察到效果 色彩管理网 gamma校正 Gamma校正(Gamma Correction)的思路是在最终的 ...

  8. 非常详细易懂的法线贴图(Normal Mapping)

    翻译:非常详细易懂的法线贴图(Normal Mapping) 本文翻译自: Shaders » Lesson 6: Normal Mapping 作者: Matt DesLauriers 译者: Fr ...

  9. 【翻译】非常详细易懂的法线贴图(Normal Mapping)

    翻译:非常详细易懂的法线贴图(Normal Mapping) 本文翻译自: Shaders » Lesson 6: Normal Mapping 作者: Matt DesLauriers 译者: Fr ...

最新文章

  1. HDU - 1269迷宫城堡 -强连通tanjar算法
  2. iOS开发之UIMenuController的基本使用
  3. 【深度学习】目标检测实战:4种YOLO目标检测的C++和Python两种版本实现
  4. Sum in the tree
  5. PCM - partner channel management 的数据库表介绍
  6. vant组件搜索并选择_Vant Weapp - 有赞出品的免费开源微信小程序组件库
  7. 后端接口如何提高性能?从MySQL、ES、HBASE等技术一起探讨下!
  8. ActiveMQ安装配置
  9. oracle安装失败 主机名_PeopleTool 8.58.04 安装
  10. python生成图像公章_科学网—python pillow库 python界的ps 实现数据批量盖章 并打包成exe - 李鸿斌的博文...
  11. MySQL数据库被删除如何恢复
  12. 解决新版edge浏览器首页被搜狗、haoqq等垃圾搜索引擎捆绑问题,并将启动首页设为edge自带新标签页
  13. hashcat在windows上的安装与简单使用
  14. X86与ARM平台下的参数传递机制
  15. 读书到什么程度才能算融会贯通?
  16. 移动开发需要了解的UI设计知识
  17. 农家女靠养花赚钱,年收入几十万
  18. 房屋中介信息管理平台
  19. Java 并发异步编程,原来十个接口的活现在只需要一个接口就搞定!
  20. 运维工程师怎么找兼职?什么样的兼职合适?

热门文章

  1. 【Axure视频教程】中继器表格——设置表格内容
  2. [AH2017/HNOI2017]影魔
  3. 4.4.3 同步提交偏移量
  4. 前端实现pdf文件预览
  5. android 版本分布 友盟,友盟推送
  6. matlab算法改进,pso算法改进含MATLAB代码
  7. 复旦大学黄萱菁:自然语言处理中的表示学习
  8. 排序系列三: 二分查找法
  9. 排序系列一: 选择排序法
  10. markdown for 蝉知 2.1.1,简单修复移动端展示