2、顶点着色器之视图矩阵
1、作用:将物体从世界坐标系转换到相机坐标系,相当于从世界坐标系转换到相机的局部(本地)坐标系。
2、基于LookAt函数的视图矩阵:
相机位置eye:(ex,ey,ez),世界坐标系下的位置
目标位置center:(cx,cy,cz),这是相机朝向的点,也是世界坐标系下的位置
上方向up:(ux,uy,uz),用于定义相机的上方向,一般都是世界坐标系的上方向,这样相机才是正对着物体而不是倾斜的
构建视图矩阵的步骤如下:
- 计算相机的方向向量(z轴方向,camera direction,相机坐标系的“视线方向”在世界坐标系中的表示):从相机位置到目标位置的反方向,用于定义新的Z轴
z = e y e − c e n t e r ∣ e y e − c e n t e r ∣ \mathbf{z}=\frac{\mathbf{eye}-\mathbf{center}}{|\mathbf{eye}-\mathbf{center}|} z=∣eye−center∣eye−center - 计算相机的右方向向量(x轴方向,camera right,相机坐标系的“右方向”在世界坐标系中的表示):通过向上方向和摄像机方向的叉积,计算出相机的右方向,用于定义新的x轴
x = u p × z ∣ u p × z ∣ \mathbf{x}=\frac{\mathbf{up}×\mathbf{z}}{|\mathbf{up}×\mathbf{z}|} x=∣up×z∣up×z - 计算相机的上方向向量(y轴方向,camera up,相机坐标系的“上方向”在世界坐标系中的表示):通过相机的Z轴和X轴的叉积,得到新的Y轴方向
y = z × x \mathbf{y}=\mathbf{z}×\mathbf{x} y=z×x - 构建视图矩阵:根据上面计算的向量,视图矩阵可以写成如下形式:
V i e w M a t r i x = [ x x x y x z − x ⋅ e y e y x y y y z − y ⋅ e y e z x z y z z − z ⋅ e y e 0 0 0 1 ] \mathbf{ViewMatrix}=\begin{bmatrix} x_x & x_y & x_z & −\mathbf{x⋅eye}\\ y_x & y_y & y_z & −\mathbf{y⋅eye}\\ z_x & z_y & z_z & −\mathbf{z⋅eye}\\ 0&0&0&1\end{bmatrix} ViewMatrix= xxyxzx0xyyyzy0xzyzzz0−x⋅eye−y⋅eye−z⋅eye1
3、示例代码:
// matrix.js
const regPos = /^-?\d+(\.\d+)?$/; // 支持整数和浮点数,支持负号
function isVector3D(vector) {if (!Array.isArray(vector)) return false;if (vector.length != 3) return false;return (regPos.test(vector[0]) && regPos.test(vector[1]) && regPos.test(vector[2]));
}function normalized(vector) {if (!isVector3D(vector)) return null;const vectorLength = Math.sqrt(Math.pow(vector[0], 2) + Math.pow(vector[1], 2) + Math.pow(vector[2], 2));return vector.map((item) => {return item / vectorLength;});
}function cross(v1, v2) {if (!isVector3D(v1)) return null;if (!isVector3D(v2)) return null;return [v1[1] * v2[2] - v1[2] * v2[1],v1[2] * v2[0] - v1[0] * v2[2],v1[0] * v2[1] - v1[1] * v2[0],];
}function dot(v1, v2) {if (!isVector3D(v1)) return null;if (!isVector3D(v2)) return null;return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}function lookAt(eye, target, up = [0, 1, 0]) {if (!isVector3D(eye)) return null;if (!isVector3D(target)) return null;if (!isVector3D(up)) return null;const eyeMinusTarget = eye.map((item, index) => item - target[index]);const z = normalized(eyeMinusTarget); // Z轴const x = normalized(cross(up, z)); // X轴const y = cross(z, x); // Y轴// glsl中的mat4类型是列主序的,这里也要改为列主序return new Float32Array([x[0],y[0],z[0],0,x[1],y[1],z[1],0,x[2],y[2],z[2],0,-dot(x, eye),-dot(y, eye),-dot(z, eye),1,]);
}export { isVector3D, normalized, dot, cross, lookAt };
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebGL2 视图矩阵示例</title><style>html,body {margin: 0;overflow: hidden;}canvas {position: fixed;top: 0;left: 0;outline: none;width: 100%;height: 100%;}</style>
</head><body><canvas id="webgl-canvas"></canvas><div style="display: flex;position: fixed;left: 10px;top: 10px;"><button id="front">从正面看</button><button id="back">从背面看</button></div><script type="module">import { lookAt } from './matrix.js'const canvas = document.getElementById("webgl-canvas");const gl = canvas.getContext("webgl2");if (!gl) {console.log("WebGL2 not supported, falling back on WebGL");}const vertexShaderSource = `#version 300 esin vec4 aPosition;uniform mat4 uViewMatrix;void main() {gl_Position = uViewMatrix * aPosition;}`;const fragmentShaderSource = `#version 300 esprecision highp float;out vec4 outColor;void main() {outColor = vec4(1.0, 0.0, 0.0, 1.0); // Red color}`;function createShader(gl, type, source) {const shader = gl.createShader(type);gl.shaderSource(shader, source);gl.compileShader(shader);if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {console.error("Shader compile failed:", gl.getShaderInfoLog(shader));gl.deleteShader(shader);return null;}return shader;}function createProgram(gl, vertexShader, fragmentShader) {const program = gl.createProgram();gl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);gl.linkProgram(program);if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {console.error("Program link failed:", gl.getProgramInfoLog(program));gl.deleteProgram(program);return null;}return program;}const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);const program = createProgram(gl, vertexShader, fragmentShader);const positionBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);const size = 0.5;const positions = new Float32Array([-size, -size, size,size, -size, size,-size, size, size,size, -size, size,size, size, size,-size, size, size,]);gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);const vao = gl.createVertexArray();gl.bindVertexArray(vao);const aPosition = gl.getAttribLocation(program, "aPosition");gl.enableVertexAttribArray(aPosition);gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);gl.useProgram(program);// 根据设备的像素比率调整 canvas 尺寸,否则很模糊const pixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * pixelRatio;canvas.height = canvas.clientHeight * pixelRatio;gl.viewport(0, 0, canvas.width, canvas.height);// 设置视图矩阵const uViewMatrix = gl.getUniformLocation(program, "uViewMatrix");let viewMatrix = new Float32Array([1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0,0, 0, 0, 1])gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix);document.getElementById("back").onclick = (e) => {// 相机在红色矩形的后面,由于启用了背面剔除,所以看不到viewMatrix = lookAt([0, 0, -1], [0, 0, 0], [0, 1, 0])gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix);alert("相机在(0,0,-1)处看向(0,0,0)处,相机在红色矩形的后面,由于启用了背面剔除,所以看不到")}document.getElementById("front").onclick = (e) => {// 相机在红色矩形的前面,可以看到viewMatrix = lookAt([0, 0, 1], [0, 0, 0], [0, 1, 0])gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix);alert("相机在(0,0,-1)处看向(0,0,0)处,相机在红色矩形的前面,所以可以看到")}function render() {gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.clear(gl.COLOR_BUFFER_BIT);gl.enable(gl.DEPTH_TEST); // 开启深度测试,防止面重叠gl.enable(gl.CULL_FACE); // 开启背面剔除gl.cullFace(gl.BACK); // 剔除背面gl.bindVertexArray(vao);gl.drawArrays(gl.TRIANGLES, 0, 6);requestAnimationFrame(render);}render();</script>
</body></html>


