WebGL チュートリアル - Web API

WebGL チュートリアル
=============

このチュートリアルでは、 `<canvas>` 要素を使用して WebGL グラフィックを描画する方法について、基本から説明します。提供されている例から、 WebGL でできることの明確な概念を知ることができ、独自のコンテンツを作成する際に役立つコードスニペットを提供します。

WebGL により、プラグインを使用することなく HTML の `<canvas>` 内で 3D グラフィックをレンダリングするための OpenGL ES 2.0 に基づく API をウェブコンテンツから使用できます。 WebGL のプログラムは JavaScript で記述する制御コードと、コンピューターの Graphics Processing Unit (GPU) で実行する特殊効果コード（シェーダーコード）で構成されます。 WebGL 要素は他の HTML 要素と混ぜられ、他のページ部品やページの背景と合成されます。

始める前に
-----

`<canvas>` 要素を使用することはそれほど難しくありませんが、 HTML や JavaScript の基礎を理解している必要があります。 `<canvas>` 要素や WebGL は一部の古いブラウザーが対応していませんが、すべての主要なブラウザーの最近のバージョンが対応しています。キャンバスにグラフィックスを描画するためには、 JavaScript のコンテキストオブジェクトを使用します。このオブジェクトは、グラフィックスをその場で生成します。

このチュートリアルの内容
------------

WebGL 入門

WebGL コンテキストのセットアップ方法です。

WebGL コンテキストへの平面コンテンツの追加

WebGL を使用して単純な平面図形を描画する方法です。

シェーダーを用いた WebGL での色の指定

シェーダーを用いた図形への色の設定方法について紹介します。

WebGL でのオブジェクトのアニメーション

簡単なアニメーションを作成するための、オブジェクトの回転と変換の方法を紹介します。

WebGL を用いた 3D オブジェクトの作成

3D オブジェクト（今回は立方体）の作成とアニメーションの方法を紹介します。

WebGL でのテクスチャの使用

オブジェクトの表面にテクスチャをマッピングする方法について紹介します。

WebGL でのライティング

WebGL コンテキストにおいて光の稿かをシミュレーションする方法です。

WebGL でのテクスチャのアニメーション

テクスチャをアニメーションさせる方法についてです。ここでは、回転する立方体の表面に Ogg ビデオをマッピングする手法を紹介します。


基本的な 2D WebGL アニメーションの例 - Web API

基本的な 2D WebGL アニメーションの例
=======================

この WebGL の例では、キャンバスを作成し、その中に WebGL を使用して回転する四角形をレンダリングします。シーンを表すために使用する座標系は、キャンバスの座標系と同じです。つまり、(0, 0) は左上隅にあり、右下隅は (600, 460) となります。

回転する正方形の例
---------

様々な手順で、回転する正方形を取得してみましょう。

### 頂点シェーダー

まず頂点シェーダーを見てみましょう。いつものように、シーンに使用している座標をクリップスペース座標に変換することです (つまり (0, 0) がコンテキストの中心にあり、コンテキストの実際のサイズに関係なく各軸が -1.0 から 1.0 に伸びるシステムです)

```html
<script id="vertex-shader" type="x-shader/x-vertex">
 attribute vec2 aVertexPosition;

 uniform vec2 uScalingFactor;
 uniform vec2 uRotationVector;

 void main() {
 vec2 rotatedPosition = vec2(
 aVertexPosition.x \* uRotationVector.y +
 aVertexPosition.y \* uRotationVector.x,
 aVertexPosition.y \* uRotationVector.y -
 aVertexPosition.x \* uRotationVector.x
 );

 gl\_Position = vec4(rotatedPosition \* uScalingFactor, 0.0, 1.0);
 }
</script>
```

メインプログラムは属性 `aVertexPosition` を共有します。これは使用している座標系の頂点の位置です。位置の両方のコンポーネントが -1.0 から 1.0 の範囲になるように、これらの値を変換する必要があります。これは、コンテキストのアスペクト比に基づいたスケーリング係数を掛けることで簡単に実行できます。この計算については、後ほど説明します。

形状も回転し、変換を適用することでそれを行うことができます。最初にそれを行います。頂点の回転位置は、JavaScript コードによって計算された均一な `uRotationVector` にある回転ベクトルを適用して計算されます。

次に、`uScalingFactor` の JavaScript コードによって提供されるスケーリングベクトルを回転位置に乗算することにより、最終位置が計算されます。2D で描画しているため、`z` と `w` の値はそれぞれ 0.0 と 1.0 に固定されています。

次に、標準 WebGL グローバル変数の `gl_Position` へ変換および回転された頂点の位置を設定します。

### フラグメントシェーダー

次はフラグメントシェーダーです。その役割はレンダリングされる形状の各ピクセルの色を返すことです。ライティングが適用されていない、テクスチャのないソリッドオブジェクトを描画しているため、これはとても簡単です。

```html
<script id="fragment-shader" type="x-shader/x-fragment">
 #ifdef GL\_ES
 precision highp float;
 #endif

 uniform vec4 uGlobalColor;

 void main() {
 gl\_FragColor = uGlobalColor;
 }
</script>
```

これは必要に応じて `float` 型の精度を指定することから始まります。次に、グローバル変数 `gl_FragColor` へ uniform 修飾子付きの `uGlobalColor` の値を設定します。これは、JavaScript コードにより正方形の描画に使用される色に設定されます。

### HTML

HTML は、WebGL コンテキストを取得する `<canvas>` のみで構成されています。

```html
<canvas id="glcanvas" width="600" height="460">
  Oh no! Your browser doesn't support canvas!
</canvas>
```

### グローバル変数と初期化

まず、グローバル変数。ここではこれらについては説明しません。代わりに、今後のコードで使用される場合について説明します。

```js
let gl = null;
let glCanvas = null;

// Aspect ratio and coordinate system
// details

let aspectRatio;
let currentRotation = [0, 1];
let currentScale = [1.0, 1.0];

// Vertex information

let vertexArray;
let vertexBuffer;
let vertexNumComponents;
let vertexCount;

// Rendering data shared with the
// scalers.

let uScalingFactor;
let uGlobalColor;
let uRotationVector;
let aVertexPosition;

// Animation timing

let previousTime = 0.0;
let degreesPerSecond = 90.0;
```

プログラムの初期化は `startup()` と呼ばれる `load` イベントハンドラーによって処理します。

```js
window.addEventListener("load", startup, false);

function startup() {
  glCanvas = document.getElementById("glcanvas");
  gl = glCanvas.getContext("webgl");

  const shaderSet = [
    {
      type: gl.VERTEX\_SHADER,
      id: "vertex-shader",
    },
    {
      type: gl.FRAGMENT\_SHADER,
      id: "fragment-shader",
    },
  ];

  shaderProgram = buildShaderProgram(shaderSet);

  aspectRatio = glCanvas.width / glCanvas.height;
  currentRotation = [0, 1];
  currentScale = [1.0, aspectRatio];

  vertexArray = new Float32Array([
    -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5,
  ]);

  vertexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY\_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY\_BUFFER, vertexArray, gl.STATIC\_DRAW);

  vertexNumComponents = 2;
  vertexCount = vertexArray.length / vertexNumComponents;

  currentAngle = 0.0;

  animateScene();
}
```

WebGL コンテキスト `gl` を取得し、シェーダープログラムを構築することから始める必要があります。ここでは、プログラムに複数のシェーダーをとても簡単に追加できるように設計されたコードを使用しています。配列 `shaderSet` にはオブジェクトのリストが含まれ、各オブジェクトはプログラムにコンパイルされる 1 つのシェーダー関数を記述しています。各関数には、タイプ (`gl.VERTEX_SHADER` または `gl.FRAGMENT_SHADER` のいずれか) と ID (シェーダーのコードを含む `<script>` 要素の ID) の両方があります。

シェーダーセットは `buildShaderProgram()` 関数に渡され、コンパイルされリンクされたシェーダープログラムを返します。次にこれがどのように機能するかを見ていきます。

シェーダープロシェグラムが構築し、幅を高さで割ってからコンテキストのアスペクト比を計算します。次に、アニメーションの現在の回転ベクトルを `[0, 1]` に設定し、スケーリングベクトルを `[1.0, aspectRatio]` に設定します。頂点シェーダーで見たスケーリングベクトルは、-1.0 から 1.0 の範囲に合うように座標をスケーリングするために使用されます。

次に頂点の配列が `Float32Array` として作成され、三角形ごとに 6 つの座標 (3 つの 2D 頂点) が描画され、合計 12 個の値が作成されます。

ご覧のとおり、各軸に -1.0 〜 1.0 の座標系を使用しています。なぜ調整する必要があるのでしょうか？これは単にコンテキストが正方形ではないためです。幅 600 ピクセル、高さ 460 のコンテキストを使用しています。これらの各ディメンションは、-1.0 〜 1.0 の範囲にマッピングされます。2 つの軸は同じ長さではないため、2 つの軸のいずれかの値を調整しないと、正方形は一方または他方に引き伸ばされます。したがって、これらの値を正規化する必要があります。

頂点配列が作成されたら、`gl.createBuffer()` を呼び出し、それらを含む新しい GL バッファーを作成します。`gl.bindBuffer()` を呼び出して標準の WebGL 配列バッファー参照をバインドし、`gl.bufferData()` を使用して頂点データをバッファーにコピーします。`gl.STATIC_DRAW` の使用法が指定されており、データは 1 回だけ設定され、変更されることはありませんが、繰り返し使用されることを WebGL に伝えます。これにより、WebGL は、その情報に基づいてパフォーマンスを向上させる可能性のある、適用可能な最適化を検討します。

WebGL に提供される頂点データを使用して、`vertexNumComponents` を各頂点のコンポーネントの数 (2D 頂点であるため 2) に設定し、`vertexCount` を頂点リストの頂点の数に設定します。

次に、まだ回転を実行していないため、現在の回転角度 (度) を 0.0 に設定し、回転速度 (画面の更新期間ごとの度、通常 60 FPS) を 6 に設定します。

最後に、`animateScene()` が呼び出されて、最初のフレームをレンダリングし、アニメーションの次のフレームのレンダリングをスケジュールします。

### シェーダープログラムのコンパイルとリンク

`buildShaderProgram()` 関数は、シェーダープログラムにコンパイルおよびリンクされるシェーダー関数のセットを記述するオブジェクトの配列を入力として受け取り、ビルドおよびリンク後にシェーダープログラムを返します。

```js
function buildShaderProgram(shaderInfo) {
  const program = gl.createProgram();

  shaderInfo.forEach((desc) => {
    const shader = compileShader(desc.id, desc.type);

    if (shader) {
      gl.attachShader(program, shader);
    }
  });

  gl.linkProgram(program);

  if (!gl.getProgramParameter(program, gl.LINK\_STATUS)) {
    console.log("Error linking shader program:");
    console.log(gl.getProgramInfoLog(program));
  }

  return program;
}
```

まず、`gl.createProgram()` は新しい空の GLSL プログラムを作成するために呼び出されます。

次に、指定されたシェーダーのリスト内の各シェーダーに対して、`compileShader()` 関数を呼び出してコンパイルし、ビルドするシェーダー関数の ID とタイプを渡します。前述のように、これらの各オブジェクトには、シェーダーコードが存在する `<script>` 要素の ID とシェーダーのタイプが含まれます。コンパイルされたシェーダーは、`gl.attachShader()` へ渡すことでシェーダープログラムにアタッチされます。

**メモ:** 実際には、ここよりさらに一歩進んで、`<script>` 要素の `type` 属性の値を見て、シェーダーのタイプを判断できます。

すべてのシェーダーがコンパイルされると、`gl.linkProgram()` を使用してプログラムがリンクされます。

プログラムのリンク中にエラーが発生した場合、エラーメッセージはコンソールに記録されます。

最後に、コンパイルされたプログラムが呼び出し元に返されます。

### 個々のシェーダーをコンパイルする

以下の `compileShader()` 関数は、単一のシェーダーをコンパイルするために `buildShaderProgram()` によって呼び出されます。

```js
function compileShader(id, type) {
  const code = document.getElementById(id).firstChild.nodeValue;
  const shader = gl.createShader(type);

  gl.shaderSource(shader, code);
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE\_STATUS)) {
    console.log(
      `Error compiling ${
 type === gl.VERTEX\_SHADER ? "vertex" : "fragment"
 } shader:`,
    );
    console.log(gl.getShaderInfoLog(shader));
  }
  return shader;
}
```

コードは指定された ID を持つ `<script>` 要素内に含まれるテキストノードの値を取得することにより、HTML ドキュメントから取得されます。次に `gl.createShader()` を使用して、指定されたタイプの新しいシェーダーが作成されます。

ソースコードは `gl.shaderSource()` を通して新しいシェーダーに送信され、そのときシェーダーは `gl.compileShader()` を使用してコンパイルされます。

コンパイルエラーはコンソールに記録されます。生成されるメッセージに正しいシェーダータイプの文字列を挿入するためのテンプレートリテラル文字列の使用に注意してください。実際のエラーの詳細は、`gl.getShaderInfoLog()`を呼び出すことによって取得されます。

最後に、コンパイルされたシェーダーが呼び出し元 (`buildShaderProgram()` 関数) へ返します。

### シーンの描画とアニメーション化

`animateScene()` 関数は各アニメーションフレームをレンダリングするために呼び出されます。

```js
function animateScene() {
  gl.viewport(0, 0, glCanvas.width, glCanvas.height);
  gl.clearColor(0.8, 0.9, 1.0, 1.0);
  gl.clear(gl.COLOR\_BUFFER\_BIT);

  const radians = (currentAngle \* Math.PI) / 180.0;
  currentRotation[0] = Math.sin(radians);
  currentRotation[1] = Math.cos(radians);

  gl.useProgram(shaderProgram);

  uScalingFactor = gl.getUniformLocation(shaderProgram, "uScalingFactor");
  uGlobalColor = gl.getUniformLocation(shaderProgram, "uGlobalColor");
  uRotationVector = gl.getUniformLocation(shaderProgram, "uRotationVector");

  gl.uniform2fv(uScalingFactor, currentScale);
  gl.uniform2fv(uRotationVector, currentRotation);
  gl.uniform4fv(uGlobalColor, [0.1, 0.7, 0.2, 1.0]);

  gl.bindBuffer(gl.ARRAY\_BUFFER, vertexBuffer);

  aVertexPosition = gl.getAttribLocation(shaderProgram, "aVertexPosition");

  gl.enableVertexAttribArray(aVertexPosition);
  gl.vertexAttribPointer(
    aVertexPosition,
    vertexNumComponents,
    gl.FLOAT,
    false,
    0,
    0,
  );

  gl.drawArrays(gl.TRIANGLES, 0, vertexCount);

  requestAnimationFrame((currentTime) => {
    const deltaAngle =
      ((currentTime - previousTime) / 1000.0) \* degreesPerSecond;

    currentAngle = (currentAngle + deltaAngle) % 360;

    previousTime = currentTime;
    animateScene();
  });
}
```

アニメーションのフレームを描画するために最初に行う必要があるのは、背景を目的の色にクリアすることです。この場合、`<canvas>` のサイズに基づいてビューポートを設定し、`clearColor()` を呼び出して使用する色を設定します。コンテンツをクリアするとき、`clear()` でバッファーをクリアします。

次に、現在の回転ベクトルは、現在の回転角度 (`currentAngle`) をラジアンに変換し、回転ベクトルの最初のコンポーネントを sin へ設定し、2 番目のコンポーネントを cos へ設定します。`currentRotation` ベクトルは、現在の角度 `currentAngle` にある単位円上の点の位置です。

`useProgram()` は、以前に確立した GLSL シェーディングプログラムをアクティブにするために呼び出されます。次に、JavaScript コードとシェーダー間 (`getUniformLocation()` (en-US) を使用) で情報を共有するために使用される各 uniform の位置を取得します。

`uScalingFactor` という名前の uniform は、以前に計算された `currentScale` 値に設定されます。覚えているかもしれませんが、これはコンテキストのアスペクト比に基づいて座標系を調整するために使用される値です。これは `uniform2fv()` (en-US) を使用して行われます (これは 2 値の浮動小数点ベクトルであるため)。

`uRotationVector` は、同じく `uniform2fv()` を使用して、現在の回転ベクトル (`currentRotation`) に設定されます。

`uGlobalColor` は `uniform4fv()` を使用して、正方形を描画するときに使用する色に設定されます。これは 4 コンポーネントの浮動小数点ベクトルです (赤、緑、青、およびアルファごとに 1 つのコンポーネント)。

これですべてが終ったので、頂点バッファーを設定して形状を描画できます。まず、`bindBuffer()` を呼び出すことにより、形状の三角形の描画に使用される頂点のバッファーを設定します。次に、`getAttribLocation()` を呼び出して、シェーダープログラムから頂点位置属性のインデックスを取得します。

頂点位置属性のインデックスが `aVertexPosition` で利用可能になったので、`enableVertexAttribArray()` を呼び出して位置属性を有効にし、シェーダープログラム (特に頂点シェーダー) で使用できるようにします。

次に、`vertexAttribPointer()` を呼び出すことにより、頂点バッファーが `aVertexPosition` 属性にバインドされます。このステップはほとんど副作用であるため、このステップは明らかではありません。ただし、結果として、`aVertexPosition` にアクセスすると、頂点バッファーからデータを取得するようになります。

シェイプの頂点バッファーと頂点を 1 つずつ頂点シェーダーに配信するために使用される `aVertexPosition` 属性との間に関連付けがあれば、`drawArrays()` を呼び出してシェイプを描画する準備が整います。

この時点で、フレームが描画されました。あとは、次の描画をスケジュールするだけです。ここでは `requestAnimationFrame()` を呼び出して、ブラウザーが画面を更新する準備ができたときにコールバック関数を実行するように要求します。

`requestAnimationFrame()` コールバックは、フレーム描画が開始された時間を指定する単一のパラメーター `currentTime` を入力として受け取ります。それと、最後のフレームが描画された保存時間、`previousTime`、および正方形が回転する 1 秒あたりの度数 (`degreesPerSecond`) を使用して、`currentAngle` の新しい値を計算します。次に、`previousTime` の値が更新され、`animateScene()` を呼び出して次のフレームを描画します (そして、次のフレームが描画されるように無限にスケジュールします )。

### 結果

これは 1 つの単純なオブジェクトを描画しているだけのとても単純な例ですが、ここで使用されている概念ははるかに複雑なアニメーションに拡張されます。

関連情報
----

* WebGL API
* WebGL チュートリアル


ウェブの行列計算 - Web API

ウェブの行列計算
========

行列は、空間内のオブジェクトの変換を表すために使用でき、画像を構築したり、ウェブ上でデータを視覚化したりするときに、多くの主要な種類の計算を実行するために使用されます。 この記事では、行列を作成する方法と、CSS 座標変換 および `matrix3d` transform 型でそれらを使用する方法について説明します。

