Ортогональная проекция WebGL 3D

Эта запись продолжение серии постов о WebGL . Начиналось всё  с основ WebGL а после я перевёл профилирование игры WebGL и так далее.

Для примеров 3D я буду использовать символ «F».

Первое, что нужно сделать, это изменить вершины шейдеров для обработки 3D. Вот старый пример шейдеров.



<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
  </script>


А вот новый



<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;

uniform mat4 u_matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = u_matrix * a_position;
}
  </script>


Стало еще проще!

Затем мы должны обеспечить данными 3D.



...

  gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);

  ...

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
            0,   0,  0,
           30,   0,  0,
            0, 150,  0,
            0, 150,  0,
           30,   0,  0,
           30, 150,  0,

          // top rung
           30,   0,  0,
          100,   0,  0,
           30,  30,  0,
           30,  30,  0,
          100,   0,  0,
          100,  30,  0,

          // middle rung
           30,  60,  0,
           67,  60,  0,
           30,  90,  0,
           30,  90,  0,
           67,  60,  0,
           67,  90,  0]),
      gl.STATIC_DRAW);
}


Далее нам нужно заменить все функции матрицы с 2D на 3D

Вот 2D до версии с makeTranslation, makeRotation и makeScale



function makeTranslation(tx, ty) {
  return [
    1, 0, 0,
    0, 1, 0,
    tx, ty, 1
  ];
}

function makeRotation(angleInRadians) {
  var c = Math.cos(angleInRadians);
  var s = Math.sin(angleInRadians);
  return [
    c,-s, 0,
    s, c, 0,
    0, 0, 1
  ];
}

function makeScale(sx, sy) {
  return [
    sx, 0, 0,
    0, sy, 0,
    0, 0, 1
  ];
}


А вот обновление до версии 3D.



function makeTranslation(tx, ty, tz) {
  return [
     1,  0,  0,  0,
     0,  1,  0,  0,
     0,  0,  1,  0,
     tx, ty, tz, 1
  ];
}

function makeXRotation(angleInRadians) {
  var c = Math.cos(angleInRadians);
  var s = Math.sin(angleInRadians);

  return [
    1, 0, 0, 0,
    0, c, s, 0,
    0, -s, c, 0,
    0, 0, 0, 1
  ];
};

function makeYRotation(angleInRadians) {
  var c = Math.cos(angleInRadians);
  var s = Math.sin(angleInRadians);

  return [
    c, 0, -s, 0,
    0, 1, 0, 0,
    s, 0, c, 0,
    0, 0, 0, 1
  ];
};

function makeZRotation(angleInRadians) {
  var c = Math.cos(angleInRadians);
  var s = Math.sin(angleInRadians);
  return [
     c, s, 0, 0,
    -s, c, 0, 0,
     0, 0, 1, 0,
     0, 0, 0, 1,
  ];
}

function makeScale(sx, sy, sz) {
  return [
    sx, 0,  0,  0,
    0, sy,  0,  0,
    0,  0, sz,  0,
    0,  0,  0,  1,
  ];
}


Заметьте, что мы сейчас имеем 3 функций вращений. Нам нужен только одина в 2D, как бы вращается, только вокруг оси Z. Теперь же, чтобы сделать 3D нам нужна возможность вращаться вокруг оси Х и также оси Y. Смотрите на них, они все очень похожи. Смотрите их упрощения, как и прежде:

Z rotation

newX = x * c + y * s;
newY = x * -s + y * c;

Y rotation

newX = x * c + z * s;
newZ = x * -s + z * c;

X rotation

newY = y * c + z * s;
newZ = y * -s + z * c;

, которые дает вам эти повороты.

Нам также необходимо обновить функцию проекции. Вот старая функция



function make2DProjection(width, height) {
  // Note: This matrix flips the Y axis so 0 is at the top.
  return [
    2 / width, 0, 0,
    0, -2 / height, 0,
    -1, 1, 1
  ];
}


которая преобразуются из пикселей обрезая пространство. Для начала попробуем расширить её в 3D.



function make2DProjection(width, height, depth) {
  // Note: This matrix flips the Y axis so 0 is at the top.
  return [
     2 / width, 0, 0, 0,
     0, -2 / height, 0, 0,
     0, 0, 2 / depth, 0,
    -1, 1, 0, 1,
  ];
}


