Текстурирование

Одним из важнейших этапов при рендеринге 3D графики является текстурирование. Текстуры позволяют добавлять дополнительные детали, которых нет в геометрии.

Начнём с рассмотрения шейдеров.

<script id="shader-fs" type="x-shader/x-fragment">
    varying highp vec2 v_texture_coord;
    uniform sampler2D u_sampler;
    void main(void) {
        gl_FragColor = texture2D( u_sampler, v_texture_coord );
    }
</script>
<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 a_vertex_position;
    attribute vec2 a_texture_coord;
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;
    varying highp vec2 v_texture_coord;
    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(a_vertex_position, 1.0);
        v_texture_coord = a_texture_coord;
    }
</script>

В вершинном шейдере заменили переменные, которые были связаны с цветом на переменные, которые связаны с координатами текстуры. Следует заметить, что изменились не только названия, но также типы и описатели. Тип sampler2D является типом uniform-переменных, используемых для чтения из текстуры. Встроенная функция texture2D используется для чтения значений из текстуры.

У нас появилась новая функция loadTexture(), которая загружает картинку и готовит её для текстурирования.

function loadTexture( url ) {
    texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    const level = 0;
    const internalFormat = gl.RGBA;
    const width = 1;
    const height = 1;
    const border = 0;
    const srcFormat = gl.RGBA;
    const srcType = gl.UNSIGNED_BYTE;
    const pixel = new Uint8Array([0, 0, 255, 255]);
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, srcFormat, srcType, pixel);

    const image = new Image();
    image.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, image);
        gl.generateMipmap(gl.TEXTURE_2D);
    };
    image.src = url;
}

Команда bindTexture() привязывает текстуру к определённому типу, пока этот объект не будет уничтожен. Основной функцией для загрузки двумерных и кубических текстур является texImage2D(). Одним из способов автоматического построения пирамиды изображений является команда generateMipmap().

Небольшим изменениям подверглась функция initShader(). В ней мы заменили извлечение атрибута одной переменной на другую, а также извлекли положение uniform-переменной.

function initShader() {
    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("Не получилось инициилизировать шейдерную программу.");
    }
    gl.useProgram(shaderProgram);
    vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "a_vertex_position");
    gl.enableVertexAttribArray(vertexPositionAttribute);

    vertexCoordAttribute = gl.getAttribLocation(shaderProgram, "a_texture_coord");
    gl.enableVertexAttribArray(vertexCoordAttribute);

    samplerUniform = gl.getUniformLocation( shaderProgram, "u_sampler" );
}

Для корректного наложения текстуры была изменена функция initBuffers().

function initBuffers() {
    vertices_buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertices_buffer);
    var vertices = [
        -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,

        -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,

         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_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

    coord_buffer = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, coord_buffer );
    var coord = [
        0.0,  0.0,
        1.0,  0.0,
        1.0,  1.0,
        0.0,  1.0,

        0.0,  0.0,
        1.0,  0.0,
        1.0,  1.0,
        0.0,  1.0,

        0.0,  0.0,
        1.0,  0.0,
        1.0,  1.0,
        0.0,  1.0,

        0.0,  0.0,
        1.0,  0.0,
        1.0,  1.0,
        0.0,  1.0,

        0.0,  0.0,
        1.0,  0.0,
        1.0,  1.0,
        0.0,  1.0,

        0.0,  0.0,
        1.0,  0.0,
        1.0,  1.0,
        0.0,  1.0
    ];
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( coord ), gl.STATIC_DRAW );

    index_buffer = gl.createBuffer();
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, index_buffer );
    var indices = [
        0,  1,  2,      0,  2,  3,
        4,  5,  6,      4,  6,  7,
        8,  9,  10,     8,  10, 11,
        12, 13, 14,     12, 14, 15,
        16, 17, 18,     16, 18, 19,
        20, 21, 22,     20, 22, 23
    ];
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW );
}

Количество вершин и индексов было увеличено для корректного наложения текстуры на каждую грань. Мы заменили данные цвета для вершин на данные координат текстуры для каждой грани.

В функцию initBuffers() было добавлено несколько новых команд и немного изменены старые команды.

function drawScene(delta) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    perspectiveMatrix = makePerspective(45, 600.0/400.0, 0.1, 100.0);
    loadIdentity();
    mvPushMatrix();
    mvTranslate([-0.0, 0.0, -6.0]);
    mvRotate(sqrRot, [1, 0, 0.7]);

    gl.bindBuffer(gl.ARRAY_BUFFER, vertices_buffer);
    gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vertexPositionAttribute);

    gl.bindBuffer(gl.ARRAY_BUFFER, coord_buffer);
    gl.vertexAttribPointer(vertexCoordAttribute, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vertexCoordAttribute);

    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, index_buffer );

    setMatrixUniforms();

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.uniform1i(samplerUniform, 0);

    var vertices = 36;
    gl.drawElements( gl.TRIANGLES, vertices, gl.UNSIGNED_SHORT, 0 );

    mvPopMatrix();
    sqrRot -= delta * 40;
}

Рассмотрим команды, предназначенные для подготовки текстуры к наложению на геометрию. Функция activeTexture() задаёт текущий текстурный блок, так что все дальнейшие вызовы bindTexture() привяжут текстуру к активному текстурному блоку. Команда uniform1i является одной из функций для записи значений переменные шейдера в зависимости от типа.

Ниже представлен результат работы программы.

Please use a browser that supports "canvas"
  < НАЗАД | ДАЛЕЕ >