www.xbdev.net
xbdev - software development
Thursday December 26, 2024
Home | Contact | Support | Fractals Natures pattern... | Fractals Natures pattern...
     
 

Fractals

Natures pattern...

 

Fractals > Fractals in 3-dimensional (adding 'z')


Popular 3D fractal algorithms
• Mandelbulb
• Mandelbox
• Quaternion Julia Set
• Menger Sponge Fractal
• Tetrahex Fractal
• Sierpinski Tetrahedron Fractal
• Fractal Cube




Mandelbulb Fractal JavaScript


Click to Show Details
<html lang="en">
<
head>
  <
meta charset="UTF-8">
  <
meta name="viewport" content="width=device-width, initial-scale=1.0">
  <
title>Mandelbulb 3D Fractal</title>
<
style>
  
body {
  
margin0;
  
width100vw;
  
height100vh;
  
background-color:black;
}
canvas {
    
width100%;
    
height100%;
  
displayblock;
}
</
style>
</
head>
<
body>
  <
canvas id="mandelbulbCanvas"></canvas>
  
  <
script>
const 
canvas document.querySelector("#mandelbulbCanvas");
const 
gl canvas.getContext("webgl");

function 
main() {
  const 
vs = `
    attribute vec4 a_position;
    void main() {
      gl_Position = a_position;
    }
  
`;
  var 
fs = `
    precision highp float;
#define march_iter 128 
#define m_step 0.25
#define z_near 0.01
#define z_far 3.0
#define z_max 1.5
#define mandlebrot_iters 32
uniform vec3      iResolution;                           
               
vec3 colorMap( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d )
{
    return a + b*cos( 6.28318*(c*t+(b*d/a)) );
}

vec3 colorPallete (float t) {
    return colorMap( t, vec3(0.5,0.5,0.5),vec3(0.5,0.5,0.5),vec3(0.01,0.01,0.01),vec3(0.00, 0.15, 0.20) );
}

vec2 mandelbulb(vec3 pos) {
  float pwr = 3.0+4.0;
    vec3 z = pos;
    float dr = 1.0;
    float r = 0.0;
    for (int i = 0; i < mandlebrot_iters ; i++) {
        r = length(z);
        if (r>z_max) break;
        float theta = acos(z.z/r);
        float phi = atan(z.y,z.x);
        dr =  pow( r, pwr-1.0)*pwr*dr + 1.0;
        
        float zr = pow( r,pwr);
        theta = theta*pwr;
        phi = phi*pwr;
        z = zr*vec3(sin(theta)*cos(phi), sin(phi)*sin(theta), cos(theta));
        z+=pos;
    }
    return vec2(0.5*log(r)*r/dr,  50.0*pow(dr,0.128/float(march_iter)));
}

vec2 trace  (vec3 origin, vec3 ray) {
    float t = 0.0;
    float c = 0.0;
    for (int i=0; i<march_iter; i++) {
        vec3 path = origin + ray * t;    
        vec2 dist = mandelbulb(path);
        t += m_step * dist.x;
        c += dist.y;
        if (dist.y < z_near) break;
    }
    return vec2(t,c);
}

void main()
{
  vec2 fragCoord = gl_FragCoord.xy;
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv * 2.0 - 1.0;
    uv.x *= iResolution.x / iResolution.y;
    vec3 ray = normalize( vec3(uv,1.0));
    
    float rotAngle = 0.5;
    ray.xz *= mat2(cos(rotAngle), -sin(rotAngle), sin(rotAngle), cos(rotAngle)); 
    float camDist = z_far*0.55;
    vec3 origin = vec3 (camDist * sin(rotAngle),0.0,-camDist *cos(rotAngle));  

    vec2 depth = trace(origin,ray);
    
    float fog = 1.0 / (1.0 + depth.x * depth.x * 0.1);
    vec3 fc = vec3(fog);
    
    gl_FragColor = vec4(colorPallete(depth.y)*fog,1.0);
}
  
`;
              
// Compile shader
            
function compileShader(glsourcetype) {
                const 
shader gl.createShader(type);
                
gl.shaderSource(shadersource);
                
gl.compileShader(shader);

                if (!
gl.getShaderParameter(shadergl.COMPILE_STATUS)) {
                    
console.error(`Shader compilation error: ${gl.getShaderInfoLog(shader)}`);
                    
gl.deleteShader(shader);
                    return 
null;
                }

                return 
shader;
            }
  
              const 
vertexShader compileShader(glvsgl.VERTEX_SHADER);
            const 
fragmentShader compileShader(glfsgl.FRAGMENT_SHADER);

            
// Link shaders into a program
            
const program gl.createProgram();
            
gl.attachShader(programvertexShader);
            
gl.attachShader(programfragmentShader);
            
gl.linkProgram(program);

            if (!
gl.getProgramParameter(programgl.LINK_STATUS)) {
                
console.error(`Unable to initialize the shader program: ${gl.getProgramInfoLog(program)}`);
                return;
            }

            
gl.useProgram(program);
  
  
  const 
positionAttributeLocation gl.getAttribLocation(program"a_position");
  const 
positionBuffer gl.createBuffer();
  
gl.bindBuffer(gl.ARRAY_BUFFERpositionBuffer);
  
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    -
1, -1,
    
1, -1,
    -
11,
    -
11,
    
1, -1,
    
11,
  ]), 
gl.DYNAMIC_DRAW);

  
gl.viewport(00gl.canvas.widthgl.canvas.height);
  
gl.useProgram(program);
  var 
resolution gl.getUniformLocation(program'iResolution');
  function 
render(deltaMS) {
    
gl.viewport(00canvas.widthcanvas.height);
    
gl.clear(gl.COLOR_BUFFER_BIT gl.DEPTH_BUFFER_BIT);
    
gl.uniform3fv(resolution, [gl.canvas.widthgl.canvas.height0]);
    
gl.enableVertexAttribArray(positionAttributeLocation);
    
gl.bindBuffer(gl.ARRAY_BUFFERpositionBuffer);
    
gl.vertexAttribPointer(
      
positionAttributeLocation,
      
2,
      
gl.FLOAT,
      
false,
      
0,
      
0,
    );
    
gl.drawArrays(
      
gl.TRIANGLES,
      
0,
      
6,
    );
    
window.requestAnimationFrame(render);
  }
  
window.requestAnimationFrame(render);
}
main();

</
script>
</
body>
</
html>



fractal bulb mandelbulb javascript shader html


Things to Try

• Tidy up the code and add in more comments/information
• Try different defaults (e.g., steps and maximum interations)
• Change the colors (instead of the color pallet/try other patterns/gradients)
• Scaling and moving the Bulb around



