【3D编程技巧】如何用四元数旋转矢量在相机空间进行光照计算

这里介绍一个小TIPS,很久没有这么有成就感了。我以前在学3D数学的时候,书上就有一句话,说你把矢量这些东西用久了,就应该形成一种“直觉”,仿佛这些东西就是你的左右手一样。而这次,我居然真的用“直觉”来解决问题了,可以说是“瞎猫碰到死耗子”式的解决问题方法:

(一)问题起因

我现在在写的是一个软件光栅化的引擎。在软件光栅化的阶段,有两个地方可以进行光照渲染,一个是在世界空间下进行,一个是在相机空间下进行。在世界空间下进行会很方便,因为光照并不涉及旋转,而只是涉及平移。但是,因为矩阵乘法可以结合,所以从物体空间变换到世界空间再变换到相机空间,这两个矩阵可以合二为一,因为我是写软件光栅化,所以节约一些算力是有必要的。

 (二)矢量旋转的问题

我有一个平行光。平行光是没有距离的,只有方向,这个方向用一个矢量(像Vector3(1,1,1)这样)。但在旋转中有两个问题:

1、矢量的旋转不像点的旋转。顶点是可以通过矩阵进行旋转的,但是矢量不行。虽然在数学上矢量和点都是等价的。但是因为旋转属于非线性变换,所以最终在数学上的结果(归一化)之后,这个矢量方向其实不是你想要的矢量。

2、从世界空间到相机空间的旋转,其实是一个旋转的逆运算。也就是说,相机的Rotation,不是将世界空间按这个Rotation旋转,而是要做逆旋转。

(三)解决的过程

当时我想了一些歪门邪道。开始的时候我尝试用两个点记录位置。比如将矢量转化为P1和P2。然后使用这P1和P2进行相机旋转,因为P1和P2就是两个点,点旋转之后,之间的位置关系是不变的。在旋转之后,再取这两个点计算矢量方向。理论上应该可行,但不知道为什么失败了。

后来我将角度反转,然后进行四元数的矢量旋转乘法,四元数的矢量乘法公式如下:

V=要变换的矢量(要进行齐次变化,W设置为0),Q=旋转的四元数,Q-1=四元数的逆

V = Q * V * (Q-1)

没到想居然成功了。这样得到的矢量居然是可以用的。光照计算没有问题。但是为什么我说瞎猫碰到死耗子?因为在我的变换矩阵中有一个BUG。相机变换矩阵是按照XYZ三个轴进行变换的。我使用的是Unity的规则,按照Z-X-Y(Roll-Pitch-Yaw)的顺序变换。我在写相机变换矩阵的时候,也是这么写的。但是我忘记了,相机变换是一个逆变换,其实应该写成Y-X-Z变换的。其实先后顺序只是规则的问题,你怎么写,实际变换上也不会出错。所以我一开始没看出这个BUG。

但是后来我在进行相机的旋转控制的时候,我发现我的旋转控制有问题:在进行了Yaw的旋转之后,再使用Pitch会沿Y轴旋转,而不是在地平线旋转。虽然在数学上这没有问题,但做为玩家控制来说有大问题。这也是为什么Unity使用Z-X-Y旋转顺序的原因。

然后查出BUG之后,才知道是我的旋转搞错了。我就把旋转改了。但这样一改,原来那个我将角度反转之后,再利用四元数旋转的方法就失灵了,得到的是一个错误的结果。

(四)解决方法

这里就是靠“直觉”解决问题了。如果将角度反转没有作用,那么,将四元数的运算反转呢?这是一个突发奇想,我在教程,书里面都没有见过这种说法,所以我说“瞎猫碰到死耗子”,当然,这应该也有其它书里面说,只是我没看到而已。但总之这个思路解决了问题。只需要将四元数旋转的运算改变,就能够得到一个能成功变换到相机空间的矢量:

使用逆运算公式:V = (Q-1) * V * Q

