计算机图形学 | 实验十:几何纹理(法线贴图)

news2024/11/14 14:59:54

计算机图形学 | 实验十:几何纹理(法线贴图)

  • 计算机图形学 | 实验十:几何纹理(法线贴图)
    • 什么是法线贴图
    • 为什么需要切线空间
    • 加载法线贴图
    • 引入切线空间
    • 结果

华中科技大学《计算机图形学》课程

MOOC地址:计算机图形学(HUST)

计算机图形学 | 实验十:几何纹理(法线贴图)

在正式搭建环境之前,我们先来介绍一下读完下面的部分你会了解些什么:

  • 了解什么是法线贴图
  • 为什么需要切线空间
  • 如何加载和使用法线贴图
  • 如何引入切线空间并在着色器中使用

接下来,我们来介绍一下绘制具有法线贴图的立方体的效果。

绘制效果如下(左图为无切线空间的法线贴图,右图为有切线空间的法线贴图):

在这里插入图片描述

什么是法线贴图

在我们进行各种图形的绘制时,为了尽量提高真实感,会采用纹理贴图的方法,但是例如我们在一个正方形上贴砖墙的纹理,现实中的砖墙是凹凸不平的,而且我们绘制的正方形却是一个平面,在加入光照模型时,也不能通过光照,反应砖墙的细节,这时我们引入法线贴图的办法去赋予纹理每个点相应的法线信息,去形成不同的光照效果。

如下图,左图为无法线贴图的情况,右图为引入法线贴图的情况。

在这里插入图片描述

法线贴图相当于针对原纹理贴图的每个像素点添加的其独特的法线细节。所有的法线贴图都是对应局部坐标的法线,他们是一种偏蓝色调的纹理(你在网上找到的几乎所有法线贴图都是这样的)。这是因为所有法线的指向都偏向z轴(0, 0, 1)这是一种偏蓝的颜色。法线向量从z轴方向也向其他方向轻微偏移,颜色也就发生了轻微变化,这样看起来便有了一种深度。例如,你可以看到在每个砖块的顶部,颜色倾向于偏绿,这是因为砖块的顶部的法线偏向于指向正y轴方向(0, 1, 0),这样它就是绿色的了。

在这里插入图片描述

为什么需要切线空间

使用Heightmap可以为物体表面增加法线细节,但是从heightmap提取的法线是局部坐标系内的法线,正常的使用需要我们建立一个切线空间去将heightmap中提取出的法线转换到世界坐标系中,之前的效果图我们可以看到,左侧为不引入切线空间的法线贴图,它的每个面的法线都是一样的,所以显得亮度相同,而右侧是引入了切线空间的法线贴图,它每个面的法线朝向都不相同。

在这里插入图片描述

加载法线贴图

加载法线贴图和加载纹理的方式可以说是一模一样的,在此不再赘述。

Texture cube_normal; 
//加载法线贴图
unsigned int cube_normal_texture = cube_normal.LoadTextureFromFile("res/texture/cube_normal.jpg");

法线贴图是把法线数据储存在纹理图片中,所以我们需要根据法线贴图和纹理坐标去采样获得一点的法线贴图的颜色值,将其rgb颜色值,作为法线的xyz轴,读取成法线向量,但是颜色的范围是在0 ~ 1之间,而法线参数是在-1 ~ 1之间,所以我们需要进行范围的转化,先乘以2扩大范围到0 ~ 2,再减去1,使法线范围变化到 -1 ~ 1,最后一般我们使用的法线都应该是单位向量,所以我们需要进行标准化操作。

vec3 normal = texture(texture_normal, fs_in.TexCoords).rgb;
normal = normalize(normal * 2.0f - 1.0f);

然后我们使用从法线贴图中取样获得的法线来计算光照即可。

引入切线空间

法线贴图中的法线向量在切线空间中,法线永远指着正z方向。如果模型上有无数的朝向不同方向的表面,这就不可行了。所以一种解决问题的方式是计算出一种矩阵,把法线从局部空间变换到一个世界空间,这样它们就能和表面法线方向对齐了。

这种矩阵叫做TBN矩阵这三个字母分别代表tangent、bitangent和normal向量。这是建构这个矩阵所需的向量。要建构这样一个把切线空间转变为不同空间的变异矩阵,我们需要三个相互垂直的向量,它们沿一个表面的法线贴图对齐于:上、右、前。

在这里插入图片描述

为了获得TBN矩阵,需要进行以下步骤

首先,我们可以把边E1和E2用切线向量T和副切线向量B的线性组合表示出来。

E是两个向量位置的差,ΔU和ΔV是纹理坐标的差。