Mandelbox Fractal (JavaScript)


Click to Show Details
<!DOCTYPE html>
<
html lang="en">
<
head>
  <
meta charset="UTF-8">
  <
meta name="viewport" content="width=device-width, initial-scale=1.0">
  <
title>3D Quaternion Julia Set Fractal</title>
  <
style>
    
body margin0overflowhidden; }
    
canvas displayblock; }
  </
style>
</
head>
<
body>
  <
canvas id="webgl-canvas"></canvas>

  <
script>
    
document.addEventListener("DOMContentLoaded", function() {
      const 
canvas document.getElementById('webgl-canvas');
      const 
gl canvas.getContext('webgl2');

      if (!
gl) {
        
console.error('Unable to initialize WebGL. Your browser may not support it.');
        return;
      }

      
// Vertex shader program
      
const vsSource = `
        attribute vec4 aVertexPosition;
        varying vec2 vUv;

        void main(void) {
          gl_Position = aVertexPosition;
          vUv = aVertexPosition.xy * 0.5 + 0.5;
        }
      
`;

      
// Fragment shader program
      
const fsSource = `
        precision mediump float;

        uniform float iTime;
        uniform vec2 iResolution;
 
 
 //Adjust to fit the box in view
#define CAM_POS vec3(0.0, 0.0, -12.0)
#define LIGHT_POS vec3(2.0, 2.0, -12.0)

//Background radius is 15 - disable it if the box is bigger than this or you need the camera further back.
//#define DISABLE_BG

//Fractal Parameters
#define ITERS 10
#define SCALE 3.0
#define MR2 0.25

//Enables Fresnel reflections - disable to reduce noise
#define SURF_REFL

//Ray march detail - lower numbers increase detail
#define DETAIL 1.0

//Enable 4x super-sampling - greatly reduces noise and your framerate
//#define SS

//GOLDEN MANDELBOX
#define BOX_ROUGHNESS 0.5
#define BOX_METAL 1.0
#define BOX_COLOUR vec3(0.854902, 0.750196, 0.368627)

float closeObj = 0.0;
const float PI = 3.14159;

mat3 rotX(float d){
    float s = sin(d);
    float c = cos(d);
    return mat3(1.0, 0.0, 0.0,
                0.0,   c,  -s,
                0.0,   s,   c );
}

mat3 rotY(float d){
    float s = sin(d);
    float c = cos(d);
    return mat3(  c, 0.0,  -s,
                0.0, 1.0, 0.0,
                  s, 0.0,   c );
}

mat3 rotZ(float d){
    float s = sin(d);
    float c = cos(d);
    return mat3(  c,  -s, 0.0,
                  s,   c, 0.0,
                0.0, 0.0, 1.0);
}

// Mandelbox DE from 
// http://www.fractalforums.com/3d-fractal-generation/a-mandelbox-distance-estimate-formula/msg21412/#msg21412

vec4 scalevec = vec4(SCALE, SCALE, SCALE, abs(SCALE)) / MR2;
float C1 = abs(SCALE-1.0), C2 = pow(abs(SCALE), float(1-ITERS));

float mandelbox(vec3 position){
  vec4 p = vec4(position.xyz, 1.0), p0 = vec4(position.xyz, 1.0);  // p.w is knighty's DEfactor
  for (int i=0; i<ITERS; i++) {
    p.xyz = clamp(p.xyz, -1.0, 1.0) * 2.0 - p.xyz;  // box fold: min3, max3, mad3
    float r2 = dot(p.xyz, p.xyz);  // dp3
    p.xyzw *= clamp(max(MR2/r2, MR2), 0.0, 1.0);  // sphere fold: div1, max1.sat, mul4
    p.xyzw = p*scalevec + p0;  // mad4
  }
  return (length(p.xyz) - C1) / p.w - C2;
}
    
vec2 vecMin(vec2 a, vec2 b){
    if(a.x <= b.x){
        return a;
    }
    return b;
}


float lastx = 0.0;
float lasty = 0.0;

vec2 mapMat(vec3 p){
    vec2 box = vec2(mandelbox(rotZ(radians(lastx) / 1.5) * rotX(radians(lasty) / 1.5) * p), 3.0);
    #ifdef DISABLE_BG
    return box;
    #endif
    vec2 cmap = vec2(-length(p) + 15.0, 1.0);
    return vecMin(box, cmap);
}

//Returns the min distance
float map(vec3 p){
    return mapMat(p).x;
}

float trace(vec3 ro, vec3 rd){
    float t = 0.5;
    float d = 0.0;
    float w = 1.3;
    float ld = 0.0;
    float ls = 0.0;
    float s = 0.0;
    float cerr = 10000.0;
    float ct = 0.0;
    float pixradius = DETAIL / iResolution.y;
    vec2 c;
    int inter = 0;
    for(int i = 0; i < 256; i++){
        ld = d;
        c = mapMat(ro + rd * t);
        d = c.x;
        
        //Detect intersections missed by over-relaxation
        if(w > 1.0 && abs(ld) + abs(d) < s){
            s -= w * s;
            w = 1.0;
            t += s;
            continue;
        }
        s = w * d;
        
        float err = d / t;
        
        if(abs(err) < abs(cerr)){
            ct = t;
            cerr = err;
        }
        
        //Intersect when d / t < one pixel
        if(abs(err) < pixradius){
            inter = 1;
            break;
        }
        
        t += s;
        if(t > 30.0){
            break;
        }
    }
    closeObj = c.y;
    if(inter == 0){
        ct = -1.0;
    }
    return ct;
}

//Approximate normal
vec3 normal(vec3 p){
    return normalize(vec3(map(vec3(p.x + 0.0001, p.yz)) - map(vec3(p.x - 0.0001, p.yz)),
                          map(vec3(p.x, p.y + 0.0001, p.z)) - map(vec3(p.x, p.y - 0.0001, p.z)),
                          map(vec3(p.xy, p.z + 0.0001)) - map(vec3(p.xy, p.z - 0.0001))));
}

vec3 camPos = vec3(0.0);
vec3 lightPos = vec3(0.0);

//Determine if a point is in shadow - 1.0 = not in shadow
float shadow(vec3 ro, vec3 rd){
    float t = 0.01;
    float d = 0.0;
    float shadow = 1.0;
    for(int iter = 0; iter < 128; iter++){
        d = map(ro + rd * t);
        if(d < 0.0001){
            return 0.0;
        }
        if(t > length(ro - lightPos) - 0.5){
            break;
        }
        shadow = min(shadow, 128.0 * d / t);
        t += d;
    }
    return shadow;
}

float occlusion(vec3 ro, vec3 rd){
    float k = 1.0;
    float d = 0.0;
    float occ = 0.0;
    for(int i = 0; i < 10; i++){
        d = map(ro + 0.1 * k * rd);
        occ += 1.0 / pow(2.0, k) * (k * 0.1 - d);
        k += 1.0;
    }
    return 1.0 - clamp(occ * 3.0, 0.0, 1.0);
}

//Square
float sqr(float x){
  return x * x;
}

//Diffusion normalisation
float diff(float albedo){
  return albedo / PI;
}

//GGX NDF
float specD(float NdotH, float a){
  float asqr = sqr(a);
  float NdotHsqr = sqr(NdotH);
  return asqr / (PI * sqr((NdotHsqr) * (asqr - 1.0) + 1.0));
}

float G1(float NdotX, float k){
  return NdotX / (NdotX * (1.0 - k) + k);
}

//Geometric attenuation term
float specG(float NdotV, float NdotL, float k){
  k /= 2.0;
  return G1(NdotV, k) * G1(NdotL, k);
}

//Schlick fresnel approximation used by Unreal Engine
float fresnel(float AdotB){
  float power = pow(2.0, (-5.55473 * AdotB - 6.98316) * AdotB);
  return 0.04 + (1.0 - 0.04) * power;
}


vec3 BRDF(vec3 L, vec3 V, vec3 N, vec3 c, vec3 lc, float metallic, float roughness, float s, float o, float ccr){
  vec3 H = normalize(L + V);
  float NdotH = dot(N, H);
  float NdotL = dot(N, L);
  float NdotV = dot(N, V);
  
  if (NdotL < 0.0 || NdotV < 0.0) return vec3(0.0);
  
  float VdotH = dot(V, H);
  float alpha = roughness * roughness;

  float conductor = 1.0 - metallic;
    
  c = c - (0.4 - 0.4 * s) * c;

  vec3 specCol = mix(vec3(1.0), c, metallic);
  
  float FresL = fresnel(NdotL);
  float FresV = fresnel(NdotV);
  float Fresd90 = 0.5 + 2.0 * sqr(VdotH) * roughness;
  float Fresd = mix(1.0, Fresd90, FresL) * mix(1.0, Fresd90, FresV); 
  
  float Ds = specD(NdotH, alpha);
  float FresH = fresnel(VdotH);
  vec3 Fress = mix(specCol, vec3(1.0), FresH);
  float Gs = specG(NdotV, NdotL, roughness);
    
  vec3 ccSpec = vec3(0.0);
  if(ccr > 0.0){
      float ccDs = specD(NdotH, sqr(ccr));
      vec3 ccFress = vec3(1.0);
      float ccGs = specG(NdotV, NdotL, ccr);
    ccSpec = ccDs * ccFress * ccGs * floor(s);
  }
    
  vec3 ref = vec3(0.0);  
  /*
  #ifdef SURF_REFL
  if(roughness > 0.5){
      ref = pow(texture(iChannel1, reflect(V, N)).xyz, vec3(2.2)) * specCol * FresV;
  }else{
    ref = pow(texture(iChannel0, reflect(V, N)).xyz, vec3(2.2)) * specCol * FresV;
  } 
  #endif 
  */
  
  return ((diff(conductor) * Fresd * max(0.0, NdotL) * o * c + Gs * Fress * Ds * floor(s) + ccSpec) + ref) * lc;
}

vec3 colour(vec3 p, float id){
    vec3 n = normal(p);
    vec3 l = normalize(lightPos - p);
    vec3 v = normalize(camPos - p);
    
    vec3 lc = pow(vec3(1.0, 0.72549, 0.631373), vec3(2.2));
    
    float o = occlusion(p, n);
    
    if(id == 1.0){
        return vec3(0.2); // pow(texture(iChannel0, n).xyz, vec3(2.2));
    }
    if(id == 3.0){
        float s = shadow(p, l);
        vec3 col = BOX_COLOUR;
        col = pow(col, vec3(1.0 / 0.4545));
        return BRDF(l, v, n, col, lc, BOX_METAL, BOX_ROUGHNESS, s, o, 0.0);
    }
    return vec3(0.0, 1.0, 0.0);
}

void main() // mainImage( out vec4 fragColor, in vec2 fragCoord ){
{
    vec2 fragCoord = gl_FragCoord.xy;
    
    vec2 uv = fragCoord.xy / iResolution.xy;
    uv = uv * 2.0 - 1.0;
    uv.x *= iResolution.x / iResolution.y;
    camPos = CAM_POS;
    lightPos = LIGHT_POS;
    
    //lastx += iMouse.x - 0.5;
    //lasty += iMouse.y - 0.5;
    
    vec3 ro = camPos;
    vec3 rd = normalize(vec3(uv, 1.5));
    float d = trace(ro, rd);
    vec3 c = ro + rd * d;
    
    vec3 col = vec3(1.0);
    //If intersected
    if(d > 0.0){
        //Colour the point
        col = colour(c, closeObj);
        
    }else{
        col = vec3(0.0);
    }
    
    #ifdef SS
    
    vec3 ssro = camPos;
    vec3 ssrd = normalize(vec3(uv.x + 0.5/iResolution.x, uv.y, 1.5));
    float ssd = trace(ssro, ssrd);
    vec3 ssc = ssro + ssrd * ssd;
    
    vec3 colss;
    
    //If intersected
    if(d > 0.0){
        //Colour the point
        
        colss = colour(ssc, closeObj);
        col = (colss + col) / vec3(2.0);
        
    }else{
        col = (vec3(0.0) + col) / vec3(2.0);
    }
    
    ssrd = normalize(vec3(uv.x + 0.5/iResolution.x, uv.y + 0.5/iResolution.y, 1.5));
    ssd = trace(ssro, ssrd);
    ssc = ssro + ssrd * ssd;
    
    //If intersected
    if(d > 0.0){
        //Colour the point
        
        colss = colour(ssc, closeObj);
        col = (colss + col) / vec3(2.0);
        
    }else{
        col = (vec3(0.0) + col) / vec3(2.0);
    }
    
    ssrd = normalize(vec3(uv.x, uv.y + 0.5/iResolution.y, 1.5));
    ssd = trace(ssro, ssrd);
    ssc = ssro + ssrd * ssd;
    
    //If intersected
    if(d > 0.0){
        //Colour the point
        
        colss = colour(ssc, closeObj);
        col = (colss + col) / vec3(2.0);
        
    }else{
        col = (vec3(0.0) + col) / vec3(2.0);
    }
    
    #endif
    
    //col *= 1.0 / exp(d * 0.25);
    col = pow( col, vec3(0.4545) );
    gl_FragColor = vec4(col,1.0);
}
 
      
`;

      function 
compileShader(glsourcetype) {
        const 
shader gl.createShader(type);
        
gl.shaderSource(shadersource);
        
gl.compileShader(shader);

        if (!
gl.getShaderParameter(shadergl.COMPILE_STATUS)) {
          
console.error(`Shader compilation error: ${gl.getShaderInfoLog(shader)}`);
          
gl.deleteShader(shader);
          return 
null;
        }

        return 
shader;
      }

      const 
vertexShader compileShader(glvsSourcegl.VERTEX_SHADER);
      const 
fragmentShader compileShader(glfsSourcegl.FRAGMENT_SHADER);

      if (!
vertexShader || !fragmentShader) {
        return;
      }

      const 
program gl.createProgram();
      
gl.attachShader(programvertexShader);
      
gl.attachShader(programfragmentShader);
      
gl.linkProgram(program);

      if (!
gl.getProgramParameter(programgl.LINK_STATUS)) {
        
console.error(`Program linking error: ${gl.getProgramInfoLog(program)}`);
        return;
      }

      
gl.useProgram(program);

      
// Create a buffer and put a single clipspace cube in it
      
const positionBuffer gl.createBuffer();
      
gl.bindBuffer(gl.ARRAY_BUFFERpositionBuffer);

      const 
positions = new Float32Array([
        -
1.0, -1.0, -1.0,
         
1.0, -1.0, -1.0,
        -
1.0,  1.0, -1.0,
         
1.0,  1.0, -1.0,
        -
1.0, -1.0,  1.0,
         
1.0, -1.0,  1.0,
        -
1.0,  1.0,  1.0,
         
1.0,  1.0,  1.0,
      ]);

      
gl.bufferData(gl.ARRAY_BUFFERpositionsgl.STATIC_DRAW);

      const 
positionAttrib gl.getAttribLocation(program'aVertexPosition');
      
gl.vertexAttribPointer(positionAttrib3gl.FLOATfalse00);
      
gl.enableVertexAttribArray(positionAttrib);

      
// Set up uniforms
      
const timeUniformLocation gl.getUniformLocation(program'iTime');
      const 
resolutionUniformLocation gl.getUniformLocation(program'iResolution');

      function 
resizeCanvas() {
        
canvas.width window.innerWidth;
        
canvas.height window.innerHeight;
        
gl.viewport(00canvas.widthcanvas.height);
        
gl.uniform2f(resolutionUniformLocationcanvas.widthcanvas.height);
      }

      
window.addEventListener('resize'resizeCanvas);
      
resizeCanvas();

      function 
render() {
        
gl.uniform1f(timeUniformLocationperformance.now() * 0.001);

        
gl.clear(gl.COLOR_BUFFER_BIT);
        
gl.drawArrays(gl.TRIANGLE_STRIP08);
        
//requestAnimationFrame(render);
      
}

      
render();
      
render();
      
render();
      
    });
  </
