【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?

news2024/11/24 20:45:15

说明

【跟月影学可视化】学习笔记。

三维仿射变换:平移

对于平移变换来说,如果向量 P( x 0 ​ x_0​ x0, y 0 y_0 y0​, z 0 ​ z_0​ z0) 沿着向量 Q( x 1 x_1 x1​, y 1 ​ y_1​ y1, z 1 ​ z_1​ z1) 平移,只需要让 P 加上 Q,就能得到变换后的坐标。

在这里插入图片描述

三维仿射变换:缩放

让三维向量乘上标量,就相当于乘上要缩放的倍数。

在这里插入图片描述

可以使用齐次矩阵来表示三维仿射变换,通过引入一个新的维度,就可以把仿射变换转换为齐次矩阵的线性变换。

在这里插入图片描述

三维物体的旋转变换比较复杂一点,下面先了解一下欧拉角。

什么是欧拉角?

中文维基百科:欧拉角

莱昂哈德·欧拉用欧拉角来描述刚体在三维欧几里得空间的取向。对于任何参考系,一个刚体的取向,是依照顺序,从这参考系,做三个欧拉角的旋转而设定的。所以,刚体的取向可以用三个基本旋转矩阵来决定。换句话说,任何关于刚体旋转的旋转矩阵是由三个基本旋转矩阵复合而成的。

比如飞机的姿态可以由这三个欧拉角来确定,绕 x 轴的旋转角度(翻滚机身)、绕 y 轴的旋转角度(俯仰),以及绕 z 轴的旋转角度(偏航)来表示。

在这里插入图片描述

具体的表示公式就是 Rx、Ry、Rz,这三个旋转矩阵相乘。

在这里插入图片描述

这里采用的是 y−x−z 顺规。

下面是欧拉角的顺规表示方式:

在这里插入图片描述
采用 y−x−z 顺规的欧拉角得到的旋转矩阵如下:
在这里插入图片描述

使用欧拉角来旋转几何体

让几何体绕 y 轴、x 轴、z 轴转过 α、β、γ 角。

下面是三维物体的旋转变换矩阵:

绕y轴旋转变换矩阵:
在这里插入图片描述
绕x轴旋转变换矩阵:
在这里插入图片描述
绕z轴旋转变换矩阵:

在这里插入图片描述

如何使用欧拉角来旋转几何体?

OGL 框架的几何网格(Mesh)对象直接支持欧拉角(默认欧拉角顺规是 y−x−z),用对象的 rotation 属性(它是一个三维向量)就可以设置欧拉角。

下面实现可以随意调整欧拉角的飞机模型效果:偏航(改变 alpha)、翻滚(改变 beta)和俯仰(改变 theta)