この記事では CSS を使用して説明を簡略化しますが、行列は WebGL、WebXR（VR および AR）API、GLSL シェーダーなどのさまざまなテクノロジーで使用されるコアコンセプトです。 この記事は、MDN コンテンツキットとしても入手できます。 実際の例では、`MDN` という名前のグローバルオブジェクトで使用できるユーティリティ関数のコレクションを使用しています。

変換行列
----

行列には多くの種類がありますが、私たちが興味を持っているのは 3D 変換行列です。 これらの行列は、 4×4 のグリッドに配置された 16 個の値のセットで構成されています。 JavaScript では、行列を配列として表すのは簡単です。

まず、**単位行列** (identity matrix) について検討します。 これは特別な変換行列であり、スカラー乗算での 1 と同じように機能します。 n \* 1 = n と同様に、任意の行列に単位行列を乗算すると、元の行列と値が一致する結果の行列が得られます。

単位行列は JavaScript では次のようになります。

```js
let identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
```

単位行列の乗算とはどのようなものでしょうか？ 最も簡単な例は、単一の点に単位行列を乗算することです。 3D の点に必要なのは 3 つの値 (`x`、`y`、`z`) だけであり、変換行列は 4×4 の値の行列なので、点には 4 番目の次元を追加する必要があります。 慣例により、この次元は**視点距離** (perspective) と呼ばれ、文字 `w` で表されます。 一般的には、 `w` を 1 に設定すると、計算がうまくいきます。

w 成分を点に追加した後、行列と点がどのようにきれいに並んでいるかに注目してください。

```js
[1, 0, 0, 0,
 0, 1, 0, 0,
 0, 0, 1, 0,
 0, 0, 0, 1]

[4, 3, 2, 1] // Point at [x, y, z, w]
```

`w` 成分には、この記事の範囲外のいくつかの追加の用途があります。 WebGL のモデル、ビュー、投影に関する記事を調べて、どのように役立つかを覗いてみてください。

### 行列と点の乗算

このサンプルコードでは、行列と点を乗算する関数 `multiplyMatrixAndPoint()` を定義しています。

```js
// 点 • 行列
function multiplyMatrixAndPoint(matrix, point) {
  // 行列の各部分に、列 c、行 r の番号で単純な変数名を付けます
  let c0r0 = matrix[0],
    c1r0 = matrix[1],
    c2r0 = matrix[2],
    c3r0 = matrix[3];
  let c0r1 = matrix[4],
    c1r1 = matrix[5],
    c2r1 = matrix[6],
    c3r1 = matrix[7];
  let c0r2 = matrix[8],
    c1r2 = matrix[9],
    c2r2 = matrix[10],
    c3r2 = matrix[11];
  let c0r3 = matrix[12],
    c1r3 = matrix[13],
    c2r3 = matrix[14],
    c3r3 = matrix[15];

  // 次に、点にある単純な名前を設定します
  let x = point[0];
  let y = point[1];
  let z = point[2];
  let w = point[3];

  // 1番目の列の各部分に対して点を乗算し、次に合計します
  let resultX = x \* c0r0 + y \* c0r1 + z \* c0r2 + w \* c0r3;

  // 2番目の列の各部分に対して点を乗算し、次に合計します
  let resultY = x \* c1r0 + y \* c1r1 + z \* c1r2 + w \* c1r3;

  // 3番目の列の各部分に対して点を乗算し、次に合計します
  let resultZ = x \* c2r0 + y \* c2r1 + z \* c2r2 + w \* c2r3;

  // 4番目の列の各部分に対して点を乗算し、次に合計します
  let resultW = x \* c3r0 + y \* c3r1 + z \* c3r2 + w \* c3r3;

  return [resultX, resultY, resultZ, resultW];
}
```

上記の関数を使用して、点に行列を掛けることができます。 単位行列を使用すると、元の行列と同じ行列が返されます。 これは、単位行列を掛けた行列は常にそれ自体と等しいためです。

```js
// identityResult を [4,3,2,1] に設定します
let identityResult = multiplyMatrixAndPoint(identityMatrix, [4, 3, 2, 1]);
```

同じ点を返すことはあまり役に立ちませんが、点に対して便利な操作を実行できる他の種類の行列があります。 次のセクションでは、これらの行列のいくつかを示します。

### 2 つの行列の乗算

行列と点を乗算することに加えて、2 つの行列を乗算することもできます。 上記の関数は、このプロセスを支援するために再利用できます。

```js
// 行列B • 行列A
function multiplyMatrices(matrixA, matrixB) {
  // 2番目の行列を行にスライスします
  let row0 = [matrixB[0], matrixB[1], matrixB[2], matrixB[3]];
  let row1 = [matrixB[4], matrixB[5], matrixB[6], matrixB[7]];
  let row2 = [matrixB[8], matrixB[9], matrixB[10], matrixB[11]];
  let row3 = [matrixB[12], matrixB[13], matrixB[14], matrixB[15]];

  // 各行に行列Aを掛けます
  let result0 = multiplyMatrixAndPoint(matrixA, row0);
  let result1 = multiplyMatrixAndPoint(matrixA, row1);
  let result2 = multiplyMatrixAndPoint(matrixA, row2);
  let result3 = multiplyMatrixAndPoint(matrixA, row3);

  // 結果の行を単一の行列に戻します
  return [
    result0[0],
    result0[1],
    result0[2],
    result0[3],
    result1[0],
    result1[1],
    result1[2],
    result1[3],
    result2[0],
    result2[1],
    result2[2],
    result2[3],
    result3[0],
    result3[1],
    result3[2],
    result3[3],
  ];
}
```

この関数の動作を見てみましょう。

```js
let someMatrix = [4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 5, 0, 4, 8, 4, 1];

let identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];

// someMatrix と同等の新しい配列を返します
let someMatrixResult = multiplyMatrices(identityMatrix, someMatrix);
```

**警告:** これらの行列関数は、説明を明確にするために書かれており、速度やメモリ管理のためには書かれていません。 これらの関数は多くの新しい配列を作成しますが、これはガベージコレクションのために、リアルタイム操作に特にコストがかかる可能性があります。 実際の製品コードでは、最適化された関数を使用するのが最善です。 glMatrix は、速度とパフォーマンスに重点を置いたライブラリーの例です。 glMatrix ライブラリーの焦点は、更新ループの前に割り当てられるターゲット配列を持つことです。

平行移動行列
------

**平行移動行列** (translation matrix) は単位行列に基づいており、3D グラフィックスで使用され、3 つの方向（`x`、`y`、`z`）の 1 つまたは複数に点またはオブジェクトを移動します。 平行移動を考える最も簡単な方法は、コーヒーカップを手に取るようなものです。 コーヒーがこぼれないように、コーヒーカップは直立させ、同じ方向に向ける必要があります。 それは、テーブルから離れて空中をあちこちと移動できます。

コーヒーを口の中に注ぐには、カップを傾けたり回転させたりする必要があるため、実際には平行移動行列だけを使用してコーヒーを飲むことはできません。 これを行うために使用する行列の種類（巧妙に\*\*回転行列\*\*と呼ばれます）を後で見ていきます。

```js
let x = 50;
let y = 100;
let z = 0;

let translationMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1];
```

3 つの軸に沿った距離を平行移動行列の対応する位置に配置し、3D 空間を移動するために必要な点または行列に掛けます。

行列で DOM を操作する
-------------

行列を使い始める本当に簡単な方法は、CSS `matrix3d()` `transform` を使用することです。 まず、コンテンツを含む単純な `<div>` を設定します。 スタイルは示しませんが、幅と高さが固定され、ページの中央に配置されます。 `<div>` には transform 用の遷移セットがあるため、何が行われているかを簡単に確認できるように行列がアニメーション化されます。

```html
<div id="move-me" class="transformable">
  <h2>Move me with a matrix</h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit…</p>
</div>
```

最後に、各例で 4×4 マトリックスを生成し、`<div>` のスタイルを更新して、transform を適用し、`matrix3d` に設定します。 行列が 4 行 4 列で構成されている場合でも、行列は 16 個の値の 1 行につぶされていることに注意してください。 行列は常に JavaScript の 1 次元のリストに格納されます。

```js
// 行列の配列から matrix3d スタイルプロパティを作成します
function matrixArrayToCssMatrix(array) {
  return `matrix3d(${array.join(",")})`;
}

// DOM 要素を取得します
let moveMe = document.getElementById("move-me");

// 次のような結果を返します: "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 50, 100, 0, 1);"
let matrix3dRule = matrixArrayToCssMatrix(translationMatrix);

// transform を設定します
moveMe.style.transform = matrix3dRule;
```

JSFiddle で見る

![行列による平行移動の例](/ja/docs/Web/API/WebGL_API/Matrix_math_for_the_web/matrix-translation.jpg)

拡大縮小行列
------

**拡大縮小行列** (scale matrix) は、幅、高さ、奥行きの 3 つの次元の 1 つ以上で何かを大きくまたは小さくします。 典型的な（デカルト）座標では、これによりオブジェクトが対応する方向に伸縮します。

幅、高さ、奥行きのそれぞれに適用する変更の量は、左上隅から右下に向かって斜めに配置されます。

```js
let w = 1.5; // width (x)
let h = 0.7; // height (y)
let d = 1; // depth (z)

let scaleMatrix = [w, 0, 0, 0, 0, h, 0, 0, 0, 0, d, 0, 0, 0, 0, 1];
```

JSFiddle で見る

![行列による拡大縮小の例](/ja/docs/Web/API/WebGL_API/Matrix_math_for_the_web/matrix-scale.jpg)

回転行列
----

**回転行列** (rotation matrix) は、点またはオブジェクトを回転させるために使用します。 回転行列は、拡大縮小行列や平行移動行列よりも少し複雑に見えます。 これは、三角関数を使用して回転を実行します。 この節では、手順を完全な詳細に分解しませんが（Wolfram MathWorld のこの記事を調べてください）、説明のためにこの例を取り上げます。

まず、行列を使用せずに原点を中心に点を回転させるコードを次に示します。

```js
// 行列なしで原点を中心に手動で点を回転
let point = [10, 2];

// 原点からの距離を計算します
let distance = Math.sqrt(point[0] \* point[0] + point[1] \* point[1]);

// ラジアンで 60 度に相当
let rotationInRadians = Math.PI / 3;

let transformedPoint = [
  Math.cos(rotationInRadians) \* distance,
  Math.sin(rotationInRadians) \* distance,
];
```

これらのタイプのステップを行列にエンコードし、`x`、`y`、`z` の各次元に対してそれを行うことができます。下記は反時計回りの Z 軸回転を左手座標系で表したものです。

```js
let sin = Math.sin;
let cos = Math.cos;

// 注: これらの変換には視点距離がないため、
// この時点での回転は div を縮小するためにのみ示されます

let a = Math.PI \* 0.3; // ラジアンでの回転量

// Z 軸を中心に回転
let rotateZMatrix = [
  cos(a),
  -sin(a),
  0,
  0,
  sin(a),
  cos(a),
  0,
  0,
  0,
  0,
  1,
  0,
  0,
  0,
  0,
  1,
];
```

JSFiddle で見る

![回転行列の例](/ja/docs/Web/API/WebGL_API/Matrix_math_for_the_web/matrix-rotation.jpg)

3 つの軸のそれぞれを中心に回転するための回転行列を返す関数のセットを次に示します。 大きな注意点の 1 つは、視点距離が適用されていないため、まだとても 3D に感じられない可能性があることです。 平面度 (flatness) は、カメラが遠くのオブジェクトにズームインで非常に接近したときと同じです。遠近感 (sense of perspective) がなくなります。

```js
function rotateAroundXAxis(a) {
  return [1, 0, 0, 0, 0, cos(a), -sin(a), 0, 0, sin(a), cos(a), 0, 0, 0, 0, 1];
}

function rotateAroundYAxis(a) {
  return [cos(a), 0, sin(a), 0, 0, 1, 0, 0, -sin(a), 0, cos(a), 0, 0, 0, 0, 1];
}

function rotateAroundZAxis(a) {
  return [cos(a), -sin(a), 0, 0, sin(a), cos(a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
}
```

JSFiddle で見る

行列合成
----

行列の本当の力は、**行列合成** (matrix composition) に由来します。 特定のクラスの行列を掛け合わせると、変換の履歴が保持され、元に戻すことができます。 つまり、平行移動、回転、拡大縮小の行列がすべて組み合わされている場合、行列の順序を逆にして再適用すると、元の点が返されます。

行列を乗算する順序は重要です。 数値を乗算する場合、 a \* b = c と b \* a = c はどちらも正しいです。 例えば、3 \* 4 = 12 と 4 \* 3 = 12 です。 数学では、これらの数値は**可換** (commutative) であると説明されます。 順序が入れ替わった場合、行列では同じであることが保証されないため、行列は**非可換** (non-commutative) です。

もう 1 つのマインドベンダーは、WebGL および CSS での行列乗算は、操作が直感的に発生するのとは逆の順序で発生する必要があることです。 例えば、何かを 80% 縮小し、200 ピクセル下に移動してから、原点を中心に 90 度回転すると、疑似コードでは次のようになります。
```

transformation = rotate * translate * scale
```

### 複数の変換の合成

行列の合成に使用する関数は `multiplyArrayOfMatrices()` です。 これは、この記事の冒頭で紹介したユーティリティ関数のセットの一部です。 行列の配列を取り、それらを掛け合わせて結果を返します。 WebGL シェーダーコードでは、これは言語に組み込まれており、`*` 演算子を使用できます。 さらに、この例では、上で定義した行列を返す `scale()` 関数と `translate()` 関数を使用しています。

```js
let transformMatrix = MDN.multiplyArrayOfMatrices([
  rotateAroundZAxis(Math.PI \* 0.5), // ステップ 3: 90度回転
  translate(0, 200, 0), // ステップ 2: 100ピクセル下に移動
  scale(0.8, 0.8, 0.8), // ステップ 1: 縮小
]);
```

JSFiddle で見る

![行列合成の例](/ja/docs/Web/API/WebGL_API/Matrix_math_for_the_web/matrix-composition.jpg)

最後に、行列がどのように機能するかを示す楽しい手順は、手順を逆にして、行列を元の単位行列に戻すことです。

```js
let transformMatrix = MDN.multiplyArrayOfMatrices([
  scale(1.25, 1.25, 1.25), // ステップ 6: 縮小を元に戻す
  translate(0, -200, 0), // ステップ 5: 移動を元に戻す
  rotateAroundZAxis(-Math.PI \* 0.5), // ステップ 4: 回転を元に戻す
  rotateAroundZAxis(Math.PI \* 0.5), // ステップ 3: 90度回転
  translate(0, 200, 0), // ステップ 2: 100ピクセル下に移動
  scale(0.8, 0.8, 0.8), // ステップ 1: 縮小
]);
```

行列が重要である理由
----------

行列が重要なのは、空間内のさまざまな変換を記述することができる小さな数の集合で構成されているからです。行列はプログラムの中で簡単に共有できます。さまざまな座標空間を行列で記述することができ、いくつかの行列の乗算によって、ある座標空間から別の座標空間へデータ集合を移動させることができます。行列は、それを生成するために使用した前回変換のすべての部分を効果的に記憶します。

WebGL で使用する場合、グラフィックスカードは、空間内の多数の点に行列を乗算するのに特に適しています。 点の配置、照明の計算、アニメのキャラクターのポーズなどのさまざまな操作はすべて、この基本的なツールに依存しています。


WebGL でのオブジェクトのアニメーション - Web API

WebGL でのオブジェクトのアニメーション
======================

* « 前のページ
* 次のページ »
正方形を回転させる
---------

この例では、実際にはカメラを回転させることで、正方形を回転させているように見せていきます。最初に、カメラの現在の回転を追跡する変数が必要です。

**メモ:** このコードを "webgl-demo.js" スクリプトの始めに追加してください。

```js
let squareRotation = 0.0;
let deltaTime = 0;
```

ここで `drawScene()` 関数を更新して、カメラを描画するときに現在の回転を適用する必要があります。カメラを正方形の初期描画位置に移動させた後、回転を適用します。

**メモ:** "draw-scene.js" モジュールで、 `drawScene()` 関数の宣言を更新し、使用する回転を渡せるようにします。

```js
function drawScene(gl, programInfo, buffers, squareRotation) {
```

**メモ:** `drawScene()` 関数の中で、 `mat4.translate()` の呼び出しの直後に次のコードを追加してください。

```js
mat4.rotate(
  modelViewMatrix, // 出力先の行列
  modelViewMatrix, // 回転する行列
  squareRotation, // ラジアンでの回転量
  [0, 0, 1],
); // 回転軸
```

これは modelViewMatrix を現在の `squareRotation` の値だけ、Z 軸を中心に回転させます。

実際にアニメーションさせるには、 `squareRotation` の値を時間とともに変化させるコードを追加する必要があります。

**メモ:** このコードを `main()` 関数の終わりに追加し、既存の `drawScene()` の呼び出しを置き換えてください。

```js
let then = 0;

// 繰り返しシーンを描画
function render(now) {
  now \*= 0.001; // 秒に変換
  deltaTime = now - then;
  then = now;

  drawScene(gl, programInfo, buffers, squareRotation);
  squareRotation += deltaTime;

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
```

このコードでは `requestAnimationFrame` を用いて、各フレームで関数 "`render`" を呼び出すようブラウザーに依頼します。 `requestAnimationFrame` はページが読み込まれてからの時刻をミリ秒単位で渡します。それを秒に変換し、そこから前回の時刻を引いて `deltaTime` を計算します。

最後に `squareRotation` を更新します。

コードを確認する | 新しいページでデモを開く

* « 前のページ
* 次のページ »


WebGL を用いた 3D オブジェクトの作成 - Web API

WebGL を用いた 3D オブジェクトの作成
=======================

* « 前のページ
* 次のページ »

正方形を三次元空間に入れて 5 つの面を追加し、立方体を作成しましょう。これを効率的に行うために、 `gl.drawArrays()` メソッドを呼び出して頂点を直接使用して描画する方法から、 `gl.drawElements()` を呼び出して頂点配列を表として使用し、その表の個々の頂点を参照して各面の頂点の位置を定義する方法に切り替えましょう。

留意点: それぞれの面について 4 つの頂点を定義しなければなりませんが、各頂点は 3 つの面で共有されます。座標の集合をすべて移動するのではなく、 24 個の頂点の配列を作り、その配列のインデックスで各頂点を参照することで、渡されるデータを大幅に減らすことができます。なぜ頂点が 8 個ではなく、 24 個も必要なのかと思われるかもしれませんが、それぞれの角は異なる色の 3 つの面に属し、それぞれの頂点が単一の固有の色を持つ必要があるからです。そのため、それぞれの頂点について、 3 つの異なる色で 3 つのコピーを作成します。

立方体の頂点の位置を定義する
--------------