script>
</
body>
</
html>


Output should look like this:





Things to Try

• Try reducing and simplifiying the code (also adding in your own comments/notes)
• Modify the colors (mix of gradients and patterns)
• Change the parameters to create different patterns/configurations
• Randomness - add in some random values into the equation
• Move the cube around (location and rotation)
• Extend the previous one to draw a second cube at a different location (multiple cubes)



Quaternion Fractal Juliet Set


Click to Show Details
<!DOCTYPE html>
<
html lang="en">
<
head>
  <
meta charset="UTF-8">
  <
meta name="viewport" content="width=device-width, initial-scale=1.0">
  <
title>3D Quaternion Julia Set Fractal</title>
  <
style>
    
body margin0overflowhidden; }
    
canvas displayblock; }
  </
style>
</
head>
<
body>
  <
canvas id="webgl-canvas"></canvas>

  <
script>
    
document.addEventListener("DOMContentLoaded", function() {
      const 
canvas document.getElementById('webgl-canvas');
      const 
gl canvas.getContext('webgl2');

      if (!
gl) {
        
console.error('Unable to initialize WebGL. Your browser may not support it.');
        return;
      }

      
// Vertex shader program
      
const vsSource = `
        attribute vec4 aVertexPosition;
        varying vec2 vUv;

        void main(void) {
          gl_Position = aVertexPosition;
          vUv = aVertexPosition.xy * 0.5 + 0.5;
        }
      
`;

      
// Fragment shader program
      
const fsSource = `
        precision mediump float;

        uniform float iTime;
        uniform vec2 iResolution;
        
// Quick little function to multiply two quaternions.
// The proof is left as an exercise to the reader
vec4 MultiplyQuaternions(vec4 a, vec4 b)
{
    float real = a.w*b.w - dot(a.xyz, b.xyz);  
    vec3 complex = (a.w*b.xyz + b.w*a.xyz + cross(a.xyz, b.xyz));
    return vec4(complex, real);
}

// Convert degrees to radians
float Radians(float deg)
{
     return deg / 360.0 * 2.0 * 3.14159; 
}

// Write a float4 function for some of the HLSL Code conversion
vec4 float4(float x, float y, float z, float w)
{
     return vec4(x,y,z,w);   
}

// Write a float3 function for the same purpose
vec3 float3(float x, float y, float z)
{
     return vec3(x,y,z);   
}

// Exact SDF for a sphere
float dSphere(vec3 pos, vec3 center, float radius)
{
    // find the distance to the center
    vec3 v = pos - center;
    
    // return that, minus the radius
    return length(v) - radius;
}

// Exact intersection of a sphere. Resolves a quatratic equation. Returns the 
// min distance, max distance, and discriminant to determine if the intersections
// actually exist.
vec3 intersections_of_sphere(vec3 pos_vector, vec3 dir_vector, float sphere_radius)
{
    // Derivation for formula:
    //        Let the ray be represented as a point P plus a scalar multiple t of the direction vector v,
    //        The ray can then be expressed as P + vt
    //
    //        The point of intersection I = (x, y, z) must be expressed as this, but must also be some distance r
    //        from the center of the sphere, thus x*x + y*y + z*z = r*r, or in vector notation, I*I = r*r
    //
    //        It therefore follows that (P + vt)*(P + vt) = r*r, or when expanded and rearranged,
    //        (v*v)t^2 + (2P*v)t + (P*P - r*r) = 0. For this we will use the quadratic equation for the points of
    //        intersection

    // a, b, and c correspond to the second, first, and zeroth order terms of t, the parameter we are trying to solve for.
    float a = dot(dir_vector, dir_vector);
    float b = 2.0 * dot(pos_vector, dir_vector);
    float c = dot(pos_vector, pos_vector) - sphere_radius * sphere_radius;

    // to avoid imaginary number, we will find the absolute value of the discriminant.
    float discriminant = b * b - 4.0 * a*c;
    float abs_discriminant = abs(discriminant);
    float min_dist = (-b - sqrt(abs_discriminant)) / (2.0 * a);
    float max_dist = (-b + sqrt(abs_discriminant)) / (2.0 * a);

    // return the two intersections, along with the discriminant to determine if
    // the intersections actually exist.
    return float3(min_dist, max_dist, discriminant);

}


// Distance estimation for a julia set.
float DE(vec3 p, vec3 c, vec4 seed)
{
    // First, offset the point by the center
    vec3 v = p - c;

    // Set C to be a vector of constants determining julia set we use
    vec4 C = seed;
    
    // Set Z to be some form of input from the vector
    vec4 Z = float4(v.z, v.y, 0.0, v.x);
    
    // I'll be honest, I'm not entirely sure how the distance estimation works.
    // Calculate the derivative of Z. The Julia set we are using is Z^2 + C,
    // So this results in simply 2z
    vec4 dz = 2.0*Z + vec4(1.0, 1.0, 1.0, 1.0);

    // Run the iterative loop for some number of iterations
    for (int i = 0; i < 64; i++)
    {
        // Recalculate the derivative
        dz = 2.0 * MultiplyQuaternions(Z, dz) + vec4(1.0, 1.0, 1.0, 1.0);
        
        // Rcacalculate Z
        Z = MultiplyQuaternions(Z, Z) + C;
        
           // We rely on the magnitude of z being fairly large (the derivation includes
        // A limit as it approaches infinity) so we're going to let it run for a bit longer
        // after we know its going to explode. i.e. 1000 instead of the usual, like 8.
        if (dot(Z, Z) > 1000.0)
        {
            break;
            }
        }
    
    // And this is where the witchcraft happens. Again, not sure how this works, but as
       // you can see, it does.
    float d = 0.5*sqrt(dot(Z, Z) / dot(dz, dz))*log(dot(Z, Z)) / log(10.0);
    
    // Return the distance estimation.
    return d;

}

void main() 
{
    vec2 fragCoord = gl_FragCoord.xy;
    
    // Define the iterations for the marcher.
    const int ITERATIONS = 200;
    
    // Define the roation speed. Set to 0 to disable
    const float ROTATION_SPEED = 0.6;
    
    // Define the start angle for the rotation (in degrees)
    const float START_ANGLE = 0.0;
    
    // Define the orbit radius
    const float ORBIT_RADIUS = 2.5;
    
    // Define the epsilon value for closeness to be considered a hit
    const float EPSILON = 0.001;
    
    // Define if we should invert the color at the end
    const bool DARK_MODE = true;
    
    //vec4 julia_seed = vec4(0.0, -0.2, 0.0, -1.17);
    //vec4 julia_seed = vec4(0.2, 0.67, 0.0, -0.5);
    vec4 julia_seed = vec4(0.33, 0.56, 0.0, -0.72);    
    //vec4 julia_seed = vec4(-0.15, -0.15, 0.0, -.95);
    
    // Define the center of the julia set
    vec3 julia_center = vec3(0.0, 0.0, 0.0);
 
    // Calculate the starting angles for the orbit
    float theta = iTime * ROTATION_SPEED;
    float phi = Radians(START_ANGLE);

    // Define an orbital path based on time
    vec3 orbit = vec3(cos(theta)*cos(phi), sin(phi), sin(theta)*cos(phi));
    
    // Cacluate the normal of the path. Since its a circle, it will just
    // be back down into the center
    vec3 normal = -normalize(orbit);
    
    // Calculate the tangent of the path
    // A circle consists of <cost, sint>, which when differentiated yields
    // <-sint, cost>. since z is already sint, and x is already cost, the equation
    // is as follows.
    vec3 tangent = normalize(vec3(-normal.z, 0.0, normal.x));
    
    // Calculate the UV coordinates
    vec2 uv = fragCoord/iResolution.xy;
    
    // Convert the UV coordinates to a range between -1 and 1
    vec2 range = uv*2.0 - vec2(1.0,1.0);
    
    // Define the Camera position
    vec3 cam_pos = orbit*ORBIT_RADIUS;
    
    // Define the forward, up, and right vectors (needs rework)
    vec3 forward = normal;
    vec3 up = normalize(cross(normal, tangent));
    vec3 right = tangent;
        
    // Calculate the aspect ratio of the screen
    float aspect = float(iResolution.y) / float(iResolution.x);
    
    // Calculate the ray as a normalized combination of the forward, right, and up vectors.
    // Note that the purely forward + horizonal combination yield vectors 45 degrees outward
    // for a 90 degree field of view. This may be updated with a fov option
    vec3 ray = normalize(forward + range.x*right + range.y*up*aspect);
    
    // Initialize the ray marched point p
    vec3 p = cam_pos;


    // Initialize the distance
    float dist = 1.0;
    
    // Calculate the exact distance from a sphere of radius 2 using a raytracing function
    vec3 init_distance = intersections_of_sphere(p - julia_center, ray, 2.0);
    
    // If we are outside a bubble around the raymarched fractal
    if (init_distance.z > 0.0)
    {
        // Step onto the sphere so we start off a bit closer.
        p += ray * clamp(init_distance.x, 0.0, init_distance.x);
    }

    // declare a dummy variable to store the number of iterations into.
    // I'm doing it this way because on my phone it didnt let me use an
    // already declared variable as the loop iterator.
    int j;
    
    float minDist = 1000.0;
    vec3 closestPoint = vec3(0.0,0.0,0.0);
    
    // Begin the raymarch
    for (int i = 0; i < ITERATIONS; i++)
    {
        // Estimate the distance to the julia set
        dist = DE(p, julia_center, julia_seed);
        
        if (dist < minDist)
        {
            closestPoint = p;
        }
        
        // Move forward that distance
        p += ray*dist;
        
        // Record the number of iterations we are on
        j = i;
        
        // If we hit the julia set, or get too far away form it
        if (dist < EPSILON || dot(p - julia_center, p-julia_center) > 8.1)
        {
            // Break the loop.
            break;   
        }
        
    }
    
    // calculate the brightness based on iterations used
    float di = float(j) / float(ITERATIONS);

    // determine if we hit the fractal or not
    float hit = step(dist, EPSILON);
    
    if (!DARK_MODE)
    {
         di = 1.0 - di;   
    }
    
    // define some phase angle
    float psi = Radians(70.0);
    
    // Time varying pixel color (included in default shadertoy project)
    vec3 col = 0.8 + 0.2*cos(iTime*0.5+uv.xyx+vec3(0,2,4) + psi*hit);
    
    // Boring old white instead of the above commented code. Will tweak rendering later
    //vec3 col = vec3(0.7,1.0,.93);
    

    // Output to screen. Modifiy the color with the brightness calculated as di.
    gl_FragColor = vec4(col*di,1.0);
    
}
      
`;

      function 
compileShader(glsourcetype) {
        const 
shader gl.createShader(type);
        
gl.shaderSource(shadersource);
        
gl.compileShader(shader);

        if (!
gl.getShaderParameter(shadergl.COMPILE_STATUS)) {
          
console.error(`Shader compilation error: ${gl.getShaderInfoLog(shader)}`);
          
gl.deleteShader(shader);
          return 
null;
        }

        return 
shader;
      }

      const 
vertexShader compileShader(glvsSourcegl.VERTEX_SHADER);
      const 
fragmentShader compileShader(glfsSourcegl.FRAGMENT_SHADER);

      if (!
vertexShader || !fragmentShader) {
        return;
      }

      const 
program gl.createProgram();
      
gl.attachShader(programvertexShader);
      
gl.attachShader(programfragmentShader);
      
gl.linkProgram(program);

      if (!
gl.getProgramParameter(programgl.LINK_STATUS)) {
        
console.error(`Program linking error: ${gl.getProgramInfoLog(program)}`);
        return;
      }

      
gl.useProgram(program);

      
// Create a buffer and put a single clipspace cube in it
      
const positionBuffer gl.createBuffer();
      
gl.bindBuffer(gl.ARRAY_BUFFERpositionBuffer);

      const 
positions = new Float32Array([
        -
1.0, -1.0, -1.0,
         
1.0, -1.0, -1.0,
        -
1.0,  1.0, -1.0,
         
1.0,  1.0, -1.0,
        -
1.0, -1.0,  1.0,
         
1.0, -1.0,  1.0,
        -
1.0,  1.0,  1.0,
         
1.0,  1.0,  1.0,
      ]);

      
gl.bufferData(gl.ARRAY_BUFFERpositionsgl.STATIC_DRAW);

      const 
positionAttrib gl.getAttribLocation(program'aVertexPosition');
      
gl.vertexAttribPointer(positionAttrib3gl.FLOATfalse00);
      
gl.enableVertexAttribArray(positionAttrib);

      
// Set up uniforms
      
const timeUniformLocation gl.getUniformLocation(program'iTime');
      const 
resolutionUniformLocation gl.getUniformLocation(program'iResolution');

      function 
resizeCanvas() {
        
canvas.width window.innerWidth;
        
canvas.height window.innerHeight;
        
gl.viewport(00canvas.widthcanvas.height);
        
gl.uniform2f(resolutionUniformLocationcanvas.widthcanvas.height);
      }

      
window.addEventListener('resize'resizeCanvas);
      
resizeCanvas();

      function 
render() {
        
gl.uniform1f(timeUniformLocationperformance.now() * 0.001);

        
gl.clear(gl.COLOR_BUFFER_BIT);
        
gl.drawArrays(gl.TRIANGLE_STRIP08);
        
//requestAnimationFrame(render);
      
}

      
render();
      
render();
      
render();
      
    });
  </
