blob: 5c2261ae8cb0e1f9aff4bc76b0d5f9b51f91d3f2 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
uniform samplerCube cube_map;
uniform sampler2D blue_noise;
uniform FragInfo {
vec2 texture_size;
float time;
}
frag_info;
in vec2 v_screen_position;
out vec4 frag_color;
const float kPi = acos(-1.0);
const float kHalfSqrtTwo = sqrt(2.0) / 2.0;
const float kEpsilon = 0.001;
// Materials (Albedo + reflectivity)
const vec4 kBottomColor = vec4(0.0, 0.34, 0.61, 0.5);
const vec4 kMiddleColor = vec4(0.16, 0.71, 0.96, 0.5);
const vec4 kTopColor = vec4(0.33, 0.77, 0.97, 0.5);
const vec4 kImpellerOuterColor = vec4(0.16, 0.71, 0.96, 0.5);
const vec4 kImpellerRimColor = vec4(0.1, 0.1, 0.1, 0.0);
const vec4 kImpellerBladeColor = vec4(0.1, 0.1, 0.1, 1.3);
// Scene
const int kMaxSteps = 70;
const float kMaxDistance = 300.0;
const vec3 kSunDirection = normalize(vec3(2, -5, 3));
const float kGlowBlend = 1.1;
const vec4 kGlowColor = vec4(0.86, 0.98, 1.0, 1);
const vec4 kGlowColor2 = vec4(1.66, 0.98, 0.5, 1);
// These refraction ratios are inverted for style purposes.
const float kAirToGlassIOR = 1.10;
const float kGlassToAirIOR = 1.0 / kAirToGlassIOR;
// Camera
const float kFocalLength = 12.0;
const float kApertureSize = 0.5;
const int kRaysPerFrag = 4;
mat3 RotateEuler(vec3 r) {
return mat3(
cos(r.x) * cos(r.y), cos(r.x) * sin(r.y) * sin(r.z) - sin(r.x) * cos(r.z),
cos(r.x) * sin(r.y) * cos(r.z) + sin(r.x) * sin(r.z), sin(r.x) * cos(r.y),
sin(r.x) * sin(r.y) * sin(r.z) + cos(r.x) * cos(r.z),
sin(r.x) * sin(r.y) * cos(r.z) - cos(r.x) * sin(r.z), -sin(r.y),
cos(r.y) * sin(r.z), cos(r.y) * cos(r.z));
}
//------------------------------------------------------------------------------
/// Noise functions.
///
vec2 Hash(float seed) {
vec2 n = vec2(dot(vec2(seed, -0.1), vec2(13.8767971, 22.2091485)),
dot(vec2(seed, -0.2), vec2(12.3432217, 48.0579381)));
return fract(sin(n) * 24791.8159993);
}
vec4 BlueNoise(vec2 uv) {
return texture(blue_noise, uv);
}
vec4 BlueNoiseWithRandomOffset(vec2 screen_position, float seed) {
return BlueNoise(screen_position / 256.0 + Hash(seed));
}
//------------------------------------------------------------------------------
/// Primitive distance functions.
///
float SphereDistance(vec3 sample_position,
vec3 sphere_position,
float sphere_size) {
return length(sample_position - sphere_position) - sphere_size;
}
float CuboidDistance(vec3 sample_position, vec3 cuboid_size) {
vec3 space = abs(sample_position) - cuboid_size;
return length(max(space, 0.0)) +
min(max(space.x, max(space.y, space.z)), 0.0);
}
//------------------------------------------------------------------------------
/// Scene distance functions.
///
float GlassBox(vec3 pos) {
mat3 basis = RotateEuler(vec3(frag_info.time * 0.21, frag_info.time * 0.24,
frag_info.time * 0.17));
vec3 glass_box_pos = pos + vec3(0, -4.5 + sin(frag_info.time), 0.0);
return CuboidDistance(basis * glass_box_pos, vec3(1, 1, 1)) - 3.0;
}
vec2 FlutterLogoField(vec3 pos) {
pos *= 1.3; // Scale down a bit.
// The shape below is made up of three parallelepipeds, each of which is a
// cuboid in scaled + skewed space. These shape fields are multiplied by the
// inverse of the max basis vector length of the space (i.e. 1 / sqrt(2); the
// same as kHalfSqrtTwo), which scales down the ray march step size by the
// right amount to avoid overstepping errors.
const float kFieldScale = kHalfSqrtTwo * 1.0 / 1.3;
vec3 r = vec3(sin(frag_info.time * 1.137) / 7.0, //
sin(frag_info.time * 1.398 + 0.7) / 8.0, //
sin(frag_info.time * 0.873 + 0.3) / 5.0);
// This homegrown rotation matrix isn't perfect, but it's fine for the < PI/2
// rotation being applied to the logo.
mat3 logo_basis = mat3(cos(r.z) * cos(r.y), sin(r.z), -sin(r.y), //
-sin(r.z), cos(r.z) * cos(r.x), -sin(r.x), //
sin(r.y), sin(r.x), cos(r.x) * cos(r.y));
vec3 logo_pos =
logo_basis * pos + vec3(-1.0, -4.0 + sin(frag_info.time), 0.0);
// Bottom prism.
float logo0 =
CuboidDistance(logo_pos + vec3(-logo_pos.y, 0, 0), vec3(1, 2, 0.6)) *
kFieldScale;
float logo0_cutoff_plane =
dot(logo_pos + vec3(0.5, 0.5, 0), normalize(vec3(1, 1, 0)));
logo0 = max(logo0, logo0_cutoff_plane);
float dist = logo0;
float material = 1.0;
// Middle prism.
float logo1 =
CuboidDistance(logo_pos + vec3(logo_pos.y, 0, 0), vec3(1, 2, 0.7)) *
kFieldScale;
float logo1_cutoff_plane =
dot(logo_pos + vec3(-0.5, 0.5, 0), normalize(vec3(1, -1, 0)));
logo1 = max(logo1, logo1_cutoff_plane);
if (logo1 < dist) {
dist = logo1;
float material_cutoff_plane =
dot(logo_pos + vec3(0.5, -0.5, 0), normalize(vec3(1, -1, 0)));
material = material_cutoff_plane > 0.0 ? 2.0 : 3.0;
}
// Top prism.
float logo2 = CuboidDistance(logo_pos + vec3(logo_pos.y - 3.0, -2, 0),
vec3(1, 3.5, 0.7)) *
kFieldScale;
logo2 = max(logo2, logo1_cutoff_plane);
if (logo2 < dist) {
dist = logo2;
material = 3.0;
}
return vec2(dist, material);
}
vec2 InnerGlassBoxField(vec3 pos) {
vec2 flutter_logo = FlutterLogoField(pos);
float dist = flutter_logo.x;
float material = flutter_logo.y;
// Inner glass box.
float glass_box = -GlassBox(pos);
if (glass_box < dist) {
dist = glass_box;
material = -3.0; // Transfer from glass to air.
}
return vec2(dist, material);
}
vec2 ImpellerField(vec3 pos) {
float xz_dist = length(pos.xz);
float impeller = min(0.5, xz_dist / 3.0) *
sin(xz_dist * 2.0 - mod(frag_info.time, kPi) * 30.0 +
atan(pos.z, pos.x) * 6.0) *
1.5;
float impeller_side = xz_dist / 2.0 - 4.0;
float stage_height =
mix(impeller, impeller_side, clamp(xz_dist - 4.6, 0.0, 1.0));
float stage_plane =
dot(pos + vec3(0, 3.0 + stage_height, 0), normalize(vec3(0, 1, 0))) * 0.5;
float stage_sphere = SphereDistance(pos + vec3(0, 2, 0), vec3(0), 6.0);
float stage = max(stage_plane, stage_sphere);
float material = 4.0;
if (xz_dist < 5.6 && pos.y > -7.0) {
material = (pos.y > -2.36 && pos.y < -2.1) ? 5.0 : 6.0;
} else {
material = 4.0;
}
return vec2(stage, material);
}
vec2 SceneField(vec3 pos) {
float glass_box = GlassBox(pos);
float dist = glass_box;
float material = -2.0; // Transfer from air to glass.
vec2 impeller = ImpellerField(pos);
if (impeller.x < dist) {
dist = impeller.x;
material = impeller.y;
}
return vec2(dist - 0.01, material);
}
/// For shadows, just ignore the glass box.
vec2 ShadowField(vec3 pos) {
vec2 flutter_logo = FlutterLogoField(pos);
float dist = flutter_logo.x;
float material = flutter_logo.y;
vec2 impeller = ImpellerField(pos);
if (impeller.x < dist) {
dist = impeller.x;
material = impeller.y;
}
return vec2(dist, material);
}
//------------------------------------------------------------------------------
/// Surface computation.
///
vec2 March(vec3 sample_position,
vec3 dir,
out int steps_taken,
bool inside_glass_box,
bool shadow) {
float depth = 0.0;
for (int i = 0; i < kMaxSteps; i++) {
if (depth > kMaxDistance) {
steps_taken = i;
return vec2(kMaxDistance, -1.0);
}
vec3 pos = sample_position + dir * depth;
vec2 result;
if (shadow) {
result = ShadowField(pos);
} else {
result = inside_glass_box ? InnerGlassBoxField(pos) : SceneField(pos);
}
if (abs(result.x) < kEpsilon) {
steps_taken = i;
return vec2(depth, result.y);
}
depth += result.x;
}
steps_taken = kMaxSteps;
return vec2(kMaxDistance, -1.0);
}
vec3 SceneGradient(vec3 sample_position) {
return normalize(
vec3(SceneField(sample_position + vec3(kEpsilon, 0, 0)).x -
SceneField(sample_position + vec3(-kEpsilon, 0, 0)).x,
SceneField(sample_position + vec3(0, kEpsilon, 0)).x -
SceneField(sample_position + vec3(0, -kEpsilon, 0)).x,
SceneField(sample_position + vec3(0, 0, kEpsilon)).x -
SceneField(sample_position + vec3(0, 0, -kEpsilon)).x));
}
vec3 InnerGlassGradient(vec3 sample_position) {
return normalize(
vec3(InnerGlassBoxField(sample_position + vec3(kEpsilon, 0, 0)).x -
InnerGlassBoxField(sample_position + vec3(-kEpsilon, 0, 0)).x,
InnerGlassBoxField(sample_position + vec3(0, kEpsilon, 0)).x -
InnerGlassBoxField(sample_position + vec3(0, -kEpsilon, 0)).x,
InnerGlassBoxField(sample_position + vec3(0, 0, kEpsilon)).x -
InnerGlassBoxField(sample_position + vec3(0, 0, -kEpsilon)).x));
}
float MarchShadow(vec3 position) {
int shadow_steps;
vec2 shadow_result = March(position + -kSunDirection * 0.03, -kSunDirection,
shadow_steps, false, true);
float shadow_percentage = (float(shadow_steps)) / float(kMaxSteps);
float shadow_multiplier = 1.6 - shadow_percentage;
if (shadow_result.x < kMaxDistance) {
shadow_multiplier = 0.6;
}
return shadow_multiplier;
}
//------------------------------------------------------------------------------
/// Color composition.
///
vec4 EnvironmentColor(vec3 ray_direction) {
return texture(cube_map, ray_direction);
}
vec4 SurfaceColor(vec3 ray_direction,
vec3 surface_position,
vec3 surface_normal,
float material,
float shadow_multiplier) {
vec3 reflection_direction = reflect(ray_direction, surface_normal);
vec4 reflection_color = texture(cube_map, reflection_direction);
vec4 material_value;
if (material < 1.5) {
material_value = kBottomColor;
} else if (material < 2.5) {
material_value = kMiddleColor;
} else if (material < 3.5) {
material_value = kTopColor;
} else if (material < 4.5) {
material_value = kImpellerOuterColor;
} else if (material < 5.5) {
material_value = kImpellerRimColor;
} else {
material_value = kImpellerBladeColor;
}
return mix(vec4(material_value.rgb * shadow_multiplier, 1.0),
reflection_color,
dot(-ray_direction, surface_normal) - 1.0 + material_value.a);
}
vec4 SceneColor(vec3 ray_position,
vec3 ray_direction,
vec3 surface_normal,
float dist,
float material,
int steps_taken,
float shadow_multiplier,
vec4 ray_noise) {
vec4 result_color;
if (dist >= kMaxDistance) {
result_color = EnvironmentColor(ray_direction);
} else {
vec3 surface_position = ray_position + ray_direction * dist;
result_color = SurfaceColor(ray_direction, surface_position, surface_normal,
material, shadow_multiplier);
}
float glow_factor = float(steps_taken) / float(kMaxSteps);
vec4 glow_color =
mix(kGlowColor, kGlowColor2, sin(frag_info.time / 3.0) * 0.5 + 0.5);
return mix(result_color, glow_color, glow_factor * kGlowBlend);
}
vec4 CombinedColor(vec3 ray_position, vec3 ray_direction, vec4 ray_noise) {
int steps_taken;
vec2 result = March(ray_position, ray_direction, steps_taken, false, false);
ray_position = ray_position + ray_direction * result.x;
vec3 surface_normal = SceneGradient(ray_position);
float glass_reflection_factor = 0.0;
vec4 glass_reflection_color = vec4(0);
if (result.y == -2.0) { // March into the glass.
vec3 glass_reflection_direction = reflect(ray_direction, surface_normal);
glass_reflection_color = EnvironmentColor(glass_reflection_direction);
glass_reflection_factor =
0.5 - dot(glass_reflection_direction, surface_normal) * 0.6;
ray_direction = refract(ray_direction, surface_normal, kAirToGlassIOR);
ray_position += ray_direction * 0.5;
int steps;
result = March(ray_position, ray_direction, steps, true, false);
steps_taken += steps;
ray_position = ray_position + ray_direction * result.x;
surface_normal = InnerGlassGradient(ray_position);
}
if (result.y == -3.0) { // March out of the glass.
ray_direction = refract(ray_direction, surface_normal, kGlassToAirIOR);
ray_position += ray_direction * 1.0;
int steps;
result = March(ray_position + ray_direction * result.x, ray_direction,
steps, false, false);
steps_taken += steps;
ray_position = ray_position + ray_direction * result.x;
surface_normal = SceneGradient(ray_position);
}
float shadow_multiplier = MarchShadow(ray_position);
vec4 scene_color =
SceneColor(ray_position, ray_direction, surface_normal, result.x,
result.y, steps_taken, shadow_multiplier, ray_noise);
return mix(scene_color, glass_reflection_color, glass_reflection_factor);
}
//------------------------------------------------------------------------------
/// Camera/lens.
///
vec3 GetFragDirection(vec2 uv, vec3 cam_forward) {
vec2 lens_uv =
(uv - 0.5 * frag_info.texture_size) / frag_info.texture_size.xx;
vec3 cam_right = cross(cam_forward, vec3(0, 1, 0));
vec3 cam_up = cross(cam_forward, cam_right);
float fov = 65.0 * kPi / 180.0;
return normalize(cam_forward * cos(fov) + cam_right * lens_uv.x * sin(fov) +
cam_up * lens_uv.y * sin(fov));
}
void main() {
float cam_time = frag_info.time / 2.0;
vec3 cam_position =
vec3(-sin(cam_time + 0.2) * 6.25, -cos(cam_time + 0.3) * 2.9 + 1.0,
-cos(cam_time - 0.1) * 5.4) *
2.0;
vec3 cam_direction = normalize(-cam_position);
cam_position += vec3(0, 2, 0);
vec3 ray_direction = GetFragDirection(v_screen_position, cam_direction);
vec3 lens_position = cam_position + ray_direction * kFocalLength;
for (int i = 0; i < kRaysPerFrag; i++) {
vec4 ray_noise = BlueNoiseWithRandomOffset(
v_screen_position, float(i) + mod(frag_info.time, 10.0));
// The rays should be starting from a flat position on the lens, but just
// jittering them around in a 3d box looks good enough.
vec3 ray_start = cam_position + ray_noise.xyz * kApertureSize;
vec3 ray_direction = normalize(lens_position - ray_start);
vec4 result_color = CombinedColor(ray_start, ray_direction, ray_noise);
frag_color += result_color / float(kRaysPerFrag);
}
}