imageを任意のスケールと座標に描画する事はできたので
lineToやquadraticCurveToといったパスから描画できないかチャレンジしていきます。
らくさんのこの記事を参考にしながら進めていきます。
WebGLでベジェ曲線を描いてみた
続:WebGLでベジェ曲線を描いてみた
上記の記事の実装するにはBufferObjectの使い方を知っておかないと
実装は難しいので簡易版を書こうと思います。
※注意※
らくさんの記事通りに実装すると描画速度は凄く早いのですが
今回の簡易版は導入は比較的簡単ですが、速度は落ちてしまうのでご了承を
目次
- 描画用のシェーダーを作る
- 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なのに遅くない??
って問題が多発したので、その時の事を書こうと思います。