Three.jsでシェーダ(GLSL)入門

シェーダが使えるようになると、表現力が向上します!そこで、Three.jsでシェーダを使用する方法を調べました。※Three.jsはr117を使用しています。
シェーダについては「three.jsによるHTML5 3Dグラフィックス」シリーズの(下)が参考になります。
シェーダとGLSL
シェーダは、3DCGオブジェクトの表面の質感や凹凸、陰影処理などの描画色を決定するプログラムです。
GLSLはWebGLで動作するシェーダ言語で、Three.jsでシェーダを使用する場合はGLSLを使います。
GLSLには、バーテックスシェーダとフラグメントシェーダがあります。バーテックスシェーダは、オブジェクトの位置や回転など頂点データをスクリーン上のどこに配置するかを計算し、フラグメントシェーダは、スクリーン上の各ピクセルの描画色を計算します。
シェーダの記述方法はいろいろありますが、テンプレート文字列を使用すると便利です。また、Three.jsでシェーダを使用する場合は、ShaderMaterialを使用します。
//バーテックスシェーダ
const vertexShader =`
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
//フラグメントシェーダ
const fragmentShader =`
void main(){
gl_FragColor = vec4(0.51,0.66,0.88,1.0);
}
`;
const geometry = new THREE.PlaneGeometry(5,5,10,10);
//ShaderMaterial
const material = new THREE.ShaderMaterial({
//バーテックスシェーダを設定
vertexShader:vertexShader,
//フラグメントシェーダを設定
fragmentShader:fragmentShader,
});
const plane = new THREE.Mesh(geometry,material);
scene.add(plane);
● バーテックスシェーダ
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
バーテックスシェーダは、各頂点ごとにシェーダプログラムが実行されます。positionはThree.jsのオブジェクトの頂点座標で、Three.jsでシミュレートされた3次元空間を2次元空間のスクリーンに投影するため、Three.jsで定義された行列modelViewMatrixとprojectionMatrixを積算し、座標変換してgl_Positionに渡します。3次元空間から2次元空間への座標変換については、「3D描画の基礎知識 - wgld.org」を参考にしてください。
● フラグメントシェーダ
gl_FragColor = vec4(0.51,0.66,0.88,1.0);
フラグメントシェーダは、バーテックスシェーダが実行された後、スクリーン上の各ピクセルの描画色を計算するために4次元ベクトル(R,G,B,A)をgl_FragColorに渡します。
Three.jsでシェーダ(GLSL)入門
バッファーオブジェクトを使用すると、シェーダプログラム内で頂点データを操作できます。そこで、頂点カラーと頂点座標をアニメーションさせる三角形ポリゴンを制作します。
● 三角形ポリゴンを生成
BufferGeometryを使用して、三角形ポリゴンを生成します。シェーダプログラム内で頂点データを操作するには、バッファーオブジェクトのattributeに頂点データを設定し、GPU上のメモリに転送してシェーダプログラム内で頂点データを取得する必要があります。
GPU上のメモリに転送した頂点データは、バーテックスシェーダでは取得できますが、フラグメントシェーダでは取得できません。そのため、varyingを介してバーテックスシェーダからフラグメントシェーダへデータを受け渡す必要があります。
//バーテックスシェーダ
const vertexShader =`
//頂点カラーの取得
attribute vec3 color;
//varyingを宣言
varying vec3 vColor;
void main(){
//フラグメントシェーダに頂点カラーを転送
vColor = color;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
//フラグメントシェーダ
const fragmentShader =`
//varyingを宣言して頂点カラーを取得
varying vec3 vColor;
void main(){
gl_FragColor = vec4(vColor,1.0);
}
`;
//型付配列で頂点座標を設定
const positions = new Float32Array([
2.5,0.0,0.0,
0,5.0,0.0,
-2.5,0.0,0.0,
]);
//型付配列で頂点カラーを設定
const colors = new Float32Array([
1.0,0.0,0.0,
0.0,0.0,1.0,
0.0,1.0,0.0,
]);
//バッファーオブジェクトを生成
const geometry = new THREE.BufferGeometry();
//バッファーオブジェクトのattributeに頂点座標を設定
geometry.setAttribute('position',new THREE.BufferAttribute(positions,3));
//バッファーオブジェクトのattributeに頂点カラーを設定
geometry.setAttribute('color',new THREE.BufferAttribute(colors,3));
const material = new THREE.ShaderMaterial({
vertexShader:vertexShader,
fragmentShader:fragmentShader,
side:THREE.DoubleSide,
});
const triangle = new THREE.Mesh(geometry,material);
scene.add(triangle);
● 頂点カラーと頂点座標をアニメーション
GPU上のメモリにデータを転送するさい、attributeとunifomの2種類の変数が存在します。attributeは頂点座標や頂点カラーなど頂点ごとに定義され、uniformはオブジェクトごとに定義されます。
毎フレームごとに値が増加するstepをuniformでシェーダに転送し、stepを使用して頂点座標と頂点カラーをアニメーションします。
let triangle;
let uniforms;
let step = 0;
const vertexShader =`
attribute vec3 color;
//カスタムattributeの取得
attribute vec3 displacement;
varying vec3 vColor;
void main(){
vColor = color;
//頂点座標positionにカスタムattributeを加えて頂点座標をアニメーション
vec3 vv = position + displacement;
gl_Position = projectionMatrix * modelViewMatrix * vec4(vv,1.0);
}
`;
const fragmentShader =`
//uniformの取得
uniform float step;
varying vec3 vColor;
void main(){
//uniformのstepを使用して頂点カラーをアニメーション
float r = vColor.r + cos(step/50.0);
float g = vColor.g + cos(step/60.0);
float b = vColor.b + cos(step/70.0);
gl_FragColor = vec4(r,g,b,1.0);
}
`;
const positions = new Float32Array([
2.5,0.0,0.0,
0,5.0,0.0,
-2.5,0.0,0.0,
]);
const colors = new Float32Array([
1.0,0.0,0.0,
0.0,0.0,1.0,
0.0,1.0,0.0,
]);
//型付配列でカスタムattribute用の変数を設定
const displacement = new Float32Array(3*3);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position',new THREE.BufferAttribute(positions,3));
geometry.setAttribute('color',new THREE.BufferAttribute(colors,3));
//バッファーオブジェクトのattributeにカスタムattributeを設定
geometry.setAttribute('displacement',new THREE.BufferAttribute(displacement,3));
//シェーダーへ転送するuniformを設定
uniforms = {
step:{type:'f',value:1.0}
};
const material = new THREE.ShaderMaterial({
vertexShader:vertexShader,
fragmentShader:fragmentShader,
//uniformを設定
uniforms:uniforms,
side:THREE.DoubleSide,
});
triangle = new THREE.Mesh(geometry,material);
scene.add(triangle);
function rendering(){
requestAnimationFrame(rendering);
//stepの値を増加して、uniformに代入
step ++;
uniforms.step.value = step;
//カスタムattributeを更新
triangle.geometry.attributes.displacement.array[0] = 1.25 * Math.sin(step/50);
triangle.geometry.attributes.displacement.array[4] = 1.25* Math.sin(step/60);
triangle.geometry.attributes.displacement.array[6] = -1.25 * Math.sin(step/70);
//カスタムattributeの更新を通知するフラグ
triangle.geometry.attributes.displacement.needsUpdate = true;
renderer.render(scene,camera);
}
完成したデモになります。シェーダが使えるようになるといろいろな表現がでそうです!Three.jsのシェーダのデモなのでパソコンとスマホで見ることができるようにしました。

