シェーダーであそぼう!
WebGLのおかげでシェーダー言語がびっくりするくらいお手軽になりました。
触って、弄って、あなただけのエフェクトを作ってみましょう!
やること
- WebGL
- レンダリングの流れ(超ざっくり)
- シェーダー言語(たっぷり)
必要なもの
- 使い慣れたエディタ (e.g. Visual Studio Code)
- 使い慣れたブラウザ (e.g. Firefox)
- 好奇心
WebGLについて
OpenGLの仲間たち
内容 | 開発言語 | ||
---|---|---|---|
OpenGL | 大本のAPI | Cなど | |
OpenGL ES | モバイル用のOpenGLのサブセット | CやJavaなど | |
WebGL | OpenGL ESをブラウザ向けに移植したもの | JavaScript | ←ここをやります |
バージョンの対応表
OpenGL | OpenGL ES | WebGL | |
---|---|---|---|
1.x | 1.x | --- | |
2.x | 2.0 | 1.0 | ←ここをやります |
3.x | 3.0 | 2.0(策定中) | |
4.x | 3.x | - |
OpenGL ES 3.0以降はAndroid4.3以降、iOS7以降でサポートされます。
OpenGL ES 3.0は2.0と互換性があるので、とりあえずOpenGL ES 2.0(WebGL 1.0)を勉強しておけば間違いは起きないと思います。
WebGLとOpenGLの違い
glプレフィックス関数を、glオブジェクトのメソッドにしたくらいの違いしかないので、
WebGLがわかればOpenGLもほぼほぼわかるようになります。
WebGL | OpenGL | |
---|---|---|
gl.vertexAttribPointer() | → | glVertexAttribPointer() |
gl.drawElements() | → | glDrawElements() |
gl.createTexture() | → | glGenTextures() |
WebGLを動かす
基本的な流れ(超ざっくり)
細かくやるとシェーダー言語よりよっぽど難しいので超ざっくりです。。。
-
モデル(球など)を作る
- 3D空間の点{x, y, z}の配列と、テクスチャマッピング{u, v}の配列のセット
-
テクスチャを用意する
-
カメラを定義する(視野変換行列)
-
シェーダーをコンパイルする
-
以下ループ
- モデルの移動・変形を定義する(行列)
- シェーダーに渡す
- 描画
サンプルを実行してみる
適当なフォルダに index.html というファイルを作って、以下のURLの内容をコピペしてブラウザで開いてください。
シェーダーもテクスチャも全て含まれています。
シェーダー言語
手っ取り早くシェーダーをいじり倒したい方は いじる まで飛ばしてください。
言語の概要
GLSL(OpenGL Shading Language)
- C言語によく似た言語
- グローバル変数から拾ってグローバル変数に書き込むのが基本的な流れ
- モデルのポリゴンの頂点に対して、バーテックスシェーダーとフラグメントシェーダーが実行される
※JavaScriptからの変数の渡し方の解説は割愛
// グローバル宣言 (種類 型 変数名;)
attribute vec3 position; // モデル - 3D空間上の座標
attribute vec2 texCoord; // モデル - テクスチャマッピング
uniform mat4 mvMatrix; // モデルの移動・変形
uniform mat4 pMatrix; // カメラ(視野)
varying vec2 v_texCoord; // fragmentシェーダーに渡す変数
void main() {
gl_Position = pMatrix * mvMatrix * vec4(position, 1.0);
v_texCoord = texCoord;
}
precision mediump float; // floatの精度の指定(フラグメントシェーダーでは必須)
// グローバル宣言 (種類 型 変数名;)
varying vec2 v_texCoord; // バーテックスシェーダーから受け取る変数
uniform sampler2D sampler; // テクスチャサンプラー
void main() {
gl_FragColor = texture2D(sampler, v_texCoord);
}
(余談)レンダリング結果をキャプチャすればGPGPU(GPUによる多目的演算)的なこともできます。
ただし、GPGPUは専用の仕組みを使うことをお勧めします。
- OpenGL 4.0以降・OpenGL ES 3.1以降だとコンピュートシェーダー
- AndroidだとRenderScript
文法
基本はポインタのないC言語のようなもの
繰り返します。ポインタはありません。なのでとても簡単です。
グローバル宣言
- JavaScriptで用意したデータをここから取り出すためのもの
- attribute
- uniform
- バーテックスシェーダーからフラグメントシェーダーに値を渡すためのもの
- varying
attribute
おさらい:シェーダーはモデルのポリゴンの頂点に対して実行されます
// この変数を使うよ!
var location = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(location);
// positionBuffer(頂点情報の一覧)を、
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 'position'に割り当てるよ!
var location = gl.getAttribLocation(program, 'position');
gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 0, 0);
attribute vec3 position; // バッファの一要素が入っている
attributeはフラグメントシェーダーでは使えません。
uniform
// 行列pMatrixを'pMatrix'に割り当てるよ!
var gl.getUniformLocation(program, 'pMatrix');
gl.uniformMatrix4fv(location, false, pMatrix);
uniform mat4 pMatrix; // 行列pMatrixが入っている
varying
varying vec2 v_texCoord; // 宣言して
...
v_texCoord = texCoord; // データを詰めておく
varying vec2 v_texCoord; // バーテックスシェーダーで詰めた値が取り出せる
|種類|使える場所|使い道|
|---|---|---|---|
|attribute|vertexのみ|座標の配列|
|uniform|vertex, fragment両方|行列やフラグなど|
||
|varying|vertex, fragmentでペア|バーテックスからフラグメントに値を渡す変数|
暗黙のグローバル変数
ここに値を割り当てておくと描画されます。
名前 | 使える場所 | 意味 |
---|---|---|
gl_Position | vertex | 座標 |
gl_FragColor | fragment | 色 |
型
全て値型
種類 | 意味 |
---|---|
float | 1次元の数値 |
vec2, vec3, vec4 | 2~4次元のベルトル |
mat4 | 4x4の行列 |
sampler2D | 座標情報と一緒にtexture2D()関数に渡すとテクスチャの色情報が得られるもの |
シェーダーの中では主にvec2~4を弄ります
生成
// コンストラクタ
vec2 v2 = vec2(0.0, 0.2);
vec3 v3 = vec3(0.0, 0.2, 0.8);
vec4 v4 = vec4(0.0, 0.2, 0.8, 0.4);
// sampler2D sampler, vec2 texCoordを用意して、
vec4 v4 = texture2D(sampler, texCoord);
変数の操作
各成分へのアクセス
添字で各ベクトル成分にアクセスできます。
vec4 v4;
色 | 座標 |
---|---|
v4.r | v4.x |
v4.g | v4.y |
v4.b | v4.z |
v4.a | v4.w |
どちらを使っても同じ値にアクセスできます。
成分の入れ替え・スライス
vec4 v4 = vec4(0.1, 0.2, 0.3, 0.4);
vec4 v4a = v4.brga; // (0.3, 0.1, 0.2, .0.4)
vec2 v2a = v4.xz; // (0.1, 0.3)
四則演算
各成分ごとに四則演算されます。
vec2 result = vec2(0.1, 0.2) * vec2(2.0, 2.0);
// -> (0.2, 0.4)
vec2 result2 = result / vec2(2.0, 2.0); // 掛けたベクトルで割ると
// -> (0.1, 0.2) 元に戻る
ベクトル演算にはdot(), cross()を使います。ソースは割愛。
いじる
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D sampler;
void main() {
// rgb成分を入れ替えると・・・?
gl_FragColor = texture2D(sampler, v_texCoord).gbra;
}
ランダムな値が欲しい
float rand(vec2 co) {
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
// フラグメントシェーダーでtexCoordをずらすと・・・?
vec2 texCoord = v_texCoord + (rand(gl_FragCoord.xy) - 0.5) / 300.0;
外からの変数を追加する
gl.uniform1f(gl.getUniformLocation(program, 'i'), false, i++);
uniform float i; // 毎フレーム1ずつ変化する変数
締め
WebGLで試すと簡単で早いので色々試すのがいいと思います
やらなかったこと
以下は端折りましたが、調べるとより理解が深まると思います。
- シェーダー言語以外のところ → ソースを読んだらなんとなくわかると思います
- 行列 → すごく難しいですが概念とライブラリの使い方を抑えれば大丈夫です
- JavaScript → MDN読もう