缘由

昨天晚上睡不着觉,突然从国外的视频网站VR视频里面得到灵感,花了40分钟写下了这个小小demo,和大家分享

效果图

xy-20210802-153030.gif

原理

首先我们使用一个球体

image.png

将视频的每一帧生成一张纹理单元按照球体的uv在片元着色器内进行展开

摄像机位置放在原点位置

使用rAf更新渲染器

准备物料

视频素材准备

首先我们从某个不知名的网站(国内国外都可以)科学手段下载一部全景视频。用vlc播放器打开,大概长这样:

image.png

代码实现

首先我们引入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,如有侵权,请联系删除。