use cvmath::*;
const SPHERE_FS: &str = r#"\
#version 330 core
out vec4 o_fragColor;
in vec3 v_worldPos;
uniform vec3 u_cameraPosition;
uniform vec3 u_globePosition;
uniform float u_globeRadius;
uniform sampler2D u_texture;
uniform vec3 u_lightPos;
uniform sampler2DShadow u_shadowMap;
uniform mat4 u_lightTransform;
const float PI = 3.141592653589793;
void main() {
// Ray from camera through fragment
vec3 rayDir = normalize(v_worldPos - u_cameraPosition);
vec3 rayOrigin = u_cameraPosition;
// Sphere centered at globePosition (world space)
vec3 oc = rayOrigin - u_globePosition;
float a = dot(rayDir, rayDir);
float b = 2.0 * dot(oc, rayDir);
float c = dot(oc, oc) - u_globeRadius * u_globeRadius;
float discriminant = b*b - 4.0*a*c;
if (discriminant < 0.0) {
// Purple debug color
// o_fragColor = vec4(0.5, 0.0, 0.5, 1.0);
discard;
return;
}
// Nearest positive intersection (handles camera-inside-sphere too)
float sqrtD = sqrt(discriminant);
float t0 = (-b - sqrtD) / (2.0 * a);
float t1 = (-b + sqrtD) / (2.0 * a);
float t = (t0 > 0.0) ? t0 : t1;
if (t < 0.0) discard;
vec3 hitPos = rayOrigin + t * rayDir;
vec3 n = normalize(hitPos - u_globePosition);
vec3 lightDir = normalize(u_lightPos - hitPos);
float diffLight = max(dot(n, lightDir), 0.0);
vec4 lightClip = u_lightTransform * vec4(hitPos, 1.0);
vec3 lightNdc = lightClip.xyz / lightClip.w;
vec3 shadowUvZ = lightNdc * 0.5 + 0.5;
float bias = 0.001;
float visibility = texture(u_shadowMap, vec3(shadowUvZ.xy, shadowUvZ.z - bias));
// Spherical UVs (equirectangular)
// World is Z-up in this demo (see ArcballCamera::new(..., up = Z)).
// Longitude around +Z axis, latitude from equator toward +Z.
float u = 0.5 + atan(n.y, n.x) / (2.0 * PI);
float v = 0.5 + asin(n.z) / PI;
// PNG rows decode top-to-bottom; OpenGL UV (0,0) samples the first row.
// Flip V so the image appears upright.
v = 1.0 - v;
// Keep within [0,1) for wrapping samplers.
u = fract(u);
vec2 uv = vec2(u, v);
vec2 uv_dx = dFdx(uv);
vec2 uv_dy = dFdy(uv);
// Fix discontinuity at the wrap seam so implicit mip selection doesn't explode.
if (abs(uv_dx.x) > 0.5) uv_dx.x -= sign(uv_dx.x);
if (abs(uv_dy.x) > 0.5) uv_dy.x -= sign(uv_dy.x);
vec3 color = textureGrad(u_texture, uv, uv_dx, uv_dy).rgb;
float ambient = 0.2;
float direct_intensity = 0.6;
float lighting = ambient + visibility * (diffLight * direct_intensity);
o_fragColor = vec4(color * lighting, 1.0);
}
"#;
const SPHERE_SHADOW_FS: &str = r#"\
#version 330 core
in vec3 v_worldPos;
uniform vec3 u_cameraPosition;
uniform vec3 u_globePosition;
uniform float u_globeRadius;
uniform mat4x3 u_viewMatrix;
uniform mat4 u_projMatrix;
void main() {
// Ray from light-camera through fragment
vec3 rayDir = normalize(v_worldPos - u_cameraPosition);
vec3 rayOrigin = u_cameraPosition;
vec3 oc = rayOrigin - u_globePosition;
float a = dot(rayDir, rayDir);
float b = 2.0 * dot(oc, rayDir);
float c = dot(oc, oc) - u_globeRadius * u_globeRadius;
float discriminant = b*b - 4.0*a*c;
if (discriminant < 0.0) {
discard;
return;
}
float sqrtD = sqrt(discriminant);
float t0 = (-b - sqrtD) / (2.0 * a);
float t1 = (-b + sqrtD) / (2.0 * a);
float t = (t0 > 0.0) ? t0 : t1;
if (t < 0.0) discard;
vec3 hitPos = rayOrigin + t * rayDir;
vec4 clip = u_projMatrix * mat4(u_viewMatrix) * vec4(hitPos, 1.0);
float ndcDepth = clip.z / clip.w;
gl_FragDepth = ndcDepth * 0.5 + 0.5;
}
"#;
const SPHERE_VS: &str = r#"\
#version 330 core
in vec3 a_pos;
uniform mat4x3 u_viewMatrix;
uniform mat4 u_projMatrix;
uniform vec3 u_globePosition;
uniform float u_globeRadius;
out vec3 v_worldPos;
void main() {
// The mesh is a unit icosahedron in [-1, 1]^3. Scale it to radius (R) and translate.
vec3 world = u_globePosition + a_pos * (1.27 * u_globeRadius);
vec4 worldPos = vec4(world, 1.0);
v_worldPos = worldPos.xyz;
gl_Position = u_projMatrix * mat4(u_viewMatrix) * worldPos;
}
"#;
pub struct Material {
shader: shade::ShaderProgram,
shadow_shader: shade::ShaderProgram,
texture: shade::Texture2D,
}
impl shade::UniformVisitor for Material {
fn visit(&self, set: &mut dyn shade::UniformSetter) {
set.value("u_texture", &self.texture);
}
}
pub struct Instance {
position: Vec3f,
radius: f32,
}
impl shade::UniformVisitor for Instance {
fn visit(&self, set: &mut dyn shade::UniformSetter) {
set.value("u_globePosition", &self.position);
set.value("u_globeRadius", &self.radius);
}
}
pub struct Renderable {
pub mesh: shade::d3::VertexMesh,
pub material: Material,
pub instance: Instance,
}
impl Renderable {
pub fn create(g: &mut shade::Graphics) -> Renderable {
let mesh = shade::d3::icosahedron::icosahedron_flat(g);
let shader = g.shader_compile(SPHERE_VS, SPHERE_FS);
let shadow_shader = g.shader_compile(SPHERE_VS, SPHERE_SHADOW_FS);
let texture = {
let image = shade::image::DecodedImage::load_file("examples/textures/2k_earth_daymap.jpg").unwrap();
let props = shade::TextureProps {
mip_levels: 8,
usage: shade::TextureUsage::TEXTURE,
filter_min: shade::TextureFilter::Linear,
filter_mag: shade::TextureFilter::Linear,
wrap_u: shade::TextureWrap::Repeat,
wrap_v: shade::TextureWrap::Repeat,
..Default::default()
};
g.image(&(&image, &props))
};
let material = Material { shader, shadow_shader, texture };
let instance = Instance {
position: Vec3f(20.0, 20.0, 20.0),
radius: 10.0,
};
Renderable { mesh, material, instance }
}
pub fn draw(&self, g: &mut shade::Graphics, _globals: &super::Globals, camera: &shade::d3::Camera, light: &super::Light, shadow: bool) {
g.draw(&shade::DrawArgs {
scissor: None,
blend_mode: shade::BlendMode::Solid,
depth_test: Some(shade::Compare::LessEqual),
cull_mode: Some(shade::CullMode::CW),
mask: if shadow { shade::DrawMask::DEPTH } else { shade::DrawMask::ALL },
prim_type: shade::PrimType::Triangles,
shader: if shadow { self.material.shadow_shader } else { self.material.shader },
uniforms: &[
camera,
light,
&self.material,
&self.instance,
&shade::UniformFn(|set| {
set.value("u_lightTransform", &light.light_view_proj);
}),
],
vertices: &[shade::DrawVertexBuffer {
buffer: self.mesh.vertices,
divisor: shade::VertexDivisor::PerVertex,
}],
vertex_start: 0,
vertex_end: self.mesh.vertices_len,
instances: -1,
});
}
}
impl super::IRenderable for Renderable {
fn update(&mut self, _globals: &crate::Globals) {
}
fn draw(&self, g: &mut shade::Graphics, globals: &crate::Globals, camera: &shade::d3::Camera, light: &crate::Light, shadow: bool) {
self.draw(g, globals, camera, light, shadow)
}
fn get_bounds(&self) -> (Bounds3f, Transform3f) {
(self.mesh.bounds, Transform3f::translation(self.instance.position) * Transform3f::scaling(Vec3f::dup(self.instance.radius)))
}
}