始めに `initBuffers()` のコードを更新して、立方体の頂点の位置を示すバッファーを構築しましょう。この方法は平面の正方形を作る場合と同じですが、頂点が 24 個（1 面につき 4 個）ありますので、コードは多少長くなります。

**メモ:** "init-buffers.js" モジュールの `initPositionBuffer()` 関数で、 `positions` 宣言を次のコードに置き換えてください。

```js
const positions = [
  // 前面
  -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,

  // 背面
  -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0,

  // 上面
  -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0,

  // 底面
  -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,

  // 右側面
  1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0,

  // 左側面
  -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0,
];
```

頂点に z 成分を追加したので、 `vertexPosition` 属性の `numComponents` を 3 に更新する必要があります。

**メモ:** "draw-scene.js" モジュールの `setPositionAttribute()` 関数で、定数 `numComponents` を `2` から `3` に変更しましょう。

```js
const numComponents = 3;
```

頂点の色を定義する
---------

24 個の座標それぞれについて、色の配列を作成しなければなりません。このコードでは始めに各面の色を定義します。次にループを用いてこれらの配列を各頂点の色情報に変換しています。

**メモ:** "init-buffers.js" モジュールの `initColorBuffer()` 関数で、 `colors` 宣言を次のコードに置き換えましょう。

```js
const faceColors = [
  [1.0, 1.0, 1.0, 1.0], // 前面: 白
  [1.0, 0.0, 0.0, 1.0], // 背面: 赤
  [0.0, 1.0, 0.0, 1.0], // 上面: 緑
  [0.0, 0.0, 1.0, 1.0], // 底面: 青
  [1.0, 1.0, 0.0, 1.0], // 右側面: 黄
  [1.0, 0.0, 1.0, 1.0], // 左側面: 紫
];

// 色の配列をすべての頂点の表に変換すうｒ

var colors = [];

for (var j = 0; j < faceColors.length; ++j) {
  const c = faceColors[j];
  // 各色を面の 4 つの頂点に対して 4 回繰り返します。
  colors = colors.concat(c, c, c, c);
}
```

要素配列の定義
-------

頂点の配列が生成されたら、要素の配列を構築する必要があります。

**メモ:** "init-buffer.js" モジュールに以下の関数を追加しましょう。

```js
function initIndexBuffer(gl) {
  const indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT\_ARRAY\_BUFFER, indexBuffer);

  // この配列はそれぞれの面を 2 つの三角形として定義しており、
  // 各三角形の位置を指定するために、頂点の配列を指し示す
  // インデックスを使用します。

  const indices = [
    0,
    1,
    2,
    0,
    2,
    3, // 前面
    4,
    5,
    6,
    4,
    6,
    7, // 背面
    8,
    9,
    10,
    8,
    10,
    11, // 上面
    12,
    13,
    14,
    12,
    14,
    15, // 底面
    16,
    17,
    18,
    16,
    18,
    19, // 右側面
    20,
    21,
    22,
    20,
    22,
    23, // 左側面
  ];

  // 要素の配列を GL に渡す

  gl.bufferData(
    gl.ELEMENT\_ARRAY\_BUFFER,
    new Uint16Array(indices),
    gl.STATIC\_DRAW,
  );

  return indexBuffer;
}
```

`indices` 配列はそれぞれの面を 2 つの三角形として定義し、それらの三角形の頂点は立方体の頂点の配列に対するインデックスで指定しています。よって、立方体は 12 個の三角形の集合体として表されます。

次に、この新しい関数を `initBuffers()` から呼び出して、作成したバッファー を返す必要があります。

**メモ:** "init-buffers.js" モジュールの `initBuffers()` 関数の終わりに以下のコードを追加し、既存の `return` 文を置き換えましょう。

```js
const indexBuffer = initIndexBuffer(gl);

return {
  position: positionBuffer,
  color: colorBuffer,
  indices: indexBuffer,
};
```

立方体の描画
------

次に、立方体のインデックスバッファーを使用して描画するコードを `drawScene()` 関数に追加し、新しい `gl.bindBuffer()` と `gl.drawElements()` 呼び出しを追加する必要があります。

**メモ:** `drawScene()` 関数の中で、 `gl.useProgram` の行の直前に以下のコードを追加しましょう。

```js
// どのインデックスを頂点のインデックスに使用するかを WebGL に指示する
gl.bindBuffer(gl.ELEMENT\_ARRAY\_BUFFER, buffers.indices);
```

**メモ:** "draw-scene.js" モジュールの `drawScene()` 関数で、 2 つの `gl.uniformMatrix4fv` 呼び出しの後で、 `gl.drawArrays()` 行を含むブロックを以下のブロックに置き換えましょう。

```js
{
  const vertexCount = 36;
  const type = gl.UNSIGNED\_SHORT;
  const offset = 0;
  gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
}
```

立方体の各面は 2 個の三角形で構成されますので、1 面あたり 6 個、立方体全体では 36 個の頂点が存在することになります。ただし、それらの多くは重複しています。

最後に、変数 `squareRotation` を `cubeRotation` に置き換えて、 x 軸周りの 2 つ目の回転を追加してみましょう。

**メモ:** "webgl-demo.js" ファイルの始めには、 `squareRotation` 宣言を次の行に置き換えましょう。

```js
let cubeRotation = 0.0;
```

**メモ:** 関数 `drawScene()` の宣言で、 `squareRotation` を `cubeRotation` に置き換えましょう。

```js
function drawScene(gl, programInfo, buffers, cubeRotation) {
```

**メモ:** `drawScene()` 関数の `mat4.rotate` 呼び出しを以下のコードに置き換えましょう。

```js
mat4.rotate(
  modelViewMatrix, // 宛先の行列
  modelViewMatrix, // 回転する行列
  cubeRotation, // ラジアンの回転量
  [0, 0, 1],
); // 回転軸 (Z)
mat4.rotate(
  modelViewMatrix, // 宛先の行列
  modelViewMatrix, // 回転する行列
  cubeRotation \* 0.7, // ラジアンの回転量
  [0, 1, 0],
); // 回転軸 (Y)
mat4.rotate(
  modelViewMatrix, // 宛先の行列
  modelViewMatrix, // 回転する行列
  cubeRotation \* 0.3, // ラジアンの回転量
  [1, 0, 0],
); // 回転軸 (X)
```

**メモ:** `main()` 関数内で、 `drawScene()` を呼び出して `squareRotation` を更新するコードを、代わりに `cubeRotation` を渡して更新するように置き換えます。

```js
drawScene(gl, programInfo, buffers, cubeRotation);
cubeRotation += deltaTime;
```

これで立方体が回転するアニメーションができ、 6 つの面が鮮やかに色づけされました。

コードを確認する | 新しいページでデモを開く

* « 前のページ
* 次のページ »


シェーダーを用いた WebGL での色の指定 - Web API

シェーダーを用いた WebGL での色の指定
======================

* « 前のページ
* 次のページ »

前のデモンストレーションで正方形を作り出すことができたら、次の明確なステップは、それに色をつけることです。これは、シェーダーを変更することで実現できます。

頂点に色を適用する
---------

GL ではオブジェクトは頂点の集合を用いて構築され、各頂点は位置と色の情報を持っています。既定では、他のピクセルの色（および位置など、その他の属性すべて）は補完を用いて計算され、自動的になめらかなグラデーションを生成します。前に使用した頂点シェーダーでは頂点に色の情報を適用していませんでした。頂点シェーダーとフラグメントシェーダーで各ピクセルに白色を固定で割り当てており、正方形全体が白一色で描画されました。

例えば、四隅が異なる色（赤、青、緑、白）である正方形にグラデーションを作成したいとします。始めに行うことは、4 つの頂点にこれらの色を設定することです。これを行うには、まず頂点の色の配列を作成し、次にその配列を WebGL のバッファーに格納します。これらは、以下に挙げるコードを `initBuffers()` 関数に追加することで実行します。

**メモ:** 以下の関数を `init-buffers.js` モジュールに追加してください。

```js
function initColorBuffer(gl) {
  const colors = [
    1.0,  1.0,  1.0,  1.0,    // 白
    1.0,  0.0,  0.0,  1.0,    // 赤
    0.0,  1.0,  0.0,  1.0,    // 緑
    0.0,  0.0,  1.0,  1.0,    // 青
  ];

  const colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY\_BUFFER, colorBuffer);
  gl.bufferData(gl.ARRAY\_BUFFER, new Float32Array(colors), gl.STATIC\_DRAW);

  return colorBuffer;
}
```

このコードは、各頂点の色に 1 つずつ、 4 つの 4 値ベクトルを含む JavaScript 配列を作成することから始まります。次に、これらの色を格納するために新しい WebGL バッファーが割り当てられ、配列が浮動小数点数に変換されてバッファーに格納されます。

もちろん、この新しい関数を `initBuffers()` から呼び出して、作成した新しいバッファーを返す必要もあります。

**メモ:** `initBuffers()` 関数の終わりで、既存の `return` 文の代わりに以下のコードを追加してください。

```js
const colorBuffer = initColorBuffer(gl);

return {
  position: positionBuffer,
  color: colorBuffer,
};
```

これらの色を使用するには、カラーバッファーから適切な色を引き出すように頂点シェーダーを更新する必要があります。

**メモ:** 以下のように `main()` 関数の `vsSource` 宣言を更新してください。

```js
// 頂点シェーダープログラム

const vsSource = `
 attribute vec4 aVertexPosition;
 attribute vec4 aVertexColor;

 uniform mat4 uModelViewMatrix;
 uniform mat4 uProjectionMatrix;

 varying lowp vec4 vColor;

 void main(void) {
 gl\_Position = uProjectionMatrix \* uModelViewMatrix \* aVertexPosition;
 vColor = aVertexColor;
 }
 `;
```

ここでの重要な違いは、各頂点に対して、 `varying` を使用してその色をフラグメントシェーダーに渡していることです。

フラグメントに色をつける
------------

各ピクセルの補間色を取得するために、フラグメントシェーダーを `vColor` から値を取得するように変更する必要があります。

**メモ:** 以下のように `main()` 関数の `fsSource` 宣言を更新してください。

```js
// フラグメントシェーダープログラム

const fsSource = `
 varying lowp vec4 vColor;

 void main(void) {
 gl\_FragColor = vColor;
 }
 `;
```

これにより各フラグメントは固定値ではなく、頂点からの相対的な位置に基づいて補完された色情報を受け取るようになります。

色情報を用いて描画する
-----------

次に、色の属性の場所を調べて、シェーダープログラムにその属性を設定するには、コードを追加する必要があります。

**メモ:** `main()` 関数の `programInfo` 宣言を次のように更新してください。

```js
// シェーダープログラムを使用するために必要な情報をすべて収集します。
// シェーダープログラムが aVertexPosition、aVertexColor にどの属性を
// 使用しているかを調べ、ユニフォームの位置も調べてください。
const programInfo = {
  program: shaderProgram,
  attribLocations: {
    vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
    vertexColor: gl.getAttribLocation(shaderProgram, "aVertexColor"),
  },
  uniformLocations: {
    projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"),
    modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
  },
};
```

次に、 `drawScene()` は正方形を描画するときにこれらの色を使用する必要があります。

**メモ:** 以下の関数を `draw-scene.js` モジュールに追加してください。

```js
// カラーバッファーから vertexColor 属性に色を取り出す方法を WebGL に
// 指示します。
function setColorAttribute(gl, buffers, programInfo) {
  const numComponents = 4;
  const type = gl.FLOAT;
  const normalize = false;
  const stride = 0;
  const offset = 0;
  gl.bindBuffer(gl.ARRAY\_BUFFER, buffers.color);
  gl.vertexAttribPointer(
    programInfo.attribLocations.vertexColor,
    numComponents,
    type,
    normalize,
    stride,
    offset,
  );
  gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
}
```

**メモ:** `setColorAttribute()` 関数は、 `drawScene()` から、 `gl.useProgram()` を呼び出す直前に呼ばれます。

```js
setColorAttribute(gl, buffers, programInfo);
```

結果は次のようになるでしょう。

コードを確認する | 新しいページでデモを開く

* « 前のページ
* 次のページ »


WebGL でのライティング - Web API

WebGL でのライティング
==============

* « 前のページ
* 次のページ »

始めに WebGL について理解すべきことは、より広い OpenGL 標準とは異なり、WebGL はライティングをサポートしていないということです。これは自分自身で行う必要があります。幸いそれは難しいことではありませんので、この記事では基礎的な内容を扱います。

3D のライティングとシェーディングをシミュレートする
---------------------------

3D グラフィックにおけるシミュレートされたライティングの理論に詳しく触れるのはこの記事の範囲を大きく超えますが、その働きを知るのに役立ちます。ここでは深く触れませんので詳しくは、よく使われるライティングモデルについて解説している Wikipedia の Phong shading (日本語版) の記事をご覧ください。

ライティングには 3 種類の基本タイプがあります。

**環境光** は環境全体に当たる光です。これは指向性がなく、環境内の表面に対して、その向きに関係なく均等に効果を与えます。

**指向性光源** は特定の方向から投射される光源です。これは遠方から照らされる光源であり、すべての光線はお互い平行に届きます。例えば太陽光が指向性光源になります。

**点光源** はある一点から全方向に向かって投射される光源です。これは現実にある多くの光源の通常動作です。例えば、電球は全方向に光を投射します。

今回は、単純な指向性光源と環境光のみを考慮することでライティングモデルを単純化します。反射光や点光源は扱いません。そして、環境光と 1 つの指向性光源を前のデモの回転するキューブに当てます。

点光源と反射光の概念を外したことにより、指向性光源を実装するために必要な情報は以下の 2 点になります:

1. 各々の頂点と **面法線** を関連づける必要があります。これは頂点の表面に垂直なベクトルです。
2. 光線が向かう方向を知る必要があります。これは **方向ベクトル** として定義されます。

そしてバーテックスシェーダーを、環境光および表面に当たった角度による指向性光源の効果を考慮して各頂点の色を調整するように更新します。シェーダーのコードを見て、ライティングを行う方法を見ていきましょう。

頂点の法線を構築する
----------

始めに行うべきことは、キューブを構成する全頂点の法線の配列を作成することです。キューブは単純なオブジェクトですので、これは簡単にできます。より複雑なオブジェクトの場合は、明らかに法線の計算が難しくなります。

```js
cubeVerticesNormalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY\_BUFFER, cubeVerticesNormalBuffer);

var vertexNormals = [
  // 前面
  0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,

  // 背面
  0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,

  // 上面
  0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,

  // 底面
  0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0,

  // 右側面
  1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,

  // 左側面
  -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
];

gl.bufferData(
  gl.ARRAY\_BUFFER,
  new WebGLFloatArray(vertexNormals),
  gl.STATIC\_DRAW,
);
```

これは、今ではもう見慣れたものでしょう。新しいバッファを作成し、これを作業用の配列にバインドします。そして、`bufferData()` を呼び出して頂点の法線の配列をバッファに送り込みます。

次に、法線の配列をシェーダーの属性にバインドして、シェーダーのコードがその配列にアクセスできるようにするためのコードを `drawScene()` に追加します:

```js
gl.bindBuffer(gl.ARRAY\_BUFFER, cubeVerticesNormalBuffer);
gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0);
```

最後に、シェーダーに対して **正規行列** を生成して渡すための、一様な行列を構築するコードを更新する必要があります。これは、光源に関するキューブの現在の向きを処理する際に法線を変換するのに使用されます:

```js
var normalMatrix = mvMatrix.inverse();
normalMatrix = normalMatrix.transpose();
var nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix");
gl.uniformMatrix4fv(
  nUniform,
  false,
  new WebGLFloatArray(normalMatrix.flatten()),
);
```

シェーダーを更新する
----------

シェーダーが必要とするデータがすべて用意できましたので、次はシェーダーのコードを更新する必要があります。

### バーテックスシェーダー

まずはバーテックスシェーダーを、環境光および指向性光源に基づいて各頂点のシェーディング値を生成するように更新します。以下のコードをご覧ください:

```html
<script id="shader-vs" type="x-shader/x-vertex">
 attribute highp vec3 aVertexNormal;
 attribute highp vec3 aVertexPosition;
 attribute highp vec2 aTextureCoord;

 uniform highp mat4 uNormalMatrix;
 uniform highp mat4 uMVMatrix;
 uniform highp mat4 uPMatrix;

 varying highp vec2 vTextureCoord;
 varying highp vec3 vLighting;

 void main(void) {
 gl\_Position = uPMatrix \* uMVMatrix \* vec4(aVertexPosition, 1.0);
 vTextureCoord = aTextureCoord;

 // ライティング効果を適用する

 highp vec3 ambientLight = vec3(0.6, 0.6, 0.6);
 highp vec3 directionalLightColor = vec3(0.5, 0.5, 0.75);
 highp vec3 directionalVector = vec3(0.85, 0.8, 0.75);

 highp vec4 transformedNormal = uNormalMatrix \* vec4(aVertexNormal, 1.0);

 highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);
 vLighting = ambientLight + (directionalLightColor \* directional);
 }
</script>
```

頂点の位置が算出されると、頂点に対応するテクセルの座標が算出され、頂点のシェーディングの計算ができるようになります。

始めに行うのは、頂点の法線に正規行列を乗じることで、法線を現在のキューブの向きと位置に基づくものに変換することです。次に、変換された法線と方向ベクトル (光線が来る方向) の点乗積を計算することにより、頂点に適用されるべき指向性光源の光量を算出することができます。光量を 0 より小さくすることはできませんので、算出結果が 0 より小さくなった場合は、その値を 0 に固定します。

指向性光源の光量が算出されたら、環境光を取り込みさらに指向性光源の色と光量を足し込むことでライティングの値を決めることができます。この結果、フラグメントシェーダーが描画する各ピクセルの色を調整するために用いる RGB 値を得ることができます。

### フラグメントシェーダー

フラグメントシェーダーは、バーテックスシェーダーが算出した光量の値を考慮するように更新する必要があります:

```js
<script id="shader-fs" type="x-shader/x-fragment">
  varying highp vec2 vTextureCoord;
  varying highp vec3 vLighting;

  uniform sampler2D uSampler;

  void main(void) {
    mediump vec4 texelColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));

    gl_FragColor = vec4(texelColor.rgb \* vLighting, texelColor.a);
  }
</script>
```

ここでは以前の例で行ったようにテクセルの色を取り出しますが、フラグメントの色を設定する前に、光源の影響を考慮してテクセルの色を調整するため、テクセルの色に光量の値を掛け合わせます。

以上で完成です!

コードを確認する | 新しいページでデモを開く

読者への課題
------

基本的な頂点ごとのライティングを実装した今回の例は、単純なものであることは明らかです。より高度なグラフィックとしてピクセルごとのライティングを実装したいと考えるのは、正しい方向性です。

同様に、光源の方向、光源の色などについても実験してみるとよいでしょう。

* « 前のページ
* 次のページ »


