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 风格的文件系统中规则如下: 一个点 . 表示当前目录本身。此外,两个点 ..…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