script>
</
body>
</
html>


Output should look like this:





Things to Try

• Tidy up the code (try and make it more concise and compact)
• Add in more comments
• Animate the visualization (rotates and can view it from other angles)
• Animate the fractal effect (e.g., increment the parameters so it animates/changes gradually)
• Randomness - insert a bit of randomness into the calculation
• Move the object around (e.g., offset position, x, y and z)
• Draw 2 quaternion fractals on screen at different locations
• Colors - use different colors/gradients to visualize the fractal in differnet ways



3D Menger Sponge Fractal (JavaScript and P5)


Click to Show Details
<!--
  
Menger Sponge Fractal click to the mouse to subdivide
-->

  <
style>
    
body {
      
background-colorrgb(200200200);
      
min-height600px;
    }

    
canvas {
      
marginauto;
    }
  </
style>

<
script src='https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.1/p5.min.js'></script>

<
script>
let a 0;
let b;
let sponge = new Array();
const 
recursiveLimit 4;
let recursiveCount 0;
let clickSe;
 
function 
setup() {
    
createCanvas(600600WEBGL);
    
ambientLight(100);
    
directionalLight(120120120, -1, -11);
    
ambientMaterial(230210255255);
    
noStroke();
    
resetCube();
}
 
function 
mousePressed() {
    ++
recursiveCount;
    if (
recursiveCount >= recursiveLimit) {
        
resetCube();
        return;
    }
 
    
let next = new Array();
    
sponge.forEach(abox => {
        
let newBoxes abox.generate();
        
next concat(nextnewBoxes);
    });
    
sponge next;
}
 