WebGL におけるテクスチャのアニメーション - Web API

WebGL におけるテクスチャのアニメーション
=======================

* « 前のページ

今回のデモンストレーションでは、前回の例で用いた静的なテクスチャを Ogg ビデオファイルのフレームに置き換えます。実はこれを行うのはとても簡単ですが、見ていて楽しいです。さっそく始めましょう。なお同様のコードを、どんな種類のデータ（`<canvas>` など）をテクスチャのソースとして用いる場合でも使用することができます。

動画へのアクセス
--------

始めに、ビデオフレームを取り出すのに使う `<video>` 要素を作成します。

**メモ:** この宣言を "webgl-demo.js" スクリプトの始めに追加しましょう。

```js
// 動画がテクスチャにコピーできる場合は true に設定する
let copyVideo = false;
```

**メモ:** この関数を "webgl-demo.js" スクリプトに追加しましょう。

```js
function setupVideo(url) {
  const video = document.createElement("video");

  let playing = false;
  let timeupdate = false;

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

  // 動画のデータが確実にあることを保証するために、
  // この 2 つのイベントを待つ

  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;
}
```

最初に動画要素を作成します。動画を自動再生し、音をミュートし、ループ再生するように設定します。次に、動画が再生され、時刻が更新されたことを確認するために 2 つのイベントを設定します。まだ利用できるデータがない動画を WebGL にアップロードするとエラーになるため、この両方のチェックが必要です。これらのイベントをどちらも調べることで、利用できるデータがあることが確認され、WebGL テクスチャへの動画のアップロードを始めるのには安全であることが保証されます。上記のコードでは、これらのイベントをどちらも取得したことを確認しています。取得した場合は、グローバル変数 `copyVideo` に true を設定し、動画をテクスチャにコピーし始めても安全であることを示しています。

そして最後に、 `src` 属性を設定し、 `play` を呼び出して動画の読み込みと再生を開始します。

WebGL にテクスチャデータを提供するために使用するためには、元の動画を安全なソースから読み込む必要があります。つまり、安全なウェブサーバーを使用するようにコードを展開する必要があるだけでなく、テストするための安全なサーバーも必要になります。ローカルにテスト用サーバーを用意するには？を参照してください。

動画フレームをテクスチャとして使用する
-------------------

次に変更するのは `initTexture()` です。画像ファイルを読み込む必要がなくなったため、とても単純になります。画像を読み込む代わりに、空のテクスチャオブジェクトを作成して、後で使用するフィルターを設定します。

**メモ:** "webgl-demo.js" の `loadTexture()` 関数を以下のコードに置き換えましょう。

```js
function initTexture(gl) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE\_2D, texture);

  // 動画はインターネット経由でダウンロードしなければならない
  // ため、準備ができるまで時間がかかる可能性があります。
  // そのため、テクスチャに単一のピクセルを置くことで、
  // 直ちにそれを利用することができます。
  const level = 0;
  const internalFormat = gl.RGBA;
  const width = 1;
  const height = 1;
  const border = 0;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED\_BYTE;
  const pixel = new Uint8Array([0, 0, 255, 255]); // 不透明の青
  gl.texImage2D(
    gl.TEXTURE\_2D,
    level,
    internalFormat,
    width,
    height,
    border,
    srcFormat,
    srcType,
    pixel,
  );

  // mips をオフにし、 wrapping を clamp to edge に設定することで、動画の大きさに関係なく動作するようになります。
  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.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_MIN\_FILTER, gl.LINEAR);

  return texture;
}
```

**メモ:** 以下の関数を "webgl-demo.js" に追加しましょう。

```js
function updateTexture(gl, texture, video) {
  const level = 0;
  const internalFormat = gl.RGBA;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED\_BYTE;
  gl.bindTexture(gl.TEXTURE\_2D, texture);
  gl.texImage2D(
    gl.TEXTURE\_2D,
    level,
    internalFormat,
    srcFormat,
    srcType,
    video,
  );
}
```

このコードは以前にも見たことがあるでしょう。前回の例の画像 onload 関数とほぼ同じですが、 `texImage2D()` を呼び出すときに `Image` オブジェクトを渡す代わりに `<video>` 要素を渡している点が異なります。 WebGL は現在のフレームを取り出し、テクスチャとして使用する方法を知っています。

次に、これらの新しい関数を `main()` 関数から呼び出す必要があります。

**メモ:** `main()` 関数で、 `loadTexture()` の呼び出しを次のコードに置き換えましょう。

```js
const texture = initTexture(gl);
const video = setupVideo("Firefox.mp4");
```

**メモ:** また、Firefox.mp4 ファイルを JavaScript ファイルと同じローカルディレクトリーにダウンロードする必要があります。

**メモ:** `main()` 関数で、 `render()` 関数をこのように置き換えましょう。

```js
// シーンを反復して描画
function render(now) {
  now \*= 0.001; // 秒へ変換
  deltaTime = now - then;
  then = now;

  if (copyVideo) {
    updateTexture(gl, texture, video);
  }

  drawScene(gl, programInfo, buffers, texture, cubeRotation);
  cubeRotation += deltaTime;

  requestAnimationFrame(render);
}
```

`copyVideo` が true の場合、 `drawScene()` 関数を呼び出す直前に `updateTexture()` を呼び出します。

以上です。

コードを確認する | 新しいページでデモを開く

関連情報
----

* HTML5 の audio 要素と video 要素の使用
* « 前のページ


WebGL 入門 - Web API

WebGL 入門
========

* 次のページ »

WebGL により、ウェブコンテンツで OpenGL ES 2.0 ベースの API を用いて、対応しているブラウザーではプラグインを使わずに、 2D および 3D の描画を HTML の `canvas` において行うことができます。

WebGL のプログラムは JavaScript で記述する制御コードと、コンピューターの Graphics Processing Unit (GPU) で実行するシェーダーコード (GLSL) で構成されます。 WebGL 要素は他の HTML 要素と混ぜられ、他のページ部品やページの背景と合成されます。

この記事では、 WebGL の基礎を紹介します。ここでは、 3D グラフィックに関して数学的に理解していることを前提とし、 3D グラフィックの概念自体の説明は行いません。

このチュートリアルで使用するコード例は、GitHub の webgl-examples リポジトリーで確認できます。

この一連の記事は WebGL 自体を紹介していることに注意してください。ただし THREE.js など、 WebGL の機能をカプセル化する数多くのフレームワークを利用して、 3D アプリケーションとゲームをより簡単に構築することが可能です。

3D 描画の準備
--------

最初に、次の 2 つのファイルを作成してください。

* "index.html"
* "webgl-demo.js"

"index.html" ファイルには、以下のような内容を入れてください。

```html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>WebGL デモ</title>
    <script src="webgl-demo.js" type="module"></script>
  </head>

  <body>
    <canvas id="glcanvas" width="640" height="480"></canvas>
  </body>
</html>
```

これは、サンプルが描画するキャンバスを宣言しています。

### WebGL コンテキストの準備

以下のコードを "webgl-demo.js" ファイルに追加してください。

```js
main();

//
// ここから開始
//
function main() {
  const canvas = document.querySelector("#glcanvas");
  // GL コンテキストを初期化
  const gl = canvas.getContext("webgl");

  // WebGL が使用可能で動作している場合にのみ続行
  if (gl === null) {
    alert(
      "WebGL を初期化できません。ブラウザーまたはマシンが対応していない可能性があります。",
    );
    return;
  }

  // クリアカラーを黒に設定し、完全に不透明する
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  // 指定されたクリアカラーでカラーバッファーをクリアする
  gl.clear(gl.COLOR\_BUFFER\_BIT);
}
```

スクリプトが読み込まれると `main()` 関数が呼び出されます。この関数の目的は WebGL コンテキストをセットアップし、コンテンツのレンダリングを始めることです。

ここで最初に行うことはキャンバスへの参照を取得し、それを `canvas` という名前の変数に割り当てることです。

キャンバスを取得したら、キャンバスの `WebGLRenderingContext` を取得するために `getContext()` を呼び出して文字列 `"webgl"` を渡します。ブラウザーが WebGL に対応していない場合、 `getContext()` は `null` を返します。その場合はユーザーにメッセージを表示して終了します。

コンテキストが正常に初期化された場合、変数 `gl` はその参照になります。この場合、クリアカラーを黒に設定し、そのカラーのコンテキストをクリア（背景色でキャンバスを再描画）します。

この時点で、 WebGL コンテキストが正常に初期化されるのに十分なコードがあり、コンテンツを受信する準備ができて待機している大きな黒い空のボックスになります。

コードを確認する | 新しいページでデモを開く

関連情報
----

* An introduction to WebGL: Luz Caballero が著し、dev.opera.com で公開しています。この記事では WebGL とは何かやどのように WebGL が動作するか（レンダリングパイプラインの概念を含む）を説明して、WebGL ライブラリーをいくつか紹介しています。
* WebGL Fundamentals
* An intro to modern OpenGL: Joe Groff が OpenGL に関するすばらしい記事シリーズを著しており、OpenGL の歴史から重要なグラフィックスパイプラインの概念までの説明、およびどのように OpenGL が動作するかを示すデモを紹介しています。OpenGL の知識を持っていない場合は、ここから始めるとよいでしょう。
* 次のページ »


WebGL でのテクスチャの使用 - Web API

WebGL でのテクスチャの使用
================

* « 前のページ
* 次のページ »

これで、サンプルプログラムに回転する 3D 立方体ができたので、単色で塗りつぶされている立方体の表面にテクスチャを割り当ててみましょう。

テクスチャの読み込み
----------

始めに、テクスチャを読み込むコードを追加します。今回は単一のテクスチャを用いて、そのテクスチャを立方体の全 6 面に貼り付けますが、テクスチャがいくつある場合でも同じ方法が適用できます。

**メモ:** テクスチャの読み込みはクロスドメインのルールに従うことへの注意が重要です。すなわち、コンテンツが CORS で認可されているサイトからのみ、テクスチャを読み込むことができます。詳しくは以下のドメインをまたぐテクスチャを参照してください。

**メモ:** これら 2 つの関数を "webgl-demo.js" スクリプトに追加しましょう。

```js
//
// テクスチャを初期化して画像を読み込みます。
// 画像の読み込みが完了したら、テクスチャにコピーします。
//
function loadTexture(gl, url) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE\_2D, texture);

  // 画像はインターネットでダウンロードする必要があるため、
  // 準備ができるまで少し時間がかかることがあります。
  // それまではテクスチャにピクセルを 1 つだけ入れて、すぐに
  // 使用できるようにしておきます。画像のダウンロードが完了
  // したら、画像のコンテンツでテクスチャを更新します。
  const level = 0;
  const internalFormat = gl.RGBA;
  const width = 1;
  const height = 1;
  const border = 0;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED\_BYTE;
  const pixel = new Uint8Array([0, 0, 255, 255]); // 不透明の青
  gl.texImage2D(
    gl.TEXTURE\_2D,
    level,
    internalFormat,
    width,
    height,
    border,
    srcFormat,
    srcType,
    pixel,
  );

  const image = new Image();
  image.onload = () => {
    gl.bindTexture(gl.TEXTURE\_2D, texture);
    gl.texImage2D(
      gl.TEXTURE\_2D,
      level,
      internalFormat,
      srcFormat,
      srcType,
      image,
    );

    // WebGL1 は画像の大きさが 2 のべき乗であるかどうかで
    // 要求されるものが異なるので、画像の両方の軸が 2 の
    // べき乗かどうかを調べます。
    if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
      // 2 のべき乗なので、 mips を作成します。
      gl.generateMipmap(gl.TEXTURE\_2D);
    } else {
      // 2 のべき乗ではないので、 mips をオフにして、
      // エッジにクランプするようにラッピングを設定します。
      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.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_MIN\_FILTER, gl.LINEAR);
    }
  };
  image.src = url;

  return texture;
}

function isPowerOf2(value) {
  return (value & (value - 1)) === 0;
}
```

`loadTexture()` ルーチンは WebGL の `createTexture()` (en-US) 関数を呼び出して WebGL テクスチャオブジェクト `texture` を作成することから始めます。その後、`texImage2D()` (en-US)を使用して単一の青いピクセルをアップロードします。たとえ画像をダウンロードするのにいくつか時間がかかったとしても、これによってテクスチャは青一色としてすぐに使えるようになります。

画像ファイルからテクスチャを読み込むために、 `Image` オブジェクトを作成し、 `src` にテクスチャとして使用したい画像の URL を割り当てています。 `image.onload` に割り当てる関数は画像のダウンロードが完了すると呼び出されます。この点で、テクスチャのソースを画像として使用して、今回の `texImage2D()` (en-US) を再び呼び出します。その後、テクスチャのフィルタリングとラッピングを、ダウンロードした画像の大きさが、どちらの次元も 2 のべき乗であるかどうかに基づいて設定します。

WebGL1 では、 2 のべき乗でない大きさのテクスチャではフィルタリングが `NEAREST` と `LINEAR` に設定されたしか使用することができず、それらのためにミップマップを生成することはできません。また、ラッピングモードも `CLAMP_TO_EDGE` に設定する必要があります。一方、テクスチャが 2 次元とも 2 のべき乗の大きさの場合、 WebGL はより高品質なフィルタリングを行うことができ、ミップマップを使用し、ラッピングモードを `REPEAT` または `MIRRORED_REPEAT` に設定することができます。

繰り返されるテクスチャの例としては、数個のレンガの画像をタイル状に並べ、レンガの壁を覆うものがあります。

ミップマッピングや UV リピートは、 `texParameteri()` (en-US) で無効化できます。これにより、ミップマッピング、 UV ラッピング、 UV タイリング、および機器がテクスチャをどのように扱うかの制御ができなくなる代わりに、 NPOT （2 のべき乗ではない）テクスチャが利用できるようになります。

```js
// gl.LINEAR の代わりに gl.NEAREST も可能。ミップマップは不可
gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_MIN\_FILTER, gl.LINEAR);
// S 座標のラッピング（繰り返し）を禁止
gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_WRAP\_S, gl.CLAMP\_TO\_EDGE);
// T 座標のラッピング（繰り返し）を禁止
gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_WRAP\_T, gl.CLAMP\_TO\_EDGE);
```

繰り返しますが、これらの引数を付加すると、 WebGL デバイスは自動的に（対応する最大サイズまでの）任意の解像度のテクスチャを受け入れます。上記の構成を行わないと、 WebGL は NPOT テクスチャのすべてのサンプルを、透明な黒 (`rgba(0,0,0,0)`) を返して失敗させる必要があります。

画像を読み込むために、 `main()` 関数内に `loadTexture()` 関数の呼び出しを追加します。これは `initBuffers(gl)` 呼び出しの後に追加できます。

しかし、ブラウザーは読み込まれたイメージのピクセルを上から下、つまり左上の角から順にコピーするのに対し、 WebGL はピクセルを下から上、つまり左下の角から順にコピーするという点に注意してください。（詳細については、 Why is my WebGL texture upside-down? を参照してください。）

そのため、レンダリング時に画像テクスチャが間違った方向になるのを防ぐために、 `pixelStorei()` (en-US) を `gl.UNPACK_FLIP_Y_WEBGL` 引数を `true` に設定して呼び出す必要があります。

**メモ:** 以下のコードを `main()` 関数の `initBuffers()` を呼び出した直後に追加してください。

```js
// テクスチャの読み込み
const texture = loadTexture(gl, "cubetexture.png");
// 画像ピクセルを WebGL が期待する下から上への順序に反転させる
gl.pixelStorei(gl.UNPACK\_FLIP\_Y\_WEBGL, true);
```

**メモ:** 最後に、 cubetexture.png ファイルを JavaScript ファイルと同じローカルディレクトリーにダウンロードしましょう。

テクスチャを表面にマッピングする
----------------

以上で、テクスチャの読み込みと使用する準備ができました。しかしテクスチャが使用できるようになるには、まず立方体の面の頂点にテクスチャの座標をマッピングする必要があります。これには `initBuffers()` にある、立方体の各面に色を設定する既存のコードを置き換えます。

**メモ:** この関数を "init-buffer.js" モジュールに追加しましょう。

```js
function initTextureBuffer(gl) {
  const textureCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY\_BUFFER, textureCoordBuffer);

  const textureCoordinates = [
    // 前面
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // 背面
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // 上面
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // 下面
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // 右面
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // 左面
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  ];

  gl.bufferData(
    gl.ARRAY\_BUFFER,
    new Float32Array(textureCoordinates),
    gl.STATIC\_DRAW,
  );

  return textureCoordBuffer;
}
```

始めに、このコードは各面のテクスチャの座標を収める GL のバッファーを作成して、そのバッファーを書き込みを行う配列としてバインドします。

`textureCoordinates` 配列は、各面の各座標に対応するテクスチャの座標を定義します。テクスチャの座標の範囲は 0.0 から 1.0 であることに注意してください。テクスチャマッピングのために、テクスチャの寸法は実際の大きさに関わらず 0.0 から 1.0 の範囲に正規化されます。

テクスチャマッピングの配列を設定したら、配列をバッファーに渡すことで GL がそのデータを使用する準備が完了します。

そしてその新しいバッファーを返します。

次に、色バッファーの代わりにテクスチャー座標バッファーを作成して返すように `initBuffers()` を更新する必要があります。

**メモ:** "init-buffers.js" モジュールの `initBuffers()` 関数で、 `initColorBuffer()` の呼び出しを以下の行に置き換えましょう。

```js
const textureCoordBuffer = initTextureBuffer(gl);
```

**メモ:** "init-buffers.js" モジュールの `initBuffers()` 関数で、 `return` 文を以下に置き換えましょう。

```js
return {
  position: positionBuffer,
  textureCoord: textureCoordBuffer,
  indices: indexBuffer,
};
```

シェーダーの更新
--------

シェーダープログラムも、単色の代わりにテクスチャを使用するように更新する必要があります。

### 頂点シェーダー

頂点シェーダーを、色データを取得する代わりに、テクスチャ座標データを取得するように置き換える必要があります。

**メモ:** 以下のように `main()` 関数の `vsSource` 宣言を更新しましょう。

```js
const vsSource = `
 attribute vec4 aVertexPosition;
 attribute vec2 aTextureCoord;

 uniform mat4 uModelViewMatrix;
 uniform mat4 uProjectionMatrix;

 varying highp vec2 vTextureCoord;

 void main(void) {
 gl\_Position = uProjectionMatrix \* uModelViewMatrix \* aVertexPosition;
 vTextureCoord = aTextureCoord;
 }
 `;
```

ここでの重要な変更は、頂点の色を取得する代わりに、テクスチャ座標を取得して頂点シェーダーに渡していることです。これにより、頂点に対応するテクスチャ内の位置を示します。

### フラグメントシェーダー

フラグメントシェーダーも同様に更新する必要があります。

**メモ:** 以下のように `main()` 関数の `fsSource` 宣言を更新しましょう。

