<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Sierpinski</title>
  
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec3 vPosition;
attribute vec3 vColor;

uniform mat4 modelView;
uniform mat4 projection;

varying vec4 color;

void main()
{
    gl_Position = projection*modelView*vec4(vPosition, 1.0);
    color = vec4(vColor, 1.0);
}
</script>

<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
   
varying vec4 color;

void main()
{
    gl_FragColor = color;
}
</script>

</head>
<body>
<canvas id="webgl" width="400" height="400" style="margin:0 auto;display:block;"></canvas>
</body>
</html>
/* Reset Default Style */
html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  font-size: 100%;
  vertical-align: baseline;
}

a {
  margin: 0;
  padding: 0;
  font-size: 100%;
  vertical-align: baseline;
  background: transparent;
  color: inherit;
  outline: none;
  text-decoration: none;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

canvas{
  margin:0 auto;
  width: 100%;
  height: 100%;
  display:block;
}
/**
 * 跨浏览器RAF
 */
window.requestAnimationFrame = (function () {
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60);
    };
})();

/**
 * 跨浏览器获取3D绘图上下文
 */
let getWebGLContext = function(canvas, opt) {
  let namesArr = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
  let context = null;
  for (let i = 0; i < namesArr.length; i++) {
    try {
      context = canvas.getContext(namesArr[i], opt);
    } catch(e) {}
    if (context) {
      break;
    }
  }
  return context;
}

/**
 * 编译GLSL ES代码,创建和初始化着色器(顶点和片元)
 * @param {WebGLContext} gl WebGL绘制上下文
 * @param {String} vertexShader 顶点着色器源码
 * @param {String} fragmentShader 片元着色器源码
 * @return program 返回所创建的程序对象
 */
function initShaders(gl, vertexShader, fragmentShader) {
    let vshader;
    vshader = gl.createShader(gl.VERTEX_SHADER);//创建着色器对象
    gl.shaderSource(vshader, vertexShader);//向着色器对象中填充着色器程序的源代码
    gl.compileShader(vshader);//编译着色器
    if (!gl.getShaderParameter(vshader, gl.COMPILE_STATUS)) {//检查着色器的状态
        let msg = "Couldn't compile the vertex shader. The error log is:"
            + "<pre>" + gl.getShaderInfoLog(vshader) + "</pre>";
        //弹出报错信息
        alert(msg);
        gl.deleteShader(vshader);//删除(不是马上删除,会等到程序对象不再使用该着色器后再删除)着色器对象
    return;
        return -1;
    }

    //片元着色器也一样
    let fshader;
    fshader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fshader, fragmentShader);
    gl.compileShader(fshader);
    if (!gl.getShaderParameter(fshader, gl.COMPILE_STATUS)) {
        let msg = "Couldn't compile the fragment shader.  The error log is:"
            + "<pre>" + gl.getShaderInfoLog(fshader) + "</pre>";
        alert(msg);
        gl.deleteShader(fshader);
        return -1;
    }

    //创建程序对象
    let program = gl.createProgram();
    //为程序对象分配着色器
    gl.attachShader(program, vshader);
    gl.attachShader(program, fshader);
    //连接程序对象(在为程序对象分配了两个着色器对象后,还需要将顶点着色器和片元着色器连接起来)
    gl.linkProgram(program);

    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        let msg = "Unable to initialise shaders.  The error log is:"
            + "<pre>" + gl.getProgramInfoLog(program) + "</pre>";
        alert(msg);
        gl.deleteProgram(gl.program);
        gl.deleteProgram(vshader);
        gl.deleteProgram(fshader);
        return -1;
    }

    return program;
}

function setViewPort(canvas, gl) {
    let b = document.body;
    let d = document.documentElement;
    let fullw = Math.max(b.clientWidth, b.scrollWidth, d.scrollWidth, d.clientWidth);
    let fullh = Math.max(b.clientHeight, b.scrollHeight, d.scrollHeight, d.clientHeight);
    canvas.width = fullw;
    canvas.height = fullh;
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    gl.viewport(0, 0, canvas.width, canvas.height);
}