function 
draw() {
    
camera(00500000010);
    
background(303060);
    
rotateX(a);
    
rotateY(0.4);
    
rotateZ(0.1);
    
sponge.forEach(abox => { abox.show(); });
    
+= 0.01;
}
 
function 
resetCube() {
    
= new Box(000300);
    
sponge = [b];
    
recursiveCount 0;
}
 
class 
Box {
    
constructor(xyzr_) {
        
this.pos createVector(xyz);
        
this.r_;
    }
 
    
generate() {
        
let boxes = new Array();
        for (
let x = -12x++) {
            for (
let y = -12y++) {
                for (
let z = -12z++) {
                    
let sum abs(x) + abs(y) + abs(z);
                    const 
newR this.3;
                    if (
sum 1) {
                        
let b = new Box(this.pos.newRthis.pos.newRthis.pos.newRnewR);
                        
boxes.push(b);
                    }
                }
            }
        }
        return 
boxes;
    }
 
    
show() {
        
push();
        
translate(this.pos.xthis.pos.ythis.pos.z);
        
box(this.r);
        
pop();
    }
}

console.log('click cube to do the Menger Sponge Fractal')

console.log('ready...');
</
script>






Things to Try

• Try making the different cubes a different color
• Draw wireframe information



Sierpinski Fractal (Tetrahedron)


