OpenGL 学习笔记 法线贴图

2024-03-16 18:59

本文主要是介绍OpenGL 学习笔记 法线贴图,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

法线贴图

效果图

在这里插入图片描述

法线贴图的意义以及基本介绍可以看opengl官方网站
https://learnopengl-cn.github.io/05%20Advanced%20Lighting/04%20Normal%20Mapping/

本此的代码立方体顶点数据来自

https://blog.csdn.net/u010385624/article/details/91994006

关于切线空间介绍的十分清楚。

法线贴图介绍

我们的场景中已经充满了多边形物体,其中每个都可能由成百上千平坦的三角形组成。我们以向三角形上附加纹理的方式来增加额外细节,提升真实感,隐藏多边形几何体是由无数三角形组成的事实。纹理确有助益,然而当你近看它们时,这个事实便隐藏不住了。现实中的物体表面并非是平坦的,而是表现出无数(凹凸不平的)细节。

例如,砖块的表面。砖块的表面非常粗糙,显然不是完全平坦的:它包含着接缝处水泥凹痕,以及非常多的细小的空洞。

切线空间

https://zhuanlan.zhihu.com/p/139593847

代码讲解

代码中渲染片段

	while (!glfwWindowShouldClose(window)){// per-frame time logic// --------------------float currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;// input// -----processInput(window);// render// ------glClearColor(0.1f, 0.1f, 0.1f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// configure view/projection matricesglm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);glm::mat4 view = camera.GetViewMatrix();shader.use();shader.setMat4("projection", projection);shader.setMat4("view", view);// render normal-mapped quadglm::mat4 model = glm::mat4(1.0f);model = glm::rotate(model, glm::radians((float)glfwGetTime() * -10.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); // rotate the quad to show normal mapping from multiple directionsshader.setMat4("model", model);shader.setVec3("viewPos", camera.Position);shader.setVec3("lightPos", lightPos);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, diffuseMap);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, normalMap);renderCube();//renderQuad();// render light source (simply re-renders a smaller plane at the light's position for debugging/visualization)model = glm::mat4(1.0f);model = glm::translate(model, lightPos);model = glm::scale(model, glm::vec3(0.1f));shader.setMat4("model", model);renderCube();//renderQuad();// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}glfwTerminate();return 0;
}

可以看到都是正常的赋值,将所需的模型坐标填入。其中我们调用了renderCube()来进行绘制立方体,而且我们其中切线空间转换也在里面进行。

void renderCube() 
{if(cubeVAO = 1){// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {// positions          // normals           // texture coords-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  0.0f,0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  0.0f,0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  1.0f,0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  1.0f,-0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  1.0f,-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  0.0f,-0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  0.0f,0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  0.0f,0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  1.0f,0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  1.0f,-0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  1.0f,-0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  0.0f,-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  0.0f,-0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  1.0f,-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  1.0f,-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  1.0f,-0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  0.0f,-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  1.0f,0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  1.0f,0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  1.0f,0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  0.0f,0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  1.0f,0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  1.0f,0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  0.0f,0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  0.0f,-0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  0.0f,-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  1.0f,-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  1.0f,0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  1.0f,0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,-0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  0.0f,-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  1.0f};//共有6个面12个三角形 36个顶点float tbnFloats[216]; // 36 个顶点的切线和副切线向量一共有 216 个浮点数for (int i = 0; i < 12; i++){int firstIndex = i * 24; // 三角形第 1 个顶点坐标起始索引int secondIndex = firstIndex + 8; // 三角形第 2 个顶点坐标起始索引int thirdIndex = secondIndex + 8; // 三角形第 3 个顶点坐标起始索引// 求得一个三角形的三个顶点坐标glm::vec3 pos1(vertices[firstIndex], vertices[firstIndex + 1], vertices[firstIndex + 2]);glm::vec3 pos2(vertices[secondIndex], vertices[secondIndex + 1], vertices[secondIndex + 2]);glm::vec3 pos3(vertices[thirdIndex], vertices[thirdIndex + 1], vertices[thirdIndex + 2]);// 求得一个三角形的三个顶点对应的 UV 坐标glm::vec2 uv1(vertices[firstIndex + 6], vertices[firstIndex + 7]);glm::vec2 uv2(vertices[secondIndex + 6], vertices[secondIndex + 7]);glm::vec2 uv3(vertices[thirdIndex + 6], vertices[thirdIndex + 7]);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);glm::vec3 tangent;glm::vec3 bitTangent;tangent.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);tangent.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);tangent.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);bitTangent.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);bitTangent.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);bitTangent.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);bitTangent = glm::normalize(bitTangent);// 将每个三角形顶点的切线和副切线数据放到数组里int startTBNIndex = i * 18;tbnFloats[startTBNIndex + 0] = tangent.x;tbnFloats[startTBNIndex + 1] = tangent.y;tbnFloats[startTBNIndex + 2] = tangent.z;tbnFloats[startTBNIndex + 3] = bitTangent.x;tbnFloats[startTBNIndex + 4] = bitTangent.y;tbnFloats[startTBNIndex + 5] = bitTangent.z;tbnFloats[startTBNIndex + 6] = tangent.x;tbnFloats[startTBNIndex + 7] = tangent.y;tbnFloats[startTBNIndex + 8] = tangent.z;tbnFloats[startTBNIndex + 9] = bitTangent.x;tbnFloats[startTBNIndex + 10] = bitTangent.y;tbnFloats[startTBNIndex + 11] = bitTangent.z;tbnFloats[startTBNIndex + 12] = tangent.x;tbnFloats[startTBNIndex + 13] = tangent.y;tbnFloats[startTBNIndex + 14] = tangent.z;tbnFloats[startTBNIndex + 15] = bitTangent.x;tbnFloats[startTBNIndex + 16] = bitTangent.y;tbnFloats[startTBNIndex + 17] = bitTangent.z;}float finishVertices[504];// 一共 36 个顶点,按照特定顺序合并即可for (int i = 0; i < 36; ++i){int finishStartIndex = i * 14;int verticesStartIndex = i * 8;int tbnStartIndex = i * 6;finishVertices[finishStartIndex + 0] = vertices[verticesStartIndex + 0];finishVertices[finishStartIndex + 1] = vertices[verticesStartIndex + 1];finishVertices[finishStartIndex + 2] = vertices[verticesStartIndex + 2];finishVertices[finishStartIndex + 3] = vertices[verticesStartIndex + 3];finishVertices[finishStartIndex + 4] = vertices[verticesStartIndex + 4];finishVertices[finishStartIndex + 5] = vertices[verticesStartIndex + 5];finishVertices[finishStartIndex + 6] = vertices[verticesStartIndex + 6];finishVertices[finishStartIndex + 7] = vertices[verticesStartIndex + 7];finishVertices[finishStartIndex + 8] = tbnFloats[tbnStartIndex + 0];finishVertices[finishStartIndex + 9] = tbnFloats[tbnStartIndex + 1];finishVertices[finishStartIndex + 10] = tbnFloats[tbnStartIndex + 2];finishVertices[finishStartIndex + 11] = tbnFloats[tbnStartIndex + 3];finishVertices[finishStartIndex + 12] = tbnFloats[tbnStartIndex + 4];finishVertices[finishStartIndex + 13] = tbnFloats[tbnStartIndex + 5];}//unsigned int cubeVAO = 1;//unsigned int cubeVBO;// configure Cube VAOglGenVertexArrays(1, &cubeVAO);glGenBuffers(1, &cubeVBO);glBindVertexArray(cubeVAO);glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(finishVertices), &finishVertices, 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(cubeVAO);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);
}

finishVertices数组包含了我们所有的顶点。

顶点数组处理方法:

#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;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);//叉乘,获得富且先向量//注意,这里我们使用transpose函数,而不是inverse函数。//正交矩阵(每个轴既是单位向量同时相互垂直)的一大属性是一个正交矩阵的置换矩阵与它的逆矩阵相等。//这个属性和重要因为逆矩阵的求得比求置换开销大;结果却是一样的。mat3 TBN = transpose(mat3(T, B, N)); 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);
}

inverse :反转矩阵

transpose:矩阵求转置矩阵

正交化处理并且输出给片段着色器

  
#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()
{  // 处理获取到的法线(从法线贴图的RGB中提取)     // 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);  // this normal is in tangent space// 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 学习笔记 法线贴图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/816409

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

Open3D 基于法线的双边滤波

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 输入参数: 输出参数: 参数影响: 2.2完整代码 三、实现效果 3.1原始点云 3.2滤波后点云 Open3D点云算法汇总及实战案例汇总的目录地址: Open3D点云算法与点云深度学习案例汇总(长期更新)-CSDN博客 一、概述         基于法线的双边

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个