tinyrenderer-切线空间法线贴图
法线贴图
法线贴图分两种,一种是模型空间中的,一种是切线空间中的
模型空间中的法线贴图的rgb代表着每个渲染像素法线的xyz,与顶点坐标处于一个空间,图片是五颜六色的。
切线空间中的法线贴图的rgb同样对应xyz,是切线空间里的坐标,切线空间里的z轴正向垂直与当前三角形便宜,x是当前三角形片元表面的一个切线,y是他们的叉积。
切线空间每个轴范围是-1到1.但图片本身0-255对应的是0-1.而多数法线都是都是(0,0,1)(即当前像素的法向量刚好就是当前片元的顶点法向量)转换到颜色空间就是(0.5,0.5,1)(法向量坐标可能为负,但颜色范围始终为正)。因此图片主要是蓝紫色
两个完全一样材质的物体,由于位置不同光照也会不同。如果用模型空间存储的法线贴图,是绝对法线,法向量会完全不同,无法使用同一张物体。而切线空间存储的法线贴图完全是基于物体自身的,是相对法线,多个物体可以复用(优点)。但再实际计算时,需要根据物体本身的缩放位移做相应的矩阵转换处理(缺点)
模型空间的法线贴图直接提取向量信息做为法线向量即可
Vec3f Model::getNormal(float x, float y) {TGAColor n = normalTex_.get(x * normalTex_.get_width(), y * normalTex_.get_height());Vec3f res;for (int i = 0; i < 3; i++) {res[i] = n.bgra[i] / 255.f * 2 - 1.f;}return res;
}
//....
struct normalTexShader :public IShader {mat<2, 3, float> uv;virtual Vec4f vertex(int iface, int vertIdx, Matrix mvp) {Vec3f v = model->vert(model->face(iface)[vertIdx]);uv.set_col(vertIdx, model->tverts(model->tface(iface)[vertIdx]));return mvp * embed<4>(v);}virtual bool fragment(Vec3f barycentricCoordinates, TGAColor& color) {Vec2f texcoords = uv * barycentricCoordinates;Vec3f normal = model->getNormal(texcoords.x, texcoords.y);float I = std::max(0.f, normal * light_dir);color = model->getDiffuseColor(texcoords.x, texcoords.y) * I;return false;}
};
切线空间的法线贴图使用,重点求出tbn矩阵,将切线空间的法线转化到世界空间中
tbn矩阵
tbn矩阵
tbn矩阵
当tbn矩阵的n是模型空间时,法线贴图取值经过tbn变换后是模型空间法线
当tbn矩阵的n是世界空间时,法线贴图取值经过tbn变换后是世界空间法线
对于三角形表面所有点,t切线和b切线都是相同的
顶点法线与面法线可能不同
面法线只是垂直于面的一条向量,规定了面的正反,而顶点法线才是用于光照信息的处理(建模软件中,顶点法线的最初是的默认情况也并非是面法线的平均,只有当在建模软件中对物体进行了平滑着色后,才会根据面法线平均得到顶点法线)
因此要通过每个顶点的切线找到曲面的法线
struct Shader :public IShader {mat<2, 3, float> uv;Vec3f vp[3];Matrix MVP = rasterizer->getProjection() * rasterizer->getModelView();mat<3, 3, float> n;virtual Vec4f vertex(int iface, int vertIdx) {Vec3f v = model->vert(model->face(iface)[vertIdx]);uv.set_col(vertIdx, model->tverts(model->tface(iface)[vertIdx]));vp[vertIdx] = v;n.set_col(vertIdx, model->nverts(model->nface(iface)[vertIdx]));return MVP * embed<4>(v);}virtual bool fragment(Vec3f barycentricCoordinates, TGAColor& color) {Vec3f N = (n * barycentricCoordinates).normalize();float u1 = uv[0][1] - uv[0][0];float v1 = uv[1][1] - uv[1][0];float u2 = uv[0][2] - uv[0][0];float v2 = uv[1][2] - uv[1][0];Vec3f e1 = vp[1] - vp[0];Vec3f e2 = vp[2] - vp[0];float f = 1.f / (u1 * v2 - u2 * v1);Vec3f T = (e1 * v2 - e2 * v1) * f;T = T - N * (T * N);T.normalize();Vec3f B = cross(N, T).normalize();mat<3, 3, float> TBN;TBN.set_col(0, T);TBN.set_col(1, B);TBN.set_col(2, N);Vec2f texcoords = uv * barycentricCoordinates;Vec3f normal = TBN * model->getNormal(texcoords.x, texcoords.y);float I = std::max(0.f, normal * light_dir);color = model->getDiffuseColor(texcoords.x, texcoords.y) * I;return false;}
};
有一点注意的是,很多文章里的tbn矩阵只到了这步推导公式
求出了t和b向量后,实际上还要加上N做一次正交化,否则是不保证相互垂直的。最终才是tbn矩阵
项目跟随练习代码地址
相关文章:

