WebGLでレイマーチングするときでも、three.jsのTHREE.OrbitControls
などでカメラを制御できたら便利だろうなと思って調べました。
先に結果をお見せすると、こんな感じです。上がTHEEE.OrbitControls
で制御したTHREE.PerspectiveCamera
で普通にレンダリングしたもので、下が同じカメラの情報を基にレイマーチングでレンダリングしたものです。色以外は同じように表示されています。
以下で、実際に動いているものを確認できます。
https://aadebdeb.github.io/study-three.js/raymarching-with-threejs-camera.html
具体的に何をしているかというと、three.jsのPerspectiveCameara
の情報をレイマーチングを行うシェーダーにuniformで渡して、その情報を基にレイを生成しています。
レイマーチングを行うシェーダー側はこのようになっています。perspectiveCamera
関数の設計に関してはこの記事も参考にしてください。
uniform vec2 resolution;
uniform vec3 cameraPosition;
uniform vec3 cameraDirection;
uniform float cameraAspect;
uniform float cameraFov;
void perspectiveCamera(in vec2 uv, in vec3 position, in vec3 cameraDirection, in float fov, in float aspect, out vec3 origin, out vec3 dir) {
vec2 st = uv * 2.0 - 1.0;
float radian = fov * PI / 180.0;
float h = tan(radian * 0.5);
float w = h * aspect;
vec3 right = normalize(cross(cameraDirection, vec3(0.0, 1.0, 0.0)));
vec3 up = normalize(cross(right, cameraDirection));
dir = normalize(right * w * st.x + up * h * st.y + cameraDirection);
origin = position;
}
void main() {
vec3 rayOrigin, rayDirection;
perspectiveCamera(gl_FragCoord.xy / resolution, cameraPosition, cameraDirection, cameraFov, cameraAspect, rayOrigin, rayDirection);
vec3 color = raymarch(rayOrigin, rayDirection);
gl_FragColor = vec4(color, 1.0);
}
JavaScript側でTHREE.PerspectiveCamera
の情報をシェーダー側に渡す箇所はこのようになっています。このコードではTHREE.RawShaderMaterial
のunifomsパラメータでunifomを渡すことを想定しています。
THREE.PerspectiveCamera
の#getWorldDirection
と#getWorldPosition
でワールド座標系でのカメラの方向と位置をそれぞれ引数に渡した変数に入れてくれます。このメソッドをレンダリングループのOrbitControls#update
を実行した後などで呼ぶことでレイマーチングのシェーダー側でも現在のカメラの情報を利用することができるようになります。
const raymarchUniforms = {
resolution: {value: new THREE.Vector2(window.innerWidth, window.innerHeight / 2.0)},
cameraPosition: {value: new THREE.Vector3()},
cameraDirection: {value: new THREE.Vector3()},
cameraAspect: {value: camera.aspect},
cameraFov: {value: camera.fov},
};
// called in rendering loop
camera.getWorldDirection(raymarchUniforms.cameraDirection.value);
camera.getWorldPosition(raymarchUniforms.cameraPosition.value);