```js
const fsSource = `
 varying highp vec2 vTextureCoord;

 uniform sampler2D uSampler;
 out vec4 fragColor;

 void main(void) {
 fragColor = texture(uSampler, vTextureCoord);
 }
 `;
```

フラグメントの色に色の値を割り当てる代わりに、フラグメントの色を texel （つまり、テクスチャ内のピクセル）を取得することで、色と同様に頂点間で補間される `vTextureCoord` の値に基づいて計算するようにします。

### 属性とユニフォームの位置

属性を変更し、ユニフォームを追加したので、それらの位置を調べていく必要があります。

**メモ:** `main()` 関数の `programInfo` 宣言を次のように更新しましょう。

```js
const programInfo = {
  program: shaderProgram,
  attribLocations: {
    vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
    textureCoord: gl.getAttribLocation(shaderProgram, "aTextureCoord"),
  },
  uniformLocations: {
    projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"),
    modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
    uSampler: gl.getUniformLocation(shaderProgram, "uSampler"),
  },
};
```

テクスチャを貼り付けた立方体の描画
-----------------

`drawScene()` 関数の変更点は簡単です。

**メモ:** "draw-scene.js" モジュールの `drawScene()` 関数内に、以下の関数を追加しましょう。

```js
// テクスチャ座標をバッファーから取り出す方法を webgl に伝える。
function setTextureAttribute(gl, buffers, programInfo) {
  const num = 2; // すべての座標は 2 つの値からなる
  const type = gl.FLOAT; // バッファー内のデータは 32 ビット浮動小数点数
  const normalize = false; // 正規化なし
  const stride = 0; // あるセットから次のセットまで、何バイトで移動するか
  const offset = 0; // バッファ内の何バイトから開始するか
  gl.bindBuffer(gl.ARRAY\_BUFFER, buffers.textureCoord);
  gl.vertexAttribPointer(
    programInfo.attribLocations.textureCoord,
    num,
    type,
    normalize,
    stride,
    offset,
  );
  gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
}
```

**メモ:** "draw-scene.js" モジュールの `drawScene()` 関数内で、 `setColorAttribute()` の呼び出しを以下の行に置き換えましょう。

```js
setTextureAttribute(gl, buffers, programInfo);
```

次に、面にマッピングするテクスチャを指定するコードを追加します。

**メモ:** `drawScene()` 関数の中で、 `gl.uniformMatrix4fv()` を 2 回呼び出した直後に、以下のコードを追加しましょう。

```js
// テクスチャユニット 0 に影響を与えたいことを WebGL に伝える
gl.activeTexture(gl.TEXTURE0);

// テクスチャをテクスチャユニット 0 にバインドする
gl.bindTexture(gl.TEXTURE\_2D, texture);

// テクスチャをテクスチャユニット 0 にバインドすることをシェーダーに伝える
gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
```

WebGL は最低 8 つのテクスチャユニットを提供します。そのうちの最初のユニットが `gl.TEXTURE0` です。ユニット 0 に適用したいことを WebGL に伝えます。そして、 `bindTexture()` (en-US) を呼び出し、テクスチャをテクスチャユニット 0 の `TEXTURE_2D` バインドポイントにバインドします。 `uSampler` にはテクスチャユニット 0 を使用するようにシェーダーに伝えます。

最後に、`drawScene()` 関数の引数として `texture` を追加します。

**メモ:** `drawScene()` 関数の宣言を更新し、新しい引数を追加しましょう。

```js
function drawScene(gl, programInfo, buffers, texture, cubeRotation) {
```

**メモ:** `main()` 関数の `drawScene()` を呼び出す場所を更新しましょう。

```js
drawScene(gl, programInfo, buffers, texture, cubeRotation);
```

これで、回転する立方体は問題なく動作します。

コードを確認する | 新しいページでデモを開く

ドメインをまたぐテクスチャ
-------------

WebGL のテクスチャの読み込みは、ドメイン間アクセス制御に従います。コンテンツで他のドメインからテクスチャを読み込むためには、 CORS で許可を得なければなりません。 CORS について詳しくは、 HTTP アクセス制御 をご覧ください。

WebGL は安全なコンテキストからテクスチャを読み込む必要があるため、 WebGL で `file:///` の URL から読み込んだテクスチャを使用することはできません。つまり、コードのテストと展開には、安全なウェブサーバーが必要だということです。ローカルでテストする場合はローカルにテスト用サーバーを用意するには？を参照してください。

CORS で許可された画像を WebGL のテクスチャとして使用する方法の説明を、こちらの hacks.mozilla.org の記事に掲載しています。

汚染された（書き込み専用の） 2D キャンバスを WebGL のテクスチャとして使用することはできません。 2D の `<canvas>` が汚染されたとは、例えば、別ドメインの画像が canvas 上に描画された状態を指します。

* « 前のページ
* 次のページ »


WebGL コンテキストへの 2D コンテンツの追加 - Web API

WebGL コンテキストへの 2D コンテンツの追加
==========================

* « 前のページ
* 次のページ »

WebGL コンテキストの作成に成功したら、レンダリングを開始できます。簡単にできることは、テクスチャが貼り付けられていない単純な正方形を描画することです。そこから始めましょう。

このプロジェクトの完全なソースコードは GitHub で利用できます。

glMatrix ライブラリーのインクルード
----------------------

このプロジェクトは、行列操作を行うために glMatrix ライブラリーを使用しますので、プロジェクトのインクルードする必要があります。 CDN からコピーを読み込みます。

**メモ:** "index.html" を更新すると、次のようになります。

```html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>WebGL デモ</title>
    <link rel="stylesheet" href="./webgl.css" type="text/css" />
    <script
 src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"
 integrity="sha512-zhHQR0/H5SEBL3Wn6yYSaTTZej12z0hVZKOv3TwCUXT1z5qeqGcXJLLrbERYRScEDDpYIJhPC1fk31gqR783iQ=="
 crossorigin="anonymous"
 defer></script>
    <script src="webgl-demo.js" type="module"></script>
  </head>

  <body>
    <canvas id="glcanvas" width="640" height="480"></canvas>
  </body>
</html>
```

シーンの描画
------

レンダリングを始める前に理解しておくべきもっとも重要なことは、今回の例では正方形の平面オブジェクトのみをレンダリングしていますが、それが三次元の空間に描画されるということです。正方形を描いているだけで、カメラの正面に視線方向に垂直に直接配置しています。単純なシーンの色を作成し、オブジェクトを描画するシェーダーを定義する必要があります。これらは正方形の平面が画面にどのように表示されるかを確立します。

### シェーダー

**シェーダー** は、 OpenGL ES Shading Language (**GLSL**) を使用して記述されたプログラムであり、図形を構成する頂点に関する情報を取得し、画面上にピクセルをレンダリングするために必要なデータ、すなわちピクセルの位置とその色を生成します。

WebGL コンテンツを描画するときに実行される 2 つのシェーダー関数があります。**頂点シェーダー**と**フラグメントシェーダー**です。これらを GLSL で記述し、コードのテキストを WebGL に渡して、GPU で実行するためにコンパイルします。 頂点シェーダーとフラグメントシェーダーのセットを合わせて、**シェーダープログラム**と呼びます。

2D の図形を WebGL コンテキストに描画する例に基づいて、2 種類のシェーダーを簡単に見てみましょう。

#### 頂点シェーダー

図形がレンダリングされるたびに、頂点シェーダーが図形の各頂点に対して実行されます。その仕事は、入力頂点を元の座標系から WebGL が使用する\*\*クリップ空間\*\*座標系に変換することです。各軸の範囲はアスペクト比、実際のサイズ、またはその他の要因に関係なく -1.0 〜 1.0 です。

頂点シェーダーは、頂点の位置で必要な座標変換を実行し、頂点ごとに行う必要がある他の調整または計算を行い、 GLSL によって提供される `gl_Position` と呼ばれる特別な変数に保存して、変換された頂点を返す必要があります。

シェーダーは必要に応じてテクセルの面のテクスチャ内の座標を決定して頂点に適用したり、法線を適用して頂点に適用する照明係数を決定したりすることもできます。この情報は、フラグメントシェーダーと共有するために、必要に応じて varying または属性に保存することができます。

以下の頂点シェーダーは、 `aVertexPosition` と呼ばれる定義した属性から頂点の位置の値を受け取ります。次に、その位置に `uProjectionMatrix` （投影行列）および `uModelViewMatrix` モデルビュー行列）という 2 つの 4 x 4 行列を乗算します。結果は `gl_Position` に設定されます。投影行列およびその他の行列の詳細については、この記事が役立つ場合があります。

**メモ:** このコードを `main()` 関数に追加してください。

```js
// 頂点シェーダーのプログラム
const vsSource = `
 attribute vec4 aVertexPosition;
 uniform mat4 uModelViewMatrix;
 uniform mat4 uProjectionMatrix;
 void main() {
 gl\_Position = uProjectionMatrix \* uModelViewMatrix \* aVertexPosition;
 }
 `;
```

頂点の位置に `vec4` 属性を使用していることに注意してください。実際には 4 成分ベクトルを使用していません。つまり、状況に応じて `vec2` または `vec3` として処理できます。ただし、演算を実行するときは `vec4` である必要があります。したがって、演算を実行するたびに `vec4` に変換するのではなく、最初から `vec4` を使用します。これにより、シェーダーで行うすべての計算で手間が省けます。パフォーマンスが重要です。

この例ではシーンにまだ何も適用していないため、照明をまったく計算していません。これは、後ほど WebGL での照明の例で説明します。また、ここではテクスチャを使用した作業がないことに注意してください。これは、 WebGL でのテクスチャの使用で追加されます。

#### フラグメントシェーダー

**フラグメントシェーダー**は、図形の頂点が頂点シェーダーによって処理された後、描画される各図形のすべてのピクセルに対して 1 回ずつ呼び出されます。その仕事は、ピクセルに適用するテクセル（つまり、図形のテクスチャ内のピクセル）を特定し、そのテクセルの色を取得し、その色に適切な照明を適用することによりそのピクセルの色を決定することです。色は特別な変数 `gl_FragColor` に保存することにより、 WebGL レイヤーに返されます。その色は、図形の対応するピクセルの正しい位置で画面に描画されます。

この場合、照明を使用せずに白い正方形を描画するだけなので、毎回単純に白を返します。

**メモ:** このコードを `main()` 関数に追加してください。

```js
const fsSource = `
 void main() {
 gl\_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
 }
 `;
```

### シェーダーの初期化

2 つのシェーダーを定義したので、それらを WebGL に渡してコンパイルし、リンクする必要があります。以下のコードは `loadShader()` を呼び出して 2 つのシェーダーを作成し、シェーダーの種類とソースを渡します。次にプログラムを作成し、シェーダーを接続してそれらをリンクします。コンパイルまたはリンクが失敗した場合、コードはアラートを表示します。

**メモ:** これらの 2 つの関数を "webgl-demo.js" スクリプトに追加してください。

```js
//
// シェーダープログラムを初期化し、WebGL にデータの描画方法を教える
//
function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX\_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT\_SHADER, fsSource);

  // シェーダープログラムの作成

  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);

  // シェーダープログラムの作成に失敗した場合、アラートを出す

  if (!gl.getProgramParameter(shaderProgram, gl.LINK\_STATUS)) {
    alert(
      `シェーダープログラムを初期化できません: ${gl.getProgramInfoLog(
 shaderProgram,
 )}`,
    );
    return null;
  }

  return shaderProgram;
}

//
// 指定された種類のシェーダーを作成し、ソースを
// アップロードしてコンパイルする
//
function loadShader(gl, type, source) {
  const shader = gl.createShader(type);

  // シェーダーオブジェクトにソースを送信

  gl.shaderSource(shader, source);

  // シェーダープログラムをコンパイル

  gl.compileShader(shader);

  // コンパイルが成功したか確認する

  if (!gl.getShaderParameter(shader, gl.COMPILE\_STATUS)) {
    alert(
      `シェーダーのコンパイル時にエラーが発生しました: ${gl.getShaderInfoLog(
 shader,
 )}`,
    );
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
```

`loadShader()` 関数は WebGL コンテキスト、シェーダーの種類とソースコードを入力として受け取り、以下の順序でシェーダーを作成しコンパイルします。

1. 新たなシェーダーは `gl.createShader()` を呼ぶことで作成されます。
2. シェーダーのソースコードは `gl.shaderSource()` を呼ぶことでシェーダーに送られます。
3. シェーダーはソースコードを得るとすぐに、 `gl.compileShader()` を用いてコンパイルします。
4. シェーダーのコンパイルが成功したか確かめるため、シェーダーの引数である `gl.COMPILE_STATUS` をチェックします。この引数を得るために `gl.getShaderParameter()` を呼び出し、シェーダーとチェックしたい引数の名前 (`gl.COMPILE_STATUS`) を指定します。もし `false` ならばシェーダーがコンパイルに失敗したということであり、 `gl.getShaderInfoLog()` を用いてコンパイラから得たログと共にアラートを表示します。そしてシェーダーを削除し、シェーダーの読み込みに失敗したことを示すため `null` を返します。
5. シェーダーのコンパイルに成功した場合、コンパイルされたシェーダーを呼び出し元に返します。

**メモ:** このコードを `main()` 関数に追加してください。

```js
// シェーダープログラムを初期化する。ここで頂点への
// 照明などがすべて確立される。
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
```

シェーダープログラムを作成した後、 WebGL が入力に割り当てた位置を調べる必要があります。この場合、 1 つの属性と 2 つのユニフォームがあります。属性はバッファーから値を受け取ります。頂点シェーダーは実行される度にその属性に割り当てられたバッファーから次の値を受け取ります。ユニフォームは JavaScript のグローバル変数に似ており、シェーダーのどの実行時においても値は不変です。属性とユニフォームの位置は、単一のシェーダープログラムに固有のものなので、それらをまとめて保存して、簡単に受け渡しできるようにします。

**メモ:** このコードを `main()` 関数に追加してください。

```js
// シェーダープログラムを使用するために必要な情報をすべて収集する。
// シェーダープログラムが aVertexPosition に使用している属性を調べ、
// ユニフォームの位置を調べる。
const programInfo = {
  program: shaderProgram,
  attribLocations: {
    vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
  },
  uniformLocations: {
    projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"),
    modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
  },
};
```

正方形の作成
------

正方形をレンダリングする前に、正方形の各頂点の座標を含むバッファーを作成し、そこに頂点の座標を書き込む必要があります。

これを行うのに `initBuffers()` 関数を使用しますが、これは別な JavaScript モジュールに実装します。より高度な WebGL の概念に踏み込むにつれて、この操作はより多くの（そしてより複雑な） 3D オブジェクトを作成するために拡張していきます。

**メモ:** "init-buffers.js" という新しいファイルを作成し、以下の内容にしてください。

```js
function initBuffers(gl) {
  const positionBuffer = initPositionBuffer(gl);

  return {
    position: positionBuffer,
  };
}

function initPositionBuffer(gl) {
  // 正方形の位置を保存するためのバッファーを作成する
  const positionBuffer = gl.createBuffer();

  // positionBuffer をバッファー操作の適用対象として
  // 選択する
  gl.bindBuffer(gl.ARRAY\_BUFFER, positionBuffer);

  // 正方形の頂点座標の配列を作成する
  const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];

  // 形を作るために頂点座標のリストを WebGL に渡す。
  // JavaScript の配列から Float32Array に変換したもので
  // バッファーを埋める。
  gl.bufferData(gl.ARRAY\_BUFFER, new Float32Array(positions), gl.STATIC\_DRAW);

  return positionBuffer;
}

export { initBuffers };
```

このルーチンは、この例のシーンの基本的な性質を考えると、かなり単純なものです。まず、`gl` オブジェクトの `createBuffer()` メソッドを呼び出してバッファーを取得し、そこに頂点の位置を格納します。次に、`bindBuffer()` メソッドを呼び出すことで、このバッファーをコンテキストに関連づけます。

それが完了すると、正方形の各頂点の座標を持った JavaScript の配列を作成します。そしてこの配列を浮動小数点数の配列へと変換し、オブジェクトの頂点座標を作るために、 `gl` オブジェクトの `bufferData()` メソッドへと渡します。

シーンをレンダリングする
------------

シェーダーが確立され、位置が参照され、正方形の平面の頂点位置がバッファーに格納されたら、実際にシーンをレンダリングすることができます。これは `drawScene()` 関数で行いますが、これも別の JavaScript モジュールで実装します。

**メモ:** "draw-scene.js" という新しいファイルを作成し、以下の内容にしてください。

```js
function drawScene(gl, programInfo, buffers) {
  gl.clearColor(0.0, 0.0, 0.0, 1.0); // 黒でクリア、完全に不透明
  gl.clearDepth(1.0); // 全てをクリア
  gl.enable(gl.DEPTH\_TEST); // 深度テストを有効化
  gl.depthFunc(gl.LEQUAL); // 奥にあるものは隠れるようにする

  // 描写を行う前にキャンバスをクリアする

  gl.clear(gl.COLOR\_BUFFER\_BIT | gl.DEPTH\_BUFFER\_BIT);

  // カメラで遠近感を再現するために使用される特殊な行列、
  // 視点マトリクスを作成する。
  // 視野角は 45 度、幅と高さの比率はキャンバスの
  // 表示サイズに合わせる。
  // カメラから 0.1 単位から 100 単位までのオブジェクトのみを
  // 表示するようにする。

  const fieldOfView = (45 \* Math.PI) / 180; // ラジアンにする
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const zNear = 0.1;
  const zFar = 100.0;
  const projectionMatrix = mat4.create();

  // メモ: glmatrix.js は常に第一引数として結果の
  // 受け取り先を取る
  mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);

  // 描写位置をシーンの中央である "identity" ポイントにセットする
  const modelViewMatrix = mat4.create();

  // そして描写位置を正方形を描写し始めたい位置に少しだけ動かす
  mat4.translate(
    modelViewMatrix, // 変換結果の格納先
    modelViewMatrix, // 変換する行列
    [-0.0, 0.0, -6.0],
  ); // 変換量

  // WebGL にどのように座標バッファーから座標を
  // vertexPosition 属性に引き出すか伝える。
  setPositionAttribute(gl, buffers, programInfo);

  // WebGL に、描画にこのプログラムを使用するよう伝える
  gl.useProgram(programInfo.program);

  // シェーダーユニフォームを設定
  gl.uniformMatrix4fv(
    programInfo.uniformLocations.projectionMatrix,
    false,
    projectionMatrix,
  );
  gl.uniformMatrix4fv(
    programInfo.uniformLocations.modelViewMatrix,
    false,
    modelViewMatrix,
  );

  {
    const offset = 0;
    const vertexCount = 4;
    gl.drawArrays(gl.TRIANGLE\_STRIP, offset, vertexCount);
  }
}

// WebGL に、位置バッファーから位置を
// vertexPosition 属性に引き出す方法を指示する。
function setPositionAttribute(gl, buffers, programInfo) {
  const numComponents = 2; // 反復処理ごとに 2 つの値を取り出す
  const type = gl.FLOAT; // バッファ内のデータは 32 ビット浮動小数点数
  const normalize = false; // 正規化なし
  const stride = 0; // 一組の値から次の値まで何バイトで移動するか
  // 0 = 上記の type と numComponents を使用
  const offset = 0; // バッファー内の何バイト目から開始するか
  gl.bindBuffer(gl.ARRAY\_BUFFER, buffers.position);
  gl.vertexAttribPointer(
    programInfo.attribLocations.vertexPosition,
    numComponents,
    type,
    normalize,
    stride,
    offset,
  );
  gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
}

export { drawScene };
```

