Пример рисования элипсоида ( WebGL и ThreeJS )

Автор: Евгений Рыжков Дата публикации: 17.03.2013

Используем WebGL

<html>
<head>
    <script id="shader-fs" type="x-shader/x-fragment">
        precision mediump float;

        void main(void) {
            gl_FragColor = vec4(1.0, 0.94, 0.0, 1.0);
        }
    </script>

    <script id="shader-vs" type="x-shader/x-vertex">
        attribute vec3 aVertexPosition;

        uniform mat4 uMVMatrix;
        uniform mat4 uPMatrix;

        void main(void) {
            gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        }
    </script>

    <script src="js/gl-matrix-0.9.5.min.js"></script>
    <script src="js/sphere.js"></script>
</head>
<body>
    <canvas id="mycanvas" width="800" height="500"></canvas>
</body>
</html>
<script type="text/javascript">
window.onload = function(){
    new World("mycanvas");// создаем мир
};

/*
 *  World
 *
 *      properties:
 *          canvas (канва),
 *          gl (контекст);
 *
 *      methods:
 *          init (инициализация обьекта),
 *          getShader (получаем код шейдеров),
 *          setMatrixUniforms ( эта функция отправляет в шейдеры матрицу трансформаций и матрицу перспективы ),
 *          initBuffers (инициализация буферов),
 *          drawScene (отрисовка сцены)
 *
 * */
