LoginSignup
1
1

More than 3 years have passed since last update.

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

Posted at

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

らくさんのこの記事を参考にしながら進めていきます。
WebGLでベジェ曲線を描いてみた
続:WebGLでベジェ曲線を描いてみた

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

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

目次

  • 描画用のシェーダーを作る
  • beginPath関数を作る
  • moveTo関数を作る
  • lineTo関数を作る
  • quadraticCurveTo関数を作る
  • fill関数を作る

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

頂点シェーダー

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

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\
}";

フラグメントシェーダー

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

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

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

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

beginPath関数を作る

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

moveTo関数を作る

/**
 * @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関数を作る

/**
 * @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関数を作る

/**
 * @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の準備

/**
 * @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;
    }
}

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

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関数


/**
 * @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なのに遅くない??
って問題が多発したので、その時の事を書こうと思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1