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 风格的文件系统中规则如下: 一个点 . 表示当前目录本身。此外,两个点 ..…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...

基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究
摘要:在消费市场竞争日益激烈的当下,传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序,探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式,分析沉浸式体验的优势与价值…...

【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...