var World = function( id ){
    this.canvas = document.getElementById( id );
    this.gl = this.canvas.getContext("experimental-webgl");


    this.init();
};
    World.prototype = {
        /* init (инициализация обьекта) */
        init: function(){
            var self = this,
                gl = self.gl;

            self.mvMatrix = mat4.create(); //создаем матрицу model-view [4*4] используя библиотеку glMatrix v0.9.5
            self.pMatrix = mat4.create(); //создаем матрицу projection [4*4] используя библиотеку glMatrix v0.9.5

            self.initShaders();
            self.initBuffers();
            self.figure = new Ellipsoid( gl );

            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            gl.enable(gl.DEPTH_TEST);

            self.drawScene();
        },
        /* /init */

        /* getShader (получаем код шейдеров) */
        getShader: function (gl, id) {
            var shaderScript = document.getElementById(id);

            if (!shaderScript) {
                return null;
            }

            var str = "",
                k = shaderScript.firstChild;

            while (k) {
                if (k.nodeType == 3) {
                    str += k.textContent;
                }
                k = k.nextSibling;
            }

            var shader;
            if (shaderScript.type == "x-shader/x-fragment") {
                shader = gl.createShader(gl.FRAGMENT_SHADER);
            } else if (shaderScript.type == "x-shader/x-vertex") {
                shader = gl.createShader(gl.VERTEX_SHADER);
            } else {
                return null;
            }

            gl.shaderSource(shader, str);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                alert(gl.getShaderInfoLog(shader));
                return null;
            }

            return shader;
        },
        /* /getShader */

        /* initShaders инициализация шейдеров */
        initShaders: function() {
            var self = this, //ссылка на обьект
                gl = self.gl, //ссылка на контекст
                fragmentShader = self.getShader(gl, "shader-fs"),//получаем пиксельный шейдер
                vertexShader = self.getShader(gl, "shader-vs"); // получаем вертексный шейдер

            /* обьединение шейдеров в программу */
            self.shaderProgram = gl.createProgram();
            gl.attachShader(self.shaderProgram, vertexShader);
            gl.attachShader(self.shaderProgram, fragmentShader);
            gl.linkProgram(self.shaderProgram);

            /*
             * следущие три условия необходимы для отладки в случае ошибки в шейдерах
             *
             * они всегда прописываются после обьеденения шейдеров в программу
             *
             * */
            if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS))
                console.log(gl.getShaderInfoLog(vs));
            if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS))
                console.log(gl.getShaderInfoLog(fs));
            if (!gl.getProgramParameter(self.shaderProgram, gl.LINK_STATUS))
                console.log(gl.getProgramInfoLog(self.shaderProgram));

            /* установка текущей программы */
            gl.useProgram(self.shaderProgram);

            /* получаем ссылку на атрибут определенный в вершинном шейдере */
            self.shaderProgram.vertexPositionAttribute = gl.getAttribLocation(self.shaderProgram, "aVertexPosition");
            /* преобразовуем vertexPositionAttribute в массив */
            gl.enableVertexAttribArray(self.shaderProgram.vertexPositionAttribute);

            /* получаем ссылки на униформы определенные в вершинном шейдере */
            self.shaderProgram.pMatrixUniform = gl.getUniformLocation(self.shaderProgram, "uPMatrix");
            self.shaderProgram.mvMatrixUniform = gl.getUniformLocation(self.shaderProgram, "uMVMatrix");
        },

        /* setMatrixUniforms эта функция отправляет в шейдеры матрицу трансформаций и матрицу перспективы */
        setMatrixUniforms: function() {
            var self = this,
                gl = self.gl;

            gl.uniformMatrix4fv(self.shaderProgram.pMatrixUniform, false, self.pMatrix);
            gl.uniformMatrix4fv(self.shaderProgram.mvMatrixUniform, false, self.mvMatrix);
        },
        /* /setMatrixUniforms */

        /* initBuffers (инициализация буферов) */
        initBuffers: function(){
            var self = this, //ссылка на обьект
                gl = self.gl, //ссылка на контекст
                verticesTriangle = [ //вершины треугольника
                    1.0,  1.0,  0.0,
                    -1.0,  1.0,  0.0,
                    1.0, -1.0,  0.0,
                    -1.0, -1.0,  0.0
                ];

            /* создание буфера треугольника */
            self.triangleVertexPositionBuffer = gl.createBuffer();

            /* установка текущего буфера */
            gl.bindBuffer( gl.ARRAY_BUFFER, self.triangleVertexPositionBuffer );

            /* заполняем текущий буфер нашими вершинами */
            gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( verticesTriangle ), gl.STATIC_DRAW );

            /* добавляем свои свойства обьекту
             *
             * по умолчанию эти свойства обьекту не нужны.
             * Они будут нам полезны позднее
             *
             * фактически свойства представляют собой три отдельных позиции вершин(numItems),
             * каждая из которых состоит из 3 цифр(itemSize)
             */
            self.triangleVertexPositionBuffer.itemSize = 3;
            self.triangleVertexPositionBuffer.numItems = verticesTriangle.length/self.triangleVertexPositionBuffer.itemSize;
        },
        /* /initBuffers  */

        /* drawScene (отрисовка сцены) */
        drawScene: function () {
            var self = this, //ссылка на обьект
                gl = self.gl;//ссылка на контекст

            /* определяем прямоугольную рабочую область на канве
             *
             *   отсчет ведется с нижнего левого угла
             *
             *   gl.viewport(x, y, width, height);
             * */
            gl.viewport(0, 0, self.canvas.width, self.canvas.height);

            /* очистка холста */
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            /* устанавливаем перспективу */
            mat4.perspective(70, self.canvas.width / self.canvas.height, 0.1, 1000.0, self.pMatrix);

            /* функция приравнивает model-view(mvMatrix) матрицу к еденичной (устонавливаем центр нашего 3D пространства),
             *
             *  model-view это отпровная точка от которой будут просчитываться все наши преобразования
             *
             */
            mat4.identity(self.mvMatrix);

            /*
             *  нарисуем треугольник
             * */

            /* перемещаем центр нашего 3D пространства в новую точку */
            mat4.translate(self.mvMatrix, [ 0, 0.0, -400.0]);

           /* устанавливаем текущий буфер */
            gl.bindBuffer(gl.ARRAY_BUFFER, self.figure.triangleVertexPositionBuffer);

            /* ????????? */
            gl.vertexAttribPointer(self.shaderProgram.vertexPositionAttribute, self.figure.triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

            /* говорим WebGL использовать текущую трансформацию */
            self.setMatrixUniforms();

            gl.drawArrays(gl.TRIANGLES, 0, self.figure.triangleVertexPositionBuffer.numItems);
        }
        /* /drawScene */
    };


