eyedropsp’s blog

プログラミング,シェーダー,モデリング,イラスト

【WebGL】デイリーコーディング5日目

~~~5日目~~~

今日はこんな絵を出しました。これはインデックスバッファを用いて描画しています。

f:id:eyedropsp:20200103193056g:plain
f:id:eyedropsp:20200103202643j:plain
赤い点が書いてある頂点は2つの三角形のうち重なっている頂点です。
四角形を作る場合は三角形が2つ必要になり、頂点でいうと合計6つの頂点が必要になります。
見た目的には頂点は4つあれば良いですが、普通にシェーダーを書いてモデルを
作ると6つ必要です。
一般的にモデルを作るとき、ポリゴンは四角ポリゴンを使っていくようで、
四角ポリゴンを作るとなると三角ポリゴンを2つ使います。
簡単なモデルなら頂点数はそこまで増えませんが、複雑なモデルとなると頂点数は膨れ上がってきます。
こういうときに頂点数を削減する方法としてインデックスバッファを使います。

インデックスバッファは頂点のインデックス情報を格納するもので、頂点を再利用する時に使用します。
インデックスバッファはGPU上のメモリ領域に直接確保され、描画のたびにGPUにデータを送信しなくてもよくなるので動作も高速化するみたいです。すごくありがたいです。

var index = [
0, 1, 2,
1, 2, 3
];

var ibo = create_ibo(index);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);

//----------中略-------------
gl.drawElements(gl.TRIANGLE, index.length, gl.UNSIGNED_SHORT, 0);
gl.flush();
//----------------------------

function create_ibo(data)
{
   var ibo = gl.createBuffer();
   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
   gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW);
   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
   return ibo;
}


インデックスバッファに関連するコードはこの辺です。

今までgl.drawArrays()で頂点を描画していた部分がgl.drawElements()になっています。ここでインデックスバッファを使用して描画をしているようです。
このチュートリアルの後に出てくる、トーラスというドーナツのような形を
描画する時にもインデックスバッファを使用しているのですが、今回のように配列を書いて~ということはしていませんでした。
頂点を計算する際にインデックスバッファも一緒に算出していました。
インデックスバッファの算出方法にも工夫が必要みたいです。

~~~所感~~~

だいぶWebGLAPIに慣れてきた感があります。(基礎だけど)
まだまだ氷山の一角ですら到達できていないのでとにかく続けてみようと思います。
ライティングは本一冊どころでは済まないぐらい奥が深いと聞きます。
気を引き締めていこうと思います。

記録用記事ですが、1日1記事はなかなかしんどいものがありました。
次からは区切りを決めて記録していこうと思います。ゲームも作りたいので...
デイリーコーディングは続けていきます。
現在、始めて20日ぐらいです。体調崩したりで数日できないときがありましたが、ほぼ毎日書いてます。

だんだんと書くことに抵抗がなくなってきました。続けるは正義!

【WebGL】デイリーコーディング4日目

~~~4日目~~~

3日目に引き続き、同じモデルを複数描画しました。
3日目同様、vboを使いまわす方法です。
今回は描画するだけでなく処理をループさせて、三角形を動かします。
早速ですが動かした結果です。

f:id:eyedropsp:20200102154349g:plain

同じ三角形が、移動・回転・拡大、縮小しています。
3日目と同様にこれもシェーダーソースには手を入れず、javascriptのソースを変更します。

動かすのでループ処理も書きます。以下はループ処理の部分になります。

var count = 0;
 
(function(){
   // キャンバスを初期化するカラーと深度を設定
   gl.clearColor(0.0, 0.0, 0.0, 1.0);
   gl.clearDepth(1.0);
   // キャンバス初期化
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
   count++;
    
   // カウンタをもとにラジアンを算出
   var rad = (count % 360) * Math.PI / 180;
 
   // 円の軌道
   var x = Math.cos(rad);
   var y = Math.sin(rad);
   // ---モデル1---
   // モデル座標行列を初期化
   m.identity(mMatrix);
   // 三角形を(0, 1)を中心に回転させる
   m.translate(mMatrix, [x, y + 1.0, 0.0], mMatrix);
 
   m.multiply(tmpMatrix, mMatrix, mvpMatrix);
   gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
   // レンダリング
   gl.drawArray(gl.TRIANGLE, 0, 3);
 
   // ---モデル2---
   m.identity(mMatrix);
   // 三角形を移動
   m.translate(mMatrix, [1.0, -1.0, 0.0], mMatrix);
   // 三角形をY軸を軸にして回転させる
   m.rotate(mMatrix, rad, [0, 1, 0], mMatrix);
    
   m.multiply(tmpMatrix, mMatrix, mvpMatrix);
   gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
   // レンダリング
   gl.drawArrays(gl.TRIANGLES, 0, 3);
 
   // ---モデル3---
   // 拡大縮小
   var s = Math.sin(rad) + 1.0;
   m.identity(mMatrix);
   m.translate(mMatrix, [-1.0, -1.0, 0.0], mMatrix);
   // 三角形をX軸方向、Y軸方向に拡大縮小させる(sinを使っているので周期的に変化する)
   m.scale(mMatrix, [s, s, 0], mMatrix);
 
   m.multiply(tmpMatrix, mMatrix, mvpMatrix);
   gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
   // レンダリング
   gl.drawArrays(gl.TRIANGLES, 0, 3);
     
   // 再描画
   gl.flush();
 
   // 無名関数自身をループさせる
   setTimeout(arguments.callee, 1000 / 60);
})();

