webGLでyuv2rgb変換
久しぶりに投稿します。さて、なぜwebGLなのかといえば、javascriptで1080pの画像を変換したら遅過ぎたからです。見てみるとyuv2rgb変換が足を引っ張っていました。実際、1枚の変換に200ms(うろ覚え)とかかかるのは意味わかりません。
検索すると、webGLtestさんの「webGLでgpuにyuv -> rgb変換させる。」というwebGLサンプルを見つけました。問題なく動きかなりの高速化!。めでたしめでたし。
しかし、初めてのwebGLのため、ソースの意味が分かりません。。yuv.yz = (texture2D(samplerUV, varyUV).ra - vec2(0.5, 0.5));
の.raって何?、構造体を定義した覚えはないよ。。とスッキリしない。以下のページで理解しました。
分かったところで、少々、ソースを自分用に変更。元サンプルでは、UVがインタリーブされていましたが、インタリーブさせるコードもオーバーヘッドになっていたので、非インタリーブ画像を表示するようにしました。また、マトリックス演算も不要なようなので省略。
備考:yuv2rgbの変換式が正しいかどうか?、は、確認していません(間違った情報を広めていたらすみません)。。だいたい合っている、では正確な色とはならないので本来は注意が必要。
追記
webGLでyuv2rgb変換(10bit)という続きの記事を書きました。10bitでやりたい方はご参考にしてください。
ソース
99%上記webGLtestさんのソースと同じなので、掲載するか迷いましたが、 YUV独立の方が嬉しい人もいるだろう、ということで書きます。長いですが、全文です。420を想定しています。
<head>
<title> YUV2RGB sample </title>
<script type="x-shader/x-vertex" id="vs">
uniform vec2 u_resolution;
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main(void) {
v_texCoord = a_texCoord;
vec2 pos = a_position / u_resolution;
gl_Position = vec4(pos, 0, 1);
}
</script>
<script type="x-shader/x-fragment" id="fs">
precision mediump float;
uniform sampler2D samplerY;
uniform sampler2D samplerU;
uniform sampler2D samplerV;
const mat3 kColorConv = mat3( 1.164, 1.164, 1.164, 0.0, -0.213, 2.112, 1.793, -0.533, 0.0 );
varying vec2 v_texCoord;
void main(void) {
mediump vec3 yuv;
lowp vec3 rgb;
yuv.x = (texture2D(samplerY, v_texCoord).x - (16.0 / 255.0));
yuv.y = (texture2D(samplerU, v_texCoord).x - 0.5);
yuv.z = (texture2D(samplerV, v_texCoord).x - 0.5);
rgb = kColorConv * yuv;
gl_FragColor = vec4(rgb, 1.0);
}
</script>
</head>
<body>
<canvas id="target" />
<script type="text/javascript" src="./yuv2rgb.js"></script>
</body>
// canvasエレメントを取得
let c = document.getElementById('target');
c.width = 320;
c.height = 240;
// webglコンテキストを取得
let gl = c.getContext('webgl') || c.getContext('experimental-webgl');
// canvasを初期化する色を設定する
gl.clearColor(0.0, 1.0, 1.0, 1.0);
// canvasを初期化
gl.clear(gl.COLOR_BUFFER_BIT);
// 頂点シェーダとフラグメントシェーダの生成
const v_shader = create_shader('vs');
const f_shader = create_shader('fs');
// プログラムオブジェクトの生成とリンク
let prg = create_program(v_shader, f_shader);
gl.viewport(0, 0, c.width, c.height);
let resolutionUniformLocation = gl.getUniformLocation(prg, "u_resolution");
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
// y要素のtexture
const w = 320, h = 240;
var dataY = new Uint8Array(w*h);
for (var i = 0; i < w; ++i) {
for (var j = 0; j < h; ++j) {
dataY[i + w * j] = 255 - parseInt(i * 255 / w);
}
}
// cbcr要素のtexture
const w2 = w / 2;
const h2 = h / 2;
dataU = new Uint8Array(w2*h2);
dataV = new Uint8Array(w2*h2);
for (var i = 0; i < w2; ++i) {
for (var j = 0; j < h2; ++j) {
dataU[i + w2 * j] = 255 - parseInt(j * 255 / h2);
dataV[i + w2 * j] = parseInt(j * 255 / h2);
}
}
// テクスチャの設定
gl.activeTexture(gl.TEXTURE0);
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataY);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.uniform1i(gl.getUniformLocation(prg, 'samplerY'), 0);
gl.activeTexture(gl.TEXTURE1);
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w2, h2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataU);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.uniform1i(gl.getUniformLocation(prg, 'samplerU'), 1);
gl.activeTexture(gl.TEXTURE2);
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w2, h2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataV);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.uniform1i(gl.getUniformLocation(prg, 'samplerV'), 2);
// 頂点情報の登録
let attLocation = gl.getAttribLocation(prg, 'a_position');
gl.enableVertexAttribArray(attLocation);
const vertex_position = new Float32Array([
-w, -h,
w, -h,
w, h,
-w, h,
]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, vertex_position, gl.STATIC_DRAW);
gl.vertexAttribPointer(attLocation, 2, gl.FLOAT, false, 0, 0);
// テクスチャ一頂点情報の登録
let uvLocation = gl.getAttribLocation(prg, 'a_texCoord');
gl.enableVertexAttribArray(uvLocation);
const uv_position = new Float32Array([
0.0, 1.0,
1.0, 1.0,
1.0, 0.0,
0.0, 0.0
]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, uv_position, gl.STATIC_DRAW);
gl.vertexAttribPointer(uvLocation, 2, gl.FLOAT, false, 0, 0);
// 描画
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
// 表示更新
gl.flush();
// シェーダを生成する関数
function create_shader(id) {
// シェーダを格納する変数
let shader;
// HTMLからscriptタグへの参照を取得
let scriptElement = document.getElementById(id);
// scriptタグが存在しない場合は抜ける
if (!scriptElement) { return; }
// scriptタグのtype属性をチェック
switch (scriptElement.type) {
// 頂点シェーダの場合
case 'x-shader/x-vertex':
shader = gl.createShader(gl.VERTEX_SHADER);
break;
// フラグメントシェーダの場合
case 'x-shader/x-fragment':
shader = gl.createShader(gl.FRAGMENT_SHADER);
break;
default:
return;
}
// 生成されたシェーダにソースを割り当てる
gl.shaderSource(shader, scriptElement.text);
// シェーダをコンパイルする
gl.compileShader(shader);
// シェーダが正しくコンパイルされたかチェック
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
// 成功していたらシェーダを返して終了
return shader;
} else {
// 失敗していたらエラーログをアラートする
alert(gl.getShaderInfoLog(shader));
}
}
// プログラムオブジェクトを生成しシェーダをリンクする関数
function create_program(vs, fs) {
// プログラムオブジェクトの生成
var program = gl.createProgram();
// プログラムオブジェクトにシェーダを割り当てる
gl.attachShader(program, vs);
gl.attachShader(program, fs);
// シェーダをリンク
gl.linkProgram(program);
// シェーダのリンクが正しく行なわれたかチェック
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
// 成功していたらプログラムオブジェクトを有効にする
gl.useProgram(program);
// プログラムオブジェクトを返して終了
return program;
} else {
// 失敗していたらエラーログをアラートする
alert(gl.getProgramInfoLog(program));
}
}
webGLの参考情報
繰り返しになりますが、上記ではwebGLtestさんのソースコードを利用させていただきました。
http://jsdo.it/webGLtest/Opoi
また、次のページ「WebGL Fundamentals」は最初にwebGLを勉強する人にオススメです。
https://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html
特にFundamentals、How It Works、Shaders and GLSL、Image Processingは、同じことを少しづつ表現を変えながら説明してくれるので、1回目で?のところも分かるようになりました。
日本語の解説もありますね。
第十一回 WebGLスクール「キューブ環境マッピング」
http://qiita.com/konweb/items/262b107b2ea9a6113776#_reference-c79295fe4df8c81ed4bf
wgld.org
https://wgld.org/sitemap.html