最初の手順は、キャンバスを背景色にクリアすることで、次にカメラの視点位置を設定します。視野角は 45 度、幅と高さの比率はキャンバスの表示寸法と一致するように設定します。また、カメラから 0.1 〜 100 単位の距離のオブジェクトだけをレンダリングするように指定します。

次に、正方形の平面の位置を設定します。同じ位置を読み込んで、カメラから 6 単位だけ遠ざかるように対応させます。その後、正方形の頂点バッファーとシェーダーが使用する `aVertexPosition` という属性を結びつけ、そこからデータを引き出す方法を WebGL に指示します。最後に `drawArrays()` メソッドを呼び出してオブジェクトを描きます。

最後に、 `initBuffers()` と `drawScene()` を呼び出してみましょう。

**メモ:** このコードを "webgl-demo.js" ファイルの先頭に追加してください。

```js
import { initBuffers } from "./init-buffers.js";
import { drawScene } from "./draw-scene.js";
```

**メモ:** このコードを `main()` 関数の末尾に追加してください。

```js
// ここでは、これから描画するすべてのオブジェクトを
// 構築するルーチンを呼び出す
const buffers = initBuffers(gl);

// シーンを描画
drawScene(gl, programInfo, buffers);
```

結果は次のようになります。

完全なコードを見る | このデモを新しいページで開く

行列ユーティリティの操作
------------

行列の操作は複雑に見えるかもしれませんが、一歩ずつ進めていけば、実はとても簡単です。一般に、人々は自分で書くよりも行列ライブラリーを使用します。この例では、人気のある glMatrix ライブラリーを使用しています。

### 関連情報

* Matrices (WebGLFundamentals)
* Matrices (Wolfram MathWorld)
* 行列 （ウィキペディア）
* « 前のページ
* 次のページ »


Clients: get() メソッド - Web API

Clients: get() メソッド
===================

**`get()`** は`Clients` インターフェイスのメソッドは、指定された `id` と一致するサービスワーカークライアントを取得し、`Promise` で返します。

構文
--

```js
get(id)
```

### 引数

`id`

取得するクライアントの id を表す文字列です。

### 返値

`Client` オブジェクトまたは `undefined` に解決される `Promise`。

例
-

```js
self.clients.get(id).then((client) => {
  self.clients.openWindow(client.url);
});
```

仕様書
---

| Specification |
| --- |
| Service Workers # clients-get |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Clients: claim() method - Web API

Clients: claim() method
=======================

**`claim()`** は `Clients` インターフェイスのメソッドで、アクティブなサービスワーカーが自身のスコープ（`scope`）内のすべてのクライアントのコントローラー（`controller`）として自分自身を設定できます。
 これにより、このサービスワーカーによって制御されるようになる `navigator.serviceWorker` の中のクライアントで "`controllerchange`" イベントが発行されます。

サービスワーカーが最初に登録されると、ページは次に読み込まれるまでそれを使用しません。 `claim()` メソッドを使用すると、これらのページがすぐに制御されます。 これにより、サービスワーカーがネットワークを介して、または別のサービスワーカーを介して定期的に読み込まれるページを制御することに注意してください。

構文
--

```js
claim()
```

### 引数

なし。

### 返値

`undefined` に解決される `Promise`。

例
-

次の例では、`claim()` をサービスワーカーの "`activate`" イベントリスナー内で使用しているため、同じスコープに読み込まれたクライアントは、フェッチがこのサービスワーカーを通過する前に再読み込みする必要がありません。

```js
self.addEventListener("activate", (event) => {
  event.waitUntil(clients.claim());
});
```

仕様書
---

| Specification |
| --- |
| Service Workers # clients-claim |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* サービスワーカーの使用
* サービスワーカーのライフサイクル
* サービスワーカーは利用できますか？（英語）
* `Promises`
* `self.skipWaiting()` - サービスワーカーの待機フェーズをスキップする


Clients: openWindow() メソッド - Web API

Clients: openWindow() メソッド
==========================

**`openWindow()`** は `Clients` インターフェイスのメソッドで、新しい最上位の閲覧コンテキストを作成し、指定された URL をロードします。 呼び出し元のスクリプトにポップアップを表示する権限がない場合、`openWindow()` は `InvalidAccessError` 例外を発生させます。

Firefox では、このメソッドは、通知クリックイベントの結果として呼び出された場合にのみ、ポップアップを表示できます。

Android 版 Chrome では、メソッドは代わりに、以前にユーザーのホーム画面に追加されたスタンドアロンのウェブアプリによって提供される既存の閲覧コンテキストで URL を開く場合があります。 最近では、これは Windows 版 Chrome でも機能します。

構文
--

```js
openWindow(url)
```

### 引数

`url`

ウィンドウで開くクライアントの URL を表す文字列。 通常、この値は呼び出し元のスクリプトと同じオリジンの URL でなければなりません。

### 返値

URL がサービスワーカーと同じオリジンからのものである場合は `WindowClient` オブジェクトに解決され、それ以外の場合は null 値に解決される `Promise`。

例
-

```js
// 適切な場合は OS に通知を送ります
if (self.Notification.permission === "granted") {
  const notificationObject = {
    body: "ここをクリックしてメッセージを表示してください。",
    data: { url: `${self.location.origin}/some/path` },
    // data: { url: 'http://example.com' },
  };
  self.registration.showNotification(
    "メッセージがあります！",
    notificationObject,
  );
}

// 通知クリックイベントリスナー
self.addEventListener("notificationclick", (e) => {
  // 通知ポップアウトを閉じます
  e.notification.close();
  // すべての Window クライアントを取得します
  e.waitUntil(
    clients.matchAll({ type: "window" }).then((clientsArr) => {
      // 対象 URL に一致するウィンドウタブが既に存在する場合は、それにフォーカスします。
      const hadWindowToFocus = clientsArr.some((windowClient) =>
        windowClient.url === e.notification.data.url
          ? (windowClient.focus(), true)
          : false,
      );
      // それ以外の場合は、適切な URL への新しいタブを開いてフォーカスします。
      if (!hadWindowToFocus)
        clients
          .openWindow(e.notification.data.url)
          .then((windowClient) => (windowClient ? windowClient.focus() : null));
    }),
  );
});
```

仕様書
---

| Specification |
| --- |
| Service Workers # clients-openwindow |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Clients: matchAll() メソッド - Web API

Clients: matchAll() メソッド
========================

**`matchAll()`** は `Clients` インターフェイスのメソッドで、サービスワーカークライアント（`Client`）オブジェクトのリストの `Promise` を返します。 関連するサービスワーカーのオリジンと同じオリジンを持つすべてのサービスワーカークライアントを返すには、`options` 引数を含めます。 オプションが含まれていなかった場合、このメソッドは、サービスワーカーによって制御されるサービスワーカークライアントのみを返します。

構文
--

```js
matchAll()
matchAll(options)
```

### 引数

`options` 省略可

照合操作のオプションを設定できるオプションオブジェクト。 利用可能なオプションは次のとおりです。

`includeUncontrolled`

論理値です。`true` に設定すると、照合操作は、現在のサービスワーカーと同じオリジンを共有するすべてのクライアントを返します。 それ以外の場合は、現在のサービスワーカーによって制御されているサービスワーカークライアントのみを返します。 既定値は `false` です。

`type`

照合するクライアントの種類を設定します。 使用可能な値は、`"window"`、`"worker"`、`"sharedworker"`、`"all"` です。 既定値は `"window"` です。

### 返値

`Client` オブジェクトの配列に解決される `Promise`。 Chrome 46/Firefox 54 以降では、このメソッドはクライアントを直近でフォーカスした順序で返し、仕様どおりに修正されました

例
-

```js
clients.matchAll(options).then((clientList) => {
  for (const client of clientList) {
    if (client.url === "index.html") {
      clients.openWindow(client);
      // または、一致するクライアントに関係する何かを行う
    }
  }
});
```

仕様書
---

| Specification |
| --- |
| Service Workers # clients-matchall |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


OscillatorNode.detune - Web API

OscillatorNode.detune
=====================

`detune` は `OscillatorNode` インターフェイスのプロパティで、 a-rate の `AudioParam` であり、発振器の離調をセントで表します。

>
> **Note:** 返される `AudioParam` は読み取り専用ですが、それが表す値は読み取り専用ではありません。
>
>
>

値
-

a-rate の `AudioParam` です。

例
-

以下の例では、発振器ノードを作成するための `AudioContext` の基本的な使い方を示しています。応用例や情報については、この Violent Theremin demo デモ（関連するコードは app.js を参照）をご覧ください。

```js
// create web audio api context
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

// create Oscillator node
const oscillator = audioCtx.createOscillator();

oscillator.type = "square";
oscillator.frequency.setValueAtTime(440, audioCtx.currentTime); // value in hertz
oscillator.detune.setValueAtTime(100, audioCtx.currentTime); // value in cents
oscillator.start();
```

仕様書
---

| Specification |
| --- |
| Web Audio API # dom-oscillatornode-detune |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* ウェブオーディオ API の使用


OscillatorNode.type - Web API

OscillatorNode.type
===================

**`type`** は `OscillatorNode` インターフェイスのプロパティで、発振器が出力する波形の形状を指定します。利用できる一般的な波形がいくつかあり、カスタム波形を指定するオプションもあります。波形は、生成される音色に影響を与えます。

値
-

発振器の波形を指定する文字列です。利用できる値は以下のとおりです。

`sine`

正弦波です。これが既定値です。

`square`

矩形波で、デューティサイクルが 0.5 のものです。すなわち、それぞれの周期で半分が「高」の信号です。

`sawtooth`

のこぎり波です。

`triangle`

三角波です。

`custom`

カスタム波形です。手動で `type` を `custom` に設定することはありません。代わりに `setPeriodicWave()` メソッドを使用して、波形を表すデータを使用してください。そうすることで、自動的に `type` が `custom` に設定されます。

### 例外

`InvalidStateError` `DOMException`

値 `custom` が指定された場合に発生します。カスタム波形を設定するには `setPeriodicWave()` を呼び出すだけです。そうすることで、自動的に型が設定されます。

例
-

以下の例は、発振器ノードを作成するための `AudioContext` の基本的な使用方法を示しています。応用例については、 Violent Theremin demo を見てみてください。（関連するコードは app.js を参照）。

```js
// create web audio api context
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

// create Oscillator node
const oscillator = audioCtx.createOscillator();

oscillator.type = "square";
oscillator.frequency.setValueAtTime(440, audioCtx.currentTime); // value in hertz
oscillator.start();
```

仕様書
---

| Specification |
| --- |
| Web Audio API # dom-oscillatornode-type |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* ウェブオーディオ API の使用


OscillatorNode.setPeriodicWave() - Web API

OscillatorNode.setPeriodicWave()
================================

**`setPeriodicWave()`** は `OscillatorNode` インターフェイスのメソッドで、 `PeriodicWave` を指し、発振器の出力とすることができる周期的な波形を定義します。 `type` が `custom` のときに使用されます。

構文
--

```js
setPeriodicWave(wave)
```

### 引数

`wave`

`PeriodicWave` オブジェクトで、発振器の出力とする周期的な波形を表します。

### 返値

なし (`undefined`)。

例
-

次の例は `createPeriodicWave()` の簡単な使い方を示し、周期波から正弦波を再現しています。

```js
const real = new Float32Array(2);
const imag = new Float32Array(2);
const ac = new AudioContext();
const osc = ac.createOscillator();

real[0] = 0;
imag[0] = 0;
real[1] = 1;
imag[1] = 0;

const wave = ac.createPeriodicWave(real, imag);

osc.setPeriodicWave(wave);

osc.connect(ac.destination);

osc.start();
osc.stop(2);
```

これは、基本音だけを含む音は定義上正弦波であるため、うまく動作します。

ここでは、 2 つの値を持つ `PeriodicWave` を作成しています。最初の値は DC オフセットで、これは発振器が開始する値です。ここでは、カーブを [-1.0; 1.0] 範囲の中央から開始したいので、0 が適しています。

2 つ目以降の値はサイン成分、コサイン成分です。これは、時間領域の値から周波数領域の値を取得するフーリエ変換の結果と考えることができます。ここでは `createPeriodicWave()` で、周波数を指定すると、ブラウザーは逆フーリエ変換を行い、発振器の周波数の時間領域のバッファーが取得されるようになっています。ここでは、基本音にフルボリューム (1.0) のひとつの成分だけを設定しているので、正弦波が得られます。

仕様書
---

| Specification |
| --- |
| Web Audio API # dom-oscillatornode-setperiodicwave |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* ウェブオーディオ API の使用
* AudioContext.createPeriodicWave


OscillatorNode.frequency - Web API

OscillatorNode.frequency
========================

**`frequency`** は `OscillatorNode` インターフェイスのプロパティで、 a-rate の `AudioParam` であり、これは発振器の周波数をヘルツ単位で表します。

>
> **Note:** 返される `AudioParam` は読み取り専用ですが、それが表す値は読み取り専用ではありません。
>
>
>

値
-

a-rate の `AudioParam` です。

例
-

以下の例は、発振器ノードを作成するための `AudioContext` の基本的な使用方法を示しています。応用例については、 Violent Theremin demo を見てみてください。（関連するコードは app.js を参照）。

```js
// create web audio api context
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

// create Oscillator node
const oscillator = audioCtx.createOscillator();

oscillator.type = "square";
oscillator.frequency.setValueAtTime(440, audioCtx.currentTime); // value in hertz
oscillator.start();
```

仕様書
---

| Specification |
| --- |
| Web Audio API # dom-oscillatornode-frequency |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* ウェブオーディオ API の使用


OscillatorNode() - Web API

OscillatorNode()
================

**`OscillatorNode()`** は ウェブオーディオ API のコンストラクターで、正弦波のような周期的な波形を表現する `AudioNode` オブジェクトを新しく作成し、オプションでノードのプロパティの値を指定したオブジェクトの値に合わせて設定することができます。

プロパティの既定値が受け入れられる場合 は、代わりに `BaseAudioContext.createOscillator()` ファクトリーメソッドを任意で使用することができます。 AudioNode の作成をご覧ください。

構文
--

```js
new OscillatorNode(context, options)
```

### 引数

`context`

`AudioContext` への参照です。

`options` 省略可

この発振器ノードのプロパティの初期値を指定するオブジェクトです。プロパティがオブジェクトから省略されると、ドキュメントにある通りの既定値を受け取ります。

`type`

ノードが生成する波形を指定します。有効な値は '`sine`', '`square`', '`sawtooth`', '`triangle`', '`custom`'です。既定値は '`sine`' です。

`detune`

指定された量だけ `frequency` をずらす離調値（セント単位）です。既定値では 0 です。

`frequency`

周期的な波形の周波数（単位はヘルツ）です。既定で 440 です。

`periodicWave`

任意の周期の波形を `PeriodicWave` オブジェクトで記述したものです。

`channelCount`

ノードへの任意の入力にアップミキシングおよびダウンミキシング接続するときに、何チャンネル使用するかを決めるために使用する整数を表わします。（詳細は `AudioNode.channelCount` を参照してください）。その使用方法と正確な定義は `channelCountMode` の値に依存します。

`channelCountMode`

ノードの入力と出力の間でチャンネルを照合する方法を記述する列挙型の値を表します。（既定値を含む詳細な情報は `AudioNode.channelCountMode` を参照してください。）

`channelInterpretation`

 チャンネルの意味を記述する列挙値を表します。この解釈により、音声のアップミキシングとダウンミキシングがどう行われるかを定義します。
 使用可能な値は `"speakers"` または `"discrete"` です。（既定値を含む詳細な情報は `AudioNode.channelCountMode` を参照してください。）

### 返値

新しい `OscillatorNode` オブジェクトのインスタンスです。

仕様書
---

| Specification |
| --- |
| Web Audio API # dom-oscillatornode-oscillatornode |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


ファイルのドラッグ & ドロップ - Web API

ファイルのドラッグ & ドロップ
================

HTML ドラッグ & ドロップインターフェイスは、ウェブアプリケーションでファイルをウェブページにドラッグ & ドロップできるようにするものです。この記事では、基礎となるプラットフォームのファイルマネージャーからドラッグされ、ウェブページにドロップされた 1 つまたは複数のファイルを、アプリケーションで受け入れる方法について記述しています。

ドラッグ & ドロップの主な手順は、ドロップゾーン（ファイルドロップの対象要素）を定義することと、 `drop` および `dragover` イベントのイベントハンドラーを定義することです。これらの手順は、コード例を含め、下記で記述します。完全なソースコードは MDN のドラッグ & ドロップリポジトリーで利用できます（プルリクエストや issue を歓迎します）。

なお、 HTML ドラッグ & ドロップでは、ファイルのドラッグ & ドロップに対応するために 2 つの異なる形の API を定義しています。一方の API は `DataTransfer` インターフェイスで、もう一方の API は `DataTransferItem` と `DataTransferItemList` インターフェイスです。この例では、両方の API の使用方法を説明します （そして、Gecko 固有のインターフェースは一切使用しません）。

ドロップゾーンの定義
----------

`drop` イベントの対象となる要素には、 `ondrop` イベントハンドラーが必要です。以下のコードでは、 `<div>` 要素を使用してこの処理を行う方法を示しています。

```html
<div id="drop\_zone" ondrop="dropHandler(event);">
  <p>Drag one or more files to this <i>drop zone</i>.</p>
</div>
```

通常、アプリケーションでは、ドロップ対象の要素に `dragover` イベントハンドラーを記述し、そのハンドラーによって、ブラウザーの既定のドラッグ動作をオフにします。このハンドラーを追加するには、 `ondragover` イベントハンドラーを記載する必要があります。

```html
<div
 id="drop\_zone"
 ondrop="dropHandler(event);"
 ondragover="dragOverHandler(event);">
  <p>Drag one or more files to this <i>drop zone</i>.</p>
</div>
```

最後に、アプリケーションはドロップ先の要素のスタイルを設定して、要素がドロップゾーンであることを視覚的に示したい場合があります。この例では、 drop ドロップ先の要素は以下のスタイル設定を使用します。

```css
#drop\_zone {
  border: 5px solid blue;
  width: 200px;
  height: 100px;
}
```

