GLSLで透視投影カメラと平行投影カメラ

GLSLでレイトレーシングするときにレイを作成する方法を忘れるのでメモ。

透視投影カメラでレイを作成するため関数は次のようになる。
引数に渡された`origin`と`ray`にそれぞれレイの原点と方向がセットされる。
`origin`はカメラの位置`position`と同じなので`out`を用いず`ray`を返り値にするだけでもよいが、後述する平行投影カメラのレイを作成する関数`orthographic`と形をできるだけ似せるためにこのようにした。

```glsl
// perspective camera
// @param st - sceen position in [0, 1]
// @param position - position of camera
// @param target - target of camera
// @param vup - up direction of camera
// @param vfov - vertical field of view
// @param aspect - aspect rage (x / y)
// @param origin - ray origin
// @param ray - ray direction
void perspective(in vec2 st, in vec3 position, in vec3 target, in vec3 vup, in float vfov, in float aspect, out vec3 origin, out vec3 ray) {
	vec2 uv = st * 2.0 - 1.0;
	float radian = vfov * PI / 180.0;
	float h = tan(radian * 0.5);
	float w = h * aspect;
	vec3 front = normalize(target - position);
	vec3 right = normalize(cross(front, normalize(vup)));
	vec3 up = normalize(cross(right, front));
	
	ray = normalize(right * w * uv.x + up * h * uv.y + front);	
	origin = position;
}
```

<img width="1260" alt="perspective.png" src="https://qiita-image-store.s3.amazonaws.com/0/61569/a47d27cc-91be-dc84-f1f1-05039fd52740.png">


平行投影カメラでレイを作成するための関数は次のようになる。
先に述べたように引数は`perspective`とほぼ同じ。

```glsl
// orthographic camera
// @param st - sceen position in [0, 1]
// @param position - position of camera
// @param target - target of camera
// @param vup - up direction of camera
// @param height - height of camera view
// @param aspect - aspect rage (x / y)
// @param origin - ray origin
// @param ray - ray direction
void orthographic(in vec2 st, in vec3 position, in vec3 target, in vec3 vup, in float height, in float aspect, out vec3 origin, out vec3 ray) {
	vec2 uv = st * 2.0 - 1.0;
	float width = height * aspect;
	vec3 front = normalize(target - position);
	vec3 right = normalize(cross(front, normalize(vup)));
	vec3 up = normalize(cross(right, front));
	
	ray = front;
	origin = position + right * width * 0.5 * uv.x + up * height * 0.5 * uv.y;
}
```

<img width="1262" alt="orthographic.png" src="https://qiita-image-store.s3.amazonaws.com/0/61569/b0f8bdf5-e996-1897-ba52-2a8f5cd713c2.png">


以下は、この関数を使ったGLSLプログラムの例である。
main関数内で`perspective`と`orthographic`のどちらかを選択する。
[glslsandbox.com](http://glslsandbox.com/e#43171.1)を使って作成したので、uniform変数などはそれに従ってる。

```glsl
#ifdef GL_ES
precision mediump float;
#endif

#extension GL_OES_standard_derivatives : enable

#define PI 3.14159265359

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

float random(vec2 st) {
	return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}

vec3 repete(vec3 p, vec3 interval) {
	return mod(p, interval) - interval / 2.0;
}

vec3 translate(vec3 p, vec3 offset) {
	return p - offset;
}

float sphere(vec3 p, float radius) {
	return length(p) - radius;
}

float box(vec3 p, vec3 size) {
	return length(max(abs(p) - size / 2.0, 0.0));
}

float scene(vec3 p) {
	vec3 q = repete(p, vec3(2.0, 0.0, 2.0));
	vec3 sp = translate(q, vec3(-0.5, 0.0, 0.0));
	vec3 bp = translate(q, vec3(0.5, 0.0, 0.0));
	return min(sphere(sp, 0.5), box(bp, vec3(0.5)));
}

vec3 normal(vec3 p) {
	float d = 0.00001;
	return normalize(vec3(
		scene(p + vec3(d, 0.0, 0.0)) - scene(p + vec3(- d, 0.0, 0.0)),
		scene(p + vec3(0.0, d, 0.0)) - scene(p + vec3(0.0, - d, 0.0)),
		scene(p + vec3(0.0, 0.0, d)) - scene(p + vec3(0.0, 0.0, - d))
	));
}

// perspective camera
// @param st - sceen position in [0, 1]
// @param position - position of camera
// @param target - target of camera
// @param vup - up direction of camera
// @param vfov - vertical field of view
// @param aspect - aspect rage (x / y)
// @param origin - ray origin
// @param ray - ray direction
void perspective(in vec2 st, in vec3 position, in vec3 target, in vec3 vup, in float vfov, in float aspect, out vec3 origin, out vec3 ray) {
	vec2 uv = st * 2.0 - 1.0;
	float radian = vfov * PI / 180.0;
	float h = tan(radian * 0.5);
	float w = h * aspect;
	vec3 front = normalize(target - position);
	vec3 right = normalize(cross(front, normalize(vup)));
	vec3 up = normalize(cross(right, front));
	
	ray = normalize(right * w * uv.x + up * h * uv.y + front);	
	origin = position;
}

// orthographic camera
// @param st - sceen position in [0, 1]
// @param position - position of camera
// @param target - target of camera
// @param vup - up direction of camera
// @param height - height of camera view
// @param aspect - aspect rage (x / y)
// @param origin - ray origin
// @param ray - ray direction
void orthographic(in vec2 st, in vec3 position, in vec3 target, in vec3 vup, in float height, in float aspect, out vec3 origin, out vec3 ray) {
	vec2 uv = st * 2.0 - 1.0;
	float width = height * aspect;
	vec3 front = normalize(target - position);
	vec3 right = normalize(cross(front, normalize(vup)));
	vec3 up = normalize(cross(right, front));
	
	ray = front;
	origin = position + right * width * 0.5 * uv.x + up * height * 0.5 * uv.y;
}

void main( void ) {
	
	vec2 st = gl_FragCoord.xy / resolution;
	
	vec3 position = vec3(mouse.x * 10.0 - 5.0, mouse.y * 10.0 - 5.0, 5.0);
	vec3 target = vec3(0.0, 0.0, 0.0);
	
	vec3 light = normalize(vec3(1.0, 2.0, 3.0));
	

	vec3 origin, ray;
	perspective(st, position, target, vec3(0.0, 1.0, 0.0), 60.0, resolution.x / resolution.y, origin, ray);
	//orthographic(st, position, target, vec3(0.0, 1.0, 0.0), 10.0, resolution.x / resolution.y, origin, ray); 
	
	float d = 0.0;
	vec3 p = origin;
	for(int i = 0; i < 64; i++) {
		d = scene(p);
		p += ray * d;
	}
	vec3 color = abs(d) < 0.001 ? clamp(dot(normal(p), light), 0.0, 1.0)  * vec3(1.0, 1.0, 1.0) + vec3(0.2) :  vec3(0.0);
	
	gl_FragColor = vec4(color, 1.0);
	
}
```