Three.js系列: 在元宇宙看电影,享受 VR 视觉盛宴
本文 gihtub 地址: https://github.com/hua1995116/Fly-Three.js
最近元宇宙的概念很火,并且受到疫情的影响,我们的出行总是受限,电影院也总是关门,但是在家里又没有看大片的氛围,这个时候我们就可以通过自己来造一个宇宙,并在 VR 设备(Oculus 、cardboard)中来观看。
今天我打算用 Three.js 来实现个人 VR 电影展厅,整个过程非常的简单,哪怕不会编程都可以轻易掌握。
想要顶级的视觉盛宴,最重要的肯定是得要一块大屏幕,首先我们就先来实现一块大屏幕。
大屏幕的实现主要有两种几何体,一种是 PlaneGeometry 和 BoxGeometry,一个是平面,一个是六面体。为了使得屏幕更加有立体感,我选择了 BoxGeometry。
老样子,在添加物体之前,我们先要初始化我们的相机、场景和灯光等一些基础的元件。
const scene = new THREE.Scene();
// 相机
const camera = new THREE.PerspectiveCamera(
75,
sizes.width / sizes.height,
0.1,
1000
)
camera.position.x = -5
camera.position.y = 5
camera.position.z = 5
scene.add(camera);
// 添加光照
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
directionalLight.position.set(2, 2, -1)
scene.add(directionalLight)
// 控制器
const controls = new OrbitControls(camera, canvas);
scene.add(camera);
然后来写我们的核心代码,创建一个 5 * 5 的超薄长方体
const geometry = new THREE.BoxGeometry(5, 5, 0.2);
const cubeMaterial = new THREE.MeshStandardMaterial({
color: '#ff0000'
});
const cubeMesh = new THREE.Mesh(geometry, cubeMaterial);
scene.add(cubeMesh);
效果如下:
然后紧接着加入我们的视频内容,想要把视频放入到3d场景中,需要用到两样东西,一个是 html 的 video 标签,另一个是 Three.js 中的视频纹理 VideoTexture
第一步将视频标签放入到 html 中,并设置自定播放以及不让他显示在屏幕中。
...
<canvas class="webgl"></canvas>
<video
id="video"
src="./pikachu.mp4"
playsinline
webkit-playsinline
autoplay
loop
style="display:none"
></video>
...
第二步,获取到 video 标签的内容将它传给 VideoTexture,并且纹理赋给我们的材质。
+const video = document.getElementById( 'video' );
+const texture = new THREE.VideoTexture( video );
const geometry = new THREE.BoxGeometry(5, 5, 0.2);
const cubeMaterial = new THREE.MeshStandardMaterial({
- color: '#ff0000'
+ map: texture
});
const cubeMesh = new THREE.Mesh(geometry, cubeMaterial);
scene.add(cubeMesh);
我们看到皮神明显被拉伸了,这里就出现了一个问题就是纹理的拉伸。这也很好理解,我们的屏幕是 1 : 1 的,但是我们的视频却是 16:9 的。想要解决其实也很容易,要么就是让我们的屏幕大小更改,要么就是让我们的视频纹理渲染的时候更改比例。
第一种方案很简单
通过修改几何体的形状(也及时我们显示器的比例)
const geometry = new THREE.BoxGeometry(8, 4.5, 0.2);
const cubeMaterial = new THREE.MeshStandardMaterial({
map: texture
});
const cubeMesh = new THREE.Mesh(geometry, cubeMaterial);
scene.add(cubeMesh);
第二种方案稍微有点复杂,需要知道一定的纹理贴图相关的知识
图1-1
首先我们先要知道纹理坐标是由 u 和 v 两个方向组成,并且取值都为 0 - 1。通过在 fragment shader 中,查询 uv 坐标来获取每个像素的像素值,从而渲染整个图。
因此如果纹理图是一张16:9 的,想要映射到一个长方形的面中,那么纹理图必要会被拉伸,就像我们上面的视频一样,上面的图为了表现出电视机的厚度所以没有那么明显,可以看一下的图。(第一张比较暗是因为 Three.js 默认贴图计算了光照,先忽略这一点)
我们先来捋一捋,假设我们的图片的映射是按照 图1-1,拉伸的情况下 (80,80,0) 映射的是 uv(1,1 ),但是其实我们期望的是点(80, 80 * 9/16, 0) 映射的是 uv(1,1),所以问题变成了像素点位 (80, 80 * 9/16, 0) 的uv值 如何变成 (80, 80, 0) 的uv 值,更加简单一些就是如何让 80 * 9 / 16 变成 80,答案显而易见,就是 让 80 * 9 / 16 像素点的 v 值 乘以 16 / 9,这样就能找到了 uv(1,1) 的像素值。然后我们就可以开始写 shader 了。
// 在顶点着色器传递 uv
const vshader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`
// 核心逻辑就是 vec2 uv = vUv * acept;
const fshader = `
varying vec2 vUv;
uniform sampler2D u_tex;
uniform vec2 acept;
void main()
{
vec2 uv = vUv * acept;
vec3 color = vec3(0.3);
if (uv.x>=0.0 && uv.y>=0.0 && uv.x<1.0 && uv.y<1.0) color = texture2D(u_tex, uv).rgb;
gl_FragColor = vec4(color, 1.0);
}
`
然后我们看到我们画面已经正常了,但是在整体屏幕的下方,所以还差一点点我们需要将它移动到屏幕的中央。
移动到中央的思路和上面差不多,我们只需要注重边界点,假设边界点 C 就是让 80 * ( 0.5 + 9/16 * 0.5 ) 变成 80 ,很快我们也可能得出算是 C * 16/9 - 16/9 * 0.5 + 0.5 = 80
然后来修改 shader,顶点着色器不用改,我们只需要修改片段着色器。
const fshader = `
varying vec2 vUv;
uniform sampler2D u_tex;
uniform vec2 acept;
void main()
{
vec2 uv = vec2(0.5) + vUv * acept - acept*0.5;
vec3 color = vec3(0.0);
if (uv.x>=0.0 && uv.y>=0.0 && uv.x<1.0 && uv.y<1.0) color = texture2D(u_tex, uv).rgb;
gl_FragColor = vec4(color, 1.0);
}
`
好了,到现在为止,我们的图像显示正常啦~
那么 Three.js 中的 textureVideo 到底是如何实现视频的播放的呢?
通过查看源码(https://github.com/mrdoob/three.js/blob/6e897f9a42d615403dfa812b45663149f2d2db3e/src/textures/VideoTexture.js)源码非常的少,VideoTexture 继承了 Texture ,最大的一点就是通过 requestVideoFrameCallback 这个方法,我们来看看它的定义,发现 mdn 没有相关的示例,我们来到了 w3c 规范中寻找 https://wicg.github.io/video-rvfc/
这个属性主要是获取每一帧的图形,可以通过以下的小 demo 来进行理解
<body>
<video controls></video>
<canvas width="640" height="360"></canvas>
<span id="fps_text"/>
</body>
<script>
function startDrawing() {
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var paint_count = 0;
var start_time = 0.0;
var updateCanvas = function(now) {
if(start_time == 0.0)
start_time = now;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
var elapsed = (now - start_time) / 1000.0;
var fps = (++paint_count / elapsed).toFixed(3);
document.querySelector('#fps_text').innerText = 'video fps: ' + fps;
video.requestVideoFrameCallback(updateCanvas);
}
video.requestVideoFrameCallback(updateCanvas);
video.src = "http://example.com/foo.webm"
video.play()
}
</script>
通过以上的理解,可以很容易抽象出整个过程,通过 requestVideoFrameCallback 获取视频每一帧的画面,然后用 Texture 去渲染到物体上。
然后我们来加入 VR 代码, Three.js 默认给他们提供了建立 VR 的方法。
// step1 引入 VRButton
import { VRButton } from 'three/examples/jsm/webxr/VRButton.js';
// step2 将 VRButton 创造的dom添加进body
document.body.appendChild( VRButton.createButton( renderer ) );
// step3 设置开启 xr
renderer.xr.enabled = true;
// step4 修改更新函数
renderer.setAnimationLoop( function () {
renderer.render( scene, camera );
} );
由于 iphone 太拉胯不支持 webXR ,特地借了台安卓机(安卓机需要下载 Google Play、Chrome 、Google VR),添加以上步骤后,就会如下显示:
点击 ENTER XR
按钮后,即可进入 VR 场景。
然后我们我们可以再花20块钱就可以买个谷歌眼镜 cardboard。体验地址如下:
https://fly-three-js.vercel.app/lesson03/code/index4.html
或者也可以像我一样买一个 Oculus 然后躺着看大片
系列其他文章:
Three.js系列: 在元宇宙看电影,享受 VR 视觉盛宴的更多相关文章
- Three.js实现脸书元宇宙3D动态Logo
背景 Facebook 近期将其母公司改名为 Meta,宣布正式开始进军 元宇宙 领域.本文主要讲述通过 Three.js + Blender 技术栈,实现 Meta 公司炫酷的 3D 动态 Logo ...
- 从产业链、架构和技术三个层面,看元宇宙与RPA的发展关系
你可能还不知道,元宇宙也将带动RPA高速发展 一文读懂RPA如何赋能元宇宙,虚拟空间更需要RPA无处不在 三个层面,解读元宇宙如何利好RPA行业发展 从产业链.架构和技术三个层面,看元宇宙与RPA的发 ...
- 另一个角度看元宇宙与RPA:人工世界、平行员工与RPA
另一个角度看元宇宙与RPA:人工世界.平行员工与RPA 从元宇宙到平行员工,人工世界推动的虚实分工利好RPA 机器人是铁打营盘人类是流水兵,未来元宇宙的虚实分工RPA机会巨大 文/王吉伟 元宇宙是平行 ...
- Awesome metaverse projects (元宇宙精选资源汇总)
Awesome Metaverse 关于 Metaverse 的精彩项目和信息资源列表. 由于关于 Metaverse 是什么存在许多相互竞争的想法,请随时以拉取请求.问题和评论的形式留下反馈. We ...
- GIS :元宇宙未来发展的有力技术支撑
摘要:元宇宙是描述未来互联网迭代发展的一个概念,是一个将现实世界和虚拟世界相互融合的一个可感知的持久.共享的3D虚拟空间组成的世界. 本文分享自华为云社区<[云驻共创]元宇宙漫游指南-新一代GI ...
- web3.0、比特币、区块链、元宇宙,以及那些待收割的韭菜们!
前几天看到周星驰在社交账号上招聘web3.0的人才,感觉有必要说说web3.0,当然不是基于技术层面,而是从另一个维度说说web3.0以及其它相关的概念,从而做到如何反欺诈,如何避免被资本割韭菜.想到 ...
- Node.js系列-http
前言: 最近一直忙着公司项目的事,战友们的留言也没空回复,博客也有段时间没有更新了,年底了就是一个的忙啊~~~(ps:同感的也给个赞吧) 现在前端的就是一直地更新一直有新的东西出来,什么ES2015, ...
- 元宇宙(metaverse)中文社区-工程实践
欢迎访问元宇宙中文社区,在这里大家可以提问,回答,分享,诉说,一起构建一个元宇宙社区. 2021年"元宇宙"的这个词的火热程度在业内绝对不亚于疫情,趁着这个热度,本文记录了如何搭建 ...
- 乘风破浪,遇见未来元宇宙(Metaverse)之进入元宇宙世界,虚拟数字人行业洞察报告
正值元宇宙热潮,虚拟数字人兴起 作为⼀个新兴领域,虚拟数字⼈已经引起市场和资本的⾼度关注,截⾄目前据不完全统计,全球范围已有500+虚拟数字人相关项目获得融资,融资总额超10亿美元,并且融资项目和总额 ...
随机推荐
- C语言超全学习路线(收藏让你少走弯路)
刚入门是否觉得C语言很难?那可能是你还没找到正确的C语言学习路线,收藏以防找不到,让你少走弯路. 基本语法 选择控制语句 if,swith 循环控制语句 while,for 控制语句相关关键字分析 变 ...
- 眼见不一定为实:调用链HBase倾斜修复
hello,大家好,我是小楼. 今天给大家分享一个关于HBase数据倾斜的排查案例,不懂调用链?不懂HBase?没关系,看完包懂~ 背景 最近HBase负责人反馈HBase存储的调用链数据偶尔出现极其 ...
- victoriaMetrics中的一些Sao操作
victoriaMetrics中的一些Sao操作 快速获取当前时间 victoriaMetrics中有一个fasttime库,用于快速获取当前的Unix时间,实现其实挺简单,就是在后台使用一个goro ...
- python操作MySQL,SQL注入的问题,SQL语句补充,视图触发器存储过程,事务,流程控制,函数
python操作MySQL 使用过程: 引用API模块 获取与数据库的连接 执行sql语句与存储过程 关闭数据库连接 由于能操作MySQL的模块是第三方模块,我们需要pip安装. pip3 insta ...
- Centons7最小化安装报错:ping: baidu.com: Name or service not know
在这之前,centos7最小化安装默认是不能联网的,首先必须切换到root用户,再解决网络问题 一. 切换到root用户 二. 解决网络问题 一.切换到root用户 Linux下 ...
- 766. Toeplitz Matrix - LeetCode
Question 766. Toeplitz Matrix Solution 题目大意: 矩阵从每条左上到右下对角线上的数都相等就返回true否则返回false 思路: 遍历每一行[i,j]与[i+1 ...
- 【系统问题】windows10打印就蓝屏-报错误代码“win32kfull.sys”
现象描述: 打印机一打印电脑就蓝屏,蓝屏错误代码为:win32kfull.sys 原因分析: 2021年3月9日-推送了KB5000802补丁更新(操作系统内部版本19041.867和19042.86 ...
- java和.net 双语言开发框架,开源的PaaS平台
当下,我国国内的PaaS平台正在蓬勃发展,各式各样的PaaS平台层出不穷,但万变不离其宗,一个优秀的PaaS平台总有自己独树一帜或与众不同的地方.那么,首先我们要了解下什么是PaaS平台?PaaS是( ...
- Spring Ioc源码分析系列--@Autowired注解的实现原理
Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...
- Similarity calculation
推荐算法入门(相似度计算方法大全) 一.协同过滤算法简介 在推荐系统的众多方法之中,基于用户的协同过滤是诞最早的,原理也比较简单.基于协同过滤的推荐算法被广泛的运用在推荐系统中,比如影视推荐.猜你喜欢 ...