class LGMV {
    _argumentsToArray(args) {
        return [].concat.apply([], Array.from(args));
    }
    radians( degrees ) {
        return degrees * Math.PI / 180.0;
    }
    //向量初始化
    vec2() {
        let result = this._argumentsToArray(arguments);
        switch (result.length) {
            case 0: result.push(0.0);
            case 1: result.push(0.0);
        }
        return result.splice(0, 2);
    }
    vec3() {
        let result = this._argumentsToArray(arguments);
        switch (result.length) {
            case 0: result.push(0.0);
            case 1: result.push(0.0);
            case 2: result.push(0.0);
        }
        return result.splice(0, 3);
    }
    vec4() {
        let result = this._argumentsToArray(arguments);
        switch (result.length) {
            case 0: result.push(0.0);
            case 1: result.push(0.0);
            case 2: result.push(0.0);
            case 3: result.push(1.0);
        }
        return result.splice(0, 4);
    }
    //矩阵初始化
    mat2() {
        let arg = this._argumentsToArray(arguments);
        let m = [];
        switch (arg.length) {
            case 0:
                arg[0] = 1;
            case 1:
                m = [
                    this.vec2(arg[0], 0.0),
                    this.vec2(0.0, arg[0])
                ];
                break;
            default:
                m.push(this.vec2(arg)); arg.splice(0, 2);
                m.push(this.vec2(arg));
                break;
        }
        m.matrix = true;
        return m;
    }
    mat3() {
        let arg = this._argumentsToArray(arguments);
        let m = [];
        switch (arg.length) {
            case 0:
                arg[0] = 1;
            case 1:
                m = [
                    this.vec3(arg[0], 0.0, 0.0),
                    this.vec3(0.0, arg[0], 0.0),
                    this.vec3(0.0, 0.0, arg[0])
                ];
                break;
            default:
                m.push(this.vec3(arg)); arg.splice(0, 3);
                m.push(this.vec3(arg)); arg.splice(0, 3);
                m.push(this.vec3(arg));
                break;
        }
        m.matrix = true;
        return m;
    }
    mat4() {
        let arg = this._argumentsToArray(arguments);
        let m = [];
        switch (arg.length) {
            case 0:
                arg[0] = 1;
            case 1:
                m = [
                    this.vec4(arg[0], 0.0, 0.0, 0.0),
                    this.vec4(0.0, arg[0], 0.0, 0.0),
                    this.vec4(0.0, 0.0, arg[0], 0.0),
                    this.vec4(0.0, 0.0, 0.0, arg[0])
                ];
                break;

            default:
                m.push(this.vec4(arg)); arg.splice(0, 4);
                m.push(this.vec4(arg)); arg.splice(0, 4);
                m.push(this.vec4(arg)); arg.splice(0, 4);
                m.push(this.vec4(arg));
                break;
        }
        m.matrix = true;
        return m;
    }
    //顶点方法
    //点乘
    dot(u, v) {
        if (u.length != v.length) {
            throw "this.dot(): vectors are not the same dimension";
        }

        let sum = 0.0;
        for (let i = 0; i < u.length; ++i) {
            sum += u[i] * v[i];
        }

        return sum;
    }
    //叉乘
    cross(u, v) {
        if (!Array.isArray(u) || u.length < 3) {
            throw "this.cross(): first argument is not a vector of at least 3";
        }

        if (!Array.isArray(v) || v.length < 3) {
            throw "this.cross(): second argument is not a vector of at least 3";
        }

        let result = [
            u[1] * v[2] - u[2] * v[1],
            u[2] * v[0] - u[0] * v[2],
            u[0] * v[1] - u[1] * v[0]
        ];

        return result;
    }
    negate(u) {
        let result = [];
        for (let i = 0; i < u.length; ++i) {
            result.push(-u[i]);
        }

        return result;
    }
    //矩阵方法
    //相加
    add(u, v) {
        let result = [];

        if (u.matrix && v.matrix) {
            if (u.length != v.length) {
                throw "add(): trying to add matrices of different dimensions";
            }

            for (let i = 0; i < u.length; ++i) {
                if (u[i].length != v[i].length) {
                    throw "add(): trying to add matrices of different dimensions";
                }
                result.push([]);
                for (let j = 0; j < u[i].length; ++j) {
                    result[i].push(u[i][j] + v[i][j]);
                }
            }

            result.matrix = true;

            return result;
        }
        else if (u.matrix && !v.matrix || !u.matrix && v.matrix) {
            throw "add(): trying to add matrix and non-matrix variables";
        }
        else {
            if (u.length != v.length) {
                throw "add(): vectors are not the same dimension";
            }

            for (let i = 0; i < u.length; ++i) {
                result.push(u[i] + v[i]);
            }

            return result;
        }
    }
    //相减
    subtract(u, v) {
        let result = [];

        if (u.matrix && v.matrix) {
            if (u.length != v.length) {
                throw "subtract(): trying to subtract matrices" +
                " of different dimensions";
            }

            for (let i = 0; i < u.length; ++i) {
                if (u[i].length != v[i].length) {
                    throw "subtract(): trying to subtact matrices" +
                    " of different dimensions";
                }
                result.push([]);
                for (let j = 0; j < u[i].length; ++j) {
                    result[i].push(u[i][j] - v[i][j]);
                }
            }

            result.matrix = true;

            return result;
        }
        else if (u.matrix && !v.matrix || !u.matrix && v.matrix) {
            throw "subtact(): trying to subtact  matrix and non-matrix variables";
        }
        else {
            if (u.length != v.length) {
                throw "subtract(): vectors are not the same length";
            }

            for (let i = 0; i < u.length; ++i) {
                result.push(u[i] - v[i]);
            }

            return result;
        }
    }
    //相乘
    mult(u, v) {
        let result = [];

        if (u.matrix && v.matrix) {
            if (u.length != v.length) {
                throw "mult(): trying to add matrices of different dimensions";
            }

            for (let i = 0; i < u.length; ++i) {
                if (u[i].length != v[i].length) {
                    throw "mult(): trying to add matrices of different dimensions";
                }
            }

            for (let i = 0; i < u.length; ++i) {
                result.push([]);

                for (let j = 0; j < v.length; ++j) {
                    let sum = 0.0;
                    for (let k = 0; k < u.length; ++k) {
                        sum += u[i][k] * v[k][j];
                    }
                    result[i].push(sum);
                }
            }

            result.matrix = true;

            return result;
        }

        if (u.matrix && (u.length == v.length)) {
            for (let i = 0; i < v.length; i++) {
                let sum = 0.0;
                for (let j = 0; j < v.length; j++) {
                    sum += u[i][j] * v[j];
                }
                result.push(sum);
            }
            return result;
        }



        else {
            if (u.length != v.length) {
                throw "mult(): vectors are not the same dimension";
            }

            for (let i = 0; i < u.length; ++i) {
                result.push(u[i] * v[i]);
            }

            return result;
        }
    }
    //相等
    equal(u, v) {
        if (u.length != v.length) { return false; }

        if (u.matrix && v.matrix) {
            for (let i = 0; i < u.length; ++i) {
                if (u[i].length != v[i].length) { return false; }
                for (let j = 0; j < u[i].length; ++j) {
                    if (u[i][j] !== v[i][j]) { return false; }
                }
            }
        }
        else if (u.matrix && !v.matrix || !u.matrix && v.matrix) {
            return false;
        }
        else {
            for (let i = 0; i < u.length; ++i) {
                if (u[i] !== v[i]) { return false; }
            }
        }

        return true;
    }
    //归一化
    normalize(u, excludeLastComponent) {
        if (excludeLastComponent) {
            let last = u.pop();
        }

        let len = this.length(u);

        if (!isFinite(len)) {
            throw "normalize: vector " + u + " has zero length";
        }

        for (let i = 0; i < u.length; ++i) {
            u[i] /= len;
        }

        if (excludeLastComponent) {
            u.push(last);
        }

        return u;
    }
    transpose(m) {
        if (!m.matrix) {
            return "transpose(): trying to transpose a non-matrix";
        }

        let result = [];
        for (let i = 0; i < m.length; ++i) {
            result.push([]);
            for (let j = 0; j < m[i].length; ++j) {
                result[i].push(m[j][i]);
            }
        }

        result.matrix = true;

        return result;
    }
    //长度
    length(u) {
        return Math.sqrt(this.dot(u, u));
    }
    //数组类型转换
    flatten(v) {
        if (v.matrix === true) {
            //矩阵先转置(WebGL使用列主序)
            v = this.transpose(v);
        }
        let n = v.length;
        let elemsAreArrays = false;

        if (Array.isArray(v[0])) {
            elemsAreArrays = true;
            n *= v[0].length;
        }

        let floats = new Float32Array(n);

        if (elemsAreArrays) {
            let idx = 0;
            for (let i = 0; i < v.length; ++i) {
                for (let j = 0; j < v[i].length; ++j) {
                    floats[idx++] = v[i][j];
                }
            }
        }
        else {
            for (let i = 0; i < v.length; ++i) {
                floats[i] = v[i];
            }
        }

        return floats;
    }
    mix(u, v, s) {
        if (typeof s !== "number") {
            throw "mix: the last paramter " + s + " must be a number";
        }

        if (u.length != v.length) {
            throw "vector dimension mismatch";
        }

        let result = [];
        for (let i = 0; i < u.length; ++i) {
            result.push((1.0 - s) * u[i] + s * v[i]);
        }

        return result;
    }
    //MVP相关
    lookAt(eye, at, up) {
        if (!Array.isArray(eye) || eye.length != 3) {
            throw "lookAt(): first parameter [eye] must be an a this.vec3";
        }

        if (!Array.isArray(at) || at.length != 3) {
            throw "lookAt(): first parameter [at] must be an a this.vec3";
        }

        if (!Array.isArray(up) || up.length != 3) {
            throw "lookAt(): first parameter [up] must be an a this.vec3";
        }

        if (this.equal(eye, at)) {
            return this.this.mat4();
        }

        let v = this.normalize(this.subtract(at, eye));  // view direction vector
        let n = this.normalize(this.cross(v, up));       // perpendicular vector
        let u = this.normalize(this.cross(n, v));        // "new" up vector

        v = this.negate(v);

        let result = this.mat4(
            this.vec4(n, -this.dot(n, eye)),
            this.vec4(u, -this.dot(u, eye)),
            this.vec4(v, -this.dot(v, eye)),
            this.vec4()
        );
        return result;
    }

