webGLでyuv2rgb変換

# webGLでyuv2rgb変換

久しぶりに投稿します。さて、なぜwebGLなのかといえば、**javascriptで1080pの画像を変換したら遅過ぎた**からです。見てみるとyuv2rgb変換が足を引っ張っていました。実際、１枚の変換に200ms（うろ覚え）とかかかるのは意味わかりません。


検索すると、webGLtestさんの「webGLでgpuにyuv -> rgb変換させる。」というwebGLサンプルを見つけました。問題なく動きかなりの高速化！。めでたしめでたし。

http://jsdo.it/webGLtest/Opoi



しかし、初めてのwebGLのため、**ソースの意味が分かりません**。。`yuv.yz = (texture2D(samplerUV, varyUV).ra - vec2(0.5, 0.5));`の.raって何？、構造体を定義した覚えはないよ。。とスッキリしない。以下のページで理解しました。

https://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html

分かったところで、少々、**ソースを自分用に変更**。元サンプルでは、UVがインタリーブされていましたが、インタリーブさせるコードもオーバーヘッドになっていたので、**非インタリーブ**画像を表示するようにしました。また、マトリックス演算も不要なようなので省略。

備考：yuv2rgbの変換式が正しいかどうか？、は、確認していません（間違った情報を広めていたらすみません）。。だいたい合っている、では正確な色とはならないので本来は注意が必要。

追記
[webGLでyuv2rgb変換(10bit)](http://qiita.com/tabetomo/items/97392d57c4d1d412098e)という続きの記事を書きました。10bitでやりたい方はご参考にしてください。

# ソース
99%上記webGLtestさんのソースと同じなので、掲載するか迷いましたが、　YUV独立の方が嬉しい人もいるだろう、ということで書きます。長いですが、全文です。420を想定しています。

```html:index.html
<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>
```

```javascript:yuv2rgb.js
// 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