需要用到的资源

  • 飞机模型json跟图片
  • dat-gui:JavaScript Controller Library
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何使用欧拉角来旋转几何体</title>
        <style>
            canvas {
                border: 1px dashed rgb(250, 128, 114);
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { Renderer, Camera, Transform, Geometry, Texture, Program, Mesh} from './common/lib/ogl/index.mjs';
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer({
                canvas,
                width: 512,
                height: 512,
            });

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, {fov: 35});
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;
                attribute vec2 uv;

                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;

                varying vec2 vUv;

                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `;

            const fragment = `
                precision highp float;

                uniform sampler2D tMap;
                varying vec2 vUv;

                void main() {
                    gl_FragColor = texture2D(tMap, vUv);
                }
            `;

            // 加载模型
            async function loadModel(src) {
                const data = await (await fetch(src)).json();
                // 创建 Geometry 对象,并返回这个对象
                const geometry = new Geometry(gl, {
                    position: {size: 3, data: new Float32Array(data.position)},
                    uv: {size: 2, data: new Float32Array(data.uv)},
                    normal: {size: 3, data: new Float32Array(data.normal)},
                });
                return geometry;
            }

            // 加载纹理
            function loadTexture(src) {
                const texture = new Texture(gl);
                return new Promise((resolve) => {
                    const img = new Image();
                    img.onload = () => {
                        texture.image = img;
                        resolve(texture);
                    };
                    img.src = src;
                });
            }

            (async function () {
                // 加载飞机几何体模型
                const geometry = await loadModel('./assets/model/airplane.json');
                // 加载飞机的纹理图片
                const texture = await loadTexture('./assets/model/airplane.jpg');

                // 渲染部分
                const program = new Program(gl, {
                    vertex,
                    fragment,
                    uniforms: {
                        tMap: {value: texture},
                    },
                });
                const mesh = new Mesh(gl, {geometry, program});
                mesh.setParent(scene);
                renderer.render({scene, camera});

                // 添加控制
                const gui = new dat.GUI();
                const palette = {
                    alpha: 0,
                    beta: 0,
                    theta: 0,
                };

                gui.add(palette, 'alpha', -180, 180).onChange((val) => {
                    mesh.rotation.y = val / 180 * Math.PI;
                    renderer.render({scene, camera});
                });

                gui.add(palette, 'beta', -180, 180).onChange((val) => {
                    mesh.rotation.x = val / 180 * Math.PI;
                    renderer.render({scene, camera});
                });

                gui.add(palette, 'theta', -180, 180).onChange((val) => {
                    mesh.rotation.z = val / 180 * Math.PI;
                    renderer.render({scene, camera});
                });
            }());
        </script>
    </body>
</html>

效果如下:

在这里插入图片描述

如何理解万向节锁?

使用欧拉角来操作几何体的方向有个缺陷叫做万向节锁 (Gimbal Lock)

什么是 Gimbal ?

平衡环架(英语:Gimbal),是一具有枢纽的装置,作用是使得一物体能以单一轴旋转。由彼此垂直的枢纽轴所组成的一组三只平衡环架,则可使架在最内的环架的物体维持旋转轴不变,而应用在船上的陀螺仪、罗盘、饮料杯架等用途上,而不受船体因波浪上下震动、船身转向的影响。

在这里插入图片描述

什么是万向节锁 (Gimbal Lock) ?

在特定的欧拉角情况下,姿态调整的自由度丢失就是万向节锁 (Gimbal Lock) 。

我们调整 beta 的角度改成 90,不管改变 alpha 还是改变 theta,飞机都绕着 y 轴旋转,始终处于一个平面上。本来飞机姿态有 x、y、z 三个自由度,现在 y 轴被固定了,只剩下两个自由度了,这就是万向节锁。

在这里插入图片描述

要避免万向节锁的产生,可以使用比较好的一种数学模型:四元数(Quaternion)

使用四元数来旋转几何体

四元数是一种高阶复数,一个四元数可以表示为:q = w + xi + yj + zk

  • i、j、k 是三个虚数单位,w 是标量
  • 满足 i 2 i^2 i2 = j 2 j^2 j2 = k 2 k^2 k2 = ijk = −1

所谓单位四元数,就是其中的参数满足 x 2 x^2 x2+ y 2 y^2 y2+ z 2 z^2 z2+ w 2 w^2 w2=1。单位四元数对应的旋转矩阵如下:

在这里插入图片描述

四元数与轴角

所谓轴角,就是在三维空间中,给定一个由单位向量表示的轴,以及一个旋转角度 ⍺,以此来表示几何体绕该轴旋转 ⍺ 角。

在这里插入图片描述
绕单位向量 u 旋转 ⍺ 角,对应的四元数可以表示为:q = (usin(⍺/2), cos(⍺/2))

下面实现一下用四元数让飞机沿着某个轴旋转:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>用四元数让飞机沿着某个轴旋转</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { Renderer, Camera, Transform, Geometry, Texture, Orbit, Program, Mesh, Polyline } from './common/lib/ogl/index.mjs';
            import { Vec3 } from "./common/lib/math/vec3.js";
            import { Quat } from "./common/lib/math/Quat.js";
            import { Color } from "./common/lib/math/Color.js";
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer({
                canvas,
                width: 512,
                height: 512,
            });

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, {fov: 35});
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;
                attribute vec2 uv;

                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;

                varying vec2 vUv;

                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `;

            const fragment = `
                precision highp float;

                uniform sampler2D tMap;
                varying vec2 vUv;

                void main() {
                    gl_FragColor = texture2D(tMap, vUv);
                }
            `;

            // 加载模型
            async function loadModel(src) {
                const data = await (await fetch(src)).json();
                // 创建 Geometry 对象,并返回这个对象
                const geometry = new Geometry(gl, {
                    position: {size: 3, data: new Float32Array(data.position)},
                    uv: {size: 2, data: new Float32Array(data.uv)},
                    normal: {size: 3, data: new Float32Array(data.normal)},
                });
                return geometry;
            }

            // 加载纹理
            function loadTexture(src) {
                const texture = new Texture(gl);
                return new Promise((resolve) => {
                    const img = new Image();
                    img.onload = () => {
                        texture.image = img;
                        resolve(texture);
                    };
                    img.src = src;
                });
            }

            const controls = new Orbit(camera);

            (async function () {
                // 加载飞机几何体模型
                const geometry = await loadModel('./assets/model/airplane.json');
                // 加载飞机的纹理图片
                const texture = await loadTexture('./assets/model/airplane.jpg');

                // 渲染部分
                const program = new Program(gl, {
                    vertex,
                    fragment,
                    uniforms: {
                        tMap: {value: texture},
                    },
                });
                const mesh = new Mesh(gl, {geometry, program});
                mesh.setParent(scene);

                // 定义轴,通过 Polyline 对象来绘制轴。
                const points = [
                    new Vec3(0, 0, 0),
                    new Vec3(0, 10, 0),
                ];

                const axis = new Polyline(gl, {
                    points,
                    uniforms: {
                    uColor: {value: new Color("#fa8072")},
                    uThickness: {value: 3},
                    },
                });
                axis.mesh.setParent(scene);
                renderer.render({scene, camera});

                // 添加控制
                const gui = new dat.GUI();
                const palette = {
                    alpha: 0,
                    x: 0,
                    y: 1,
                    z: 0
                };

                // 更新轴
                function updateAxis() {
                    const {x, y, z} = palette;
                    const v = new Vec3(x, y, z).normalize().scale(10);
                    points[1].copy(v);
                    axis.updateGeometry();
                    renderer.render({scene, camera});
                }

                // 更新四元数
                function updateQuaternion(val) {
                    const theta = 0.5 * val / 180 * Math.PI;
                    const c = Math.cos(theta);
                    const s = Math.sin(theta);
                    const p = new Vec3().copy(points[1]).normalize();
                    const q = new Quat(p.x * s, p.y * s, p.z * s, c);
                    mesh.quaternion = q;
                    renderer.render({scene, camera});
                }
                gui.add(palette, 'x', -10, 10).onChange(updateAxis);
                gui.add(palette, 'y', -10, 10).onChange(updateAxis);
                gui.add(palette, 'z', -10, 10).onChange(updateAxis);
                gui.add(palette, 'alpha', -180, 180).onChange(updateQuaternion);
            }());
        </script>
    </body>
