Flash Advent Calendar 10日目 - WebGL導入への道のり03 -

imageを任意のスケールと座標に描画する事はできたので
lineToやquadraticCurveToといったパスから描画できないかチャレンジしていきます。

らくさんのこの記事を参考にしながら進めていきます。
[WebGLでベジェ曲線を描いてみた](https://lab.sonicmoov.com/development/programming/webgl-bezier-shader/)
[続：WebGLでベジェ曲線を描いてみた](https://lab.sonicmoov.com/development/programming/webgl-bezier-shader-2/)

上記の記事の実装するにはBufferObjectの使い方を知っておかないと
実装は難しいので簡易版を書こうと思います。

※注意※
らくさんの記事通りに実装すると描画速度は凄く早いのですが
今回の簡易版は導入は比較的簡単ですが、速度は落ちてしまうのでご了承を:bow:

# 目次
* 描画用のシェーダーを作る
* beginPath関数を作る
* moveTo関数を作る
* lineTo関数を作る
* quadraticCurveTo関数を作る
* fill関数を作る

# 描画用のシェーダーを作る

## 頂点シェーダー

setTransform(matrix)で変形させたいので、そこを考慮しながらシェーダーを組んでみます。

```javascript
const VERTEX_SHADER = "\
precision highp float;\n\
attribute vec2 a_vertex;\n\
uniform vec2 u_viewport;\n\
uniform mat3 u_transform;\n\
void main() {\n\
    vec3 pos = u_transform * vec3(a_vertex, 1.0);\n\
    pos.x = (2.0 * pos.x / u_viewport.x) - 1.0;\n\
    pos.y = -((2.0 * pos.y / u_viewport.y) - 1.0);\n\
    gl_Position = vec4(pos.xy, 0.0, 1.0);\n\
}";
```

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

今回は色情報だけなので簡易的に組んでいきます。

```javascript
const FRAGMENT_SHADER = "\
precision mediump float;\n\
uniform vec4 u_color;\n\
void main() {\n\
    gl_FragColor = u_color;\n\
}";
```

こんな感じで、任意の座標に描画できるようになるかと思います。

では、Canvas2Dで使ってる関数を実装していきます。


# beginPath関数を作る

```javascript
/**
 * @return {void}
 * @public
 */
beginPath ()
{
    this.vertices    = []; // moveToする度にcurrentPathにあるパス情報をまとめる
    this.currentPath = []; // moveToから始まる描画情報
}
```

# moveTo関数を作る

```javascript
/**
 * @param  {number} x
 * @param  {number} y
 * @return {void}
 * @public
 */
moveTo (x, y)
{
    // 直線のパス情報を一回まとめる
    if (this.currentPath.length) {
        this.vertices.push(this.currentPath.slice(0));
    }

    // 初期化
    this.currentPath = [];

    // 開始
    this.currentPath.push(x);
    this.currentPath.push(y);
};
```

# lineTo関数を作る

```javascript
/**
 * @param  {number} x
 * @param  {number} y
 * @return {void}
 * @public
 */
lineTo (x, y)
{
    // 開始情報がない時は(0,0)から出発させる
    if (this.currentPath.length === 0) {
        this.moveTo(0, 0);
    }

    this.currentPath.push(x, y);
};
```

# quadraticCurveTo関数を作る

```javascript
/**
 * @param  {number} cx
 * @param  {number} cy
 * @param  {number} dx
 * @param  {number} dy
 * @return {void}
 * @public
 */
quadraticCurveTo (cx, cy, dx, dy)
{
    // 開始情報がない時は(0,0)から出発させる
    if (this.currentPath.length === 0) {
        this.moveTo(0, 0);
    }

    // 直前のx,y座標をセット
    const fromX = this.currentPath[this.currentPath.length - 2];
    const fromY = this.currentPath[this.currentPath.length - 1];

    let xa = 0;
    let ya = 0;

    // TODO 本来はちゃんと計算した方いいけど
    // swfでは20もあれば十分滑らかな曲線を描けるので固定
    const length = 20;
    for (let idx = 0; idx < 20; idx++) {
        const f = idx / length;
       
        // 開始座標から目的座標までの外周のパスをpushしていく
        xa = fromX + ((cx - fromX) * f);
        ya = fromY + ((cy - fromY) * f);
        this.currentPath.push(xa + (((cx + ((x - cx) * f)) - xa) * f));
        this.currentPath.push(ya + (((cy + ((y - cy) * f)) - ya) * f));
    }
}
```

# fill関数を作る

事前にattributeの部分を汎用的に使えるようにしておきます。

## attributeの準備

```javascript
/**
 * @return {void}
 * @public
 */
createBuffer = function ()
{
    // reset
    this.attributes = [
        {
            "name": "a_vertex",
            "value": {
                "streamType": gl.STREAM_DRAW,
                "spacing": 2,
                "buffer": null,
                "value": [0,0, 0,0, 0,0],
                "location": 0
            }
        },
        // ... 以下割愛
    ];

    // set attributes
    var length = this.attributes.length;
    for (let idx = 0; idx < length; idx++) {

        var attribute = this.attributes[idx];

        var buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(attribute.value.value), attribute.value.streamType);

        attribute.value.location = this.gl.getAttribLocation(this.program, attribute.name);
        this.gl.enableVertexAttribArray(attribute.value.location);

        // clear
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);

        // set
        attribute.value.buffer = buffer;
    }
}
```

情報を受け取る関数も準備しておく

```javascript
updateBuffer () 
{
    const length = this.attributes.length|0;
    for (let idx = 0; idx < length; idx++) {

        const attribute = this.attributes[idx];

        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, attribute.value.buffer);


        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(attribute.value.value), attribute.value.streamType);

        this.gl.vertexAttribPointer(attribute.value.location, attribute.value.spacing, gl.FLOAT, false, 0, 0);

        // clear
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);

    }
}
```

## fill関数

```javascript

/**
 * @return {void}
 * @public
 */
fill ()
{
    // 最低限の三角形がない場合は何もしない
    if (this.currentPath.length < 6) {
        return ;
    }

    // set
    this.vertices.push(this.currentPath.slcie(0));

    // clear
    this.currentPath = [];

    this.gl.useProgram(this.program);
    
    // Uniformの処理は9日目の記事を参照ください。
    this.updateUniforms([
        { "name": "u_color", "value": this.fillStyle },
        { "name": "u_transform", "value": this.matrix },
        { "u_viewport": [this.canvas.width, this.canvas.height] } // <= ここは用途に合わせて可変
    ]);

    const length = this.vertices.length;
    for (let idx = 0; idx < length; idx++) {

        var vertex = this.vertices[idx];
        this.attributes["a_vertex"].value = vertex;
        this.updateBuffer();
        this.gl.drawArrays(this.gl.TRIANGLE_FAN, 0, vertex.length / 2);
    }

    // 初期化
    this.vertices = [];
}
```


最終的には上記の記事にあるフラグメントシェーダーの補完機能を利用した手法の描画に変更するのですが
まず始める！っというところとしては、こんな感じで開始しました。

個人的にはこの辺りからWebGLって凄く楽しい！！
ってなってきました。

今ではWebGL大好きってなってますw

次は、WebGLによくある、あれ？WebGLなのに遅くない？？
って問題が多発したので、その時の事を書こうと思います。