**メモ:** `dragstart` および `dragend` イベントは、 OS からブラウザーへファイルをドラッグしているときには発生しません。 OS のファイルがブラウザーへドラッグされてきたことを検出するには、 `dragenter` および `dragleave` を使用してください。

ドロップの処理
-------

`drop` イベントは、ユーザーがファイルをドロップしたときに発行されます。以下のドロップハンドラーでは、ブラウザーが `DataTransferItemList` インターフェイスを使用している場合は `getAsFile()` メソッドを使用して各ファイルにアクセスし、そうでない場合は `DataTransfer` インターフェイスの `files` プロパティを使用して各ファイルにアクセスしています。

この例では、ドラッグしたそれぞれのファイル名をコンソールに書き出す方法を説明します。実際のアプリケーションでは、 `File API` を使用してファイルを処理したいかもしれません。

この例では、ファイルでないドラッグアイテムは無視されることに注意してください。

```js
function dropHandler(ev) {
  console.log("File(s) dropped");

  // 既定の動作で防ぐ（ファイルが開かれないようにする）
  ev.preventDefault();

  if (ev.dataTransfer.items) {
    // DataTransferItemList インターフェイスを使用して、ファイルにアクセスする
    [...ev.dataTransfer.items].forEach((item, i) => {
      // ドロップしたものがファイルでない場合は拒否する
      if (item.kind === "file") {
        const file = item.getAsFile();
        console.log(`… file[${i}].name = ${file.name}`);
      }
    });
  } else {
    // DataTransfer インターフェイスを使用してファイルにアクセスする
    [...ev.dataTransfer.files].forEach((file, i) => {
      console.log(`… file[${i}].name = ${file.name}`);
    });
  }
}
```

ブラウザー既定のドラッグ動作を阻止する
-------------------

以下の `dragover` イベントハンドラーは `preventDefault()` を呼び出して、ブラウザーの既定のドラッグ & ドロップハンドラーをオフにしています。

```js
function dragOverHandler(ev) {
  console.log("File(s) in drop zone");

  // 既定の動作で防ぐ（ファイルが開かれないようにする）
  ev.preventDefault();
}
```

関連情報
----

* HTML ドラッグ & ドロップ API (概要)
* ドラッグ操作
* HTML Living Standard: Drag and Drop


推奨されるドラッグのデータ型 - Web API

推奨されるドラッグのデータ型
==============

HTML ドラッグ & ドロップ API は、プレーンテキスト、URL、HTML コード、ファイルなど、さまざまな形式のデータのドラッグをサポートしています。この記事では、一般的なドラッグ可能なデータ形式のベストプラクティスについて説明しています。

**メモ:** この記事の中で、 `mozSetDataAt()` のような `moz` の接頭辞を持つすべてのメソッドとプロパティは、Gecko ベースのブラウザーでのみ動作します。

テキストのドラッグ
---------

テキストをドラッグする時は、`text/plain` 型を使用してください。2 番目の引数には、ドラッグした文字列を指定します。例えば、以下のようになります。

```js
event.dataTransfer.setData("text/plain", "これはドラッグされるテキストです");
```

ウェブページのテキストボックスや選択範囲の文字列のドラッグは、ブラウザーが自動的に処理を行うので、自分で処理する必要はありません。

そのデータが代替テキストでは表現できない物である場合を除いて、他のデータ形式に対応していないアプリケーションやドロップ対象のためのフォールバック用に、常に `text/plain` 型のデータを提供することをおすすめします。そのために、データを追加する時には最後に `text/plain` 型のデータを登録しておいてください。

メモ: 古いコードにおいて、`text/unicode` や `Text` といった型の記述を見かけることがあるかもしれません。これらはどちらも `text/plain` と等しく扱われ、プレーンテキスト型のデータとして登録・取得されます。

リンクのドラッグ
--------

ドラッグされたハイパーリンクには、`text/uri-list` と `text/plain` の*2 種類*のデータを含める必要があります。どちらの形式もリンクの URL をデータに使用しなければなりません。例えば、以下のようになります。

```js
const dt = event.dataTransfer;
dt.setData("text/uri-list", "https://www.mozilla.org");
dt.setData("text/plain", "https://www.mozilla.org");
```

`text/uri-list` 型のフォールバックとして、`text/plain` 型を最後に設定します。

メモ: URL 用の型は `uri-list` で、L ではなく I であることに注意してください。

複数のリンクをドラッグするには、それぞれのリンクを `text/uri-list` データ内で CRLF 改行で区切ってください。ナンバー記号 (`#`) で始まる行はコメントで、有効な URL として扱われません。コメントは、リンクの目的を示したり、リンクに関連づけられたタイトルを保持したりする目的で利用できます

**警告:** 複数のリンクのための `text/plain` 型のフォールバックは、すべての URL を含むべきですが、コメントを含めるべきではありません。

例えば、以下のサンプル `text/uri-list` データには、2 つのリンクと 1 つのコメントが含まれています。
```

http://www.mozilla.org
# 2 つ目のリンク
http://www.example.com
```

ドロップされたリンクを取得する時は、コメントを含めて複数のリンクをドラッグした場合の処理を確実に行ってください。便宜上、`text/uri-list` 型のデータ内の最初の有効なリンクを参照するために、特別な型として `URL` を使用することができます。

**警告:** `URL` 型でデータを追加しないでください - それを行うと、代わりに `text/uri-list` 型のデータとして登録されます。

```js
const url = event.dataTransfer.getData("URL");
```

Mozilla 特有の型として、`text/x-moz-url` 型のデータを見かけることがあるかもしれません。この型が表示される場合は、`text/uri-list` 型の前に表示されるはずです。この型のデータは、リンクの URL に続いてリンクのタイトルが保持されており。例えば、以下のようになります。
```

http://www.mozilla.org
Mozilla
http://www.example.com
Example
```

HTML と XML のドラッグ
----------------

HTML の内容は `text/html` 型を使用します。この型のデータはドラッグされる HTML をシリアライズしたものであるべきです。具体的には、この型のデータとして登録されるのに適した値は、要素の ``innerHTML`` プロパティの値です。

XML の内容は `text/xml` 型を使用することができますが、内容は整形式の XML に変換しておくべきです。

また、`text/plain` 型を使用して、HTML または XML のプレーンテキストでの表現を提供することもできます。その場合のデータは単純なテキストであるべきで、タグや属性などのソース文字列を含めるべきではありません。例えば、以下のようになります。

```js
const dt = event.dataTransfer;
dt.setData("text/html", "こんにちは、<strong>見知らぬ人</strong>");
dt.setData("text/plain", "こんにちは、見知らぬ人");
```

ファイルのドラッグ
---------

ローカルのファイルは `application/x-moz-file` 型で、 `nsIFile` のオブジェクトとしてドラッグされます。特権を持っていないウェブページでは、この型のデータを取得することも変更することもできません。

ファイルを文字列にはできないため、データを登録するには `mozSetDataAt()` メソッドを使用する必要があります。同様に、データを取得するには `mozGetDataAt()` メソッドを使わなくてはなりません。

```js
event.dataTransfer.mozSetDataAt("application/x-moz-file", file, 0);
```

可能であれば、`text/uri-list` 型と `text/plain` 型の両方を使ってファイルの URL を含めてください。これらの型は最後に登録されるべきで、それによって、 `application/x-moz-file` 型は優先度の高い、より適切な型となります。

複数のファイルは、データ転送中に複数のアイテムとしてドロップ中に受信されます。これについての詳細は、複数の項目のドラッグ & ドロップを参照してください。

以下の例は、ドロップしたファイルを受信するための領域を作成する方法を示しています。

```html
<listbox
 ondragenter="return checkDrag(event)"
 ondragover="return checkDrag(event)"
 ondrop="doDrop(event)" />

<script>
 function checkDrag(event) {
 return event.dataTransfer.types.contains("application/x-moz-file");
 }

 function doDrop(event) {
 const file = event.dataTransfer.mozGetDataAt("application/x-moz-file", 0);
 if (file instanceof Components.interfaces.nsIFile) {
 event.currentTarget.appendItem(file.leafName);
 }
 }
</script>
```

この例では、データ転送に `application/x-moz-file` 型が含まれている場合にのみ、イベントが false を返します。ドロップイベントの間、ファイル型に関連付けられたデータが取得され、ファイルのファイル名がリストボックスに追加されます。`mozGetDataAt()` メソッドは、`nsISupports` を返すので、nsIFile 型に変換するために、ここでは `instanceof` 演算子を使用していることに注意してください。これは、誤ってファイルではない型のデータを登録した場合のためのチェックとしても有効です。

### DataTransfer.types の更新

最新の仕様では、`DataTransfer.types` は `DOMStringList` ではなく、凍結された文字列の配列返すようになっています（これは Firefox 52 以上でサポートされています）。

その結果、contains メソッドはもう機能しません。代わりに includes メソッドを使用し、以下のようなコードで特定の形式のデータが提供されているかどうかを確認する必要があります。

```js
if ([...event.dataTransfer.types].includes("text/html")) {
  // 実行するコード
}
```

機能検出を使用して、どのメソッドがサポートされている`型`なのかを判断し、適切なコードを実行することができます。

画像のドラッグ
-------

画像の直接のドラッグは一般的ではありません。そのため、Mozilla は Mac と Linux での画像の直接のドラッグをサポートしていません。その代わり、画像は通常その URL としてドラッグされます。そのためには、他の URL と同様に `text/uri-list` 型を使用します。データは、画像の URL、または画像が Web 上やディスク上に無い場合は データ URL である必要があります。

リンクと同様に、`text/plain` 型のデータには URL も含まれている必要があります。しかし、データ URL は通常のテキストの内容には有用ではないので、このような状況では `text/plain` 型のデータを除外した方がよいでしょう。

Chrome などの特権的なコードでは、画像の種類に応じて、`image/jpeg`、`image/png`、`image/gif` のいずれかの形式を使用することもできます。データは `nsIInputStream` インターフェイスを実装したオブジェクトでなければなりません。このストリームが読み込まれる時には、そのファイル形式での画像のデータビットを提供しなければなりません。

画像がディスク上にある場合は、`application/x-moz-file` 型も含める必要があります。実際に、これは画像ファイルをドラッグする一般的なやり方です。

最も適切なデータ形式からそうでない形式まで、正しい順序でデータを登録することが重要です。最初に `image/jpeg` のような標準的な画像型を設定し、次に `application/x-moz-file` 型を設定します。次に、`text/uri-list` 型を設定し、最後に `text/plain` 型を設定します。例えば、以下のようになります。

```js
const dt = event.dataTransfer;
dt.mozSetDataAt("image/png", stream, 0);
dt.mozSetDataAt("application/x-moz-file", file, 0);
dt.setData("text/uri-list", imageurl);
dt.setData("text/plain", imageurl);
```

`mozGetDataAt()` メソッドは、テキスト以外のデータに使用されることに注意してください。内容によっては、これらの型の一部しか含まれていない場合があるので、ドロップされた画像を受信する時には、どの型が利用可能になっているかを確認することが重要です。

ノードのドラッグ
--------

ドキュメント内のノードや要素は、`application/x-moz-node` 型を使ってドラッグすることができます。型のデータは DOM ノードでなければなりません。これにより、ドロップ対象はドラッグが開始された実際のノードを受け取ることができます。ノードがドロップされていても、異なるドメインからの呼び出し元はそのノードにアクセスできないことに注意してください。

ノードの内容は常に `text/plain` 型の代替文字列で提供するべきです。

独自データのドラッグ
----------

独自の目的のために、他の型を使うこともできます。そのデータが特定のサイトやアプリケーションに固有のものでない限り、常に `text/plain` 型の代替文字列を含めるようにしてください。代替テキストを用意しなかった場合は、他の場所にドロップできなくなります。

OS のフォルダーにファイルをドラッグ
-------------------

既存のドラッグイベントセッションにファイルを追加したり、コードが対象フォルダーの場所の通知を受信したときに、オペレーティングシステム内のフォルダーに対してのドロップ操作だった場合、ファイルをディスクに書き出したい場合があります。これは拡張機能（またはその他の特権コード）でのみ動作し、`application/moz-file-promise` 型を使用する必要があります。次のサンプルでは、この高度なケースの概要を説明します。

```js
// currentEvent is an existing drag operation event

currentEvent.dataTransfer.setData("text/x-moz-url", URL);
currentEvent.dataTransfer.setData("application/x-moz-file-promise-url", URL);
currentEvent.dataTransfer.setData(
  "application/x-moz-file-promise-dest-filename",
  leafName,
);
currentEvent.dataTransfer.mozSetDataAt(
  "application/x-moz-file-promise",
  new dataProvider(success, error),
  0,
  Components.interfaces.nsISupports,
);

function dataProvider() {}

dataProvider.prototype = {
  QueryInterface(iid) {
    if (
      iid.equals(Components.interfaces.nsIFlavorDataProvider) ||
      iid.equals(Components.interfaces.nsISupports)
    )
      return this;
    throw Components.results.NS\_NOINTERFACE;
  },
  getFlavorData(aTransferable, aFlavor, aData, aDataLen) {
    if (aFlavor === "application/x-moz-file-promise") {
      const urlPrimitive = {};
      const dataSize = {};

      aTransferable.getTransferData(
        "application/x-moz-file-promise-url",
        urlPrimitive,
        dataSize,
      );
      const url = urlPrimitive.value.QueryInterface(
        Components.interfaces.nsISupportsString,
      ).data;
      console.log(`URL file original is = ${url}`);

      const namePrimitive = {};
      aTransferable.getTransferData(
        "application/x-moz-file-promise-dest-filename",
        namePrimitive,
        dataSize,
      );
      const name = namePrimitive.value.QueryInterface(
        Components.interfaces.nsISupportsString,
      ).data;

      console.log(`target filename is = ${name}`);

      const dirPrimitive = {};
      aTransferable.getTransferData(
        "application/x-moz-file-promise-dir",
        dirPrimitive,
        dataSize,
      );
      const dir = dirPrimitive.value.QueryInterface(
        Components.interfaces.nsILocalFile,
      );

      console.log(`target folder is = ${dir.path}`);

      const file = Cc["@mozilla.org/file/local;1"].createInstance(
        Components.interfaces.nsILocalFile,
      );
      file.initWithPath(dir.path);
      file.appendRelativePath(name);

      console.log(`output final path is = ${file.path}`);

      // now you can write or copy the file yourself…
    }
  },
};
```

関連情報
----

* HTML ドラッグ & ドロップ API (概要)
* ドラッグ操作
* 複数のアイテムのドラッグ & ドロップ
* HTML Living Standard: Drag and Drop


ドラッグ操作 - Web API

ドラッグ操作
======

以下は、ドラッグ & ドロップ操作が行われる時の各段階についての解説です。

**メモ:** この文書で記述されているドラッグ操作は `DataTransfer` インターフェイスを使用します。この文書では `DataTransferItem` インターフェイスや `DataTransferItemList` インターフェイスは説明*しません*。

draggable 属性
------------

ウェブページにおいては、既定のドラッグ & ドロップの挙動が使われる場合がいくつかあります。文字列の選択範囲、画像、リンクなどのドラッグなどがこれにあたります。画像かリンクがドラッグされた時は、画像もしくはリンク先の URL がドラッグデータとして設定され、ドラッグ操作が始まります。その他の要素は、既定のドラッグ操作が行われるためには選択範囲に含まれていなければなりません。実際の様子を確認するには、ウェブページの一部を選択して、その上でマウスのボタンを押下し、そのまま選択範囲をドラッグしてください。ドラッグ中、選択範囲の内容を半透明で描画した物がマウスポインターに伴って表示されるでしょう。ただしこの挙動は、ドラッグされたデータを加工するイベントリスナーが存在しない場合の、既定のドラッグの挙動によるものです。

HTML では、画像、リンク、選択範囲の上での既定の動作を除くと、既定でドラッグ可能な他の要素はありません。

上記以外の他の HTML 要素をドラッグできるようにするには、以下の 3 つのことをしなくてはなりません。

1. ドラッグできるようにしたい要素の `draggable` 属性の値を `"true"` に設定する。
2. `dragstart` イベントにリスナーを設定し、そのリスナーの中でドラッグデータを設定する。
3. 上記で定義されたリスナーの中でドラッグデータを設定する。

以下は、コンテンツの一部がドラッグできるようにする例です。

```html
<p draggable="true">このテキストはドラッグが<strong>できます</strong>。</p>
```

```js
const draggableElement = document.querySelector('p[draggable="true"]');

draggableElement.addEventListener("dragstart", (event) =>
  event.dataTransfer.setData("text/plain", "このテキストはドラッグができます"),
);
```

`draggable` 属性を `"true"` に設定すると、その要素はドラッグできるようになります。この属性が設定されていない、あるいは `"false"` に設定されている場合、その要素をドラッグする事はできず、代わりにテキストが選択されるでしょう。

`draggable` 属性は画像やリンクを含めてあらゆる要素に設定できます。ただし、画像とリンクについてだけは既定値が `true`となっていますので、実際にこれらの要素で使う場合は、要素をドラッグできないようにするために `draggable` 属性に `false` を設定するという場合がほとんどでしょう。

**メモ:** 要素がドラッグ可能になった場合、文字列やその要素に含まれている他の要素が、マウスによるクリックやドラッグなどの通常の操作では選択する事ができなくなることに注意してください。ユーザーが文字列を選択するには、通常の操作の代わりに、 `Alt` キーを押しながらマウスで選択するか、キーボードで操作を行う必要があります。

ドラッグ操作の開始
---------

この例では、 `dragstart` イベントのリスナーを `addEventListener()` メソッドで追加します。

```html
<p draggable="true">このテキストはドラッグが<strong>できます</strong>。</p>
```

```js
const draggableElement = document.querySelector('p[draggable="true"]');
draggableElement.addEventListener("dragstart", (event) =>
  event.dataTransfer.setData("text/plain", "このテキストはドラッグができます"),
);
```

ユーザーがドラッグを開始しようとした時、 `dragstart` イベントが発行されます。

この例では `dragstart` のリスナーは、ドラッグされる要素自身に追加されていますが、他の多くのイベントがそうであるようにドラッグイベントもバブリングしますので、より上位の祖先要素でイベントを監視することもできます。

`dragstart` イベントでは、以下で解説している**ドラッグデータ**、**フィードバック画像**、**ドラッグの効果**を設定することができます。ドラッグデータの指定は必須ですが、多くの状況では、フィードバック画像とドラッグの種類は既定のもので問題ありません。

ドラッグデータ
-------

すべてのドラッグイベントは、ドラッグデータを保持するための `dataTransfer` と呼ばれるプロパティを持っています (`dataTransfer` は `DataTransfer` オブジェクトの一つです)。