/* Ellipsoid
 *
 *      properties:
 *          gl (контекст),
 *          paremetres (объект параметров);
 *
 *      methods:
 *          init (инициализация обьекта)
 * */
var Ellipsoid = function( gl, paremetres ){
    this.paremetres = paremetres || {
        diam: { d1:600, d2: 300, d3: 150 },
        detail: 100
    };
    this.gl = gl;

    this.init();
};
Ellipsoid.prototype = {
    /*init (инициализация обьекта)*/
    init: function() {
        var self = this,
            vertices = [],
            diam = self.paremetres.diam,
            detail = self.paremetres.detail,
            pointCount = detail * 2,
            step1 = diam.d1 / detail,
            r1 = diam.d1 / 2,
            delta1 = r1 - ( diam.d2 / 2 ),
            delta2 = r1 - ( diam.d3 / 2 ),
            i, j,
            faces = [];

        //создаем масив вершин
        for( i = -r1; i <= (r1 + 0.05); i += step1  ){
            if( i < 0.05 && i > -0.05 ) i = 0;
            if( r1 < i ) i = r1;

            var curR = Math.sqrt( r1 * r1 - i * i ) - ( ( Math.sqrt( r1 * r1 - i * i ) / r1 ) * delta1 ),
                curR2 = Math.sqrt( r1 * r1 - i * i ) - ( ( Math.sqrt( r1 * r1 - i * i ) / r1 ) * delta2 ),
                curStep = ( curR * 2 ) / detail,
                curDelta = curR - curR2,
                tempArr = [];


            for ( j = -curR; j <= (curR + 0.05) ; j += curStep  ){
                var z = 0;

                if( j < 0.05 && j > -0.05 ) j = 0;
                if( curR < 0.05 && curR > -0.05 ) j = 0;

                if( j == 0 && curR == 0 ){
                    for( n = 0; n <= pointCount - 1 ; n++ ){

                        vertices.push( [ i, j, z ] );
                    }
                    break;
                }

                if( curR < j ) j = curR;
                z = Math.sqrt( curR * curR - j * j ) - ( ( Math.sqrt( curR * curR - j * j ) / curR ) * curDelta );



                if( z < 0.05 && z > -0.05 ) {
                    z = 0;
                } else {
                    tempArr.unshift( [ i, j, -z ] );
                }
                vertices.push( [ i, j, z ] );

            }


            if( tempArr.length ) {
                for( j = 0; j < tempArr.length; j++ ) {
                    vertices.push( tempArr[ j ] );
                }
            }
        }

        verticesCount = vertices.length;

        //создаем масив граней
        for( i = pointCount; i < verticesCount; i += pointCount  ) {

            for( j = i; j < i + pointCount; j++ ){
                if( j == i + pointCount-1 ){

                    faces.push( vertices[ i - pointCount ][ 0 ] );
                    faces.push( vertices[ i - pointCount ][ 1 ] );
                    faces.push( vertices[ i - pointCount ][ 2 ] );

                    faces.push( vertices[ j - pointCount ][ 0 ] );
                    faces.push( vertices[ j - pointCount ][ 1 ] );
                    faces.push( vertices[ j - pointCount ][ 2 ] );

                    faces.push( vertices[ j ][ 0 ] );
                    faces.push( vertices[ j ][ 1 ] );
                    faces.push( vertices[ j ][ 2 ] );

                    faces.push( vertices[ i - pointCount ][ 0 ] );
                    faces.push( vertices[ i - pointCount ][ 1 ] );
                    faces.push( vertices[ i - pointCount ][ 2 ] );

                    faces.push( vertices[ j ][ 0 ] );
                    faces.push( vertices[ j ][ 1 ] );
                    faces.push( vertices[ j ][ 2 ] );

                    faces.push( vertices[ i ][ 0 ] );
                    faces.push( vertices[ i ][ 1 ] );
                    faces.push( vertices[ i ][ 2 ] );
                } else {
                    faces.push( vertices[ j - pointCount + 1 ][ 0 ] );
                    faces.push( vertices[ j - pointCount + 1 ][ 1 ] );
                    faces.push( vertices[ j - pointCount + 1 ][ 2 ] );

                    faces.push( vertices[ j - pointCount ][ 0 ] );
                    faces.push( vertices[ j - pointCount ][ 1 ] );
                    faces.push( vertices[ j - pointCount ][ 2 ] );

                    faces.push( vertices[ j ][ 0 ] );
                    faces.push( vertices[ j ][ 1 ] );
                    faces.push( vertices[ j ][ 2 ] );

                    faces.push( vertices[ j - pointCount + 1 ][ 0 ] );
                    faces.push( vertices[ j - pointCount + 1 ][ 1 ] );
                    faces.push( vertices[ j - pointCount + 1 ][ 2 ] );

                    faces.push( vertices[ j ][ 0 ] );
                    faces.push( vertices[ j ][ 1 ] );
                    faces.push( vertices[ j ][ 2 ] );

                    faces.push( vertices[ j + 1 ][ 0 ] );
                    faces.push( vertices[ j + 1 ][ 1 ] );
                    faces.push( vertices[ j + 1 ][ 2 ] );
                }
            }
        }

        /* создание буфера треугольника */
        self.triangleVertexPositionBuffer = self.gl.createBuffer();

        /* установка текущего буфера */
        self.gl.bindBuffer( self.gl.ARRAY_BUFFER, self.triangleVertexPositionBuffer );

        /* заполняем текущий буфер нашими вершинами */
        self.gl.bufferData( self.gl.ARRAY_BUFFER, new Float32Array( faces ), self.gl.STATIC_DRAW );

        /* добавляем свои свойства обьекту*/
        self.triangleVertexPositionBuffer.itemSize = 3;
        self.triangleVertexPositionBuffer.numItems = faces.length/3;
    }
    /*/init*/
};
</script>