// 旋转矢量
Vector3 RotateDirection(Vector direction, Quaternion rotation)
{Quaternion re(rotation.GetEular());Vector3 transDirection = re.RotateMulInverse(direction);return transDirection.Normalize();
}// 求四元数的逆
Quaternion Quaternion::GetInverse(void) const {// 这是四元数的长度// 因为四元数长度始终是单位1,所以其实这一步可以省,只要你确保它没发生蠕变float len = Magnitude(); return Quaternion(-x / len, -y / len, -z / len, w / len);
}// 四元数的乘法(正常)
Vector3 Quaternion::RotateMul(const Vector3& v) const {Quaternion vq(v.x, v.y, v.z, 0.0);Quaternion r = *this * vq * this->GetInverse();return Vector3(r.x, r.y, r.z);
}// 四元数的乘法(逆向)
Vector3 Quaternion::RotateMulInverse(const Vector3& v) const {Quaternion vq(v.x, v.y, v.z, 0.0);Quaternion r = this->GetInverse() * vq * (*this);return Vector3(r.x, r.y, r.z);
}

OK。以上就是解决方案了。

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

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

相关文章

【开源库学习】libodb库学习(三)

4 查询数据库 如果我们不知道我们正在寻找的对象的标识符,我们可以使用查询在数据库中搜索符合特定条件的对象。ODB查询功能是可选的,我们需要使用--generate-query ODB编译器选项显式请求生成必要的数据库支持代码。 ODB提供了一个灵活的查询API&#x…

复现Android中GridView的bug并解决

几年前的一个bug,GridView的item高度不一致。如下图: 复现bug的代码: import android.os.Bundle; import android.widget.BaseAdapter; import android.widget.GridView; import androidx.appcompat.app.AppCompatActivity; import java.uti…

麻省理工学院 - MIT - 线性代数学习笔记

学习视频地址 文章目录 1.01方程组的几何解释2.02矩阵消元3.03乘法和逆矩阵乘法逆 4.04矩阵A的LU分解5.05转置,置换,向量空间置换转置向量空间 6.06列空间和零空间7.07求解Ax0:主变量,特解 1.01方程组的几何解释 对于二元方程组&…

Scratch自制:《袁坤》游戏攻略

大家好!我也是很久没有动静了,这次我又来了,并且还带来了一个用Scratch制作的游戏,大家还记得我很久以前用C制作的《袁坤》吗?(详见C自制游戏《袁坤》1.2版本发布!-CSDN博客)这次它又…

种类并查集

最近玩的太嗨了,都忘了自己还有三篇博客还在拖更,也是今天一更到底好吧,边更新边写题,让看官老爷有更多的样题去联系 引入—— 在学这个之前,我相信各位应该已经接触过了并查集了吧,嗯?什么&a…

Kubernetes 1.24 版弃用 Dockershim 后如何迁移到 containerd 和 CRI-O

在本系列的上一篇文章中,我们讨论了什么是 CRI 和 OCI,Docker、containerd、CRI-O 之间的区别以及它们的架构等。最近,我们得知 Docker 即将从 kubernetes 中弃用!(查看 kubernetes 官方的这篇文章)那么让我…

VSCODE 下 openocd Jlink 的配置笔记

