2021年12月18日

プログラミング

GLSLを使用した透過動画の実装方法(半透明要素がある場合)

目次

  1. はじめに
  2. 透過動画について
  3. パフォーマンスについて
  4. 実装について
  5. まとめ

はじめに

この記事は新しい技術にチャレンジし続けるpalanのアドベントカレンダーDay18の記事です!

昨日は「ラズパイでオリジナルPCをつくろう!〜メリーゴーランド編〜」についての記事でした。

ラズパイでオリジナルPCをつくろう!〜メリーゴーランド編〜

今回は下記リンクのデモページのように、
半透明要素がある透過動画を実装する方法について紹介していきます!
https://webvr-lab.eishis.com/namecard/transparent-video-with-glsl/1224/index.html

透過動画について

Webでは動画ファイルのアルファ値は考慮されず、
透明になる想定で制作した部分が真っ黒に表示されてしまいます。

これはブラウザの仕様なのでしょうがないんですよね…。

じゃあ、どうすれば良いのかといいますと、
動画とは別に実装が必要となります。

実装内容は下記の2種類あり、12月4日に前者を紹介したので本日は後者を紹介します!
・動画ファイル内に半透明要素がない場合
・動画ファイル内に半透明要素がある場合

半透明要素がある場合に準備するものは下記の2点です。
色情報と透過情報を分けた動画
色情報と透過情報を組み合わせる処理

色情報と透過情報を分けた動画

下記のように、上半分に色情報を下半分に透過情報を分けて配置している動画です。
・上半分: くり抜きたい要素そのまま配置
・下半分: くり抜きたい要素を白で塗りつぶして配置し、任意の透過度に設定

くり抜きたい要素にも透過度を設定すると背景の黒色も混ざった色になるので、
必ず上半分のくり抜きたい要素はそのまま配置してください!

また、「canvas自体に透過度設定すれば良いのでは?」という声もありますが、
動画内に複数要素がありそれぞれに異なる透過度を設定したい場合は、
こちら作り方が一番理想の見え方になると思います!

今回は下半分の透過は50%に設定しています!

色情報と透過情報を組み合わせる処理

先ほどの色情報と透過情報を分けた動画の情報を組み合わせて、
半透明の要素を表現する処理のことを指します。

考えうる実装方法は下記の2種類です。
A: canvas で処理
パフォーマンスは悪く、実装は比較的易しい
B: GLSL で処理
パフォーマンスが良く、実装は比較的難しい

弊社では透過動画を使ったxRの案件を数多くいただいており、
パフォーマンスを考慮することの方が多いので、
Bについてさらに詳しく解説して行きます!

パフォーマンスについて

先ほど挙げた実装方法2種類のパフォーマンスが良い悪いの違いは何か。
それは、適切な処理領域で実装できているかということです。

適切な処理領域を選択しないと処理落ちが発生し、
コマ落ちのように見えてしまうことがあります。

デバイスの処理領域は下記の2種類あり、それぞれ向き不向きがあります。
CPU: 重いタスクを順番に処理することに向いている
GPU: 軽いタスクを並列に処理することに向いている

今回のクロマキー処理(緑をプログラムで取得し透明に差し替える)は、
軽いタスクなのですがピクセル毎に発生し、
とにかく大量なので GPU で行う方が適切ということになります。

実装について

じゃあ、クロマキー処理を GPU で実装したい時は何をすれば良いのか。
そこで登場するのが WebGL と GLSL という技術です。

WebGL: JavaScript と GPU と繋ぐ役割を持つ API
GLSL: GPU での処理を記述できる言語

※もっと適切な説明はあるのですが、さらにややこしくなるので簡潔に…笑
詳しいことは doxas さんが運営する wgld.org を参照することをオススメします。

この2つの技術を使って実装をしていきます!

WebGL

難しそうなコードがいっぱい並んでいますね…。

ただ、どんなプロジェクトでも同じようなコードになるくらいのお決まりのコードなので、
覚える必要は全くありません。