демо-пример

Используем ThreeJS

<html>
<head>
    <link rel="stylesheet" href="css/main.css" />

    <script src="js/three.min.js"></script>
    <script src="js/three.sphere.js"></script>

</head>
<body>

    <canvas id="world" width="800" height="500"></canvas>
</body>
</html>
<script type="text/javascript">
window.onload = function(){

    var world = new World(),  // создаем мир
        ellipsoid = new Ellipsoid(); // создаем елипсоид

    world.addObject( ellipsoid.object ); //добавляем элипсоид в мир

    /* функция анимации */
    (function animate() {
        requestAnimationFrame( animate );

        world.animate();
    })();
};

/*
 *  World
 *
 *      properties:
 *          canvas,
 *          aspect
 *          camera,
 *          scene,
 *          renderer;
 *
 *      methods:
 *          animate (анимируем мир)
 *          init (инициализация обьекта),
 *          addObject(добавляем обьект в сцену)
 * */
var World = function() {
    this.canvas = document.getElementById( 'world' );  // получаем канву

    this.aspect = this.canvas.width / this.canvas.height;  // соотношение сторон канвы
    this.camera = new THREE.PerspectiveCamera( 70, this.aspect, 1, 1000 );// создаем камеру
    this.scene = new THREE.Scene();// создаем сцену
    this.renderer = new THREE.WebGLRenderer( { canvas: this.canvas } );// создаем рендер мира

    this.init();// инициализируем обьект
};
    World.prototype = {
        /* animate (анимируем мир) */
        animate: function(){
            var self = this;

            self.renderer.render( self.scene, self.camera );
        },
        /* /animate */

        /* init (инициализация обьекта)*/
        init: function() {
            var self = this;

            self.camera.position.z = 400;//
            self.scene.add(self.camera);

            self.renderer.setSize( self.canvas.width, self.canvas.height );
        },
        /* /init */

        /* addObject (добавляем обьект в сцену)*/
        addObject: function( obj ) {
            var self = this;

            self.scene.add( obj );
        }
        /* /addObject */
    };

