实践[threejs] 贴图和视频结合实现专场效果

574 阅读2分钟

目前尝试着做一些有意思的页面特效而不仅限于这静态页面,该特效实现了视频的播放和动画的专场。

gnpdf-k1g64.gif

该功能分为两个部分,分别是视频播放和视频专场

  1. 视频播放基于video标签实现动画的播放。
  2. 视频的转场效果是通过使用PlaneGeometry创建的平面几何体完成的动画。

该特效的处理逻辑为:

  1. 展示起始图像
  2. 播放视频
  3. 视频播放完毕后隐藏video
  4. 将几何体粒子展开
  5. 展开过程中替换下一张图片的开头图片
  6. 粒子还原
  7. 重复2-6

准备资源

image.png

图片资源分别对应视频的起始和结尾,这样我们可以在视频播放完成时将视频替换为平面几何体,然后实现过渡。

组件编写

<div className={style.box}>
  <canvas ref={canvas} style={{ width: "100%", height: "100%" }}></canvas>
  <video onClick={handlePlay} className={style.video} ref={video}></video>
</div>

视频播放地址

每个视频资源对应两张开始和结束图像。control是我们创建的threejs控制器,用于控制播放几何体和切换图像,同时在theejs控制器播放完成时设置下一个播放视频的索引。

const videos = [
    "/transparent/video/video-01.mp4", 
    "/transparent/video/video-02.mp4", 
    "/transparent/video/video-03.mp4"
];

const canvas = useRef<HTMLCanvasElement | null>(null);
const video = useRef<HTMLVideoElement | null>(null);
let control = Experience();
control.animateControl.on("end", () => {
    if (video.current && index <= 2) {
        gsap.to(video.current, {
        opacity: 1,
        duration: 0.1,
        });
        video.current.src = videos[index];
        index++;
    } else {
        // 播放完成重置 索引
        control.animateControl.shaderImgIndex = -1;
        index = 0;
    }
});

video.current.onended = () => {
    gsap.to(video.current, {
        opacity: 0,
        duration: 0.1,
    });
    control.animateControl.first();
};

video.current.onloadstart = () => {
    console.log("loaded and prestart", video.current?.src);
    if (video.current) video.current.play();
};

动画播放控制器

这里我们定义了 first动画和end动画,我们在执行动画时,将贴图传入着色器中,并通过gsap实现动画并在延时动画完成后将图像索引指向下一帧,并执行end动画,end动画组执行完毕后触发 外部end事件,触发视频播放操作。

export default class AnimateControl extends EventEmitter {
    shaderImgFirst = [
        new THREE.TextureLoader().load("/transparent/img/video-01-first.jpg"),
        new THREE.TextureLoader().load("/transparent/img/video-02-first.jpg"),
        new THREE.TextureLoader().load("/transparent/img/video-03-first.jpg")
    ];
    shaderImgEnd = [
        new THREE.TextureLoader().load("/transparent/img/video-01-end.jpg"),
        new THREE.TextureLoader().load("/transparent/img/video-02-end.jpg"),
        new THREE.TextureLoader().load("/transparent/img/video-03-end.jpg")
    ];
    shaderImgIndex = 0;
    exprience: Exprience;
    model: ShaderBox;
    isAdd = true;
    max = 15;
    min = 0;
    postProcess: PostProcessing;
    constructor() {
        super();
        this.exprience = new Exprience();
        this.postProcess = this.exprience.postProcessing;
        this.model = this.exprience.resource.shaderBox;
    }
    // 扩散动画
    first() {
        if (this.model.box.material instanceof THREE.ShaderMaterial) {
            this.model.box.material.uniforms.u_texture.value = 
                this.shaderImgEnd[this.shaderImgIndex];
            gsap.to(this.model.box.material.uniforms.u_scale, {
                value: 10,
                duration: 5,
            });
            
            gsap.to(this.postProcess.unealBlommPass, {
                threshold: 0,
                duration: 5,
                onComplete: () => {
                    this.shaderImgIndex++;
                    console.log(this.shaderImgIndex);
                    this.end();
                },
            });
        }
    }
    
    // 收敛动画
    end() {
        if (this.model.box.material instanceof THREE.ShaderMaterial) {
            this.model.box.material.uniforms.u_texture.value =
                this.shaderImgFirst[this.shaderImgIndex];
            gsap.to(this.model.box.material.uniforms.u_scale, {
                value: 0,
                duration: 5,
            });
            gsap.to(this.postProcess.unealBlommPass, {
                threshold: 1,
                duration: 5,
                onComplete: () => {
                    if (this.shaderImgIndex === 2) {
                        this.shaderImgIndex = 0;
                    }
                    this.emit("end");
                }
            })
        }
    }
    next() {}
}

平面几何体

我们创建一个平面几何体,并使用ShaderMaterial着色器矩阵来渲染几何体。

const geometry2 = new BufferGeometry();
const biLi = 480 / 820;
const geometry = new THREE.PlaneGeometry(
    11.3 * biLi,11.3,
    240 * 6,480 * 6
);
geometry2.setAttribute(
    "position",
    new THREE.BufferAttribute(
        //@ts-ignore
        geometry.attributes.position.array,
        3
    )
);
const material = new THREE.ShaderMaterial({
    uniforms: {
        u_time: { value: 0 },
        u_scale: { value: 0 },
        u_texture: {
            value: new THREE.TextureLoader().load("/transparent/img/video-01-end.jpg")
        }
    },
    fragmentShader: fs,
    vertexShader: vs,
});
console.log(material);
const mesh = new THREE.Points(geometry, material);
return mesh;

片元着色器

将贴图传入片元着色器,并通过texture2D获取rgba颜色。

// 纹理
uniform sampler2D u_texture;
void main() {
    vec3 color = texture2D(u_texture, v_uv).xyz;
    gl_FragColor = vec4(vec3(color), 1.0);
}