Click to Show Details
<!DOCTYPE html>
<
html lang="en">
<
head>
    <
meta charset="UTF-8">
    <
meta name="viewport" content="width=device-width, initial-scale=1.0">
    <
title>Sierpinski 3D Fractal</title>
    <
script src='https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.6.0/gl-matrix-min.js'></script>
    <
style>
        
body margin0; }
        
canvas displayblock;  }
    </
style>
</
head>
<
body>
    <
canvas id="sierpinskiCanvas" width='500' height='500'></canvas>
    <
script>
/*
   sierpinski 3d - build geometry
*/
let baseColors = [
  [
1.00.00.36],
  [
0.611.00.3],
  [
1.00.520.14],
  [
1.00.01.0]
]

// creating a simple series of points
let vertices = [
   [
0.0000,  -0.1500, -1.0000],
   [
0.0000,  0.8428,  0.3333],
   [-
0.8165, -0.5714,  0.3333],
   [
0.8165, -0.5714,  0.3333]
]
let points = []
let colors = []

function 
flatten(array) {
  
let f = new Float32Array(array.length*3)
  array.forEach(function (
pointindex) {
    
f[index*3]   = point[0]
    
f[index*3+1] = point[1]
    
f[index*3+2] = point[2]
  })
  return 
f
}


