R"raw_string(
#version 120

struct intersection
{
	bool intersected;
	vec3 point;
	vec3 normal;
	vec3 color;
	float reflection;
	int idx;
	//todo different types
};

varying vec2 outvertex;

//settings
uniform float aspectratio;
uniform float time;
uniform int supersamples;
uniform int reflections;
uniform bool shadows;
uniform vec3 backgroundcolor;

//light
uniform vec3 l_position;
uniform float l_radius;

//spheres
//todo ubo?
uniform int s_length;
uniform vec3 s_positions[100];
uniform float s_radii[100];
uniform vec4 s_colors[100];
uniform float s_reflections[100];

const float PI = 3.14159265359;

//din = german industrial normal
float light_diffuse(vec3 pos, vec3 din)
{
	vec3 l_dir = normalize(l_position - pos);
	return max(dot(l_dir, din), 0.0f);
}

//adapted from https://wiki.delphigl.com/index.php/shader_ConeVolumeShadow
float light_shadow(vec3 pos)
{
	float s = 1.0f;

	for(int l = 0; l < s_length; l++)
	{
		vec3 s_pos = s_positions[l];
		float s_rad = s_radii[l];

		// project fragment (pos) on the cone axis => F_
		vec3 nvLO = s_pos - l_position;
		float dLO = length(nvLO);
		nvLO /= dLO;
		vec3 vLF = pos - l_position;
		float dLF_ = dot(vLF, nvLO);
		if (dLF_ < dLO) {
			// fragment before occluder => no shadow
			continue;
		}
		vec3 F_ = l_position + dLF_ * nvLO;
		float rF = distance(F_, pos);

		// compute outer and inner radius at F_
		float rF_outer = (s_rad + l_radius) * (dLF_ / dLO) - l_radius;
		if (rF >= rF_outer) {
			// outside the outer cone => no shadow
			continue;
		}
		float rF_inner = (s_rad - l_radius) * (dLF_ / dLO) + l_radius;
		if (rF_inner >= rF) {
			// inside the inner cone => full shadow
			return 0.0;
		}
		else if (rF_inner >= 0.0 || rF >= -rF_inner) {
			// soft shadow, linear interpolation
			s *= (rF - rF_inner) / (rF_outer - rF_inner);
		}
		else {
			// light from both sides of the occluder
			s *= (-2.0*rF_inner) / (rF_outer - rF_inner);
		}

	}

	return s;
}

intersection intersect_sphere(vec3 r_pos, vec3 r_dir, vec3 s_pos, float s_rad, inout float minimum)
{
	intersection result = intersection(false,vec3(0.0f, 0.0f, 0.0f),
						 vec3(0.0f, 0.0f, 0.0f),
						 vec3(0.0f, 0.0f, 0.0f), 0.0f, -1);

	vec3 v = r_pos - s_pos;
	float a = dot(r_pos, r_pos);
	float b = dot(v, r_dir);
	float c = dot(v, v) - (s_rad * s_rad);
	float d = b * b - c;

	if(d >= 0)
	{
		float w1 = -b - sqrt(d);
		float w2 = -b + sqrt(d);

		if(w1 > 0.0f || w2 > 0.0f)
		{
			float w = min(w1, w2);
			float o = max(w1, w2);

			if(w1 <= 0.0f)
				w = w2;
			else
			if(w2 <= 0.0f)
				w = w1;

			if(w < minimum)
			{
				minimum = w;
			}
			else
			{
				return intersection(false,vec3(0.0f, 0.0f, 0.0f),
							  vec3(0.0f, 0.0f, 0.0f),
							  vec3(0.0f, 0.0f, 0.0f), 0.0f, -1);
			}

			vec3 p = r_pos + (w * r_dir);
			result.intersected = true;
			result.point = p;
			result.normal = normalize((p - s_pos) / s_rad);
			return result;
		}
	}

	return result;
}

intersection intersect(vec3 r_pos, vec3 r_dir, int idx)
{
	intersection result = intersection(false,vec3(0.0f, 0.0f, 0.0f),
						 vec3(0.0f, 0.0f, 0.0f),
						 vec3(0.0f, 0.0f, 0.0f), 0.0f, -1);
	float minimum = 1000.0f;

	for(int l = 0; l < s_length; l++)
	{
		if(idx == l)
			continue;

		float maximum;

		intersection r = intersect_sphere(r_pos, r_dir, s_positions[l], s_radii[l], minimum);
		if(r.intersected)
		{
			r.color = s_colors[l].rgb;
			r.idx = l;
			r.reflection = s_reflections[l];

			result = r;
		}
		//todo other geometric objects
		//todo handle min with other geometric objects
	}

	return result;
}

vec3 cast_ray(vec3 r_pos, vec3 r_pixel)
{
	vec3 r_dir = normalize(r_pixel - r_pos);

	vec3 color = backgroundcolor;

	intersection i = intersection(false,vec3(0.0f, 0.0f, 0.0f),
					    vec3(0.0f, 0.0f, 0.0f),
					    vec3(0.0f, 0.0f, 0.0f), 0.0f, -1);

	vec3 p = r_pos;
	vec3 d = r_dir;
	int idx = -1;

	for(int bounce = 0; bounce < reflections; bounce++)
	{
		intersection i = intersect(p,d, idx);

		if(i.intersected)
		{
			float l_factor = light_diffuse(i.point, i.normal);

			if(shadows && l_factor > 0.004f)
			{
				l_factor *= light_shadow(i.point);
			}

			if(bounce == 0)
			{
				color = i.color * l_factor;
			}
			else
			{
				color = mix(color, color * i.color * l_factor, i.reflection);
			}

			//d = 2*(dot(p, i.normal))*i.normal - p;
			d = reflect(-p, i.normal);
			p = i.point;
			idx = i.idx;
		}
		else
			break;
	}

	return color;
}

void main() 
{
	vec2 rp = outvertex;
	rp.x = rp.x * aspectratio;
	vec4 outcolor = vec4(cast_ray(vec3(0.0f, 0.0f, 1.0f), vec3(rp, 0.0f)), 0.0f);
	for(int supersample = 1; supersample < supersamples; supersample++)
	{
		outcolor = mix(outcolor, vec4(cast_ray(vec3(sin(supersample * PI/supersamples)/200, cos(supersample * PI/supersamples)/200, 1.0f), vec3(rp, 0.0f)), 0.0f), 0.5f);
	}
  gl_FragColor = outcolor;
}

)raw_string"
