【webgl】模拟摄像机
摄像机
建议在了解摄像机之前,如果你对3d图形中的坐标系统不是很了解的话,可以先去阅读一下坐标系统讲解的文章,在回到这里继续阅读。
摄像机示意图(来源LearnOpengl_cn官网)
一.介绍
对马岛之魂 镜井仁
多人在线的游戏世界中,同时在线的玩家一般有很多个。虽然他们游戏中的角色同处于一个游戏世界,但是每个玩家所见的场景是不一样的(屏幕上看到的场景),即使玩家站在游戏世界中同一个坐标,所见的场景一般也会不同,因为他们视线的方向不一样,看到的景色当然也不一样。即使角色坐标和视线方向完全一样,他们所见的场景也可能因为玩家的游戏设置导致看到的区域范围也不一样。
玩家在游戏世界中移动的的速度也不一样,玩家A可能在慢走状态,玩家B可能在奔跑,玩家C也许在骑马,玩家D可能正在睡觉。其实这些玩家在游戏中的角色就是本篇文章要讨论的摄像机。
通过以上描述,总结下摄像机(游戏角色)的几个功能:
- 移动坐标:前、后、左、右不同方向移动,一般移动都是通过键盘控制的,键位无所谓,你可以用w、a、s、d,也可以用↑、↓、←、→。
- 改变拍摄(视线)方向:一般游戏世界中改变拍摄方向都是通过鼠标的移动控制的,鼠标上移表示抬头的动作,下移表示低头,左移表示向左看,右移表示向右看。
- 缩放(附加功能):用过一些建模软件(CAD、Blender等)的朋友肯定知道软件有缩放的功能,通过鼠标的滚轮实现的。
在移动坐标的功能里1我们可以设置一个变量控制移动速度,在实现改变拍摄方向和缩放功能时也可以添加一个鼠标灵敏度的变量控制场景变化幅度。
二.类实现
在3D图形学中的矩阵变换(二)文章中简单的介绍了摄像机的概念,并讲述了基变换和lookAt矩阵。
摄像机的观察矩阵就是lookAt矩阵, 对矩阵证明感兴趣的可以去看一看我写的三篇有关矩阵证明的文章。
在3d图形中的坐标系统文章中,我介绍了gl-matrix和glm, 这两个都是三维数学库,gl-matrix是js写的,glm是c写的,有了这两个库,我们就不需要自己手动去实现lookAt矩阵了。
gl-matrix和glm都提供了lookAt方法。函数的参数也完全相同,以gl-matrix为例,函数原型如下:
function lookAt(out: mat4, eye: ReadonlyVec3, center: ReadonlyVec3, up: ReadonlyVec3): mat4;
param | description |
---|---|
mat4 | 存储结果矩阵。 |
eye | 摄像机在世界坐标系中的坐标。 |
center | 拍摄的视线中心。 |
up | 世界坐标的上方向,一般为[0, 1, 0]。 |
例如摄像机的坐标为(0, 0, 10),看向(0, 0, 0)点,那么可以如下创建lookAt矩阵:
var result = mat4.create();
mat4.lookAt(result, [0, 0, 10], [0, 0, 0], [0, 1, 0]);
所以,定义一个camera类,我们可以定义几个如下变量:
class Camera {public worldUp: vec3; // 世界方向的上方向向量public position: vec3; // 摄像机位置public front: vec3; // 摄像机的拍摄方向向量
}
获得lookAt矩阵,可以按照如下方式:
class Camera {...public up: vec3; // 摄像机坐标系的上轴public getViewMatrix() {const center = vec3.add(vec3.create(), this.position, this.front);return mat4.lookAt(mat4.create(), this.position, center, this.up); // 使用gl-matrix的lookAt方法获得lookAt矩阵}
}
值得注意的是上面代码中的lookAt方法的第三个参数,我用的是up
变量,表示的是摄像机的Y轴正向,而不是世界坐标系中的Y轴正向,是因为一般在飞行游戏中,需要模拟飞机绕自身的纵向的旋转,所以这里我使用的是摄像机坐标系统的上轴而不是世界坐标的上轴,当然你用worldUp传入lookAt矩阵的第三个参数也是满足接下来要实现的功能的。
我们需要让用户传入position
、worldUp
和front
参数创建一个摄像机类, 并增加一个updateCameraVectors
方法,用于更新向量。
class Camera {public worldUp: vec3; // 世界方向的上方向向量public position: vec3; // 摄像机位置public front: vec3 = [0, 0, -1]; // 摄像机的拍摄方向向量public up: vec3 = [0, 1, 0]; // 摄像机坐标系的上轴,默认指向世界坐标系y轴正向public right: vec3 = [1, 0, 0]; // 增加摄像机右轴,默认方向为世界坐标系x轴正向public constructor(position: vec3 = [0, 0, 0], worldUp: vec3 = [0, 1, 0]) {this.position = position;this.worldUp = worldUp;this.updateCameraVectors();}private updateCameraVectors() {vec3.cross(this.right, this.front, this.worldUp); // 通过front和worldUp的向量叉积更新rightvec3.cross(this.up, this.right, this.front); // 通过right和front的叉积更新up向量}...
}
初始的相机类如上,接下来我们实现移动功能。
移动
处理移动我们可以添加一个processKeyboard
方法,如下:
/**移动方向 **/
enum Camera_Movement {FORWARD,BACKWARD,LEFT,RIGHT
}class Camera {...public movementSpeed: number = SPEED; // 移动速度public processKeyboard(direction: Camera_Movement, deltaTime: number) {const velocity = this.movementSpeed * deltaTime;switch (direction) {case Camera_Movement.FORWARD:var movement = vec3.scale(vec3.create(), this.front, velocity);vec3.add(this.position, this.position, movement);break;case Camera_Movement.BACKWARD:var movement = vec3.scale(vec3.create(), this.front, velocity);vec3.sub(this.position, this.position, movement);break;case Camera_Movement.LEFT:var movement = vec3.scale(vec3.create(), this.right, velocity);vec3.sub(this.position, this.position, movement);break;case Camera_Movement.RIGHT:var movement = vec3.scale(vec3.create(), this.right, velocity);vec3.add(this.position, this.position, movement);break;default:break;}}
}
processKeyboard
方法比较简单,就是通过传入的移动方向枚举值,改变position
变量。第二个参数deltaTime
,是为了处理不同机器(性能不一样)可能帧频率不一样造成移动速度悬殊比较大的问题,所以开放一个参数用于调整。
视线方向
俯仰角(pitch)
俯仰角就是我们抬头或者低头时,视线方向与水平方向的夹角。修改Camera的视线方向,就是修改Camera类的front变量,由上图可得front变量更新方法如下:
const x = Math.cos(glMatrix.toRadian(pitch)); // 注意我们需要将pitch转化为弧度
const y = Math.sin(glMatrix.toRadian(pitch));
const z = Math.cos(glMatrix.toRadian(pitch));
vec3.normalize(this.front, [x, y, z]);
偏航角
偏航角是当我们水平转动摄像机方向时(模拟人向左向右移动视线),摄像机front向量与x正向的夹角。由上图可得x、y、z分量计算如下:
const x = Math.cos(glMatrix.toRadian(yaw));
const z = Math.sin(glMatrix.toRadian(yaw));
// y不受偏航角影响
结合俯仰角,front变量更新如下:
const x = Math.cos(glMatrix.toRadian(yaw)) * Math.cos(glMatrix.toRadian(pitch));
const y = Math.sin(glMatrix.toRadian(pitch));
const z = Math.sin(glMatrix.toRadian(yaw)) * Math.cos(glMatrix.toRadian(pitch));
vec3.normalize(this.front, [x, y, z]);
更新Camera
引入pitch和yaw更新Camera。
const YAW = -90;
const PITCH = 0;
...class Camera {.../**yaw angle: 偏航角 */public yaw: number;/**pitch angle: 俯仰角 */public pitch: number;public constructor(position: vec3 = [0, 0, 0], worldUp: vec3 = [0, 1, 0], yaw: number = YAW, pitch: number = PITCH) {this.position = position;this.worldUp = worldUp;this.yaw = yaw;this.pitch = pitch;this.updateCameraVectors();}private updateCameraVectors() {const x = Math.cos(glMatrix.toRadian(this.yaw)) * Math.cos(glMatrix.toRadian(this.pitch));const y = Math.sin(glMatrix.toRadian(this.pitch));const z = Math.sin(glMatrix.toRadian(this.yaw)) * Math.cos(glMatrix.toRadian(this.pitch));vec3.normalize(this.front, [x, y, z]);vec3.cross(this.right, this.front, this.worldUp);vec3.cross(this.up, this.right, this.front);}
}
监听鼠标移动
接下来,通过鼠标移动来控制摄像机拍摄方向。增加processMouseMovement
方法:
...
const SENSITIVITY = 0.1;class Camera {...public mouseSensitivity: number = SENSITIVITY; // 鼠标灵敏度public processMouseMovement(xoffset: number, yoffset: number, constrainPitch: boolean = true) {xoffset *= this.mouseSensitivity;yoffset *= this.mouseSensitivity;this.yaw += xoffset;this.pitch -= yoffset;if (constrainPitch) {if (this.pitch > 89) this.pitch = 89;if (this.pitch < -89) this.pitch = -89;}this.updateCameraVectors();}
}
第二个参数constrainPitch
,是用于限制俯仰角的,一般玩家在游戏世界中,只允许抬头低头在负90至正90度范围。
至此,我们已经完成了主要的移动和更改视线方向的功能。
缩放(附加功能)
缩放功能,可以通过几种方式实现,第一种可以改变相机距离物体的距离,第二种通过视野改变也就是透视矩阵的fov,即mat4.perspective
的第二个参数:
function perspective(out: mat4, fovy: number, aspect: number, near: number, far: number): mat4;
当视野从小变大,视野中的物体由于相对关系会变得越来越小。这两种方式,第一种显然不是我们想要的。所以我们使用第二种方式。
关于透视矩阵,可以看之前的文章:3D图形学中的矩阵变换。
使用第二种方式,我们在Camera类中增加一个zoom变量。
const ZOOM = 45;
class Camera {public zoom: number = ZOOM;// 处理鼠标滚轮,控制zoom增减public processMouseScroll(yoffset: number) {this.zoom += yoffset;if (this.zoom < 1) this.zoom = 1;if (this.zoom > 60) this.zoom = 60;this.updateCameraVectors();}
}
在使用时,只需要将zoom变量传入perspective函数,创建透视矩阵即可:
const projection = mat4.perspective(mat4.create(), glMatrix.toRadian(camera.zoom), gl.canvas.width / gl.canvas.height, 0.1, 100);
至此所有功能均已完成。
可以在这里获得完整代码:camera.ts
下面是我写的一个简单的使用demo。
代码:cameraDemo。
(完)
【webgl】模拟摄像机相关推荐
- TVM将深度学习模型编译为WebGL
TVM将深度学习模型编译为WebGL TVM带有全新的OpenGL / WebGL后端! OpenGL / WebGL后端 TVM已经瞄准了涵盖各种平台的大量后端:CPU,GPU,移动设备等.这次,添 ...
- unity项目build成webgl时选择生成目录(解决方法)
在unity里点击File>>Build Settings...>>勾选你要生成的Scenes>>选择webgl>>后面Development Buil ...
- webGL的一些咨询--web3D
来自: http://www.bumao.com/index.php/2010/06/webgl-overview.html 什么是webGL WebGL是一种3D绘图标准,这种绘图技术标准允许把Ja ...
- webgl 游戏_30个令人惊叹的WebGL示例和演示
WebGl仍在增长,尽管大多数现代浏览器都支持它,但它也可能需要在旧的浏览器上工作.在本文中,我遇到了很多WebGL的示例和演示,它们可以增进您对这项新技术的理解. 因此,请坐下来放松身心,使用最新的 ...
- WebGL 3D 工业隧道监控实战
2019独角兽企业重金招聘Python工程师标准>>> 前言 监控隧道内的车道堵塞情况.隧道内的车祸现场,在隧道中显示当前车祸位置并在隧道口给与提示等等功能都是非常有必要的.这个隧道 ...
- Kataspace:用HTML5和WebGL创建基于浏览器的虚拟世界
源自斯坦福的创业公司Katalabs发布了一个用于创建基于浏览器的虚拟世界的开源框架. 名叫KataSpace的软件,利用了新兴的HTML5技术,以及WebGL和WebSockets,允许用户无需安装 ...
- WebGL光栅化流水线
三种图元 Point Line Triangle WebGL绘制流程 准备数据阶段:提供顶点坐标.索引(三角形绘制顺序).uv(决定贴图坐标).法线(决定光照效果),以及各种矩阵(比如投影矩阵) 生成 ...
- 基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)
今天没有延续上一篇讲的内容,穿插一段小插曲,WebSocket 实时数据通讯同步的问题,今天我们并不是很纯粹地讲 WebSocket 相关知识,我们通过 WebGL 3D 拓扑图来呈现一个有趣的 De ...
- webgl值得重视的基础构建
此篇文章的主要目的是巩固自己对于创建webgl的时候一些知识点,主要参考了<webgl编程指南>以及_Hahn_的webgl环境搭建这篇文章,在此附上链接,方便大家查看( juejin.i ...
最新文章
- 【一周入门MySQL—1】数据库概述、数据定义、数据操作
- 老鸟运维该何去何从?
- 月入10万和月入5千的人关键区别是什么???
- Java并发编程实战~协程
- java网站短信接口_网云JAVA短信接口API
- 为什么出现股市二八现象?
- iview在项目中遇到的坑
- 送书,手快有!!Android音视频开发、Android进阶解密
- Linux bash介绍
- 计算机基础命令系统,Win7定时开关命令_计算机基础知识_IT /计算机_信息
- 2-2.基金的投资交易与结算
- pip 使用豆瓣镜像
- 百兆宽带插网线只有1mb/s的下载速度问题!
- 惠普bios硬重置_惠普笔记本电脑BIOS恢复【五个免费修复步骤】
- NS2:添加一个新的流量发生器(poisson分布)
- 数据仓库、数据湖、数据集市、和数据中台的故事
- 苹果宣布 2022 年 Apple 设计大奖得主
- kali没有wlan0解决方案
- python——字符串练习:句子反转
- 文章本天成|跟我一起来一场简洁易懂的servlet的过滤器Filter的学习吧