// utility to scale a vector
function scale(pointscale) {
  return [
point[0]*scalepoint[1]*scalepoint[2]*scale]
}


function 
triangle (abccolor) {
  
colors.push(scale(baseColors[color], 1))
  
points.push(a)
  
colors.push(scale(baseColors[color], 1))
  
points.push(b)
  
colors.push(baseColors[color])
  
points.push(c)
}

function 
tetra (abcd) {
  
triangle(acb0)
  
triangle(acd1)
  
triangle(abd2)
  
triangle(bcd3)
}

function 
getMiddlePoint(uv){
   return [
0.5*(u[0] + v[0]), 0.5* (u[1] + v[1]), 0.5* (u[2] + v[2])]
}


function 
divideTetra(abcdcount) {
  if(
count===0){
    
tetra(abcd)
    return
  }
  
let ab getMiddlePoint(ab)
  
let ac getMiddlePoint(ac)
  
let ad getMiddlePoint(ad)
  
let bc getMiddlePoint(bc)
  
let bd getMiddlePoint(bd)
  
let cd getMiddlePoint(cd)
  --
count
  divideTetra
(aabacadcount)
  
divideTetra(abbbcbdcount)
  
divideTetra(acbcccdcount)
  
divideTetra(adbdcddcount)
}

divideTetra(vertices[0], vertices[1], vertices[2], vertices[3], 3);


let vcolors    flatten(colors);
let vpositions flatten(points);
console.log('num points (3 points per triangle):'points.length );

// -------------------------------------
      

// Vertex shader program
const vsSource = `
      attribute vec4 aVertexPosition;
      uniform mat4 uModelViewMatrix;
      uniform mat4 uProjectionMatrix;
      void main(void) {
          gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      }
`;

// Fragment shader program
const fsSource = `
      precision mediump float;
      void main(void) {
          gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
      }
`;

// Initialize WebGL
const canvas document.getElementById('sierpinskiCanvas');
const 
gl canvas.getContext('webgl');
if (!
gl) { console.error('Unable to initialize WebGL. Your browser may not support it.'); }

// Compile shaders, create program, and link it
function initShaderProgram(glvsSourcefsSource) {
  const 
vertexShader loadShader(glgl.VERTEX_SHADERvsSource);
  const 
fragmentShader loadShader(glgl.FRAGMENT_SHADERfsSource);

  const 
shaderProgram gl.createProgram();
  
gl.attachShader(shaderProgramvertexShader);
  
gl.attachShader(shaderProgramfragmentShader);
  
gl.linkProgram(shaderProgram);

  if (!
gl.getProgramParameter(shaderProgramgl.LINK_STATUS)) {
    
console.error('Unable to initialize the shader program: ' gl.getProgramInfoLog(shaderProgram));
    return 
null;
  }

  return 
shaderProgram;
}

// Compile shader
function loadShader(gltypesource) {
  const 
shader gl.createShader(type);
  
gl.shaderSource(shadersource);
  
gl.compileShader(shader);

  if (!
gl.getShaderParameter(shadergl.COMPILE_STATUS)) {
    
console.error('An error occurred compiling the shaders: ' gl.getShaderInfoLog(shader));
    
gl.deleteShader(shader);
    return 
null;
  }
  return 
shader;
}

const 
shaderProgram initShaderProgram(glvsSourcefsSource);
gl.useProgram(shaderProgram);

// Set up a buffer to hold the Sierpinski fractal vertices
const vertexBuffer gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFERvertexBuffer);
gl.bufferData(gl.ARRAY_BUFFERvpositionsgl.STATIC_DRAW);

const 
position gl.getAttribLocation(shaderProgram'aVertexPosition');
gl.vertexAttribPointer(position3gl.FLOATfalse00);
gl.enableVertexAttribArray(position);

// Set up matrices for projection and model view
const projectionMatrix mat4.create();
const 
modelViewMatrix mat4.create();

// Set up the projection matrix
mat4.perspective(projectionMatrixMath.PI 4canvas.width canvas.height1100);
const 
uProjectionMatrix gl.getUniformLocation(shaderProgram'uProjectionMatrix');
gl.uniformMatrix4fv(uProjectionMatrixfalseprojectionMatrix);

// Set up the model view matrix
mat4.lookAt(modelViewMatrix, [003], [000], [0.110]);
const 
uModelViewMatrix gl.getUniformLocation(shaderProgram'uModelViewMatrix');
gl.uniformMatrix4fv(uModelViewMatrixfalsemodelViewMatrix);

// Set the clear color and enable depth testing
gl.clearColor(0.00.00.01.0);
gl.clear(gl.COLOR_BUFFER_BIT gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);

// Draw the Sierpinski fractal
gl.drawArrays(gl.TRIANGLES0vpositions.length 3);
</
script>
</
body>
</
html>






Things to Try

• Add the colors into the program (color values are calculated but not passed to the shader - for you todo)
• Animate the shape so it rotates
• Move the position so it's offset and draw a second one (multiple Sierpinski pyramids)
• Randomness - insert some randomness into the recursive loop (just a small amount but enough to make thing interesting)



Tetrahex Fractal (3D)


Click to Show Details
<html lang="en">
<
head>
  <
meta charset="UTF-8">
  <
meta name="viewport" content="width=device-width, initial-scale=1.0">
  <
title>3D Tetrahex Fractal</title>
  <
style>
    
body margin0; }
    
canvas displayblock; }
  </
style>
</
head>
<
body>
  <
canvas id="fractalCanvas" width='600' height='600' ></canvas>
  <
script>
    
// WebGL setup
    
const canvas document.getElementById('fractalCanvas');
    const 
gl canvas.getContext('webgl');

    if (!
gl) {
      
console.error('Unable to initialize WebGL. Your browser may not support it.');
    }

    
// Vertex and fragment shaders
    