Переход от пикселей clipspace х и у, для z мы должны сделать то же самое. В этом случае мы делаем Z единиц пространства пикселей. Некоторое значение похожи на width (ширина) и depth (глубина), чтобы наши пространства были от 0 до ширины пикселей в ширину, от 0 до высоты пикселей в высоту, но глубина будет be -depth / 2 to +depth / 2. (углубленное / 2 + глубина / 2 .)

Наконец мы должны обновить код, который вычисляет матрицу.



// Compute the matrices
  var projectionMatrix =
      make2DProjection(canvas.width, canvas.height, canvas.width);
  var translationMatrix =
      makeTranslation(translation[0], translation[1], translation[2]);
  var rotationXMatrix = makeXRotation(rotation[0]);
  var rotationYMatrix = makeYRotation(rotation[1]);
  var rotationZMatrix = makeZRotation(rotation[2]);
  var scaleMatrix = makeScale(scale[0], scale[1], scale[2]);

  // Multiply the matrices.
  var matrix = matrixMultiply(scaleMatrix, rotationZMatrix);
  matrix = matrixMultiply(matrix, rotationYMatrix);
  matrix = matrixMultiply(matrix, rotationXMatrix);
  matrix = matrixMultiply(matrix, translationMatrix);
  matrix = matrixMultiply(matrix, projectionMatrix);

  // Set the matrix.
  gl.uniformMatrix4fv(matrixLocation, false, matrix);


И вот этот образец.

Первая проблема состоит в том, что из за нашей геометрии плоскости, F плохо видно в 3D. Чтобы это исправить, давайте расширим геометрию в 3D. F состоит из 3-х прямоугольников, 2-х треугольника в каждой. Чтобы сделать 3D потребуется в общей сложности 16 прямоугольников. Это довольно много, чтобы перечислять. 16 прямоугольников * 2 треугольников в прямоугольник * 3 вершины в треугольнике 96 вершин. Если вы хотите увидеть все, смотрите код на образце.

Подключаем больше вершин, вот так:



// Draw the geometry.
    gl.drawArrays(gl.TRIANGLES, 0, 16 * 6);


И вот эта версия

При перемещении ползунка довольно трудно сказать, что это 3D. Давайте попробуем покрасить стороны прямоугольника в разный цвет. Для этого мы добавим еще один атрибут нашей вершины шейдеров, а также передать его из вершины шейдеров на фрагменты шейдеров.

Вот новая вершина шейдеров



<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 a_color;

uniform mat4 u_matrix;

varying vec4 v_color;

void main() {
  // Multiply the position by the matrix.
  gl_Position = u_matrix * a_position;

  // Pass the color to the fragment shader.
  v_color = a_color;
}
  </script>


И мы должны использовать эти цвета в фрагменте шейдеров



<script id="3d-vertex-shader" type="x-shader/x-fragment">
precision mediump float;

// Passed in from the vertex shader.
varying vec4 v_color;

void main() {
   gl_FragColor = v_color;
}
  </script>


Нам нужно для поиска места на поставку цветов, а затем установить другой буфер и атрибуты, чтобы придать ему цвет.



...
  var colorLocation = gl.getAttribLocation(program, "a_color");

  ...
  // Create a buffer for colors.
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.enableVertexAttribArray(colorLocation);

  // We'll supply RGB as bytes.
  gl.vertexAttribPointer(colorLocation, 3, gl.UNSIGNED_BYTE, true, 0, 0);

  // Set Colors.
  setColors(gl);

  ...
// Fill the buffer with colors for the 'F'.