/* Ellipsoid
 *
 *      properties:
 *          object (объект),
 *          paremetres (объект параметров);
 *
 *      methods:
 *          init (инициализация обьекта)
 * */
var Ellipsoid = function( paremetres ){
    this.object = new THREE.Object3D();
    this.paremetres = paremetres || {
        diam: { d1:600, d2: 300, d3: 150 },//длина осей элипсоида
        detail: 100// детализация элипсоида
    };

    this.init();
};
    Ellipsoid.prototype = {
        /* init (инициализация обьекта) */
        init: function() {
            var self = this,
                geometry = new THREE.Geometry(),//create geometry
                material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ), //создаем материал ( нужно расписать все материалы )
                mesh,
                diam = self.paremetres.diam,
                detail = self.paremetres.detail,
                pointCount = detail * 2,
                step1 = diam.d1 / detail,
                r1 = diam.d1 / 2,
                delta1 = r1 - ( diam.d2 / 2 ),
                delta2 = r1 - ( diam.d3 / 2 ),
                i, j,
                verticesCount;

            //создаем масив вершин
            for( i = -r1; i <= (r1 + 0.05); i += step1  ){
                if( i < 0.05 && i > -0.05 ) i = 0;
                if( r1 < i ) i = r1;

                var curR = Math.sqrt( r1 * r1 - i * i ) - ( ( Math.sqrt( r1 * r1 - i * i ) / r1 ) * delta1 ),
                    curR2 = Math.sqrt( r1 * r1 - i * i ) - ( ( Math.sqrt( r1 * r1 - i * i ) / r1 ) * delta2 ),
                    curStep = ( curR * 2 ) / detail,
                    curDelta = curR - curR2,
                    tempArr = [];


                for ( j = -curR; j <= (curR + 0.05) ; j += curStep  ){
                    var z = 0;

                    if( j < 0.05 && j > -0.05 ) j = 0;
                    if( curR < 0.05 && curR > -0.05 ) j = 0;

                    if( j == 0 && curR == 0 ){
                        for( n = 0; n <= pointCount - 1 ; n++ ){

                            geometry.vertices.push( new THREE.Vector3( i, j, z ) );
                        }
                        break;
                    }

                    if( curR < j ) j = curR;
                    z = Math.sqrt( curR * curR - j * j ) - ( ( Math.sqrt( curR * curR - j * j ) / curR ) * curDelta );



                    if( z < 0.05 && z > -0.05 ) {
                        z = 0;
                    } else {
                        tempArr.unshift( new THREE.Vector3( i, j, -z ) );
                    }
                    geometry.vertices.push( new THREE.Vector3( i, j, z ) );

                }


                if( tempArr.length ) {
                    for( j = 0; j < tempArr.length; j++ ) {
                        geometry.vertices.push( tempArr[ j ] );
                    }
                }

            }

            verticesCount = geometry.vertices.length;

            //создаем масив граней
            for( i = pointCount; i < verticesCount; i += pointCount  ) {

                for( j = i; j < i + pointCount; j++ ){
                    if( j == i + pointCount-1 ){
                        geometry.faces.push( new THREE.Face3( i - pointCount, j - pointCount, j ) );
                        geometry.faces.push( new THREE.Face3( i - pointCount, j, i ) );
                    } else {
                        geometry.faces.push( new THREE.Face3( j - pointCount + 1, j - pointCount, j ) );
                        geometry.faces.push( new THREE.Face3( j - pointCount + 1, j, j + 1 ) );
                    }
                }
            }

            geometry.computeBoundingSphere();

            //создаем фигуру из геометрии и материала
            mesh = new THREE.Mesh( geometry, material );

            //добавляем фигуру в объект
            self.object.add( mesh );
        }
    };
</script>

демо-пример