четверг, 19 февраля 2015 г.

Пример рисования элементарных графических примитивов при помощи WebGL

Небольшая демонстрация рисования всех элементарных графических примитивов, которые можно создать средствами WebGL. Более сложные объекты создаются из комбинации простых.


В приведённом примере код шейдеров читается из внешнего файла, в связи с чем страницу следует открывать при помощи локального web-сервера, вместо того, чтобы открывать её непосредственно из проводника. Подробней на эту тему сообщалось здесь.

Код html-странички:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" lang="ru"/>
    <title>WebGL sandbox</title>
  </head>
  <body onload="main()">
    <canvas id="webgl" width="400" height="400">
      Текущий браузер не поддерживает элемент 'canvas'.
    </canvas>
    <script src="./scripts/cuon-utils.js"></script>
    <script src="./scripts/webgl-utils.js"></script>
    <script src="./scripts/webgl-debug.js"></script>
    <script src="./scripts/index.js"></script>
  </body>
</html>

Вершинный шейдер:

// vertex.shader
precision mediump float; // allowed also: highp, mediump, lowp
attribute vec4 a_Position;
attribute float a_PointSize;

void main(){
  gl_Position = a_Position;
  gl_PointSize = a_PointSize;
}

Фрагментный шейдер:

// fragment.shader
precision mediump float; // allowed also: highp, mediump, lowp
uniform vec4 u_FragColor;

void main(){
  gl_FragColor = u_FragColor;
}

Код JavaScript:

// Andrey Bushman, 2015.
// Draw the simple shapes through the WebGL buffer using.
var VSHADER_SOURCE = null;
var FSHADER_SOURCE = null;
//*****************************************************

// entry point
function main(){
  var canvas = document.getElementById('webgl');
  if(!canvas){
    console.log('Element with the "webgl" id wasn\'t found.');
    return;
  }
  var gl = getWebGLContext(canvas);
  if(!gl){
    console.log('WebGL context wasn\'t gotten.');
    return;
  }
  loadShaderFromFile(gl, './shaders/vertex.shader', gl.VERTEX_SHADER);
  loadShaderFromFile(gl, './shaders/fragment.shader', gl.FRAGMENT_SHADER);
}
//*****************************************************

function loadShaderFromFile(gl, fileName, shaderType){
  var request = new XMLHttpRequest();
  request.onreadystatechange = function(){
    if(request.readyState == 4 && request.status != 404){      
      onShaderLoad(gl, request.responseText, shaderType);
    }
    if(VSHADER_SOURCE && FSHADER_SOURCE){
      start(gl);
    }
  }
  request.open('GET', fileName, true);
  request.send();
}
//*****************************************************

// initializing the VSHADER_SOURCE and FSHADER_SOURCE variables
function onShaderLoad(gl, src, shaderType){
  if(shaderType == gl.VERTEX_SHADER){
    VSHADER_SOURCE = src;
  }
  else if(shaderType == gl.FRAGMENT_SHADER){
    FSHADER_SOURCE = src;
  }
  else{
    console.log('Unexpected shaderType.');
    return;
  }
}
//*****************************************************

// This step will be made after the shapes code sources will be gotten.
function start(gl){
  
  if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
    console.log('Failed shader initializing.');
    return;
  }
  
  // Uncomment necessary variant of the shape. 
  // Must to be uncommented a single variant only.
  var shape = gl.POINTS;
  // var shape = gl.LINES;
  // var shape = gl.LINE_STRIP;
  // var shape = gl.LINE_LOOP;
  // var shape = gl.TRIANGLES;
  // var shape = gl.TRIANGLE_STRIP;
  // var shape = gl.TRIANGLE_FAN;
  
  var pointsCount = initVertexBuffer(gl, shape);
  if(pointsCount < 0){
    console.log('Failed initialization of vertex buffer.');
    return;
  }
  
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.drawArrays(shape, 0, pointsCount);
}
//*****************************************************

// initializing of the global variables and shape buffer.
function initVertexBuffer(gl, shape){
  var points = null;
  if(shape != gl.TRIANGLE_FAN){
    points = new Float32Array([
    -0.75,  0.00, // P0
    -0.50,  0.50, // P1
    -0.25,  0.00, // P2
     0.00,  0.50, // P3
     0.25,  0.00, // P4
     0.50,  0.50  // P5
     ]);
  }
  else{
    points = new Float32Array([
     0.00, -0.50, // P0
    -0.50,  0.00, // P1
    -0.35,  0.25, // P2
     0.00,  0.40, // P3
     0.35,  0.25, // P4
     0.50,  0.00  // P5
     ]);
  }
   
  var _buffer = gl.createBuffer();
  if(!_buffer){
    console.log('Failed to create the buffer object.');
    return -1;
  }
  var recordLenght = 2; // x and y only.
  gl.bindBuffer(gl.ARRAY_BUFFER, _buffer);
  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
  
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttribPointer(a_Position, recordLenght, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Position);
  
  // It is used only when you draw the points
  if (shape == gl.POINTS){
    var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
    if(!a_PointSize){
      console.log('Can\'t to get the "a_PointSize" variable.');
      return -1;
    }  
    gl.vertexAttrib1f(a_PointSize, 10.0);
  }
  
  var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
  if(!u_FragColor){
    console.log('Can\'t to get the "u_FragColor" variable.');
    return -1;
  }
  
  gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);  
  return points.length / recordLenght;
}

В коде JavaScript обратите внимание на фрагмент кода:

// Uncomment necessary variant of the shape. 
// Must to be uncommented a single variant only.
var shape = gl.POINTS;
// var shape = gl.LINES;
// var shape = gl.LINE_STRIP;
// var shape = gl.LINE_LOOP;
// var shape = gl.TRIANGLES;
// var shape = gl.TRIANGLE_STRIP;
// var shape = gl.TRIANGLE_FAN;

Комментируя ту или иную строку обозначенного фрагмента кода мы будем рисовать разные типы графических объектов. Нумерация точек обозначена в комментариях кода. В коде используется система координат WebGL.

POINTS:



LINES:


LINE_STRIP:


LINE_LOOP:


TRIANGLES:


TRIANGLE_STRIP:


TRIANGLE_FAN:


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

Через TRIANGLE_FAN:

Массив точек указываем такой:

points = new Float32Array([
  -0.50,  0.00, // P0
  -0.50,  0.50, // P1
   0.00,  0.50, // P2
   0.00,  0.00, // P3
   ]);

Через TRIANGLE_STRIP:

Порядок следования точек несколько изменится:

points = new Float32Array([
  -0.50,  0.50, // P0
  -0.50,  0.00, // P1
   0.00,  0.50, // P2
   0.00,  0.00, // P3
   ]);

 В обоих случаях результат работы кода будет выглядеть следующим образом:


Сгенерированное при помощи WebGL изображение можно сохранить как png-файл, выбрав для этого соответствующий пункт контекстного меню элемента canvas (доступно во всех браузерах, кроме IE).

Комментариев нет:

Отправить комментарий