Drawing and Lighting a Sphere by Recursive Segmentation of an Icosahedron


First, we draw three orthogonal golden rectangles centered on the origin. A golden rectangle has the ratio of its edge lengths equal to the golden ratio.

A1B1A2B2

And the ratio can be solved by:A1B1=A2B2,B1=A2,A1=A2+B2\dfrac{A_1}{B_1} = \dfrac{A_2}{B_2}, B_1 = A_2, A_1 = A_2 + B_2A2+B2A2=A2B2\dfrac{A_2 + B_2}{A_2} = \dfrac{A_2}{B_2}1+B2A2=A2B21 + \dfrac{B_2}{A_2} = \dfrac{A_2}{B_2}A22B22A2B21=0\dfrac{A_2^2}{B_2^2} - \dfrac{A_2}{B_2} - 1 = 0x2x1=0x^2 - x - 1 = 0Then use the quadratic equationx=1+52x = \dfrac{1 + \sqrt{5}}{2}Then arrange them like this:



Then connect the corners of the golden rectangles to their 5 neighbors. Each triplet of adjacent points can form a triangle.


Then add a triangle face to each adjacent triplet of corners. Now we have a 20 face icosahedron.


Then split each triangle into 4, and normalize all the corners to be the same distance away. Since each face is now 4 faces, there are now 80 faces.


Do it again to get 320 faces.


Then again to get 1280 faces. Now it looks like a sphere.


Then we can remove the rectangles and fill in the faces of thetriangles.


To light and modify the shader we can diffusive the light linearly on the cosine of the angle between the light and the normal vector for the surface.

θ

For any point on the sphere pp with normal vector v\vec{v}, and any point light source at position ll the new color can be calculated from the base color by:


cos(θ)=(pl)vplv\cos(\theta) = \frac{(p-l) \cdot \vec{v}}{|p-l| |\vec{v}|}
r,g,b=r,g,b×lerp(A,B,cos(clamp(θ,0,1)))\langle r', g', b' \rangle = \langle r, g, b \rangle \times \text{lerp} (A, B, \cos(\text{clamp}(\theta, 0, 1)))

Where AA is the ambient brightness, and BB is the brightness of the light. lerp\text{lerp} just means linear interpolation, and clamp\text{clamp} will just keep the value of the cosine from going negative.


Then also add a spectral effect, based on the angle between the reflected light and the camera position.

θ

Given the point pp, the light source position ll, the camera position cc, and the normal vector v\vec{v} the intensity of a spectral effect can be calculated:

First reflect the incoming light vector lpl-p across the normal vector:


k=2v(lp)(lp)(lp)\vec{k} = \dfrac{2 \vec{v} \cdot (l-p)}{|(l-p)|}(l-p)

Then find the cosine between the incoming light and the camera direction


cos(θ)=k(pc)k(pc)\cos(\theta) = \dfrac{\vec{k} \cdot (p-c)}{|\vec{k}||(p-c)|}

Then the spectral factor can be calculated by exponentiating the cosine


t(θ)=cos(θ)shinynesst(\theta) = \cos(\theta)^{\text{shinyness}}

Then the final color can be calculated by interpolating between the balls diffuse color and the lights color