このようにモデルを3つ動かしました。数学要素が出てきましたがそこまで難しいものではないので自分でも
何となくわかりました。
どんな数値が入っているのかなどはもう少し理解したいです。

勉強させていただいたページです。

~~~調べたこと~~~

gl.uniformMatrix4fvが気になったので調べてみました。
「4fv」と書いてあるので2fv, 3fvもあるようです、そしてこの数字は行列の行数列数を表しているので、
なんらかの行列をuniformの値に指定できます。第1引数にuniformの値を、指定する行列は第3引数に指定します。
第2引数の真偽値は行列を転地するかどうかを決めるようです。これはfalseでなければならないみたいです。
(なぜ...?)

~~~所感~~~

2日前ぐらいに書いたコードの記録を書きました。(本日2つ目) 一気に記録を書くのが良いのか悪いのかわかりませんが、気持ち早く書けるようになっているのではないか、と感じました。(コーディング力がついてない)
記録をすることで実質書いたコードをもう一度見ることになるので、一度書いて終わりだった時に比べ記憶の定着率も上がっていると思います。
まぁ...今が正月休みで時間があるので、書けているだけかもしれません。
仕事が始まったらまた滞ったりしそうですが、できる限り毎日継続を目標にします。

【WebGL】デイリーコーディング3日目

~~~3日目~~~

前回の頂点色を塗った三角形を2つ表示させました。
2つ表示させるといっても2つのモデルを用意するのではなく
VBO(VertexBufferObject)を使いまわして1つのモデルで複数モデルを表示させました。

同じ頂点バッファを使用するので2回モデル座標移動を行い、
それぞれ座標変換行列を計算して描画すれば、2つのモデルを表示することができました。

今回はglslの コードに変更は加えていません。
その代わりに、シェーダーのコンパイルやバーテックスシェーダーとフラグメントシェーダーのリンクなどを行うjavascriptのコードに変更を加えました。

// シェーダー作成
var v_shader = create_shader('vs');
var f_shader = create_shader('fs');
 
// プログラムオブジェクトの生成とリンク
var prg = create_program(v_shader, f_shader);
 
// attributeLocationを配列に取得、格納
var attLocation = new Array(2);
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');
 
// attributeで宣言された変数の要素数を格納する
// 頂点なら3つ、カラーなら4つ
var attStride = new Array(2);
attStride[0] = 3;
attStride[1] = 4;
 
// 頂点座標の定義
var position = [
    0.0, 1.0, 0.0,
    1.0, 0.0, 0.0,
    -1.0, 0.0,0.0
];
// 頂点色の定義
var color = [
    1.0, 0.0, 0.0, 1.0,
    0.0, 1.0, 0.0, 1.0,
    0.0, 0.0, 1.0, 1.0    
];
 
// vboの生成
var pos_vbo = create_vbo(position);
var col_vbo = create_vbo(color);
 
// vboをシェーダーにバインドして登録する(ユーザー定義)
set_attribute([pos_vbo, col_vbo], attLocation, attStride);
 
// uniformLocationの取得
var uniLocation = gl.getUniformLocation(prg, 'mvpMatrix');
 
// 座標変換行列の計算は複数モデルで共通
// miniMatrix.jsのmatIVメソッドをインスタンス化
var m = new matIV();
 
// M,V,P座標初期化
var mMatrix = m.identity(m.create());
var vMatrix = m.identity(m.create());
var pMatrix = m.identity(m.create());
var tmpMatrix = m.identity(m.create());
var mvpMatrix = m.identity(m.create());
 
// 座標変換行列算出
m.lookAt([0.0, 0.0, 3.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], vMatrix);
m.perspective(90, c.width / c.height, 0.1, 100.0, pMatrix);
m.multiply(pMatrix, vMatrix, tmpMatrix);
 
//  -----モデル1-----
// ここからモデルごとにモデル座標を移動させてレンダリング
// モデル1の座標移動
m.translate(mMatrix, [1.5, 0.0, 0.0], mMatrix);
// 座標変換行列完成
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
 
// uniformLocationへ座標変換行列を登録し描画する
gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
 
// レンダリング
gl.drawArrays(gl.TRIANGLES, 0, 3);
 
// -----モデル2-----
// モデル2用にモデル座標変数の初期化、座標移動
m.identity(mMatrix);
m.translate(mMatrix, [-1.5, 0.0, 0.0], mMatrix);
 
// 座標変換行列完成
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
 
// uniformLocationへ座標変換行列を登録し描画する
gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
 
// レンダリング
gl.drawArrays(gl.TRIANGLES, 0, 3);

モデルを2つ描画させるのに行った特別な処理は主に上のようになりました。
元のモデルを複数用意するのではなくvboを使いまわすことで同じモデルを複数描画しています。
同じモデルを違う形で大量に表示する時に効果を発揮してくれそう打と思いました。(違うかも)

f:id:eyedropsp:20200102140611p:plain
三角形が2つ出てきました。うれしい!

勉強させていただいたページです。


~~~所感~~~

コーディングのほうは毎日続いているのですが、記録記事の更新がまだ慣れず...
今日は上のようにコードを一気に書いてコメント入れて残しておくみたいなやり方を試しました。
見にくい...ほかのブログを読んで何が良いかを研究してみます。

ブログにはそこまで重きを置いていないのですが、基本「続ける」をテーマにやっているので、
どうせなら自分も気持ちよく書けたらなと思います。

今日できたらもう1記事書いてみます。

【WebGL】デイリーコーディング2日目

~~~2日目~~~

今回はフラグメントシェーダーで色を塗るのではなく
バーテックスシェーダーで頂点に色を塗りました。

前回のバーテックスシェーダー

attribute vec3 position;
uniform mat4 mvpMatrix;
void main(void)
{
     gl_Position = mvpMatrix * vec4(position, 1.0);
}

今回のバーテックスシェーダー

attribute vec3 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec4 vColor;
void main(void)
{
     vColor = color;
     gl_Position = mvpMatrix * vec4(position, 1.0);
}

前回と比べてattribute宣言の変数とvarying宣言の変数が増えました。
attributeは頂点一つ一つに使用され、
varyingはバーテックスシェーダーとフラグメントシェーダーの橋渡しを行います。

フラグメントシェーダー

precision mediump float;
varying vec4 vColor;
void main(void)
{
     gl_FragColor = vColor;
{

フラグメントシェーダーにもvaryingで変数が宣言されています。
これも橋渡しのために必要みたいです。
橋渡しに使われるvColorに頂点色が代入されているのでフラグメントシェーダーに色情報が渡っている...
と考えられます(ここ内部的に何が起こってるのかわからない)

色がついた

f:id:eyedropsp:20191231221449p:plain
三角形


今回、勉強させていただいたページです


1日目からかなり時間が空きました。
記録用とはいえ学習しながらブログ書いてはなかなか忙しいですね...
記録を書くことに集中してしまうと、手が止まってしまったりであまりよくないです。
デイリーコーディングのほうはだいぶ軌道に乗ってきました。
ブログはまとめて書いてみたりしてゆっくり慣れていこうと思います。

【WebGL】デイリーコーディング1日目

GLSLを毎日書く

こちら

シェーダーアドベントカレンダー Advent Calendar 2019 20日

northprintさんの「GLSLを毎日書く」という記事に触発されました。

 

そこで自分もnorthprintさんを習ってデイリーコーディングしてみようと思いました。

記録用にやったこと、所感などを書いていこうと思います。

私はシェーダー初学者です、なので初歩的なことから始めます。

今回、デイリーコーディングの題材にさせていただくのは

doxasさんのサイトです。(2012年から書かれてる...すごい...)

解説読みつつサンプルコードを書きます。自分なりにコードを変更するのも効果的なようです。

ちょっと前に読み進めてたのですが挫折したので振り出しからやり始めました。

【開発環境】

~~~1日目~~~

まず三角形を出すところからです。

2か月前に少しだけ勉強したときには、「三角形だけでこんなにコードを書くのか...」

と驚きました。

Vulkanとかだともっと記述量が増えるみたいです。

今日はこちらの解説を読みサンプルを書きます。

attribute vec3 position;
uniform mat4 mvpMatrix;
void main(void)
{
     gl_Position = mvpMatrix * vec4(position, 1.0);
}

バーテックスシェーダーのコードです。
attributeとuniform大切
attributeで宣言された変数に頂点情報が、
uniformで宣言された変数にモデル、ビュー、プロジェクションを掛け合わせた
座標変換行列が格納されるみたいです。
座標変換行列に頂点の座標を掛け合わせた結果をgl_Positionに渡すことで画面上に頂点が描画されます。

void main(void)
{
     gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

フラグメントシェーダーのコードです。
バーテックスシェーダーと違って変数が出てきてないですが
これで最低限色を塗ることができるみたいです。
gl_FragColorにはColorと書いてある通り色情報を渡します。

f:id:eyedropsp:20191227002933p:plain
Triangle

三角形が出ました。これだけでもうれしい。

感想

普段は実装を考えながらのコーディングはしているのですが、学習しながらは
書いていません (調べる→書いてみる→理解の順が多い)
デイリーコーディングをするにあたって、学習しつつ手を動かしつつ...
2つのことをいっぺんに意識してしまい、なかなか学習する内容とコードが頭に
入ってきませんでした。
(映画見るときに技法の観察と映画そのものの感動体験などを両立させる感じ?)
何を書いたかとか何が理解できなかったとかを書いてないので少しずつ追記していくつもりです。

何はともあれ1日目、明日からも続けてみます。