    //平行投影
    ortho(left, right, bottom, top, near, far) {
        //视见体是对称的
        //坐标映射:x => [-1.1] y => [-1,1] z => [0,1]
        //矩阵表示(通用): 
        //r => right l => left t => top b => bottom f => far n => near 
        //[ 2/(r-l), 0, 0, -(r+l)/(r-l)
        //  0, 2/(t-b), 0, -(t+b)/(t-b)
        //  0, 0, -2/(f-n), -(f+n)/(f-n)
        //  0, 0, 0, 1]
        // 对称时:r = -l, t = -b ....
        // 可简化成:
        //[ 1/r, 0, 0, 0
        //  0, 1/t, 0, 0
        //  0, 0, -2/(f-n), -(f+n)/(f-n)
        //  0, 0, 0, 1]
         if (left == right) { throw "ortho(): left and right are equal"; }
        if (bottom == top) { throw "ortho(): bottom and top are equal"; }
        if (near == far) { throw "ortho(): near and far are equal"; }

        let w = right - left;
        let h = top - bottom;
        let d = far - near;

        let result = this.mat4();
        result[0][0] = 2.0 / w;
        result[1][1] = 2.0 / h;
        result[2][2] = -2.0 / d;
        result[0][3] = -(left + right) / w;
        result[1][3] = -(top + bottom) / h;
        result[2][3] = -(near + far) / d;

        return result;
    }