相关文章:
2、顶点着色器之视图矩阵
1、作用:将物体从世界坐标系转换到相机坐标系,相当于从世界坐标系转换到相机的局部(本地)坐标系。 2、基于LookAt函数的视图矩阵: 相机位置eye:(ex,ey,ez),世界坐标系下的位置 目标位置center:(cx,cy,cz…...
crontab实现2026年开始每个月1号执行一次
要在 crontab 中设置一个任务,使其从 2026 年开始每个月的 1 号执行一次,可以使用以下格式: 0 0 1 * * <你的命令>这条规则的解释如下: 0 0:表示在每个月的 1 号的零点(00:00)执行。1&a…...
计算机网络803-(5)运输层
目录 一.运输层的两个主要协议:TCP 与 UDP 1.TCP/IP 的运输层有两个不同的协议: 2.端口号(protocol port number) (1)软件端口与硬件端口 (2)TCP 的端口 (3)三类端口 二.用户…...
八 MyBatis中接口代理机制及使用
八、MyBatis中接口代理机制及使用 实际上,第七章所讲内容mybatis内部已经实现了。直接调用以下代码即可获取dao接口的代理类: AccountDao accountDao (AccountDao)sqlSession.getMapper(AccountDao.class);使用以上代码的前提是:AccountMa…...
【解决】Ubuntu18.04 卸载python之后桌面异常且终端无法打开,重启后进入tty1,没有图形化界面
我因为python版本太过于混乱 (都是为了学习os) ,3.6—3.9版本我都安装了,指向关系也很混乱,本着“重装是最不会乱”的原则,我把全部版本都卸载了。然后装了3.9 发现终端打不开了,火狐浏览器的图…...
OpenEmbedded、yocto和poky是什么关系?
Yocto项目是基于OpenEmbedded构建系统发展而来的。Yocto采用了OpenEmbedded的许多核心概念和工具,比如BitBake构建工具。BitBake在这两个系统中都是用于解析和处理recipes文件,这些recipes文件包含了软件包构建的指令、依赖关系、安装步骤等内容。 它们…...
记录页面——一个蛮好看的登录页(uni-app)
效果图 <template><view class"container"><view class"flex-col login-box"><view class"flex-col" style"width: 80%"><view class"flex-col"><text class"welcome-text-font&qu…...
Android文件选择器[超级轻量级FilePicker测试没有问题][挣扎解决自带文件管理器获取不到绝对地址问题而是返回msf%3A1000038197]
超级轻量级FilePicker测试没有问题 本文摘录于:https://blog.csdn.net/gitblog_00365/article/details/141449437只是做学习备份之用,绝无抄袭之意,有疑惑请联系本人! 今天真的是发了疯的找文件管理器,因为调用系统自带的文件管理…...
【论文速读】| RED QUEEN: 保护大语言模型免受隐蔽多轮越狱攻击
基本信息 原文标题:RED QUEEN: Safeguarding Large Language Models against Concealed Multi-Turn Jailbreaking 原文作者:Yifan Jiang, Kriti Aggarwal, Tanmay Laud, Kashif Munir, Jay Pujara, Subhabrata Mukherjee 作者单位:Hippocr…...
39.第二阶段x86游戏实战2-HOOK实现主线程调用
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 本人写的内容纯属胡编乱造,全都是合成造假,仅仅只是为了娱乐,请不要…...
wordpress argon主题美化方面
1、页面前端额外CSS: /*字体*/ font-face {font-family: myFont1; src:url(https://blog.yangmumu.com/css/fonts/Dancing.ttf) ;font-display: swap; } font-face {font-family: myFont2; src:url(https://blog.yangmumu.com/css/fonts/Regular.ttf) ;font-displa…...
qt QRadioButton详解
QRadioButton 是一个可以切换选中(checked)或未选中(unchecked)状态的选项按钮。单选按钮通常呈现给用户一个“多选一”的选择,即在一组单选按钮中,一次只能选中一个按钮。 重要方法 QRadioButton(QWidget…...
Qt 最小化,最大化,关闭窗口
Qt 最小化,最大化 在Qt中,你可以使用QWidget类提供的方法来实现窗口的最小化、最大化等操作。 最小化窗口 你可以使用QWidget的showMinimized()方法来最小化窗口。这将隐藏窗口并将其显示为系统托盘区域的图标。 connect(ui->btnMin,&QPushButton::click…...
【vue项目中添加告警音频提示音】
一、前提: 由于浏览器限制不能自动触发音频文件播放,所以实现此类功能时,需要添加触发事件,举例如下: 1、页面添加打开告警声音开关按钮 2、首次进入页面时添加交互弹窗提示:是否允许播放音频 以上两种方…...
百度SEO分析实用指南 提升网站搜索排名的有效策略
内容概要 在数字化时代,搜索引擎优化(SEO)已经成为提升网站曝光度的关键工具。本指南将带您了解SEO的基本知识,帮助您在复杂的网络环境中立足。我们将从关键词优化开始,重点讲解如何选择合适的关键词来提高搜索引擎排…...
高并发场景下的性能测试方法!
在现代互联网应用中,高并发场景下的性能测试显得尤为重要。无论是电商平台的秒杀活动,还是社交应用的突发流量,都需要确保系统能够在高并发情况下稳定运行。本文将详细介绍高并发场景下的性能测试方法,并提供具体的方案和实战演练…...
杂项——USB键盘与鼠标流量分析——BUUCTF——流量分析
第一次做USB键盘与鼠标流量分析的题目,现在来好好做一个总结 1. 基础知识 USB流量指的是USB设备接口的流量,攻击者能够通过监听usb接口流量获取键盘敲击键、鼠标移动与点击、存储设备的铭文传输通信、USB无线网卡网络传输内容等等。 在正式介绍 USB H…...
Java如何实现企业微信审批流程
大家好,我是 V 哥。最近的一个项目中,用到企业微信的审批流程,整理出来分享给大家。在企业微信中实现审批流程可以通过调用企业微信的开放API完成,企业微信提供了审批应用接口,用于创建审批模板、发起审批流程以及获取…...
GEE app:在地图上构建一个可以查看局部的小窗
目录 简介 函数 ee.Geometry.MultiLineString(coords, proj, geodesic, maxError) Arguments: Returns: Geometry.MultiLineString getBounds(asGeoJSON) Arguments: Returns: GeoJSONGeometry|List|String setControlVisibility(all, layerList, zoomControl, scaleC…...
leetcode71:简化路径
给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 / 开头),请你将其转化为 更加简洁的规范路径。 在 Unix 风格的文件系统中规则如下: 一个点 . 表示当前目录本身。此外,两个点 ..…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