tinyrenderer-切线空间法线贴图
法线贴图 法线贴图分两种,一种是模型空间中的,一种是切线空间中的 模型空间中的法线贴图的rgb代表着每个渲染像素法线的xyz,与顶点坐标处于一个空间,图片是五颜六色的。 切线空间中的法线贴图的rgb同样对应xyz,是切线…...

C++的vector使用优化
我们在上一章说了如何使用这个vector动态数组,这章我们说说如何更好的使用它以及它是如何工作的。当你创建一个vector,然后使用push_back添加元素,当当前的vector的内存不够时,会从内存中的旧位置复制到内存中的新位置,…...

关于stm32的复用和重映射问题
目录 需求IO口的复用和重映射使用复用复用加重映射 总结参考资料 需求 一开始使用stm32c8t6,想实现pwm输出,但是原电路固定在芯片的引脚PB10和PB11上,查看了下引脚的功能,需要使用到复用功能。让改引脚作为定时器PWM的输出IO口。…...
遍历数组1
package demo; import java.util.ArrayList; public class Arrilist { public static void main(String[] args) { ArrayList<String>listnew ArrayList<>(); list.add("汤神"); list.add("yyx"); list.add("hong go…...
Go语言 一些问题了解
一、读取文件数据,是阻塞还是非阻塞的? 分两种情况:常规读取文件数据,和网络IO读取数据 1. 常规读取文件数据: io.Reader 和 bufio.Reader 是阻塞进行的。 bufio.Reader 提供缓冲的读取操作,意味着数据是…...

C++ Primer 第五版 第15章 面向对象程序设计
面向对象程序设计基于三个基本概念:数据抽象、继承和动态绑定。 继承和动态绑定对编写程序有两方面的影响:一是我们可以更容易地定义与其他类相似但不完全相同的新类;二是在使用这些彼此相似的类编写程序时,我们可以在一定程度上…...

finebi或者finereport发邮件
我们二次开发中,如果想利用产品自带的发邮件的功能,来发送自己的邮件内容。 首先 决策系统中邮件相关信息要配置好之后: 这里配好了发件人,以及默认发件人后, private void sendEmail(String content,String subject)…...

基于聚类和回归分析方法探究蓝莓产量影响因素与预测模型研究
🌟欢迎来到 我的博客 —— 探索技术的无限可能! 🌟博客的简介(文章目录) 目录 背景数据说明数据来源思考 正文数据预处理数据读取数据预览数据处理 相关性分析聚类分析数据处理确定聚类数建立k均值聚类模型 多元线性回…...

【数据结构】从前序与中序遍历,或中序与后序遍历序列,构造二叉树
欢迎浏览高耳机的博客 希望我们彼此都有更好的收获 感谢三连支持! 首先,根据先序遍历可以确定根节点E,再在中序遍历中通过E确定左树和右数 ; 设立inBegin和inEnd,通过这两个参数的游走,来进行子树的创建&a…...

ARM公司发展历程
Arm从1990年成立前开始,历经漫长岁月树立各项公司里程碑及产品成就,一步步成为全球最普及的运算平台。 添加图片注释,不超过 140 字(可选) Acorn 时期 1978年,Chris Curry和Hermann Hauser共同创立了Acorn…...

C# :IQueryable IEnumerable
文章目录 1. IEnumerable2. IQueryable3. LINQ to SQL4. IEnumerable & IQueryable4.1 Expression4.2 Provider 1. IEnumerable namespace System.Collections: public interface IEnumerable {public IEnumerator GetEnumerator (); }public interface IEnumerator {pubi…...
三、生成RPM包
文章目录 1、编译生成so、bin 通过此工程编译生成so\bin文件 2、将so\bin打包到rpm中 ###### 1.生成可执行文件、库文件 ######### cmake_minimum_required(VERSION 3.15)project(compute) set(target zls_bin) set(target2 libcompute.so) # 依赖的头文件 include_directori…...

单实例11.2.0.4迁移到11.2.0.4RAC_使用rman异机恢复
保命法则:先备份再操作,磁盘空间紧张无法备份就让满足,给自己留退路。 场景说明: 1.本文档的环境为同平台、不同版本(操作系统版本可以不同,数据库版本相同),源机器和目标机器部分…...
MySQL之查询性能优化(二)
查询性能优化 慢查询基础:优化数据访问 查询性能低下最基本的原因是访问的数据太多。某些查询可能不可避免地需要筛选大量数据,但这并不场景。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效的查询,我们发现通过下面两个步骤…...
The Best Toolkit 最好用的工具集
The Best Toolkit 工欲善其事,必先利其器,整理过往工作与生活中遇到的最好的工具软件 PDF合并等 PDF24 Tools PDF查看器 SumatraPDF 可以使用黑色来查看,相对不伤眼睛,也有电子书相关的阅读器 Kindle pdf裁边工具 briss 软件卸载…...
使用C#反射中的MAKEGENERICTYPE函数,来为泛型方法和泛型类指定(泛型的)类型
MakeGenericType 是一个在 C# 中用于创建开放类型的实例的方法。开放类型是一种未绑定类型参数的泛型类型。当你有一个泛型类型定义,并且想要用特定的类型实例化它时,你可以使用 MakeGenericType 方法。 public Type MakeGenericType (params Type[] ty…...

sql注入 (运用sqlmap解题)
注:level参数 使用–batch参数可指定payload测试复杂等级。共有五个级别,从1-5,默认值为1。等级越高,测试的payload越复杂,当使用默认等级注入不出来时,可以尝试使用–level来提高测试等级。 --level 参数决定了 sql…...
HTML5 Canvas 绘图教程二
在本教程中,我们将探讨 canvas 的高级用法,包括复杂的绘图 API、坐标系统和变换操作、平滑动画技术以及复杂应用和游戏开发的实践。 1. 绘图 API 高级方法 1.1 二次贝塞尔曲线 (quadraticCurveTo) 二次贝塞尔曲线需要两个点:一个控制点和一…...
Linux 命令 find 的深度解析与使用
Linux 命令 find 的深度解析与使用 在 Linux 系统中,find 命令是一个功能强大的工具,用于在文件系统中搜索文件或目录。无论是基于文件名、文件类型、文件大小、文件权限,还是基于文件的最后修改时间等,find 命令都能提供灵活的搜…...
字符串操作记录
1 拼接 Concat():拼接字符串 Let stringvalue “hello ”; Let result stringvalue.concat(“world”) Console.log(result) // “hello world” 2 删 Let stringvalue “hello world”Console.log(stringvalue.slice(3)); // ‘lo world’Console.log(stringvalue.subst…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...