function setColors(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Uint8Array([
          // left column front
        200,  70, 120,
        200,  70, 120,
        200,  70, 120,
        200,  70, 120,
        200,  70, 120,
        200,  70, 120,

          // top rung front
        200,  70, 120,
        200,  70, 120,
        ...
        ...
      gl.STATIC_DRAW);
}


Теперь мы получаем это.

Ой! оказывается, все различные части, «F» в 3D, передняя, ​​задняя, ​​боковые и т.д. втянулись в порядке их появления в нашей геометрии. Это не дает нам желаемого результата, так как иногда из них в спину втянуться после того, как те, в передней части.

В WebGL есть концепция для треугольников передней облицовки со спины. Фронтальный треугольник имеет вершину и идут по часовой стрелке. Спиной треугольник имеет вершины и идёт в направлении против часовой стрелки

webgl

WebGL имеет возможность быть лицом по ходу движения или спиной. Мы можем превратить эту функцию с



gl.enable(gl.CULL_FACE);


которую мы делали только один раз, в самом начале нашей программы. С этой функцией WebGL включено  по умолчанию «culling» спина треугольников. «culling» в данном случае является причудливым словом «not drawing».

Обратите внимание, поскольку WebGL в conserned, или считается не треугольник происходит по часовой стрелке или против часовой стрелки в зависимости от вершин этого треугольника в clipspace. Другими словами, WebGL выясняет, треугольник спереди или сзади. После прикладной математики с вершинами из вершины шейдеров. Это означает, например, часовой треугольник, который измеряется в X на -1 становится против часовой стрелки треугольника или часовой треугольник повернут на 180 градусов вокруг X или ось Y становится против часовой стрелки треугольника. Потому что мы отключены от CULL_FACE мы видим, как по часовой стрелке (спереди) и против часовой стрелки (сзади) треугольников. Теперь, когда мы превратили его в любое время фронтальный треугольник переворачивается вокруг либо из-за масштабирования и вращения или по любой причине, WebGL это не будет делать. Это хорошая вещь, как правило, треугольники перед вами с передней облицовкой.

CULL_FACE включил то, что мы в результате получаем:

Эй! Куда делись все треугольники? Оказывается, многие из них сталкиваются неправильно. Поверните его и вы увидите, они появляются, когда вы смотрите в другую сторону. К счастью, это легко исправить. Мы просто смотрим на те какие из них являются отсталыми и обмен 2-х их вершин. Например, если один обратный треугольник



           1 ,    2 ,    3 ,             
           40 ,   50 ,   60 ,            
           700 ,  800 ,  900 ,


Просто переверните последние 2 вершины, чтобы сдвинуть его вперед.



           1 ,    2 ,    3 ,
           700 ,  800 ,  900 ,             
           40 ,   50 ,   60 ,


Проходя и фиксируя назад все треугольники, подводит нас к этому:

Это ближе, но есть еще одна проблема. Даже с учетом всех треугольников, которые в правильном направлении и спиной которых являются взятые у нас еще есть места, где треугольники, которые должны быть в спину втягиваются в больше чем треугольники, которые должны быть впереди.

Введите буфер глубины.

Глубина буфера, которую иногда называют Z-буфер, представляет собой прямоугольник из глубины пикселей, одна глубина пикселей для каждого цвета пикселя используется для изображений. Как WebGL рисует каждый пиксель цвета он также может обратить на глубине пикселя. Он делает это, основываясь на значениях мы возвращаемся из вершинных шейдеров для Z. Как мы должны были преобразовать в клип пространство для X и Y, так Z в пространстве или клипа (от -1 до +1). Это значение затем преобразуется в значение глубины пространства (0 +1). WebGL рисует цвет пикселя будет проверять соответствие глубины пикселя. Если глубина значение пикселя речь идет о том как обратить больше, чем значение соответствующего пикселя глубина то WebGL не сделать новый цвет пикселя. В противном случае это привлекает как новый цвет пиксела с цветом от вашего фрагмент шейдеров и это привлекает глубиной пикселей с новым значением глубины. Это означает, что пиксели, которые находятся за другими пикселями не будут нарисованы.

Мы можем включить эту функцию почти так же просто:



gl.enable(gl.DEPTH_TEST);


Нам также необходимо очистить задний буфер глубины до 1,0, прежде чем начать рисовать.



// Draw the scene.
  function drawScene() {
    // Clear the canvas AND the depth buffer.
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    ...


А теперь мы получаем

которое 3D!


Для тех из вас кто хорошо ориентируется вы могли бы заметить, мы определили 2-ва атрибута



attribute vec4 a_position;
attribute vec4 a_color;


оба из которых являются «vec4″, но когда мы говорим о WebGL, как взять данные из нашего буфера мы использовали



gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribPointer(colorLocation, 3, gl.UNSIGNED_BYTE, true, 0, 0);


Это ‘3 ‘в каждой из этих говорит только вытащить 3 значения в атрибуте. Это работает, потому что в вершинах шейдеров WebGL обеспечивает по умолчанию для тех, кого не предоставляет. По умолчанию: 0, 0, 0, 1, где х = 0, у = 0, Z = 0 и w = 1. Мы проходили по х и у, и нам необходимо 1, но так как по умолчанию 0 мы должны явно указать 1. Для 3D, хотя, даже если мы не поставляем «W» он по умолчанию 1, что нам нужно для работы матрицы.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML -теги и атрибуты: <a href= http://pixelcom.crimea.ua/"" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>