ドラッグが行われた際には、何をドラッグするのかを識別するためのデータをドラッグに関連付ける必要があります。例えば、テキストボックス内で選択されたテキストがドラッグされた場合、ドラッグデータアイテムに関連付けられたデータはテキストそのものです。同様に、ウェブページ上のリンクがドラッグされた場合、ドラッグデータにはリンクの URL が含まれます。

`ドラッグデータ`には、データの型 (または形式) とデータの値の 2 つの情報が含まれています。形式は型の文字列 (テキストデータの場合は `text/plain` など) で、値はテキストの文字列です。ドラッグの開始時に、型とデータを指定してデータを追加します。ドラッグ中、 `dragenter` および `dragover` イベントのイベントリスナーでは、ドラッグされるデータのデータ型を使って、ドロップが許可されているかどうかをチェックします。たとえば、リンクを受け付けるドロップターゲットでは、 `text/uri-list` というデータ型がチェックされます。ドロップイベントが発生すると、リスナーはドラッグされたデータを取得し、ドロップ位置に挿入します。

ドラッグデータの `types` プロパティは、 `text/plain` や `image/jpeg` のような MIME タイプの文字列のリストを返します。独自の型を作成することもできます。よく使用される型は、推奨されるドラッグ型の記事に記載されています。

一つのドラッグ操作で、複数の異なる形式のデータを提供できます。この仕組みにより、独自の形式や、その形式のデータを受け取れない要素向けのフォールバック用の形式など、データをより適切な形式で引き渡すことができます。通常、最後のフォールバック先として使われる形式は、 `text/plain` 型として表される普通のテキストデータです。このデータは元のテキストの単純な文字列となるでしょう。

データを `dataTransfer` に設定するには、 `setData()` メソッドを使います。このメソッドは、次の例のようにデータの型とデータの値の 2 つの引数を取ります。

```js
event.dataTransfer.setData("text/plain", "ドラッグされたテキスト");
```

この例では、データの値は「ドラッグされたテキスト」で、形式は `text/plain` です。

データは複数の形式で提供できます。これを実現するには、異なる形式を指定して `setData()` メソッドを複数回呼び出します。最も具体的な形式から、具体的でない形式に向けて呼び出します。

```js
const dt = event.dataTransfer;
dt.setData("application/x.bookmark", bookmarkString);
dt.setData("text/uri-list", "https://www.mozilla.org");
dt.setData("text/plain", "https://www.mozilla.org");
```

これは、 3 つの異なる型のデータを追加する例です。最初の型の `application/x.bookmark` は独自の型です。他のアプリケーションはこの型に対応していないでしょうが、同じウェブサイトやアプリケーションの中の領域同士でのドラッグでは、このような独自の形式を利用できます。

また、他の型でもデータを提供することで、このような独自形式に対応していない他のアプリケーション向けにも、代替の形式でドラッグできるようになります。 `application/x.bookmark` 型はそのアプリケーションの中ではより使いやすく詳細な情報を提供できますが、他の型で渡されるデータは、単純な 1 つの URL もしくは文字列となります。

なお、この例では `text/uri-list` と `text/plain` も同じデータを含んでいます。このようにすることが多いのですが、こうしなければならない訳ではありません。

同じ形式で 2 回データを登録すると、古いデータは新しいデータによって置き換えられますが、データの形式の登録の順番自体は古いデータを登録した時のままになります。

登録したデータは `clearData()` メソッドによって削除できます。このメソッドは、削除するデータの形式を引数として求めます。

```js
event.dataTransfer.clearData("text/uri-list");
```

`clearData()` メソッドの引数によるデータ形式の指定は省略可能です。データの形式が指定されなかった時は、すべての型のデータが削除されます。ドラッグ開始時にデータが 1 つも登録されなかった場合、もしくは後の処理で全てのデータが削除された場合、ドラッグ操作は発生しません。

ドラッグのフィードバック画像の設定
-----------------

ドラッグが行われた時、ドラッグ元 (`dragstart` イベントが発行された要素) を元にして OS によって画像が生成され (例えば Windows では半透明の画像になります)、ドラッグしている間マウスポインターと一緒に表示されます。この画像は自動的に生成されるため、あなたが用意する必要はありません。しかし、 `setDragImage()` によって、独自のドラッグ中のフィードバック画像を指定することができます。

```js
event.dataTransfer.setDragImage(image, xOffset, yOffset);
```

3 つの引数が必要です。一つ目は、画像への参照です。この参照は、通常は `<img>` 要素ですが、 `<canvas>` やその他の要素でもよいでしょう。フィードバック画像は、画像が画面上でどのように見えるかを考慮して生成されますが、画像の場合は、元のサイズで描画されます。 `setDragImage()` メソッドの第 2、第 3 引数には、マウスポインターに対する相対的な画像の表示位置を指定します。

文書中に存在しないものをフィードバック画像として使うために、以下の例のようにして、画像や canvas を利用することもできます。

```js
function dragWithCustomImage(event) {
  const canvas = document.createElement("canvas");
  canvas.width = canvas.height = 50;

  const ctx = canvas.getContext("2d");
  ctx.lineWidth = 4;
  ctx.moveTo(0, 0);
  ctx.lineTo(50, 50);
  ctx.moveTo(0, 50);
  ctx.lineTo(50, 0);
  ctx.stroke();

  const dt = event.dataTransfer;
  dt.setData("text/plain", "ドラッグされるデータ");
  dt.setDragImage(canvas, 25, 25);
}
```

この例では、 canvas の大きさは `50`×`50` ピクセルで、オフセット値はそれぞれの半分の値 (`25`) となっており、画像はマウスポインターの中央に表示されます (マウスポインターが画像の中央に表示されます)。

ドラッグの効果
-------

ドラッグを行う時の操作には、いくつかの種類があります。 `copy` (コピー) はドラッグされているデータが現在の場所からドロップ先の場所にコピーされることを示します。 `move` (移動) はドラッグされているデータがドロップ先に移動されることを示し、 `link` (リンク) はドラッグ元とドロップ先の場所との間に何らかの形での関連付けや繋がりが作られることを示します。

`dragstart` イベントのリスナーにおいて、 `effectAllowed` プロパティに値を設定することで、 ドラッグ元について上記の 3 つの操作のうちどれが許可されているのかを示すことができます。

```js
event.dataTransfer.effectAllowed = "copy";
```

この例では、コピー (**copy**) のみが許可されています。

複数の種類の操作を組み合わせることもできます。

`none`

どの操作も許可されていない（ドロップを禁止）。

`copy`

コピーのみが許可されている。

`move`

移動のみが許可されている。

`link`

リンクのみが許可されている。

`copyMove`

コピーまたは移動のみが許可されている。

`copyLink`

コピーまたはリンクのみが許可されている。

`linkMove`

リンクまたは移動のみが許可されている。

`all`

コピー、移行、リンクの全ての操作が許可されている。

初期化されていない場合

既定値は `all` です。

上に列挙されている値のいずれかと全く等しい値だけが利用可能であることに注意してください。 `effectAllowed` プロパティを `copyMove` に設定すると、コピーや移動の操作を許可しますが、ユーザーがリンク操作を行うことを防ぐことができます。 `effectAllowed` プロパティを変更しない場合、 '`all`' が指定された時と同様に、すべての操作が許可されます。ですので、特定の種類の操作を除外したい場合を除いて、プロパティの値を手動で設定する必要はありません。

ドラッグ操作の間、 `dragenter` または `dragover` イベントのリスナーは、操作が許可されているかどうかを確かめるために `effectAllowed` プロパティを参照できます。これらのイベ`ント`において、関連するプロパティである `dropEffect` プロパティへ、実際に行われる操作の種類 1 つだけが指定されるべきです。 `dropEffect` プロパティの値として妥当なものは、`none`、`copy`、`move`、または `link` のみです。このプロパティへは、複数の操作を組み合わせた値は指定できません。

`dragenter` および `dragover` イベントにおいて、 `dropEffect` プロパティはユーザーが要求している操作に初期化されます。ユーザーは操作の種類を修飾キーを押すことにより変更することができます。実際に使用されるキーはプラットフォームごとに異なりますが、大抵の場合は `Shift` キーと `Control` キーが、コピー・移動・リンクの各操作の切り替えに使われるでしょう。マウスポインターはどの操作が望まれているのかを示すために、例えば `copy` ならカーソルの横に「＋」記号が表示される、といった風に変化するでしょう。

`dragenter` または `dragover` イベントの間に `dropEffect` プロパティの値を変更すると、ユーザーが選択した操作の種類を上書きし、特定のドロップ操作を強制することができます。この時に指定できる操作の種類は、 `effectAllowed` プロパティの値として列挙されている操作に含まれていなくてはならないことに注意してください。それ以外の値を設定した場合は、許可されている操作の中から代わりの値が設定されます。

```js
event.dataTransfer.dropEffect = "copy";
```

この例では、「コピー」が行なわれる効果です。

その場所へのドロップが禁止されていることを示すために、値として `none` を設定することもできます。

`drop` および `dragend` イベントの中では、 `dropEffect` プロパティをチェックすることで最終的に選択されている効果を特定できます。選択された効果が "`move`" であれば、 `dragend` イベントの中でドラッグ元から元のデータを削除するべきです。

ドロップ先の指定
--------

`dragenter` および `dragover` イベントのリスナーは、ドラッグされている項目がどの場所にドロップされようとしているのかを正確に示す働きをすることが多いです。ウェブページやアプリケーションのほとんどの領域は、ドロップデータを受け取る場所としては不適切です。従って、これらのイベントに対する既定の動作はドロップを禁止する働きをします。

ドロップを許可したい場合は、 `dragenter` および `dragover` イベントの両方をキャンセルして、既定の処理を防ぐ必要があります。これを行うには、イベントの `preventDefault()` メソッドを呼び出してください。

```js
const draggableElement = document.querySelector('p[draggable="true"]');

draggableElement.addEventListener("dragenter", (event) => {
  event.preventDefault();
});

draggableElement.addEventListener("dragover", (event) => {
  event.preventDefault();
});
```

`preventDefault()` メソッドを呼び出すと、 `dragenter` および `dragover` イベントのどちらにおいても、その場所がドロップ可能な場所であるということを示します。多くの場合は、例えばリンクがドラッグされている時だけなど、特定の状況でのみ `preventDefault()` メソッドを呼び出したいと思うでしょう。

これを実現するには、条件を確かめて、条件が満たされている時だけイベントをキャンセルするような関数を使って下さい。条件が満たされていない時はイベントをキャンセルしないでおけば、ユーザーがマウスのボタンを放してもその場所へのドロップは行われません。

ドロップを受け付けるか拒絶するかを決める最も一般的な方法は、データ転送の仕組みに含まれているドラッグデータの型を判別するものです。例えば、画像やリンク、もしくはその両方のみを受け付けるといった事ができます。これを実現するには、イベントの `dataTransfer` (プロパティ) の `types` プロパティを確認します。 types プロパティはドラッグが開始された時に登録されたタイプ文字列のリストで、最も適切なものから最も適切でないものの順で並んでいます。

```js
function doDragOver(event) {
  const isLink = event.dataTransfer.types.includes("text/uri-list");
  if (isLink) {
    event.preventDefault();
  }
}
```

この例では、型のリストの中に text/uri-list 型があるかどうかを確認するために `contains` メソッドを使用しています。もし条件が真であれば、イベントはキャンセルされて、ドロップが許可されるでしょう。もしドラッグデータがリンクを含んでいなければ、イベントはキャンセルされず、その場所でのドロップも行われません。

実際に行われる処理の種類をより適切に示すために、 `effectAllowed` や `dropEffect` プロパティのいずれか、あるいはその両方に値を指定したいと思う事もあるでしょう。当然ですが、イベントをキャンセルするのを忘れると、これらのプロパティの値を変えても何も起こりません。

ドロップのフィードバック
------------

その場所へのドロップが許可されていることをユーザーに示す方法はいくつかあります。マウスポインターは `dropEffect` プロパティの値に応じて適切なものに変化します。

実際の正確な表示のされ方はユーザーのプラットフォームに依存しますが、通常は例えば「コピー」に対しては「＋」記号が表示され、また、ドロップが許可されていない時は「ここにはドロップできません」という意味のアイコンが表示されるでしょう。多くの場合において、このポインターによるフィードバックは十分に役立ちます。

より凝った視覚効果のために、例えばドロップが行われる位置に要素を挿入するなど、 `dragenter` イベントの間に他の操作をすることもできます。この例なら、挿入される要素は、挿入箇所を示すマーカーあるいはドラッグされている要素が新しい位置に挿入された時の状態のプレビューなどとして利用できるでしょう。このような効果は、例えば `<img>` 要素を生成して、 `dragenter` イベントの処理中にドキュメント中に単に挿入するだけで実現できます。

`dragover` イベントは、マウスポインターが現在指している要素において発行されます。挿入点のマーカーを `dragover` イベントの発行に応じて移動させたいと思うのは自然な欲求でしょう。そのような場合には、他のマウスイベントでマウスポインターの位置を取得するために使われるのと同じ要領で、イベントの `clientX` と `clientY` プロパティを利用できます。

最後に、ドラッグ中にマウスポインターが要素の上を離れる時、 `dragleave` イベントが発行されます。これは挿入点のマーカーやハイライト表示を消すのにちょうどいいタイミングです。このイベントをキャンセルする必要はありません。 `dragleave` イベントは、ドラッグがキャンセルされた時でも常に発行されますので、このイベントによって、挿入点の消去などを確実に行うことができます。

ドロップの実行
-------

ユーザーがマウスのボタンを離した時、ドラッグ & ドロップの操作は終了します。

有効なドロップ対象となっている要素の上でマウスのボタンが離された場合、最後の `dragenter` と `dragover` イベントはキャンセルされて、ドロップが成功し、 `drop` イベントがそのドロップ対象において発行されます。それ以外の場所でボタンが放された場合は、ドラッグ操作はキャンセルされ、 `drop` イベントは発行されません。

`drop` イベントの間、あなたはドロップされたデータをイベントから取得して、ドロップ位置に挿入することになります。どのドラッグ & ドロップ操作が望まれていたのかは、 `dropEffect` プロパティで判別することができます。

すべてのドラッグ & ドロップ関連のイベントにおいて、イベントの `dataTransfer` プロパティはドラッグされた対象に関するデータを保持しています。データの取得には `getData()` メソッドを利用することになるでしょう。

```js
function onDrop(event) {
  const data = event.dataTransfer.getData("text/plain");
  event.target.textContent = data;
  event.preventDefault();
}
```

`getData()` メソッドは、取得したいデータの型を引数として取ります。実行すると、ドラッグ操作の開始時に `setData()` メソッドによって登録された値が文字列として返されます。その型に対するデータが存在しない場合は、空文字が返されます。当然ながら、直前の `dragover` イベントでの処理においてチェックした時と同様に、あなたはデータの正しい形式が利用可能かどうかを知りたいと思うでしょう。

上記の例では、まずデータを取得し、ドロップ対象の内容テキストとしてそれを挿入しています。これは `p` 要素や `div` 要素がドロップ対象の領域として使われる事を想定しており、ドラッグされたテキストをドロップ位置に挿入するという効果をもたらします。

ウェブページにおいては、ドロップを受け付けた場合、イベントの `preventDefault()` メソッドを呼び出すべきです。これによって、ブラウザー内でのドロップ時の既定の挙動がキャンセルされます。例えば、リンクがウェブページにドロップされた場合、 Firefox はそのリンク先を読み込もうとします。イベントをキャンセルすることで、この動作は抑止されます。

他の形式でデータを取得することもできます。データがリンクであった場合、そのデータは `text/uri-list` 型でも提供されているでしょう。その場合、リンクを内容に挿入することができます。

```js
function doDrop(event) {
  const lines = event.dataTransfer.getData("text/uri-list").split("\n");
  lines
    .filter((line) => !line.startsWith("#"))
    .forEach((line) => {
      const link = document.createElement("a");
      link.href = line;
      link.textContent = line;
      event.target.appendChild(link);
    });
  event.preventDefault();
}
```

この例は、ドラッグされたデータからリンクを挿入します。名前から想像できる通り、 `text/uri-list` 型は実際に複数の URL の改行区切りのリストを含んでいる場合があります。このコードでは、 `split` を使って文字列を行ごとに分割し、各行に繰り返し処理を行って、それぞれをリンクとして文書中に挿入しています。ナンバー記号 (`#`) で始まるものはコメントとして除外していることに注意してください。

単純な使い方として、リストの中の最初の有効な URL を取得するために、特別な型 `URL` も利用できます。

```js
const link = event.dataTransfer.getData("URL");
```

これによって、コメントの除外などの処理は一切不要になります。しかし、これはリストの中の最初の URL だけしか取得できないという制限があります。

`URL` 型は特別な省略表記用の型で、 `types` プロパティで取得できる型のリストには列挙されません。

時には、複数の形式をサポートして、そのうち最も適切な形式で提供されたデータを取得したいと思う事もあるでしょう。以下の例では、3 つの形式がドロップ対象によってサポートされています。

以下の例は、提供されたデータの中で最も適切なデータを返す例です。

```js
function doDrop(event) {
  const supportedTypes = [
    "application/x-moz-file",
    "text/uri-list",
    "text/plain",
  ];
  const types = event.dataTransfer.types.filter((type) =>
    supportedTypes.includes(type),
  );
  if (types.length) {
    const data = event.dataTransfer.getData(types[0]);
    // Use this type of data…
  }
  event.preventDefault();
}
```

ドラッグの終了
-------

ドラッグ操作が終了すると、 `dragend` イベントがドラッグ元 (`dragstart` イベントが発行されるのと同じ要素) において発行されます。このイベントは、ドラッグ操作が成功したかキャンセルされたかに関わらず発行されます。どの操作が行われたのかは、 `dropEffect` プロパティを参照して知ることができます。

`dragend` イベントにおいて `dropEffect` プロパティの値が`none`である場合、ドラッグ操作がキャンセルされたことを意味します。それ以外の場合は、プロパティの値は実際に行われた操作の種類を示します。ドラッグ元はこの情報に基づいて、ドラッグされた項目を「移動」の操作の後に元の場所から削除することができます。 `mozUserCancelled` プロパティの値は、ユーザーが（Escape キーを押すなどして）ドラッグ操作をキャンセルした場合は true となり、不正なドロップ先だった場合などの他の理由でドラッグ操作がキャンセルされた場合や、ドロップに成功した場合は false となります。

ドロップ操作は同じウィンドウの中または他のアプリケーションの上で行われ得ます。いずれの場合も常に `dragend` イベントは発行されます。このイベントの `screenX` および `screenY` プロパティの値には、ドロップが行われたときの画面上での座標が設定されます。

`dragend` イベントの伝搬が終了した後、ドラッグ & ドロップの操作は完了します。

関連情報
----

* HTML ドラッグ & ドロップ API (概要)
* 複数の項目のドラッグ & ドロップ
* 推奨されるドラッグ型
* HTML5 Living Standard: Drag and Drop