title: VSCODE 下 openocd Jlink 的配置笔记 tags: STM32HalCubemax 文章目录 内容VSCODE 下 openocd Jlink 的配置笔记安装完成后修改jlink的配置文件然后修改你的下载器为jlink烧录你的项目绝对会出现下面的问题那么打开下载的第一个软件 (点到这个jlink右键&…

Kafka架构详解之分区Partition

目录 一、简介二、架构三、分区Partition1.分区概念2.Offsets(偏移量)和消息的顺序3.分区如何为Kafka提供扩展能力4.producer写入策略5.consumer消费机制 一、简介 Apache Kafka 是分布式发布 - 订阅消息系统,在 kafka 官网上对 kafka 的定义…

【11】微服务链路追踪SkyWalking

1、skywalking是什么 1.1 链路追踪介绍 对于一个大型的几十个、几百个微服务构成的微服务架构系统,通常会遇到下面一些问题,比如: 如何串联整个调用链路,快速定位问题?如何缕清各个微服务之间的依赖关系?…

【AI学习】LLaMA 系列模型的进化(二)

在前面LLaMA 系列模型的进化(一)中学习了LLama模型的总体进化发展,再来看看其中涉及的一些重要技术。 PreLayerNorm Layer Norm有Pre-LN和Post-LN两种。Layer Normalization(LN)在Transformer架构中的放置位置对模型…

基于PaddleOCR + NLP实现证件识别

基于PaddleOCR NLP实现证件识别 PaddleOCR识别paddleOCR安装安装 anconda虚拟环境(可参考yolov5的安装教程) paddleOCR识别PaddleNLP模型信息抽取paddle打包exe 进行ocr识别 什么是PaddleOCR? PaddleOCR 旨在打造一套丰富、领先、且实用的 OCR 工具库,助…

【HarmonyOS开发】Navigation使用

简介 Navigation是路由容器组件,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。适用于模块内和跨模块的路由切换。 在页面跳转时,应该使用页面路由router,在页面内的页面跳转时,建议使用Navigation达到更好的转场动效…

Spring Boot集成Spring Batch快速入门Demo

1.什么是Spring Batch? Spring Batch 是一个轻量级的开源框架,它提供了一种简单的方式来处理大量的数据。它基于Spring框架,提供了一套批处理框架,可以处理各种类型的批处理任务,如ETL、数据导入/导出、报表生成等。S…

Armv8/Armv9架构的学习大纲-学习方法-自学路线-付费学习路线

本文给大家列出了Arm架构的学习大纲、学习方法、自学路线、付费学习路线。有兴趣的可以关注,希望对您有帮助。 如果大家有需要的,欢迎关注我的CSDN课程:https://edu.csdn.net/lecturer/6964 ARM 64位架构介绍 ARM 64位架构介绍 ARM架构概况…

uniapp,vue3上传图片组件封装

首先创建一个 components 文件在里面进行组件的创建 下面是 vip组件的封装 也就是图片上传组件 只是我的命名是随便起的 <template><!--图片 --><view class"up-page"><!--图片--><view class"show-box" v-for"(item,ind…

蓝桥杯Python算法竞赛常用的函数库

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;Python关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ ​ 目录 math collectcions heapq functool itertools 常用的库函数 m…

web服务器1

&#xff08; 1 &#xff09;仅提供用户浏览的单向静态网页 单纯是由服务器单向提供数据给客户端&#xff0c; Server 不需要与 client 端有互动&#xff0c;所以你可以到该网站上去浏 览&#xff0c;但是无法进行数据的上传。 &#xff08; 2 &#xff09;提供用户互动接口的…

深入理解Linux网络(三):TCP对象创建

深入理解Linux网络&#xff08;三&#xff09;&#xff1a;TCP对象创建 TCP对象创建inet_createsock_init_data TCP对象创建 常见的三句TCP编程&#xff1a; int main() {int sk socket(AF_INET, SOCK_STREAM, 0);connect(sk, ...)recv(sk, ...) }简单的两三⾏代码&#xff…

十年前的老电脑能装win10吗_十年前的老电脑用U盘安装win10教程

十年前的老电脑能装win10吗&#xff1f;十年前的老电脑只要满足win10最低要求的配置都可以安装win10。安装win10方法很多&#xff0c;有一键重装方法、U盘安装、硬盘安装等方式&#xff0c;但最靠谱的方式还是U盘安装。十年前的老电脑用U盘安装win10首先要将u盘制作成u盘启动盘…

react自定义校验报错问题修复 ProFormText

1、以下是tsx组件 自定义校验告警导致表单无法提交问题修复 修改如下&#xff1a;