const vertexShaderSource = `
      attribute vec4 a_position;
      uniform mat4 u_modelViewMatrix;
      void main() {
        gl_Position = u_modelViewMatrix * a_position;
      }
    
`;

    const 
fragmentShaderSource = `
      precision mediump float;
      void main() {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
      }
    
`;

    
// Compile shaders
    
function compileShader(glsourcetype) {
      const 
shader gl.createShader(type);
      
gl.shaderSource(shadersource);
      
gl.compileShader(shader);

      if (!
gl.getShaderParameter(shadergl.COMPILE_STATUS)) {
        
console.error('Shader compilation error:'gl.getShaderInfoLog(shader));
        
gl.deleteShader(shader);
        return 
null;
      }

      return 
shader;
    }

    const 
vertexShader compileShader(glvertexShaderSourcegl.VERTEX_SHADER);
    const 
fragmentShader compileShader(glfragmentShaderSourcegl.FRAGMENT_SHADER);

    
// Create shader program
    
const shaderProgram gl.createProgram();
    
gl.attachShader(shaderProgramvertexShader);
    
gl.attachShader(shaderProgramfragmentShader);
    
gl.linkProgram(shaderProgram);
    
gl.useProgram(shaderProgram);

    
// Set up vertices for 3D Tetrahex fractal
    
const vertices = [];

    function 
generateTetrahex3D(verticesdepthabc) {
      if (
depth === 0) {
        
vertices.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
      } else {
        const 
ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
        const 
ac = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2, (a[2] + c[2]) / 2];
        const 
bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2, (b[2] + c[2]) / 2];

        const 
mid = [(ab[0] + ac[0] + bc[0]) / 3, (ab[1] + ac[1] + bc[1]) / 3, (ab[2] + ac[2] + bc[2]) / 3];

        
generateTetrahex3D(verticesdepth 1aabmid);
        
generateTetrahex3D(verticesdepth 1bbcmid);
        
generateTetrahex3D(verticesdepth 1cacmid);
        
generateTetrahex3D(verticesdepth 1abbcac);
      }
    }

    
generateTetrahex3D(vertices4, [-1, -10], [1, -10], [010]);

    
// Create buffer and bind vertices
    
const vertexBuffer gl.createBuffer();
    
gl.bindBuffer(gl.ARRAY_BUFFERvertexBuffer);
    
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

    const 
positionAttribLocation gl.getAttribLocation(shaderProgram'a_position');
    
gl.vertexAttribPointer(positionAttribLocation3gl.FLOATfalse00);
    
gl.enableVertexAttribArray(positionAttribLocation);

    
// Set up model view matrix for rotation
    
let modelViewMatrix = new Float32Array([
      
1000,
      
0100,
      
0010,
      
0001  // Move the fractal back along the z-axis for better visibility
    
]);

    
// [cz,-sz,0,0], [sz,cz,0,0], [0,0,1,0], [0,0,0,1]);):
    
let rzm = (rz)=>{
        
let sz Math.sin(rz)
        
let cz Math.cos(rz)
        const 
rotz = new Float32Array([
          
cz, -sz,  1,  0,
          
sz,  cz,  0,  0,
          
0,   0,   1,  0,
          
0,   0,   0,  1  
        
]);
        return 
rotz;
    }

    
// ( [1,0,0,0], [0,cx,-sx,0], [0,sx,cx,0], [0,0,0,1]);):
    
let rzx = (rx)=>{
        
let sx Math.sin(rx)
        
let cx Math.cos(rx)
        const 
rotx = new Float32Array([
          
1,   0,   0,   0,
          
0,   cx, -sx,  0,
          
0,   sx,  cx,  0,
          
0,   0,   0,   1  
        
]);
        return 
rotx;
    }

    
modelViewMatrix rzx1.2 );

    

    const 
u_modelViewMatrix gl.getUniformLocation(shaderProgram'u_modelViewMatrix');
    
gl.uniformMatrix4fv(u_modelViewMatrixfalsemodelViewMatrix);

    
// Render loop
    
let angx 0.0;
    function 
render() {

      
modelViewMatrix rzxangx );
      
angx += 0.01;
      
gl.uniformMatrix4fv(u_modelViewMatrixfalsemodelViewMatrix);

      
gl.clearColor(0.00.00.01.0);
      
gl.clear(gl.COLOR_BUFFER_BIT);
      
      
gl.drawArrays(gl.TRIANGLES0vertices.length 3);

      
requestAnimationFrame(render);
    }

    
render();
  </
script>
</
body>
</
html>


The output looks like this:




Things to Try

• Rotate the shape around different axis (x, y and z)
• Offset 'z' for each vertex (currently set to a consts but increase it as the number of triangles are added)
• Change the colors of the different triangels (show more or less details)
• Add user input (cursor keys and add/remove recursion, regenerates the buffer)
• Move the shape around (located in an x,y,z position)
• Draw '2' fractals each at a different locations
• Generate lots of them and place them around the 3d scene
• Randomness - add some randomness to the recusion (just a small amount so it makes each fractal a little unique)
• Extrude and modify the depth/shape of the fractal (tetrahedron)


Fractal Cube (JavaScript/WebGPU API)


Click to Show Details

The source code for the fractal cube is available on the WebGPU Lab Sandbox [LINK]. The code is written in Javascript but uses the WebGPU API and WGSL shader language for the graphics.


recursively adding cubes to create a fractal shape



















WebGPU Development Pixels - coding fragment shaders from post processing to ray tracing! WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Games WGSL 2d 3d interactive web-based fun learning WebGPU Compute WebGPU API - Owners WebGPU Development Cookbook - coding recipes for all your webgpu needs! WebGPU & WGSL Essentials: A Hands-On Approach to Interactive Graphics, Games, 2D Interfaces, 3D Meshes, Animation, Security and Production Kenwright graphics and animations using the webgpu api 12 week course kenwright learn webgpu api kenwright programming compute and graphics applications with html5 and webgpu api kenwright real-time 3d graphics with webgpu kenwright webgpu for dummies kenwright webgpu api develompent a quick start guide kenwright webgpu by example 2022 kenwright webgpu gems kenwright webgpu interactive compute and graphics visualization cookbook kenwright wgsl webgpu shading language cookbook kenwright WebGPU Shader Language Development: Vertex, Fragment, Compute Shaders for Programmers Kenwright wgsl webgpugems shading language cookbook kenwright WGSL Fundamentals book kenwright WebGPU Data Visualization Cookbook kenwright Special Effects Programming with WebGPU kenwright WebGPU Programming Guide: Interactive Graphics and Compute Programming with WebGPU & WGSL kenwright



 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.