图中我们可以看到边E2纹理坐标的不同,E2是一个三角形的边,这个三角形的另外两条边是ΔU2和ΔV2,它们与切线向量T和副切线向量B方向相同。这样我们可以把边E1和E2用切线向量T和副切线向量B的线性组合表示出来(注意T和B都是单位长度,在TB平面中所有点的T、B坐标都在0到1之间,因此可以进行这样的组合):

在这里插入图片描述

然后也可以写成这样:

在这里插入图片描述

上面的方程允许我们把它们写成另一种格式:矩阵乘法。

在这里插入图片描述

两边都乘以ΔUΔV的逆矩阵等于:

在这里插入图片描述

最后,用1除以逆矩阵的行列式,再乘以它的共轭矩阵。

在这里插入图片描述

由此,我们可以手工算出TBN矩阵,当然,也可以通过编写算法去计算相应的TBN矩阵。

以下为TBN矩阵的计算算法(其中pos1,pos2,pos3为世界空间内坐标,uv1,uv2,uv3为纹理坐标),由于三个向量两两正交,则可以计算出第三个向量:

//我们先计算三角形的边和deltaUV坐标: 
glm::vec3 edge1 = pos2 - pos1; //即计算公式中的E1 
glm::vec3 edge2 = pos3 - pos1; //即计算公式中的E2 
glm::vec2 deltaUV1 = uv2 - uv1; //即计算公式中的U1 V1 
glm::vec2 deltaUV2 = uv3 - uv1; //即计算公式中的U2 V2 
//有了计算切线和副切线的必备数据,计算: 
GLfloat 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); 
tangent1 = glm::normalize(tangent1); 
//把结果转换成单位矩阵 
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); 
bitangent1 = glm::normalize(bitangent1); //把结果转换成单位矩阵

切线空间的TN向量我们从顶点数组中传入,但是,需要注意的是传入的T N向量是局部坐标系的TN向量,我们需要乘以Model将其转换至世界坐标系,model矩阵是mat4所以我们需要先将T向量变化为4维向量,乘完后再变回三维向量,最后依旧不要忘记标准化操作,最后得到了我们需要的TN向量:

vec3 T = normalize(vec3(model * vec4(aTangent, 0.0f))); 
vec3 N = normalize(vec3(model * vec4(aNormal, 0.0f))); 
vec3 B = normalize(cross(T, N));

接下来,只需要在从法线贴图读取了法线数据并进行范围转化之后,将TBN矩阵与法线相乘,即可引入切线空间,将法线变换到世界空间中。

// 从法线贴图范围[0,1]获取法线 
vec3 normal = texture(texture_normal, fs_in.TexCoords).rgb; 
// 将法线向量转换为范围[-1,1] 
normal = normalize(normal * 2.0f - 1.0f); 
//引入切线到世界空间变换
normal = normalize(fs_in.TBN * normal);

结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/547685.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PCB基础~电源和地平面,去耦电容

电源和地平面 • 应该尽可能的使用电源和地平面, Why? – 在设备和电源之间提供一个低阻抗的路径 – 提供屏蔽 – 提供散热 – 降低分布电感 • 一个完整的无破损的平面是最优选择 – 破碎的地平面会在走线的上下层之间 引入寄生电感 • Remember! • 低频时&…

【服务器】利用树莓派搭建 web 服务器-无需公网IP

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员,2024届电子信息研究生 目录 概述 使用 Raspberry Pi Imager 安装 Raspberry Pi OS 设置 Apache Web 服务器 测试 web 站点 安装静态样例站点 将web站点发布到公网 安装 Cpolar内网穿透 cpolar进行tok…

SQL-DDL语句DQL语句

