Используя координаты текстуры мы моем более точно проецировать ее на поверхность объекта. Итак, создадим следующую страницу:
<!DOCTYPE html> <html> <head> <title>3D in WebGL!</title> <meta charset="utf-8" /> </head> <body> <canvas id="canvas3D" width="400" height="300">Ваш браузер не поддерживает элемент canvas</canvas> <script id="shader-fs" type="x-shader/x-fragment"> precision highp float; uniform sampler2D uSampler; varying vec2 vTextureCoords; void main(void) { gl_FragColor = texture2D(uSampler, vTextureCoords); } </script> <script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; attribute vec2 aVertexTextureCoords; varying vec2 vTextureCoords; void main(void) { gl_Position = vec4(aVertexPosition, 1.0); vTextureCoords = aVertexTextureCoords; } </script> <script type="text/javascript"> var gl; var shaderProgram; var vertexBuffer; var indexBuffer; var textureCoordsBuffer; // буфер координат текстуры var texture; // переменная для хранения текстуры function initShaders() { var fragmentShader = getShader(gl.FRAGMENT_SHADER, 'shader-fs'); var vertexShader = getShader(gl.VERTEX_SHADER, '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); shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.vertexTextureAttribute = gl.getAttribLocation(shaderProgram, "aVertexTextureCoords"); gl.enableVertexAttribArray(shaderProgram.vertexTextureAttribute); } function getShader(type,id) { var source = document.getElementById(id).innerHTML; var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert("Ошибка компиляции шейдера: " + gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function initBuffers() { var vertices =[ -0.5, -0.5, 0.5, // v1 -0.5, 0.5, 0.5, // v2 0.5, 0.5, 0.5, // v3 0.5, -0.5, 0.5 // v4 ]; vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); vertexBuffer.itemSize = 3; var indices = [0, 1, 2, 2, 3, 0]; indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); indexBuffer.numberOfItems = indices.length; // Координаты текстуры var textureCoords = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0 ]; // Создание буфера координат текстуры textureCoordsBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordsBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW); textureCoordsBuffer.itemSize=2; // каждая вершина имеет две координаты } function draw() { gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordsBuffer); gl.vertexAttribPointer(shaderProgram.vertexTextureAttribute, textureCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture); gl.drawElements(gl.TRIANGLES, indexBuffer.numberOfItems, gl.UNSIGNED_SHORT,0); } function setupWebGL() { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT); gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); } function setTextures(){ texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); var image = new Image(); image.onload = function() { handleTextureLoaded(image, texture); } image.src = "brickwall.png"; shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler"); gl.uniform1i(shaderProgram.samplerUniform, 0); } function handleTextureLoaded(image, texture) { gl.bindTexture(gl.TEXTURE_2D, texture); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.bindTexture(gl.TEXTURE_2D, null); } window.onload=function(){ var canvas = document.getElementById("canvas3D"); try { gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); } catch(e) {} if (!gl) { alert("Ваш браузер не поддерживает WebGL"); } if(gl){ gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; initShaders(); initBuffers(); setTextures(); (function animloop(){ setupWebGL(); draw(); requestAnimFrame(animloop, canvas); })(); } } window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element) { return window.setTimeout(callback, 1000/60); }; })(); </script> </body> </html>
Данная веб-страничка будет иметь тот же эффект, что и из предыдущего параграфа, только в данном случае сопоставление текстуры с объектом будет идти по координатам. В конце функции инициализации буферов у нас есть такой код:
// Координаты текстуры var textureCoords = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0 ]; // Создание буфера координат текстуры textureCoordsBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordsBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW); textureCoordsBuffer.itemSize=2; // каждая вершина имеет две координаты
Каждая объявленная в массиве textureCoords точка на текстуре соответствует вершине двухмерного объекта:
А создание буфера координат текстуры аналогично созданию буфера вершин.
Функция setTextures()
, которая выполняет загрузку и настройку текстуры осталась та же, что и в прошлом параграфе за тем исключением,
что теперь из нее вынесены методы setupWebGL()
и draw()
в функцию анимации.
После создания буфера координат текстуры нам надо его содержание передать в шейдер. И чтобы сделать это, нам, во-первых, надо создать атрибут
(это делается в функции настройки шейдеров initShaders): shaderProgram.vertexTextureAttribute = gl.getAttribLocation(shaderProgram, "aVertexTextureCoords");
Далее в функции отрисовки draw подобно тому, как мы создаем указатель на атрибут для вершинного буфера, то же самое проделываем с буфером координат текстуры:
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordsBuffer); gl.vertexAttribPointer(shaderProgram.vertexTextureAttribute, textureCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture);
То есть также создаем указатель на атрибут и подключаем его и далее делаем активной текстуру (gl.TEXTURE0) и связываем ее. WebGL поддерживает работу с несколькими текстурами одновременно, а использование gl.TEXTURE0 отсылает нас к первой текстуре.
И в завершении нам надо получить в шейдере атрибут aVertexTextureCoords и как-то его использовать. Для этого в вершинном шейдере заводим переменную для передачи значений текстуры во фрагментный шейдер, а данные она берет как раз из атрибута aVertexTextureCoords:
attribute vec3 aVertexPosition; attribute vec2 aVertexTextureCoords; varying vec2 vTextureCoords; void main(void) { gl_Position = vec4(aVertexPosition, 1.0); vTextureCoords = aVertexTextureCoords; }
И во фрагментном шейдере так же проходим семплером: gl_FragColor = texture2D(uSampler, vTextureCoords);