    //透视投影
    perspective(fovy, aspect, near, far) {
        //aspect = width /height => 2r/2t(对称的情况下) => r/t
        //f = 1/ (top/near)  视角还少一半 => n/t
        var f = 1.0 / Math.tan(this.radians(fovy) / 2);
        var d = far - near;
        //和平息投影原理时一样的,后面的推导加个相似三角形
        //通用:
        //[ 2n/(r-l), 0, (r+l)/(r-l), 0
        //  0, 2n/(t-b), (t+b)/(t-b), 0
        //  0, 0, -(f+n)/(f-n), -2fn/(f-n)
        //  0, 0, -1, 0]
        //对称简化后:
        //[ n/r, 0, 0, 0
        //  0, n/t, 0, 0
        //  0, 0, -(f+n)/(f-n), -2fn/(f-n)
        //  0, 0, -1, 0 ]
        var result = this. mat4();
        result[0][0] = f / aspect;
        result[1][1] = f;
        result[2][2] = -(near + far) / d;//这里的符号问题
        result[2][3] = -2 * near * far / d;//这里的符号问题
        result[3][2] = -1;
        result[3][3] = 0.0;//默认是1,手动置0

        return result;
    }
}

//起手式
let canvas = document.getElementById("webgl");
let gl = getWebGLContext(canvas);
setViewPort(canvas, gl);
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.enable(gl.DEPTH_TEST);