export default class WebGLController {
gl: WebGLRenderingContext | null;

constructor() {
this.gl = null;
}

// MEMO: WebGLコンテキストを初期化する関数
initWebGLContext(canvas: HTMLCanvasElement) {
this.gl = canvas.getContext("webgl");
}

// MEMO: WebGLコンテキストを返す関数
getWebGLContext() {
return this.gl;
}

// MEMO: テクスチャを初期化する関数
initTexture() {
const texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
const level = 0;
const internalFormat = this.gl.RGBA;
const width = 1;
const height = 1;
const border = 0;
const srcFormat = this.gl.RGBA;
const srcType = this.gl.UNSIGNED_BYTE;
const pixel = new Uint8Array([0, 0, 0, 255]);
this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
this.gl.texImage2D(this.gl.TEXTURE_2D, level, internalFormat, width, height, border, srcFormat, srcType, pixel);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
return texture;
}

// MEMO: テクスチャを更新する関数
updateTexture(texture: WebGLTexture, video: HTMLVideoElement) {
const level = 0;
const internalFormat = this.gl.RGBA;
const srcFormat = this.gl.RGBA;
const srcType = this.gl.UNSIGNED_BYTE;
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
this.gl.texImage2D(this.gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, video);
}

// -----
// シェーダとは...?
// 3Dオブジェクトをディスプレイに描画するためのプログラム
// -----
// MEMO: シェーダを生成する関数
createShader(type: "vertex" | "fragment", source: string) {
let shader;

switch (type) {
case "vertex":
shader = this.gl.createShader(this.gl.VERTEX_SHADER);
break;

case "fragment":
shader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
break;
default:
return;
}

this.gl.shaderSource(shader, source);

this.gl.compileShader(shader);
if (this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
return shader;
} else {
alert(this.gl.getShaderInfoLog(shader));
console.log(this.gl.getShaderInfoLog(shader));
}
}

// -----
// プログラムオブジェクトとは...?
// vertexシェーダとfragmentシェーダを紐付けたり
// JavaScriptからGLSLへ変数を渡す役割をもつオブジェクトのこと
// ------
// MEMO: プログラムオブジェクトを生成しシェーダをリンクする関数
createProgram(vertexShader: string, fragmentShader: string) {
const program = this.gl.createProgram();
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);
if (this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
this.gl.useProgram(program);
return program;
} else {
return null;
}
}

// -----
// VBOとは...?
// 頂点データの格納オブジェクトのこと
// ------
// MEMO: VBOを生成する関数
createVbo(vboArray: number[]) {
const vbo = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vbo);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vboArray), this.gl.STATIC_DRAW);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
return vbo;
}

// -----
// IBOとは...?
// 頂点データの肥大化を抑えるため、
// 頂点データを使い回すインデックスデータの格納オブジェクトのこと
// ------
// MEMO: IBOを生成する関数
createIbo(iboArray: number[]) {
const ibo = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, ibo);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Int16Array(iboArray), this.gl.STATIC_DRAW);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
return ibo;
}
}

※ 下記からも閲覧可能です。
https://github.com/palan-inc/transparent-video-with-glsl/blob/main/src/ts/modules/WebGLController.ts

GLSL

ここが一番重要なコードです!

動画の上半分から色情報を取得し、
動画の下半分から透過情報を取得し、
色情報と透過情報を組み合わせて出力しています。

precision mediump float;
uniform float time;
uniform vec2  mouse;
uniform vec2  resolution;
uniform sampler2D map;

void main(){
vec2 uv = gl_FragCoord.xy/resolution.xy;
// MEMO: 動画の上半分から色情報を取得
vec2 uvTop = vec2(uv.x, uv.y / 2.0 + 0.5);
vec4 colorTop = texture2D(map, uvTop);
// MEMO: 動画の下半分から透過情報を取得
vec2 uvBottom = vec2(uv.x, uv.y / 2.0);
vec4 alphaBottom = texture2D( map, uvBottom );
// MEMO: 色情報と透過情報を組み合わせて出力
gl_FragColor = vec4( colorTop.r, colorTop.g, colorTop.b, alphaBottom.b);
}

※ 下記からも閲覧可能です。
https://github.com/palan-inc/transparent-video-with-glsl/blob/main/src/ts/script-1224.ts

以上の実装で完成です!

下記リンクのデモページで実装結果を確認することができます!
https://webvr-lab.eishis.com/namecard/transparent-video-with-glsl/1224/index.html

まとめ