</html>

在这里插入图片描述

拓展阅读

  • 四元数与三维旋转
  • 三维旋转:欧拉角、四元数、旋转矩阵、轴角之间的转换

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

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

相关文章

BI系统用户访问数据分析

商业智能&#xff08;Business Intelligence&#xff0c;简称&#xff1a;BI&#xff09;&#xff0c;又称商业智慧或商务智能&#xff0c;指用现代数据仓库技术、线上分析处理技术、数据挖掘和数据展现技术进行数据分析以实现商业价值。 ——百度百科 文章目录前言一、痛点梳理…

新手入门学传奇开区技术常见问题跟解答

首先&#xff0c;欢迎各位新GM加入传奇爱好者&#xff0c;虽然是新加入&#xff0c;但是GM们对传奇的了解也大相径庭&#xff0c;有的是资深GM&#xff0c;而多数则是纯新人&#xff0c;今天又有坛友提出了疑问&#xff0c;对自己的前进方向很迷茫&#xff0c;所以写一篇新人关…

奇异值分解(SVD)和np.linalg.svd()函数用法

一、简介 奇异值分解是一种十分重要但又难以理解的矩阵处理技术&#xff0c;在机器学习中是最重要的分解没有之一的存在。那么&#xff0c;奇异值分解到底是在干什么呢&#xff1f; 矩阵 A 表示的是高维数据&#xff0c;通常情况下高维数据分布并不是雨露均沾的&#xff0c;而往…

matlab图像的增强

1.灰度变换增强 &#xff08;1&#xff09;图像直方图 &#xff08;2&#xff09;图像直方图的均衡化 2.频域滤波增强 &#xff08;1&#xff09;低通滤波器 &#xff08;2&#xff09;高通滤波器 &#xff08;3&#xff09;同态滤波器 3.彩色增强 &#xff08;1&#xff09;真…

[附源码]JAVA毕业设计高校智能排课系统(系统+LW)

[附源码]JAVA毕业设计高校智能排课系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术…

Ubuntu搭建Hadoop环境

本文是在一个全新的Ubuntu 22.04 虚拟机上安装Hadoop 3.3.4。 环境 Ubuntu 22.04JDK 11Hadoop 3.3.4 安装Hadoop 首先安装JDK&#xff1a; # apt install openjdk-11-jdk-headless安装好以后查看&#xff1a; # java -version openjdk version "11.0.17" 2022-…

基于人工神经网络的车牌识别系统的研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

用HTTP proxy module配置一个简单反向代理服务器