let MV = new LGMV();

//顶点
let vertices = [
    //单位圆上四点
    MV.vec3(0.0000, 0.0000, -1.0000),
    MV.vec3(0.0000, 0.9428, 0.3333),
    MV.vec3(-0.8165, -0.4714, 0.3333),
    MV.vec3(0.8165, -0.4714, 0.3333)
];

//起始点
let points = [];
let colors = [];
let NumTimesToSubdivide = 3;//分型次数
//分型
divideTetra(vertices[0], vertices[1], vertices[2], vertices[3],
    NumTimesToSubdivide);

let vshader = document.getElementById("vertex-shader").text;
let fshader = document.getElementById("fragment-shader").text;
let program = initShaders(gl, vshader, fshader);
gl.useProgram(program);

let cBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cBuffer);
gl.bufferData(gl.ARRAY_BUFFER, MV.flatten(colors), gl.STATIC_DRAW);

let vColor = gl.getAttribLocation(program, "vColor");
gl.vertexAttribPointer(vColor, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vColor);

let vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferData(gl.ARRAY_BUFFER, MV.flatten(points), gl.STATIC_DRAW);

let vPosition = gl.getAttribLocation(program, "vPosition");
gl.vertexAttribPointer(vPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);

let modelView = gl.getUniformLocation(program, "modelView");
let projection = gl.getUniformLocation(program, "projection");

//MVP矩阵所需变量
let MVMatrix, pMatrix;//MVP!!!
let eye;

let radius = 1.0;
let phi = 0.0;
let dr = 1.0 * Math.PI / 180.0;//1度(角度转弧度便于Math.sin\cos)

const at = MV.vec3(0.0, 0.0, 0.0);//look at point
const up = MV.vec3(0.0, 1.0, 0.0);//视点上方向

let fovy = 45.0;
let aspect =  canvas.width/canvas.height;
let near = 0.3;
let far = 4.0;
//平行投影
pMatrix = MV.ortho(-1.0, 1.0, -1.0, 1.0, 0.0, 4.0);//left, right, bottom, top, near, far
//透视投影
//pMatrix = MV.perspective(fovy, aspect, near, far);
gl.uniformMatrix4fv(projection, false, MV.flatten(pMatrix));

//render
function render() {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    phi += dr;
    //变换视点的位置(x、y、z)
    eye = MV.vec3(
        radius * Math.sin(phi), 
        1, 
        radius * Math.cos(phi));

    MVMatrix = MV.lookAt(eye, at, up);

    gl.uniformMatrix4fv(modelView, false, MV.flatten(MVMatrix));
    gl.drawArrays(gl.TRIANGLES, 0, points.length);

    window.requestAnimationFrame(render);
}

render();

//分型方法
function divideTetra(a, b, c, d, count) {
    if (count === 0) {
        tetra(a, b, c, d);
    }

    else {
        //6个中点
        var ab = MV.mix(a, b, 0.5);
        var ac = MV.mix(a, c, 0.5);
        var ad = MV.mix(a, d, 0.5);
        var bc = MV.mix(b, c, 0.5);
        var bd = MV.mix(b, d, 0.5);
        var cd = MV.mix(c, d, 0.5);

        --count;
        //4个小三角体
        divideTetra(a, ab, ac, ad, count);
        divideTetra(ab, b, bc, bd, count);
        divideTetra(ac, bc, c, cd, count);
        divideTetra(ad, bd, cd, d, count);
        //继续迭代
    }
}

function tetra(a, b, c, d) {
    triangle(a, c, b);
    triangle(a, c, d);
    triangle(a, b, d);
    triangle(b, c, d);
}


function triangle(a, b, c) {
    //顶点随机取色
    colors.push(MV.vec3(Math.random(), Math.random(), Math.random()));
    points.push(a);
    colors.push(MV.vec3(Math.random(), Math.random(), Math.random()));
    points.push(b);
    colors.push(MV.vec3(Math.random(), Math.random(), Math.random()));
    points.push(c);
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.