2.3 비디오 렌더링

이미지를 텍스처로 로드하고 렌더링하는 방법을 배웠습니다. 마찬가지로, 비디오를 텍스처로 로드할 수 있는데, 이는 웹 기반 비디오 처리 애플리케이션을 만드는 데 특히 유용합니다.

비디오는 일련의 이미지로 볼 수 있으므로, 이미지 텍스처와 근본적으로 다르지 않습니다. 핵심 과제는 이미지를 효율적으로 업데이트하는 것입니다. 이 튜토리얼에서는 수동 프레임 업데이트와 외부 텍스처를 통한 자동 업데이트라는 두 가지 방법을 살펴보겠습니다.

수동 프레임 업데이트

플레이그라운드 실행 - 2_03_1_video_1

먼저, 비디오 파일 URL이 주어지면 문서에 숨겨진 비디오 태그를 설치하는 함수가 필요합니다. 그런 다음 비디오 재생을 모니터링합니다.

function setupVideo(url) {
    const video = document.createElement('video');

    let playing = false;
    let timeupdate = false;

    video.playsInline = true;
    video.muted = true;
    video.loop = true;

    // Waiting for these 2 events ensures
    // there is data in the video

    video.addEventListener('playing', () => {
        playing = true;
        checkReady();
    }, true);

    video.addEventListener('timeupdate', () => {
        timeupdate = true;
        checkReady();
    }, true);

    video.src = url;
    video.play();

    function checkReady() {
        if (playing && timeupdate) {
            copyVideo = true;
        }
    }

    return video;
}
• • •
videoTag = setupVideo('../data/Firefox.mp4');

(async () => {
    return new Promise(resolve => {
        let timer = setInterval(() => {
            if (copyVideo) {
                clearInterval(timer);
                resolve();
            }
        }, 300);
    })
})().then(() => {
    webgpu();
});

playingtimeupdate 이벤트를 모두 주시해야 합니다. 두 이벤트가 모두 발생해야 비디오가 시작되었다고 확신할 수 있습니다. 비디오가 시작되기를 기다리지 않으면 비디오 데이터가 없거나 비디오 크기가 할당되지 않을 수 있습니다. 따라서 진행하기 전에 비디오가 시작될 때까지 기다려야 합니다.

const textureDescriptor = {
    size: { width: videoTag.videoWidth, height: videoTag.videoHeight },
    format: 'rgba8unorm',
    usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
};
const texture = device.createTexture(textureDescriptor);

videoTag.ontimeupdate = async (event) => {
    let imageData = await createImageBitmap(videoTag);
    //console.log(imageData)
    device.queue.copyExternalImageToTexture({ source: imageData }, { texture }, textureDescriptor.size);
};

텍스처 맵의 크기는 비디오 태그의 크기와 일치합니다. 이 텍스처 맵에는 RENDER_ATTACHMENT 사용을 요청해야 한다는 점에 유의하세요. 이는 copyExternalImageToTexture 함수 호출에 필요하기 때문입니다. 마지막으로, timeupdate 이벤트에 대한 이벤트 리스너를 생성합니다. 이 이벤트 핸들러에서는 createImageBitmap 함수를 사용하여 비트맵을 생성하고, copyExternalImageToTexture를 통해 해당 콘텐츠를 텍스처 맵에 로드합니다.

나머지 렌더링 코드는 이전과 동일하므로 자세한 내용은 생략하겠습니다.

외부 텍스처 임포트하기

플레이그라운드 실행 - 2_03_2_video_2

이전 예제에서는 비디오 프레임이 업데이트될 때마다 수동으로 데이터를 복사했습니다. 다른 접근 방식은 비디오 태그에서 외부 텍스처를 임포트하는 것입니다. 이 방법은 텍스처 맵을 직접 생성하는 것을 피할 수 있지만, 임포트된 텍스처의 수명을 염두에 두어야 합니다. 비디오가 새 프레임으로 전환되거나 VideoFrame의 close() 함수가 호출되면 텍스처가 만료될 수 있습니다.

function render() {
    requestAnimationFrame(render);

    if (copyVideo) {

        const externalTexture = device.importExternalTexture({ source: videoTag });
• • •
            let uniformBindGroup = device.createBindGroup({
                layout: uniformBindGroupLayout,
                entries: [
                    {
                        binding: 0,
                        resource: {
                            buffer: translateMatrixUniformBuffer
                        }
                    },
                    {
                        binding: 1,
                        resource: {
                            buffer: projectionMatrixUniformBuffer
                        }
                    },
                    {
                        binding: 2,
                        resource: externalTexture
                    },
                    {
                        binding: 3,
                        resource:
                            sampler
                    }
                ]
            });


            commandEncoder = device.createCommandEncoder();

            passEncoder = commandEncoder.beginRenderPass(renderPassDesc);
            passEncoder.setViewport(0, 0, canvas.width, canvas.height, 0, 1);
            passEncoder.setPipeline(pipeline);
            passEncoder.setBindGroup(0, uniformBindGroup);
            passEncoder.setVertexBuffer(0, positionBuffer);
            passEncoder.setVertexBuffer(1, texCoordsBuffer);
            passEncoder.draw(4, 1);
            passEncoder.end();

            device.queue.submit([commandEncoder.finish()]);
        }
    }

    requestAnimationFrame(render);
}

텍스처 만료를 방지하려면, 동일한 렌더 함수 호출 내에서 텍스처를 즉시 임포트하고 사용하는 것이 중요합니다. 이러한 필요성 때문에 새 외부 텍스처 맵을 통합하기 위해 각 렌더링 프레임에 대해 바인드 그룹을 생성합니다.

GitHub에 의견 남기기