Загрузка модели из Blender — 2

Ранее у нас уже получилось загрузить тор из Blender — точнее, каркас тора. Но простого каркаса явно маловато — нужна сплошная фигура, чтобы у нее был цвет и чтобы на ней был виден результат освещения. Целью этой статьи будет получение сплошной фигуры с заданным цветом и подготовленными нормалями, что будет служить нам болванкой в следующей статье об основах освещения.

Итак, приступим!


Сплошная фигура

Так как и вершины, и индексы у нас уже есть, переход к сплошной фигуре выполняется проще простого. Во-первых, раз дело касается сплошных поверхностей, нам нужно включить проверку глубины:


    function webGLStart() {
        var canvas = document.getElementById("canvas");
        initGL(canvas);
        initShaders();

        gl.clearColor(0.2, 0.2, 0.2, 1.0);
        gl.enable(gl.DEPTH_TEST);
        
        loadJsonModel();
    }

А во-вторых, нужно заменить отрисовку линиями на отрисовку треугольниками:


gl.drawElements(gl.TRIANGLES, indexBuffer.numItems, gl.UNSIGNED_SHORT, 0);

После этих небольших изменений мы уже получим сплошной тор белого цвета. Теперь подготовим нормали.


Нормали

Начинаем с загрузки значений нормалей из XML-файла в формате COLLADA. Для этого в функции parseCollada добавляем следующие строки:


        var jsonResult = {};
        // находим /COLLADA/library_geometries/geometry/mesh/source/float_array
        var mesh = xmlDoc.getElementsByTagName("mesh")[0];
        var verticesSources = mesh.getElementsByTagName("source")[0];
        var verticesNode = verticesSources.getElementsByTagName("float_array")[0];
        // в этой секции хранятся координаты через пробел - получаем их массив
        jsonResult.vertices = verticesNode.textContent.split(" ");

        var normalsSources = mesh.getElementsByTagName("source")[1];
        var normalsNode = normalsSources.getElementsByTagName("float_array")[0];
        // по аналогии получаем нормали
        jsonResult.normals = normalsNode.textContent.split(" ");

        // находим /COLLADA/library_geometries/geometry/mesh/polylist/p
        var polyNode = mesh.getElementsByTagName("polylist")[0];
        var indecesNode = polyNode.getElementsByTagName("p")[0];
        var allIndices = indecesNode.textContent.split(" ");

Здесь все просто — получаем содержимое тега нормалей, в котором расположены значения нормалей через пробел, и записываем все в массив.

После этого мы помещаем нормали в глобальную переменную vertexNormals, на основании которой инициализируем буфер:


    var vertices;
    var indices;
    var vertexNormals;
    var vertexBuffer;
    var indexBuffer;
    var vertexNormalBuffer;

    function initBuffers() {
        vertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        
        vertexBuffer.itemSize = 3;
        vertexBuffer.numItems = vertices.length / 3;
        
        indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
        
        indexBuffer.itemSize = 1;
        indexBuffer.numItems = indices.length;
        
        vertexNormalBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals), gl.STATIC_DRAW);
        vertexNormalBuffer.itemSize = 3;
        vertexNormalBuffer.numItems = vertexNormals / 3;
    }

А буфер используем при отрисовке сцены:


    function drawScene() {
        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        mat4.perspective(pMatrix, 45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);

        mat4.identity(mvMatrix);
		
        mat4.translate(mvMatrix, mvMatrix, [0.0, 0, -3.0]);
        mat4.rotateX(mvMatrix, mvMatrix, degToRad(rX));

        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
        
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, vertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);
        
        setMatrixUniforms();
        
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.drawElements(gl.TRIANGLES, indexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
    }

Осталось лишь заполнить пробелы в инициализации шейдеров — включить соответствующий атрибут вершины и создать ссылку на матрицу нормалей:


    function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(shaderProgram);

        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
        
        shaderProgram.vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal");
        gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute);

        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
        shaderProgram.nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix");
    }

Если с атрибутом нормали все понятно — его значение берется из файла COLLADA, — то происхождение матрицы нормалей пока остается неосвещенным. А чтобы пролить свет на эту «неосвещенность», заглянем в функцию setMatrixUniforms, которая занимается передачей матриц из JavaScript в программу видеокарты:


    function setMatrixUniforms() {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
        
        var normalMatrix = mat3.create();
        mat3.normalFromMat4(normalMatrix, mvMatrix);
        gl.uniformMatrix3fv(shaderProgram.nMatrixUniform, false, normalMatrix);
    }

Как видим из этого фрагмента, матрица нормалей формируется из матрицы модель-вид. Для получения матрицы нормалей нужно взять верхнюю левую подматрицу 3х3 матрицы модель-вид, затем транспонировать ее и обратить (начиная с версии 2 библиотеки glMatrix за это преобразование отвечает одна функция — normalFromMat4). Если вам интересно узнать, почему для преобразования нормалей нельзя использовать матрицу модель-вид, или почему берется именно транспонированная обратная подматрица 3х3, вы можете посмотреть на математическое обоснование — например, здесь.

На этом работа с нормалями окончена. И хотя кода было довольно много, вы не увидите никаких изменений в отображении тора, так как нормали будут использоваться в следующий раз — при добавлении освещения на сцену. Поэтому перейдем к более простой и более наглядной части статьи — к добавлению цвета фигуры.


Цвет

Для простейшего добавления цвета можно указать его прямо в шейдере:


    void main(void) {
        gl_FragColor = vec4(0.0, 0.7, 1.0, 1.0);
    }

Но мы пойдем дальше и дадим пользователю возможность задавать цвет объекта. Для этого воспользуемся первой попавшейся в поиске библиотекой. Скачиваем библиотеку и подключаем скрипт:


    <script type="text/javascript" src="jscolor.js"></script>

Переписываем фрагментный шейдер, чтобы он мог принимать цвет из JavaScript:


    precision mediump float;

    uniform vec3 uColor;
    
    void main(void) {
        gl_FragColor = vec4(uColor, 1.0);
    }

Добавляем uniform-переменную цвета в функцию initShaders и задаем ей начальное значение:


        shaderProgram.colorUniform = gl.getUniformLocation(shaderProgram, "uColor");
        gl.uniform3fv(shaderProgram.colorUniform, [0.0, 0.0, 1.0]);

Теперь добавляем на страницу сам элемент выбора цвета с тем же начальным значением цвета, которое мы задавали для шейдера:


<body onload="webGLStart();">
    <input class="color {onImmediateChange:'updateColor(this);'}" value="0000ff" />
    <br />
    <canvas id="canvas" width="500" height="500"></canvas>
</body>

И, наконец пишем обработчик на изменение цвета, который просто передает выбранный цвет в uniform-переменную:


    function updateColor(elem) {
        gl.uniform3fv(shaderProgram.colorUniform, elem.rgb);
    }

Готово! Можно смотреть на результат:

Ссылки:

Загрузка модели из Blender — 2
Метки:    

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *