Edited at

WebGLでGLSL (Shader) を書く流れを(ざっくり)まとめた


概要

WebGLでは固定機能パイプライン (Fixed-function pipeline) が使えないので、すべてシェーダ (Shader) を自作する必要がある。

Shader記述言語であるGLSL (OpenGL Shading Language)を使ってシェーダを書くというのはどういうことか理解する。


動機

個人的にHTMLとJavaScriptを行ったり来たりするので、どこがどうなってるのか分からなかった(いまだにわかってない部分も多いが)、のでメモがてら書く。


シェーダの種類

シェーダには2つの種類がある。頂点シェーダ (Vertex shader)とフラグメントシェーダ (Fragment shader)。

基本的には頂点シェーダを使って座標系を操作し、フラグメントシェーダを使って色や光などをつけるイメージ。


各シェーダの役割


  • 頂点シェーダ

    最初に呼ばれるシェーダ。頂点情報をもっており、頂点の色情報や座標情報をフラグメントシェーダに受け渡すことができる。


  • フラグメントシェーダ

    頂点シェーダから受け取った情報を元に色情報を追加する。



GLSLの記述方法

一般的にはHTMLの要素として解釈されないscript要素を使って、記述する。

JavaScriptの文字列としても記述できるが、つらいのでおすすめしない(Coffee Scriptならありかも?)。

以下は一例

  // 頂点シェーダ

<script type="x-shader/x-vertex" id="vs">
attribute vec3 position;
attibute vec4 color;
uniform mat4 matrix;
varying vec4 vColor;

void main (void) {
vColor = color;
gl_Position = matrix * position;
}
</script>

// フラグメントシェーダ
<script type="x-shader/x-fragment" id="fs">
precision mediump float;

varying vec4 vColor;
gl_FlagColor = vColor;
</script>

これより下は上記の例をもとに説明していく。


変数と修飾子

変数は


  • vec(n)

  • mat(n)

  • float

  • int

などがある。

それぞれの解説は今回しない。

変数には修飾子が存在している。


  • attribute

頂点ごとに異なる情報を受け取る時に使う。

例えば、頂点座標(position)は各頂点に対して異なる。


  • uniform

すべての頂点に一律で適応したい処理に使う。

例えば、座標の変換行列(matrix)はすべての頂点に対して一律である。


  • varying

varying修飾子は頂点シェーダからフラグメントシェーダに変数を受け渡したい時に使う。

例えば、頂点の色(vColor)はフラグメントシェーダを使ってピクセル上に描画したいのでvaryingをつけて宣言する。頂点シェーダとフラグメントシェーダで同じ変数を使う。


シェーダを使って描画するまでのステップ(ざっくり)


  1. 頂点シェーダとフラグメントシェーダを書く

  2. 各シェーダをシェーダソースにぶちこんでコンパイルする

  3. できあがった2つのシェーダをプログラム(WebGL固有)にぶちこんでリンクする

  4. シェーダプログラムから変数を取り出したり入れたりする

  5. 描画する


STEP1 - 各シェーダの記述

例でも取り上げたように、HTML内にHTMLに解釈されないtypeを用いて記述する。

idを使ってJavaScript内にtextとして持ってくる。

  var vs = document.getElementById ("vs");

var vertexShader = createShader(vs);

function createShader(elem) {
var shaderElem;
switch (elem.type) {
case 'x-shader/x-vertex':
shaderElem = gl.createShader (gl.VERTEX_SHADER);
break;
case 'x-shader/x-fragment':
shaderElem = gl.createShader (gl.FRAGMENT_SHADER);
break;
default:
break;
}
return shaderElem;
}


STEP2 - 各シェーダからシェーダソースを作る

各シェーダをWebGLが解釈できる形に変換する必要がある。

  // STEP1のつづき

gl.shaderSource (vertexShader, vs.text);
gl.compileShader (vertexShader);

// シェーダがコンパイルできてるかチェックする
if (gl.getShaderParameter (vertexShader, gl.COMPILE_STATUS)){
return true;
} else {
throw new Error (gl.getShaderInfoLog(vertexShader));
}


STEP3 - シェーダソースをリンクしてプログラムを作成する

各シェーダソースを元にプログラムを作る。これを作らないと各シェーダの変数にアクセス出来ない。


// 両シェーダを元にプログラムを作る
var program = createProgram (vs, fs);

function createProgram (vs, fs) {
var program = gl.createProgram ();

gl.attachShader (program, vs);
gl.attachShader (program, fs);
gl.linkProgram (program);

// シェーダがリンクできたかチェックする
if (gl.getProgramParameter (program, gl.LINK_STATUS) {
gl.useProgram (program);
return program;
} else {
throw new Error (gl.getProgramInfoLog (program));
}
}


STEP4 - プログラムから変数にアクセスする

変数には修飾子がついている。

この修飾子によってアクセス方法が異なる。


attribute, varying修飾子

変数にアクセスするときには、変数自体の順番と変数の型の順番がある。順番を守らないとエラーになる。

順番は変数を配列に入れることで守るようにする。


var attLocation = [];
// 変数名を文字列で指定して取り出す
// 0番目にposition, 1番目にcolor
attLocation[0] = gl.getUniformLocation (program, 'position');
attLocation[1] = gl.getUniformLocation (program, 'color')

// 変数の順番と型の順番を合わせる (0:position, 1:color)
var attStride = [];
attStride[0] = 3; // positionは vec3 なので 3
attStride[1] = 4; // colorは vec4 なので 4

これらの修飾子付きの変数は、VBO (Vertex Buffer Object)を作ってデータを入れ込む


var position = [
-1.0, 1.0, 0.0,
1.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
];
var positonVbo = gl.createBuffer ();
gl.bindBuffer (gl.ARRAY_BUFFER, positonVbo);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (position), gl.STATIC_DRAW);

var color = [];
......

var vbos = [positionVbo, colorVbo];

for (var i in vbos) {
gl.bindBuffer (gl.ARRAY_BUFFER, vbos[i]);
gl.enableVertexAttribArray (attLocation[i]);
gl.vertexAttribPointer (attLocation[i], attStride[i], false, 0, 0);
}


uniform修飾子

uniform修飾子の場合は、それぞれ異なるメソッドを使って変数にアクセスする。

  var uniLocation = [];

uniLocation[0] = gl.getUniformLocation (program, 'matrix');

// セットする変数(値)を宣言
var matrix = [......];

// メソッドを使ってセットする
gl.uniformMatrix4fv (uniLocation[0], false, matrix);


STEP5 - 描画する

それぞれの変数に値を入れたら描画を走らせる


// 画面上ではなく、バッファ上に描画される
gl.drawArrays (gl.TRIANGLES, 0, 3);

// コンテキストを再描画することで描画される
gl.flush ();


まとめ

まだすべてを把握してるわけではないが、だいたいこんな感じのことをしているというのを自分なりにまとめたかった、振り返りのために。

そのため、ざっくり流れだけ説明するために、いろいろと省いてしまっているので、大事なところが抜けていたり、処理がまとまっていなかったりします。

すごくざっくりまとめた備忘録に近いものなので、あまり参考にはならないかもしれません :anguished:

私自身まだすべてをちゃんと理解していないので、間違いなどあれば、ご指摘頂けると幸いです :relieved:


参考

wgld.org

http://wgld.org/

ここが一番参考になるし、一番参考にしている、というかここを見ながら学習している(いつもありがとうございます)。

GLUTによる「手抜き」OpenGL入門

http://www.wakayama-u.ac.jp/~tokoi/opengl/libglut.html

ここを少しだけやってWebGLに移った。webgl.orgと行き来してやるとWebGLとOpenGLの違うところや、同じようにできるところがわかって良いかもしれない。

TechSketch - 3Dプログラミングの基礎知識

http://tech-sketch.jp/2011/10/3d1.html

基礎的な知識ならここがよさそう。まだ用語を完全に理解していないけど、なんとなくつかめるものがある。

OpenGL Insights - OpenGL Pipeline Map

http://openglinsights.com/pipeline.html

これは図になっていて流れが分かりやすい。大きすぎて見づらいけど、固定機能パイプラインがどういうことをしているか、とか各シェーダがどうやって処理されているか、のようなものをつかめるのでは。