webGLでyuv2rgb変換(10bit)

連続投稿です。前の記事
[webGLでto変換](http://qiita.com/tabetomo/items/d156b7c1c9c1f2e93850)
では、8bitを想定していましたが、時代は10bitです。で、10bit化したところ、ハマりましたので記事にします。


``gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_SHORT, bufY);``
とすると、
``GL ERROR :GL_INVALID_OPERATION : glTexImage2D: invalid internalformat/format/type combination GL_LUMINANCE/GL_LUMINANCE/GL_UNSIGNED_SHORT``
と怒られ、``gl.LUMINANCE16``もdame
openGLでなくwebGLだからか、
http://stackoverflow.com/questions/31808920/displaying-16bit-unsigned-integers-in-opengl
もダメ。

# ポイント
美しくないが以下の通り解決。

**16bitのまま動作させる方法がない**！
http://stackoverflow.com/questions/6413744/looking-to-access-16-bit-image-data-in-javascript-webgl
ので、**8bit２つ**として後から合成します。ちょうど、formatとして２つの8ビットコンポーネントからなる「**gl.LUMINANCE_ALPHA**」があるのでそれを使うことにする。

## 変更
- Uint8Arrayのバッファサイズを、以下のように２倍で確保

```
var dataY = new Uint8Array(w*h*2);
var dataU = new Uint8Array(w2*h2*2);
var dataV = new Uint8Array(w2*h2*2);
```

−　１０ビットデータとして描画（自分で入力画像を生成するなら、読み出すなら不要）

```
    val = 1023 - parseInt(i * 1023 / w);
    dataY[i*2 + w*2 * j + 0] = val & 255;
    dataY[i*2 + w*2 * j + 1] = val >> 8;
```

- **gl.LUMINANCEをgl.LUMINANCE_ALPHA**に
 
```
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, w, h, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, dataY);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, w2, h2, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, dataU);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, w2, h2, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, dataV);
```

- **上下バイト(以下では、rコンポーネントとaコンポーネント)**を組み合わせて描画

```
      yuv.x = ((texture2D(samplerY, v_texCoord).r + texture2D(samplerY, v_texCoord).a*256.0)/4.0 - (16.0 / 255.0)); 
      yuv.y = ((texture2D(samplerU, v_texCoord).r + texture2D(samplerU, v_texCoord).a*256.0)/4.0 - 0.5);
      yuv.z = ((texture2D(samplerV, v_texCoord).r + texture2D(samplerV, v_texCoord).a*256.0)/4.0 - 0.5);
```

# ソース全文（１０ビット版）
前記事にも書きましたが、ほぼ98%以下のwebGLtestさんのソースと同じです。
http://jsdo.it/webGLtest/Opoi

８ビット版は前記事参照
[webGLでto変換](http://qiita.com/tabetomo/items/d156b7c1c9c1f2e93850)

```html:index_10b.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;
// 8bit
//      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);

//10bit
      yuv.x = ((texture2D(samplerY, v_texCoord).r + texture2D(samplerY, v_texCoord).a*256.0)/4.0 - (16.0 / 255.0)); 
      yuv.y = ((texture2D(samplerU, v_texCoord).r + texture2D(samplerU, v_texCoord).a*256.0)/4.0 - 0.5);
      yuv.z = ((texture2D(samplerV, v_texCoord).r + texture2D(samplerV, v_texCoord).a*256.0)/4.0 - 0.5);
      rgb = kColorConv * yuv;
      gl_FragColor = vec4(rgb, 1.0);
    }
  </script>
</head>

<body>
  <canvas id="target" />
  <script type="text/javascript" src="./yuv2rgb_10b.js"></script>
</body>

```

```javascript:yuv2rgb_10b.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); // 8bit
var dataY = new Uint8Array(w*h*2); // 10bit
for (var i = 0; i < w; ++i) {
  for (var j = 0; j < h; ++j) {
//8bit
    //    dataY[i + w * j] = 255 - parseInt(i * 255 / w);
//10bit
    val = 1023 - parseInt(i * 1023 / w);
    dataY[i*2 + w*2 * j + 0] = val & 255;
    dataY[i*2 + w*2 * j + 1] = val >> 8;
  }
}
// cbcr要素のtexture
const w2 = w / 2;
const h2 = h / 2;
//dataU = new Uint8Array(w2*h2); // 8bit
//dataV = new Uint8Array(w2*h2); // 8bit
dataU = new Uint8Array(w2*h2*2); // 10bit
dataV = new Uint8Array(w2*h2*2); // 10bit
for (var i = 0; i < w2; ++i) {
  for (var j = 0; j < h2; ++j) {
//8bit
//    dataU[i + w2 * j] = 255 - parseInt(j * 255 / h2);
//    dataV[i + w2 * j] = parseInt(j * 255 / h2);
//10bit
    valu = 1023 - parseInt(j * 1023 / h2);
    valv = parseInt(j * 1023 / h2);
    dataU[i*2 + w2*2 * j    ] = valu & 255;
    dataU[i*2 + w2*2 * j + 1] = valu >> 8;
    dataV[i*2 + w2*2 * j    ] = valv & 255;
    dataV[i*2 + w2*2 * j + 1] = valv >> 8;
  }
}
// テクスチャの設定
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); // 8bit
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, w, h, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, dataY); // 10bit
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); // 8bit
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, w2, h2, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, dataU); // 10bit
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); // 8bit
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, w2, h2, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, dataV); // 10bit
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));
  }
}
```

# 表示例

![スクリーンショット 2017-02-25 15.35.41.png](https://qiita-image-store.s3.amazonaws.com/0/93734/b5541f3a-bd65-b18d-d614-86f2a8789e1e.png)

綺麗なのは、webGLtestさんのサンプルの画像が綺麗なだけで。。。
http://jsdo.it/webGLtest/Opoi



# 参考情報
http://jsdo.it/webGLtest/Opoi

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

第十一回 WebGLスクール「キューブ環境マッピング」
http://qiita.com/konweb/items/262b107b2ea9a6113776#_reference-c79295fe4df8c81ed4bf

wgld.org
https://wgld.org/sitemap.html