r,g,b=lerp(r,g,b,lr,lg,lb,t(θ))\langle r'', g'', b'' \rangle = \text{lerp}(\langle r', g', b' \rangle, \langle l_r, l_g, l_b \rangle, t(\theta))

Now we turn off the wireframe.


Then we can add a beach ball as the base color.


The fragment shader can be found here:

#version 300 es

precision highp float;

uniform uint fragEnum;
uint ENUM_RED = 0u;
uint ENUM_GREEN = 1u;
uint ENUM_BLUE = 2u;
uint ENUM_BLACK = 3u;
uint ENUM_WHITE = 4u;
uint ENUM_CLEAR_RED = 5u;
uint ENUM_CLEAR_GREEN = 6u;
uint ENUM_CLEAR_BLUE = 7u;
uint ENUM_REFLECTIVE_RED = 8u;
uint ENUM_SPECTRAL_RED = 9u;
uint ENUM_BEACHBALL = 10u;

uniform vec3 lightPos;
uniform mat4 cameraMatrix;

in vec3 model_pos;
in vec3 world_pos;
out vec4 outColor;

float AMBIENT_BRIGHTNESS = 0.4;
float DIFFUSE_BRIGHTNESS = 0.6;

vec3 getBeachBallColor(){
  float x = model_pos.x;
  float y = model_pos.y;
  float r = x*x + y*y;
  if (r < 0.03) {
    return vec3(1, 1, 0);
  }
  float c2 = x*x/r;
  if (c2 < 0.75) {
    if (x > 0.0) {
      if (y > 0.0) {
	return vec3(0, 1, 0);
      } else {
	return vec3(0, 0, 1);
      }
    } else {
      return vec3(1, 1, 1);
    }
  } else {
    if (x > 0.0) {
      return vec3(1, 1, 1);
    } else {
      return vec3(1, 0, 0);
    }
  }
  return vec3(1.0, 0.0, 0.0);
}

float diffuse_reflect_factor(){
  vec3 dir = vec3(lightPos) - vec3(world_pos);
  float cosine = dot(normalize(dir), normalize(vec3(model_pos)));
  float cf = clamp((AMBIENT_BRIGHTNESS * cosine) + DIFFUSE_BRIGHTNESS, 0.0, 1.0);
  return cf;
}

float specular_reflect_factor(){
  vec3 camera_pos = vec3(inverse(cameraMatrix) * vec4(0.0, 0.0, 0.0, 1.0));
  vec3 incident = -(camera_pos - vec3(world_pos));
  vec3 optimal_dir = normalize(reflect(incident, normalize(vec3(model_pos))));
  vec3 light_dir = normalize(lightPos - vec3(world_pos));
  float cos = dot(optimal_dir, light_dir);
  float shinyness = 5.0;
  float factor = pow(clamp(cos, 0.0, 1.0), shinyness);
  return factor;
}

void main() {
  if(fragEnum == ENUM_RED){
    outColor = vec4(1.0, 0.0, 0.0, 1.0);
  }else if(fragEnum == ENUM_GREEN){
    outColor = vec4(0.0, 1.0, 0.0, 1.0);
  }else if(fragEnum == ENUM_BLUE){
    outColor = vec4(0.0, 0.0, 1.0, 1.0);
  }else if(fragEnum == ENUM_BLACK){
    outColor = vec4(0.0, 0.0, 0.0, 1.0);
  }else if(fragEnum == ENUM_WHITE){
    outColor = vec4(1.0, 1.0, 1.0, 1.0);
  }else if(fragEnum == ENUM_CLEAR_RED){
    outColor = vec4(0.5, 0.0, 0.0, 0.5);
  }else if(fragEnum == ENUM_CLEAR_GREEN){
    outColor = vec4(0.0, 0.5, 0.0, 0.5);
  }else if(fragEnum == ENUM_CLEAR_BLUE){
    outColor = vec4(0.0, 0.0, 0.5, 0.5);
  }else if(fragEnum == ENUM_REFLECTIVE_RED){
    outColor = vec4(diffuse_reflect_factor() * vec3(1.0, 0.0, 0.0), 1.0);
  }else if(fragEnum == ENUM_SPECTRAL_RED){
    vec4 regular_color = vec4(diffuse_reflect_factor() * vec3(1.0, 0.0, 0.0), 1.0);
    vec4 light_color = vec4(1.0, 1.0, 1.0, 1.0);
    float specular_factor = specular_reflect_factor();
    outColor = (specular_factor * light_color) + ((1.0-specular_factor) * regular_color);
  }else if(fragEnum == ENUM_BEACHBALL){
    vec4 regular_color = vec4(diffuse_reflect_factor() * getBeachBallColor(), 1.0);
    vec4 light_color = vec4(1.0, 1.0, 1.0, 1.0);
    float specular_factor = specular_reflect_factor();
    outColor = (specular_factor * light_color) + ((1.0-specular_factor) * regular_color);
  }else{
    outColor = vec4(model_pos, 1.0);
  }
}