缘由
昨天晚上睡不着觉,突然从国外的视频网站VR视频里面得到灵感,花了40分钟写下了这个小小demo,和大家分享
效果图
原理
首先我们使用一个球体
将视频的每一帧生成一张纹理单元按照球体的uv在片元着色器内进行展开
摄像机位置放在原点位置
使用rAf
更新渲染器
准备物料
视频素材准备
首先我们从某个不知名的网站(国内国外都可以)科学手段下载一部全景视频。用vlc播放器打开,大概长这样:
代码实现
首先我们引入three和其自带的轨道控制器
1 2 3
| import * as THREE from "three/build/three.module"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
在html里面加入如下DOM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <div class="player"> <div> <button @click="$refs.video.play()">播放</button> </div> <video preload ref="video" controls loop style="width: 100%; visibility: hidden; position: absolute" :src="src" ></video>
<canvas style="width: 80%; height: 823px" width="1920" height="823" ref="canvas" ></canvas> </div>
|
初始化视频纹理
1 2 3 4 5 6
| initVideoTexture() { this.videoTexture = new THREE.VideoTexture(this.$refs.video); this.videoTexture.needsUpdate = true; this.videoTexture.updateMatrix(); }
|
这里我们使用Three.js 自带的视频纹理,当让也可以自己实现,创建一个离屏canvas,drawImage 把 video 传进去,通过 Texture 实例化也是可以的。记得要把 needUpdate 属性设为true
初始化场景
1 2 3 4
| initScene() { this.scene = new THREE.Scene(); }
|
初始化相机
注意,透视摄像机的起始点要设为(0,0,0)
1 2 3 4 5 6 7 8 9 10 11 12
| initCamera() { this.camera = new THREE.PerspectiveCamera(45, 1024 / 768, 1, 1000); this.camera.position.z = 30; this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.maxDistance = 100;
this.controls.update(); // const helper = new THREE.CameraHelper(this.camera); // this.scene.add(helper); this.scene.add(this.camera); }
|
初始化网格
注意,这里定义材质我们要利用uniforms
对象来吧我们刚刚初始化的纹理对象传进去,这里我们使用 tex_0 作为我们使用的变量名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| initMesh() { this.geometry = new THREE.SphereGeometry(100, 32, 16); this.material = new THREE.ShaderMaterial({ wireframe: false, side: THREE.DoubleSide, map: this.videoTexture, uniforms: { tex_0: new THREE.Uniform(this.videoTexture), }, vertexShader: require("@/components/v.glsl").default, fragmentShader: require("@/components/f.glsl").default, }); this.mesh = new THREE.Mesh(this.geometry, this.material); }
|
tick函数
1 2 3 4 5 6
| update() { this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.update); }
|
mounted函数
由于我使用的是vue SFC ,在mounted 钩子里面编写
1 2 3 4 5 6 7 8 9 10
| mounted() { this.initRenderer(); this.initScene(); this.initVideoTexture(); this.initMesh(); this.initCamera(); this.addMeshToScene(); this.update(); }
|
着色器实现
顶点着色器
这里我们使用three.js 自带的MVP矩阵对传入的顶点坐标进行世界转换,然后把我们的uv通过varying 关键字定义的的变量 v_uv传入到片元着色器
1 2 3 4 5 6 7 8 9
| precision highp float; varying vec2 v_uv; void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.0); v_uv = uv; }
|
片元着色器
注意,由于球体的默认法线是由内向外的,所以我们的uv纹理坐标的映射也是向外的,我们在设置双面渲染的时候,由于我们的摄像机是被球体包裹在内的,因此会发生球内纹理翻转的现象,这明显是不对的,因此我们需要把uv 坐标坐下翻转,很简单,用 1.0 - v_uv.x 即可
1 2 3 4 5 6 7 8 9
| precision highp float; varying vec2 v_uv; uniform sampler2D tex_0; void main() { vec4 texColor = texture2D(tex_0, vec2(1. - v_uv.x, v_uv.y)); gl_FragColor = texColor; }
|
这样我们的全景视频就实现了。demo我已经放上gitee了,各位大佬有什么想法或者分享也可以私信我哦~~
本文转自 https://juejin.cn/post/6991757741874216968,如有侵权,请联系删除。