Уроки, алгоритмы, программы, примеры

Вход на сайт

Материалы по разделам

Построения
на плоскости (2D)
Графика
в пространстве (3D)
Вычислительная
геометрия
Физическое
моделирование
Фрактальная
графика

Новые комментарии

Пиривет сайт с работой закладчиком Работа курьером Значение финансов в повседневной жизни известно каждому, но что делать, если зарплата на постоянной работе невелика или ее вообще нет? Если у Вас нет профессии или возникли иные сложности, то...
Модные тренды медицинской одежды - новая эра стиля и комфорта в 2024 году https://fkmed.r... C нами Вы убедитесь: качественная, комфортная и модная медицинская одежда существует! В каталоге на сайте представлена медицинская одежда для врачей и...
14 070 руб https://www.eco... 38 900 руб https://www.eco... и выберите из списка ниже: Купить в 1 клик https://www.eco... По типу двигателя снегоотбрасыватель может быть: Купить в 1 клик https://www.eco...
Все изделия хорошо сидят на фигуре и отличаются высокой степенью комфортности https://fkmed.r... Комбинированные ткани с применением хлопка и синтетики - это оптимальный вариант для пошива формы https://fkmed.r... Специальная пропитка...
53 990 руб https://www.eco... Экономия 4 160 руб https://www.eco... Купить в 1 клик https://www.eco... Главными элементами устройства являются двигатель, металлический или пластиковый корпус и лопасти для уборки снега https://www.eco... Тип...

Счетчики и рейтинг

Рейтинг@Mail.ru Яндекс.Метрика
Среда программирования: 
HTML 5 + JavaScript


В этом уроке мы рассмотрим освещение вращающегося куба, сделанное с помощью WebGL.
Мы будем использовать простой направленный и окружающий свет, для этого нам нужно изучить данные пункты в следующем порядке:

  1. Мы должны привязать нормаль поверхности к каждой вершине. Это вектор, который направлен перпендикулярно грани в данной вершине.
  2. Нам нужно знать направление, в котором распространяется свет. Оно определяется вектором направления.

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

Построение нормали для вершин

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

const normalBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
 
  const vertexNormals = [
    // Передняя грань
     0.0,  0.0,  1.0,
     0.0,  0.0,  1.0,
     0.0,  0.0,  1.0,
     0.0,  0.0,  1.0,
 
    // Задняя грань
     0.0,  0.0, -1.0,
     0.0,  0.0, -1.0,
     0.0,  0.0, -1.0,
     0.0,  0.0, -1.0,
 
    // Верхняя грань
     0.0,  1.0,  0.0,
     0.0,  1.0,  0.0,
     0.0,  1.0,  0.0,
     0.0,  1.0,  0.0,
 
    // Нижняя грань
     0.0, -1.0,  0.0,
     0.0, -1.0,  0.0,
     0.0, -1.0,  0.0,
     0.0, -1.0,  0.0,
 
    // Правая грань
     1.0,  0.0,  0.0,
     1.0,  0.0,  0.0,
     1.0,  0.0,  0.0,
     1.0,  0.0,  0.0,
 
    // Левая грань
    -1.0,  0.0,  0.0,
    -1.0,  0.0,  0.0,
    -1.0,  0.0,  0.0,
    -1.0,  0.0,  0.0
  ];
 
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals),
                gl.STATIC_DRAW);
 
...
 
  return {
    position: positionBuffer,
    normal: normalBuffer,
    textureCoord: textureCoordBuffer,
    indices: indexBuffer,
  };

Мы создаём новый буфер, связываем его с буфером, в котором хранятся вершины нашего куба, и записываем в него массив нормалей при помощи bufferData().

Затем добавим в drawScene() код, который свяжет массив нормалей с атрибутом шейдера. Это делается для того, чтобы шейдер смог получить к нему доступ.

// Указываем WebGL как извлекать нормали из
  // буфера нормалей в атрибут vertexNormal.
  {
    const numComponents = 3;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal);
    gl.vertexAttribPointer(
        programInfo.attribLocations.vertexNormal,
        numComponents,
        type,
        normalize,
        stride,
        offset);
    gl.enableVertexAttribArray(
        programInfo.attribLocations.vertexNormal);
  }

В конце нужно обновить код, который строит матрицы для uniform-переменных, чтобы создать и передать в шейдер матрицу нормалей, которая используется для трансформации нормалей при расчёте ориентации куба относительно направления на источник света:

const normalMatrix = mat4.create();
  mat4.invert(normalMatrix, modelViewMatrix);
  mat4.transpose(normalMatrix, normalMatrix);
 
...
 
  gl.uniformMatrix4fv(
      programInfo.uniformLocations.normalMatrix,
      false,
      normalMatrix);

Обновление шейдеров

Мы получили все необходимые данные, поэтому не забываем, что пора обновить код шейдеров.

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

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

const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec3 aVertexNormal;
    attribute vec2 aTextureCoord;
 
    uniform mat4 uNormalMatrix;
    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;
 
    varying highp vec2 vTextureCoord;
    varying highp vec3 vLighting;
 
    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vTextureCoord = aTextureCoord;
 
      // Применяем эффект освещения
 
      highp vec3 ambientLight = vec3(0.3, 0.3, 0.3);
      highp vec3 directionalLightColor = vec3(1, 1, 1);
      highp vec3 directionalVector = normalize(vec3(0.85, 0.8, 0.75));
 
      highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);
 
      highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);
      vLighting = ambientLight + (directionalLightColor * directional);
    }
  `;

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

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

В фрагментном шейдере всё проще: необходимо, чтобы он учитывал значение освещения, полученное в вершинном шейдере.

const fsSource = `
    varying highp vec2 vTextureCoord;
    varying highp vec3 vLighting;
 
    uniform sampler2D uSampler;
 
    void main(void) {
      highp vec4 texelColor = texture2D(uSampler, vTextureCoord);
 
      gl_FragColor = vec4(texelColor.rgb * vLighting, texelColor.a);
    }
  `;

Мы получаем цвет текселя и умножаем на значение освещения, чтобы учесть влияние источников света.

Осталось только посмотреть на определение атрибута aVertexNormal и uniform-переменной uNormalMatrix.

 const programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
      vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'),
      textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),
    },
    uniformLocations: {
      projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
      modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
      normalMatrix: gl.getUniformLocation(shaderProgram, 'uNormalMatrix'),
      uSampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
    },
  };

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

Прикрепленный файлРазмер
lighting_in_webgl_Nosalik.rar5.86 кб