Текстурирование
Одним из важнейших этапов при рендеринге 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 является одной из функций для записи значений переменные шейдера в зависимости от типа.
Ниже представлен результат работы программы.