SQL学习笔记 DDL语句--操作数据表 /* 快捷键: insert键 在插入 和 替换模式之间切换 ctrl 字母z 撤销上一步操作 tab 往后缩进(默认4个空格) shift tab 往前缩进(默认4个空格) …

SpringBoot配置文件和日志

目录 SpringBoot配置文件 SpringBoot配置文件的作用 项目中的重要数据写在配置文件当中 降低代码耦合 SpringBoot配置文件的格式 properties配置文件 读取配置文件中的内容(Value注解使用${}格式读取) properties优缺点 yml配置文件 yml特…

ChatGPT官方APP正式发布!附安装使用教程

目录 前言 APP功能演示 1.与机器人聊天,询问问题 2.语音输入,人机交互 3.聊天历史,新建聊天分组 安装教程 1.下载应用 2.登录账号 3.愉快的玩耍吧 总结 写到最后 大家好,我是大侠,AI领域的专业博主 前言 …

Docker安装常用软件-Kafka集群

零、为了方便开发调试,使用kafka部署一套kafka环境,进行功能调试,方便快捷 一、部署zookeeper 1、下载镜像 docker pull wurstmeister/zookeeper 2、运行zookeeper镜像 docker run -d --restartalways --log-driver json-file --log-op…

(转载)从0开始学matlab(第10天)—自顶向下的编程思想

在前面的内容中,我们开发了几个完全运转的 MATLAB 程序。但是这些程序都十分简单,包括一系列的 MATLAB 语句,这些语句按照固定的顺序一个接一个的执行。像这样的程序我们称之顺序结构程序。它首先读取输入,然后运算得到所需结果&a…

QT学习记录(三)绘图

按照下面两个教程学习 QT学习教程(全面)_Strive--顾的博客-CSDN博客_qt学习 天山老妖S的博客_QT开发(3)_51CTO博客 1、绘图 VC项目右键增加QT GUI Class,在QT Designer中编辑DlgDraw.ui 在DlgDraw中重载函数 void DlgDraw::paintEvent(Q…

Flutter控件之图片Image封装

Flutter控件之基类Widget封装 Flutter控件之文本Text封装 为什么要进行繁琐的封装?直接用也挺好啊,这个回答一点毛病没有,大部分视图都可以原生绘制,可在Flutter中偏偏原生的控件,少了很多需要又常用的属性&#xff…

最小生成树—Kruskal算法和Prim算法

1.最小生成树 连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任 意一对顶点都是连通的,则称此图为连通图。 生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通…

Java基础-面向对象总结(3)

本篇文章主要讲解Java面向对象的知识点 面向对象的三大特性类的扩展(抽象类,接口,内部类,枚举) 目录 面向对象和面向过程的区别? 面向对象的五大基本原则 面向对象三大特性 继承 怎么理解继承 ? 继承和聚合的区别? 封装 多态 什么是多态 什么是运行时多…

面试阿里、字节全都一面挂,被面试官说我的水平还不如应届生

测试员可以先在大厂镀金,以后去中小厂毫无压力,基本不会被卡,事实果真如此吗?但是在我身上却是给了我很大一巴掌... 所谓大厂镀金只是不卡简历而已,如果面试答得稀烂,人家根本不会要你。况且要不是大厂出来…

MySQL数据库基础4-内置函数

文章目录 日期函数字符串函数数学函数其他函数 日期函数 函数名称描述current date()当前日期current time()当前时间current timestamp()当前时间戳date(datetime)返回datetime参数的日期部分date add(date, interval d_value type)在date中添加日期或时间,interv…

GitHub Actions Error “Waiting for a runner to pick up this job”

GitHub Actions Error “Waiting for a runner to pick up this job” 什么是GitHub Actions GitHub Actions 是一个 CI/CD(持续集成和持续部署)平台,可以让您自动化工作流程并与 GitHub 存储库中的代码集成。使用 GitHub Actions&#xff…

智能排班系统 【数据库设计】

文章目录 数据库设计规范ER图物理模型数据表登录日志表操作日志表菜单表角色表企业表门店表省市区表门店节日表消息表职位表排班规则表排班任务表排班结果存储scheduling_date排班日表scheduling_shift排班班次表shift_user班次员工中间表 定时通知表用户表中间表role_menu角色…

适合小白的网络安全书籍推荐

学习的方法有很多种,看书就是一种不错的方法,但为什么总有人说:“看书是学不会技术的”。 其实就是书籍没选对,看的书不好,你学不下去是很正常的。 一本好书其实不亚于一套好的视频教程,尤其是经典的好书…

MATLAB系列(2)——plot画图函数

一、plot plot画的是折线图。plot可以画出多种线类型的图,比如实线、虚线、星线、圆圈线等,一个图里可以画多条折线,方便对比。 1.1 设置坐标轴标签 和 图名 使用xlabel 和ylabel,title设置图名,fontsize设置名字字体大小 1.2 …

Netty核心技术三--NIO编程

1. JAVA NIO基本介绍 Java NIO 全称 java non-blocking IO,是指 JDK 提供的新API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的 NIO 相关类都被放在 java.nio 包及子包下&…

【软件测试基础】

🎉🎉🎉点进来你就是我的人了博主主页:🙈🙈🙈戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔🤺🤺🤺 目录 1、什么是软件测试(CASE) 1.1 软件测试就是验…

亲测解决:项目无法编译/打包,提示找不到符号的问题

我在对服务进行打包的过程中遇到了“Error…找不到符号”的问题,但是我的项目是能够正常启动的,为什么会出现这个问题呢? 有的博主说是因为我没有正常打包,然后我又学习了一遍如何对项目进行打包…但是然并卵(然而并没…