本日は後者を紹介しました!
・動画ファイル内に半透明要素がない場合
・動画ファイル内に半透明要素がある場合

bageleeを運営しているpalanでは透過動画を使ったxRの案件を数多くいただいております。
ご興味のある方はぜひお問い合わせください!

参考

https://wgld.org/d/glsl/g001.html

https://wgld.org/d/webgl/w080.html

https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Animating_textures_in_WebGL

JavaScriptのお仕事に関するご相談

Bageleeの運営会社、palanではJavaScriptに関するお仕事のご相談を無料で承っております。
zoomなどのオンラインミーティング、お電話、貴社への訪問、いずれも可能です。
ぜひお気軽にご相談ください。

無料相談フォームへ

0

0

AUTHOR

sasai

ささい エンジニア

フロントエンドエンジニア WebGLとReactが強みと言えるように頑張ってます。

アプリでもっと便利に!気になる記事をチェック!

記事のお気に入り登録やランキングが表示される昨日に対応!毎日の情報収集や調べ物にもっと身近なメディアになりました。

簡単に自分で作れるWebAR

「palanAR」はオンラインで簡単に作れるWebAR作成ツールです。WebARとはアプリを使用せずに、Webサイト上でARを体験できる新しい技術です。

palanARへ
palanar

palanはWebARの開発を
行っています

弊社では企画からサービスの公開終了まで一緒に関わらせていただきます。 企画からシステム開発、3DCG、デザインまで一貫して承ります。

webar_waterpark

palanでは一緒に働く仲間を募集しています

正社員や業務委託、アルバイトやインターンなど雇用形態にこだわらず、
ベテランの方から業界未経験の方まで様々なかたのお力をお借りしたいと考えております。

話を聞いてみたい

運営メンバー

eishis

Eishi Saito 総務

SIerやスタートアップ、フリーランスを経て2016年11月にpalan(旧eishis)を設立。 マーケター・ディレクター・エンジニアなど何でも屋。 COBOLからReactまで色んなことやります。

sasakki デザイナー

アメリカの大学を卒業後、日本、シンガポールでデザイナーとして活動。

yamakawa

やまかわたかし デザイナー

フロントエンドデザイナー。デザインからHTML / CSS、JSの実装を担当しています。最近はReactやReact Nativeをよく触っています。

Sayaka Osanai デザイナー

Sketchだいすきプロダクトデザイナー。シンプルだけどちょっとかわいいデザインが得意。 好きな食べものは生ハムとお寿司とカレーです。

はらた

はらた エンジニア

サーバーサイドエンジニア Ruby on Railsを使った開発を行なっています

kobori

こぼり ともろう エンジニア

サーバーサイドエンジニア。SIerを経て2019年7月に入社。日々学習しながらRuby on Railsを使った開発を行っています。

sasai

ささい エンジニア

フロントエンドエンジニア WebGLとReactが強みと言えるように頑張ってます。

damien

Damien

WebAR/VRの企画・開発をやっています。森に住んでいます。

ゲスト bagelee

ゲスト bagelee

かっきー

かっきー

まりな

まりな

suzuki

suzuki

miyagi

ogawa

ogawa

雑食デザイナー。UI/UXデザインやコーディング、時々フロントエンドやってます。最近はARも。

いわもと

いわもと

デザイナーをしています。 好きな食べ物はラーメンです。

kobari

taishi kobari

フロントエンドの開発を主に担当してます。Blitz.js好きです。

shogokubota

kubota shogo

サーバーサイドエンジニア。Ruby on Railsを使った開発を行いつつ月500kmほど走っています!

nishi tomoya

aihara

aihara

グラフィックデザイナーから、フロントエンドエンジニアになりました。最近はWebAR/VRの開発や、Blender、Unityを触っています。モノづくりとワンコが好きです。

nagao

SIerを経てアプリのエンジニアに。xR業界に興味があり、unityを使って開発をしたりしています。

kainuma

Kainuma

サーバーサイドエンジニア Ruby on Railsを使った開発を行なっています

sugimoto

sugimoto

asama

ando

iwasawa ayane

oshimo

oshimo

異業界からやってきたデザイナー。 palanARのUIをメインに担当してます。 これからたくさん吸収していきます!

CONTACT PAGE TOP