预备知识 反向代理 反向代理&#xff08;reverse proxy&#xff09;方式是指用代理服务器来接受Internet上的连接请求&#xff0c;然后将 请求转发给内部网络中的上游服务器&#xff0c;并将从上游服务器上得到的结果返回给Internet上请求 连接的客户端&#xff0c;此时代理服…

自定义表单、自定义流程、自定义页面、自定义报表应用开发平台

真正的大师,永远都怀着一颗学徒的心&#xff01; 一、项目简介 Java开发框架&#xff0c;自定义表单、自定义页面、自定义流程、自定义报表应用开发平台 二、实现功能 支持系统文件在线管理 支持代码在线编辑 支持URL 路由 支持黑白名单 支持定时任务 支持在线监控 支持…

Java+JSP+MySQL基于SSM的会议交接平台的设计与实现-计算机毕业设计

项目介绍 随着社会竞争压力的不断加强&#xff0c;企事业单位内部的会议都在不断的增加&#xff0c;有效的会议可以提高企事业内部的沟通&#xff0c;更好的做出符合战略目标的决策&#xff0c;但是传统的会议交接有一定的问题存在&#xff0c;首先就是必须面对面进行传达&…

突破卡脖子技术 AVS3标准在世界杯实现移动端规模化商用

11月25日,在2022卡塔尔世界杯B组第二轮比赛中,亚洲球队又一次脱颖而出,伊朗连进2球,2:0战胜威尔士,双方鏖战至最后时刻,奉上了一场精彩的比赛。 作为2022卡塔尔世界杯持权转播商,中国移动咪咕上线“睛彩视界”独家视角,通过国家自主的AVS3、Audio Vivid编解码标准为用户带来“…

基于51单片机智能IC卡燃气表控制(仿真+源程序+全套资料)

资料编号&#xff1a;199 功能介绍&#xff1a; 采用51单片机作为主控CPU&#xff0c;使用按键进行模拟冲卡&#xff08;模拟缴费冲卡&#xff09;&#xff0c;通过按键来控制当前是否使用燃气&#xff0c;并且LCD1602实时显示当前燃气可用量剩余多少&#xff0c;当燃气不足时…

ARM-A架构入门基础(三)MMU

14天学习训练营导师课程&#xff1a;周贺贺《ARMv8/ARMv9架构-快速入门》 1. MMU&#xff08;Memory Management Unit&#xff09; 内存管理单元&#xff0c;MMU的意义在于将软件程序的虚拟地址转换为真实的物理地址。 2. MMU种类 Secure EL1&0 translation regime, wh…

基于约束关系的图表设计

标准的基于 GUI 的图形设计工具仅支持有限的“对齐向导”风格的定位&#xff0c;具有基本的对象分组系统&#xff0c;并实现对齐或分布对象的原始功能。这些工具没有办法记住对象之间的约束和关系&#xff0c;也没有办法定义和重用抽象。我一直不满意现有的设计工具&#xff0c…

[附源码]Python计算机毕业设计Django二次元信息分享平台的设计及实现

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;我…

短视频不知道怎么定位?教你三个自我商业定位的方法,收藏学习

上一篇内容是我在我赢助手小禾呈序上讲了哪几类产品更适合私域变现&#xff0c;当然我相信有很多做抖音但没想好怎么变现的对吧&#xff1f; 如果说你还停留在我也不知道我可以靠什么赚钱这样的一个状态当中。那我给你三个自我商业定位的方法。 第一个方法&#xff0c;从工作上…

虹科Pico汽车示波器学院 | 第二课直播精彩回顾

直播精彩回顾&#xff08;11.19&#xff09; 第二课主题&#xff1a;《为什么使用示波器&#xff0c;可加速技师的技术水平提升&#xff1f;》 11月19日&#xff0c;虹科Pico汽车示波器学院第二课成功开课。第二课中&#xff0c;戈老师向学员们讲解了&#xff1a; 汽车示波器…

【网安神器篇】——LaZagne凭证收集工具

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

144.二叉树的前序遍历 递归 | 94.二叉树的中序遍历 递归 |145.二叉树的后序遍历 递归

144.二叉树的前序遍历 题目 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a;root …

11月动态|通过PWmat计算的离子浓度自由能相关文献发表在JCTC

11月 11月&#xff0c;龙讯旷腾完成Q-Flow和Q-Studio新版本的升级&#xff0c;完成了40余项功能的更新和上线&#xff1b;签约并行科技在高性能计算领域再下一城&#xff1b;汪林望博士受海河实验室邀请作线上主题报告&#xff1b;通过PWmat计算的离子浓度自由能相关文献发表在…