Применение материалов в WebGL по сути не привносит ничего сложного. Мы просто определяем для каждого типа отражения свой материал и передаем его в вершинный шейдер. Код веб-странички будет следующим:
<!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 type="text/javascript" src="gl-matrix-min.js"></script> <script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; attribute vec3 aVertexNormal; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat3 uNMatrix; uniform vec3 uLightPosition; uniform vec3 uAmbientLightColor; uniform vec3 uDiffuseLightColor; uniform vec3 uSpecularLightColor; uniform vec3 uAmbientMaterialColor; uniform vec3 uDiffuseMaterialColor; uniform vec3 uSpecularMaterialColor; varying vec3 vLightWeighting; const float shininess = 16.0; void main() { // установка позиции наблюдателя сцены vec4 vertexPositionEye4 = uMVMatrix * vec4(aVertexPosition, 1.0); vec3 vertexPositionEye3 = vertexPositionEye4.xyz / vertexPositionEye4.w; // получаем вектор направления света vec3 lightDirection = normalize(uLightPosition - vertexPositionEye3); // получаем нормаль vec3 normal = normalize(uNMatrix * aVertexNormal); // получаем скалярное произведение векторов нормали и направления света float diffuseLightDot = max(dot(normal, lightDirection), 0.0); // получаем вектор отраженного луча и нормализуем его vec3 reflectionVector = normalize(reflect(-lightDirection, normal)); // установка вектора камеры vec3 viewVectorEye = -normalize(vertexPositionEye3); float specularLightDot = max(dot(reflectionVector, viewVectorEye), 0.0); float specularLightParam = pow(specularLightDot, shininess); // отраженный свет равен сумме фонового, диффузного и зеркального отражений света vLightWeighting = uAmbientMaterialColor * uAmbientLightColor + uDiffuseMaterialColor * uDiffuseLightColor * diffuseLightDot + uSpecularMaterialColor * uSpecularLightColor * specularLightParam; gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); } </script> <script id="shader-fs" type="x-shader/x-fragment"> precision mediump float; varying vec3 vLightWeighting; void main() { gl_FragColor = vec4(vLightWeighting.rgb, 1); // gl_FragColor = vec4(0.0, 0.5, 0.0, 1); } </script> <script type="text/javascript"> var gl; var shaderProgram; var vertexBuffer; var indexBuffer; var vertexNormalBuffer; // буфер нормалей вершин var texture; // переменная для хранения текстуры var yAngle = 2.0;//угол вращения в радианах вокруг оси Y var zTranslation = -2.0; // смещение по оси Z var xAngle = 0.1; // угол вращения в радианах вокруг оси Х var mvMatrix = mat4.create(); // матрица вида модели var pMatrix = mat4.create(); // матрица проекции var nMatrix = mat3.create(); // матрица нормалей // установка шейдеров 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.vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal"); gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute); // настройка параметров uniform матриц для передачи в шейдер shaderProgram.MVMatrix = gl.getUniformLocation(shaderProgram, "uMVMatrix"); shaderProgram.ProjMatrix = gl.getUniformLocation(shaderProgram, "uPMatrix"); shaderProgram.NormalMatrix = gl.getUniformLocation(shaderProgram, "uNMatrix"); // настройка переменных uniform освещения для передачи в шейдер // позиция источника света shaderProgram.uniformLightPosition = gl.getUniformLocation(shaderProgram, "uLightPosition"); // фоновое отражение света shaderProgram.uniformAmbientLightColor = gl.getUniformLocation(shaderProgram, "uAmbientLightColor"); // диффузное отражение света shaderProgram.uniformDiffuseLightColor = gl.getUniformLocation(shaderProgram, "uDiffuseLightColor"); // зеркальное отражение света shaderProgram.uniformSpecularLightColor = gl.getUniformLocation(shaderProgram, "uSpecularLightColor"); shaderProgram.uniformAmbientMaterialColor = gl.getUniformLocation(shaderProgram, "uAmbientMaterialColor"); shaderProgram.uniformDiffuseMaterialColor = gl.getUniformLocation(shaderProgram, "uDiffuseMaterialColor"); shaderProgram.uniformSpecularMaterialColor = gl.getUniformLocation(shaderProgram, "uSpecularMaterialColor"); } // настройка цветов освещения function setupLights() { gl.uniform3fv(shaderProgram.uniformLightPosition, [0.0, 10.0, 5.0]); gl.uniform3fv(shaderProgram.uniformAmbientLightColor, [0.1, 0.1, 0.1]); gl.uniform3fv(shaderProgram.uniformDiffuseLightColor, [0.7, 0.7, 0.7]); gl.uniform3fv(shaderProgram.uniformSpecularLightColor, [1.0, 1.0, 1.0]); } // установка материалов function setupMaterials() { gl.uniform3fv(shaderProgram.uniformAmbientMaterialColor, [0.0, 1.0, 0.0]); gl.uniform3fv(shaderProgram.uniformDiffuseMaterialColor, [0.7, 0.7, 0.7]); gl.uniform3fv(shaderProgram.uniformSpecularMaterialColor, [1.0, 1.0, 1.0]); } function setMatrixUniforms(){ gl.uniformMatrix4fv(shaderProgram.ProjMatrix,false, pMatrix); gl.uniformMatrix4fv(shaderProgram.MVMatrix, false, mvMatrix); gl.uniformMatrix3fv(shaderProgram.NormalMatrix, false, nMatrix); } // Функция создания шейдера 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, //v0 -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 -0.5, 0.5, -0.5, //v5 0.5, 0.5, -0.5, //v6 0.5, -0.5, -0.5, //v7 // левая боковая часть -0.5, -0.5, 0.5, //v8 -0.5, 0.5, 0.5, //v9 -0.5, 0.5, -0.5, //v10 -0.5, -0.5, -0.5, //v11 // правая боковая часть 0.5, -0.5, 0.5, //v12 0.5, 0.5, 0.5, //v13 0.5, 0.5, -0.5, //v14 0.5, -0.5, -0.5 //v15 ]; var indices = [ // лицевая часть 0, 1, 2, 2, 3, 0, // задняя часть 4, 5, 6, 6, 7, 4, //левая боковая часть 8, 9, 10, 10, 11, 8, // правая боковая часть 12, 13, 14, 14, 15, 12 ]; vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); vertexBuffer.itemSize = 3; 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 normals = [ // Лицевая сторона 0.0, 0.0, 1.0, //v0 0.0, 0.0, 1.0, //v1 0.0, 0.0, 1.0, //v2 0.0, 0.0, 1.0, //v3 // Задняя сторона 0.0, 0.0, -1.0, //v4 0.0, 0.0, -1.0, //v5 0.0, 0.0, -1.0, //v6 0.0, 0.0, -1.0, //v7 // Левая боковая сторона -1.0, 0.0, 0.0, //v8 -1.0, 0.0, 0.0, //v9 -1.0, 0.0, 0.0, //v10 -1.0, 0.0, 0.0, //v11 // Правая боковая сторона 1.0, 0.0, 0.0, //v12 1.0, 0.0, 0.0, //v13 1.0, 0.0, 0.0, //v14 1.0, 0.0, 0.0, //v15 ]; // Создаем буфер нормалей куба vertexNormalBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW); vertexNormalBuffer.itemSize = 3; } function draw() { 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); gl.enable(gl.DEPTH_TEST); 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); mat4.perspective(pMatrix, 1.04, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0); mat4.identity(mvMatrix); mat4.translate(mvMatrix,mvMatrix,[0, 0, zTranslation]); mat4.rotateX(mvMatrix,mvMatrix, xAngle); mat4.rotateY(mvMatrix,mvMatrix, yAngle); mat3.normalFromMat4(nMatrix, mvMatrix); } 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){ document.addEventListener('keydown', handleKeyDown, false); gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; initShaders(); initBuffers(); setupMaterials(); setupLights(); (function animloop(){ setupWebGL(); setMatrixUniforms(); draw(); requestAnimFrame(animloop, canvas); })(); } } function handleKeyDown(e){ switch(e.keyCode) { case 39: // стрелка вправо yAngle+=0.1; break; case 37: // стрелка влево yAngle-=0.1; break; case 40: // стрелка вниз xAngle+=0.1; break; case 38: // стрелка вверх xAngle-=0.1; break; case 83: // клавиша S zTranslation+=0.1; break; case 87: // клавиша W zTranslation-=0.1; break; } } 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>
В плане кода настройка, передача в шейдер и использование материалов ничем не отличается от использования цветов освещения. В итоге наш куб будет выглядеть следующим образом:
Во-первых, здесь я убрал весь код, связанный с текстурами. В принципе он нам не особо нужен. Потому что цвет фигуры будут задавать цвета материалов.
Затем, также как и с цветовыми настройками освещения, мы задаем цвет материалов:
// установка материалов function setupMaterials() { gl.uniform3fv(shaderProgram.uniformAmbientMaterialColor, [0.0, 1.0, 0.0]); gl.uniform3fv(shaderProgram.uniformDiffuseMaterialColor, [0.7, 0.7, 0.7]); gl.uniform3fv(shaderProgram.uniformSpecularMaterialColor, [1.0, 1.0, 1.0]); }
И в вершинном шейдере мы можем их получить и использовать при настройке освещения по модели отражения Фонга:
vLightWeighting = uAmbientMaterialColor * uAmbientLightColor + uDiffuseMaterialColor * uDiffuseLightColor * diffuseLightDot + uSpecularMaterialColor * uSpecularLightColor * specularLightParam;