Создание освещенного объекта по модели Фонга. Шейдеры

Последнее обновление: 1.11.2015

Основная работа по созданию освещения объекта выполняется в шейдерах в GPU, в стандартном коде javascript от нас требуется только установить ряд объектов и передать их в шейдеры. Это следующие объекты:

  • Матрицу нормалей

  • Нормали вершины

  • Направления света и световые точки

  • Цвета освещения

Сразу перейдем к шейдерам, чтобы рассмотреть принцип создания освещенной трехмерной сцены. А затем отдельно рассмотрим код javascript.

Итак, оба шейдера буду выглядеть так:

<script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 aVertexPosition;
  attribute vec3 aVertexNormal;
  attribute vec2 aVertexTextureCoords;

  uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;
  uniform mat3 uNMatrix;
  
  uniform vec3 uLightPosition;
  uniform vec3 uAmbientLightColor;
  uniform vec3 uDiffuseLightColor;
  uniform vec3 uSpecularLightColor;
  
  varying vec2 vTextureCoords;
  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 = uAmbientLightColor + uDiffuseLightColor * diffuseLightDot +
                      uSpecularLightColor * specularLightParam;
    
     // Finally transform the geometry
     gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
     vTextureCoords = aVertexTextureCoords;  
  }                
</script>

<script id="shader-fs" type="x-shader/x-fragment">
  precision mediump float;
  
  varying vec2 vTextureCoords;
  varying vec3 vLightWeighting;
  uniform sampler2D uSampler;
  
  void main() {   
    vec4 texelColor = texture2D(uSampler, vTextureCoords);
    gl_FragColor = vec4(vLightWeighting.rgb * texelColor.rgb, texelColor.a);
  } 
</script>

Все переменные типа attribute и uniform мы затем передадим из основной программы в шейдер. Эти переменные и задают параметры освещения.

В модели отражения Фонга отраженный свет представлен как сумма фонового, диффузного и зеркального отражений. В шейдерах за хранение параметров отраженного света отвечает переменная varying vec3 vLightWeighting. В вершинном шейдере мы находим эту сумму и затем передаем переменную во фрагментный шейдер для окончательной установки цвета фрагмента.

Все цвета для нахождения параметров отраженного света передаются через переменные uniform vec3 uAmbientLightColor, uniform vec3 uDiffuseLightColor и uniform vec3 uSpecularLightColor.

Также передаем в шейдер положение источника света через переменную uniform vec3 uLightPosition.

Физический смысл модели Фонга и разбор кода шейдеров

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

	// установка позиции наблюдателя сцены
    vec4 vertexPositionEye4 = uMVMatrix * vec4(aVertexPosition, 1.0);
    vec3 vertexPositionEye3 = vertexPositionEye4.xyz / vertexPositionEye4.w;
  
    // получаем вектор направления света
    vec3 lightDirection = normalize(uLightPosition - vertexPositionEye3);
    
    // получаем нормаль
    vec3 normal = normalize(uNMatrix * aVertexNormal);

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

Получение отражений света

Фоновое отражение света (ambient light) представляет собой отражение при естественном освещении. Его можно выразить формулой I = Ka*Ia, где Ka - цветовые параметры материала в виде значений RGB, а Ia - это цвет фонового света также в виде значений RGB.

При диффузном отражении света (diffuse light) лучи отражаются под несколькими углами, а не под одним, как при зеркальном отражении. Диффузное отражение характерно прежде всего для неровных шершавых поверхностей.

Диффузное отражение света высчитывается по формуле I = Kd *Id *max(cos θ, 0). Здесь кроме диффузного материала Kd и цвета освещения Id присутствует дополнительный параметр. Этот параметр учитывает направление направленного на поверхность луча. А угол θ как раз представляет собой угол между нормалью поверхности и вектором направления луча света. Наличие функции максимума, которая выбирает максимальное число из косинуса угла и нуля позволяет отсечь отрицательные значения и свести их к нулю.

Схематично диффузное отражение можно показать так:

Диффузное отражение света

Но что такое cos θ? Это скалярное произведение векторов N (вектор нормали) и L (вектор, представляющий луч света). И, таким образом, зная эти вектора, мы можем рассчитать диффузное отражение (для определения скалярного произведения в языке шейдеров есть специальная функция dot):

    // получаем скалярное произведение векторов нормали и направления света
    float diffuseLightDot = max(dot(normal, lightDirection), 0.0);

Затем получаем зеркальное отражение света (specular light). Подобное отражение характеризуется тем, что падающий луч света отражается под одним углом. Зеркальное отражение рассчитывается по формуле I = Ks * Is max(cos θ, 0)a. В данном случае Ks - материал, а Is - цвет зеркального отражения. Угол θ здесь представляет угол между вектором, направленным от точки к наблюдателю, и вектором отражаемого луча. Степень a указывает на блеск материала.

Схематично формулу можно представить себе так:

Зеркальное отражение света

И опять же cos θ в данном случае это скалярное произведение векторов R (вектор отраженного луча) и V (вектор, направленный к наблюдателю). А, получив на предыдущем шаге векторы нормали и направления падающего луча, мы можем получить вектор луча отражения R и вычислить значение зеркального отражения: R=2(L * N)*N-L. Однако язык шейдеров имеет встроенную функцию reflect, которая позволяет найти вектор отраженного луча по нормали и направлению падающего света. Собственно эта функция и применяется.

Но поскольку при зеркальном отражении вектор луча падающего света направлен в противоположную сторону в отличие от вектора, который применяется при подсчете диффузного отражения света, поэтому мы используем знак минуса: vec3 reflectionVector = normalize(reflect(-lightDirection, normal));

А затем применяем скалярное произведение и возводим в степень shininess. Параметр shininess задается произвольно: чем он выше, тем более блестящим будет казаться отблекс.

    vec3 viewVectorEye = -normalize(vertexPositionEye3);
    
    float specularLightDot = max(dot(reflectionVector, viewVectorEye), 0.0);
    
    float specularLightParam = pow(specularLightDot, shininess);

И в конце мы собираем все световые значения и получаем общее значение отраженного света, которое затем передаем во фрагментный шейдер. А там применяем полученные значения к цветам текстуры: gl_FragColor = vec4(vLightWeighting.rgb * texelColor.rgb, texelColor.a);. И в итоге получаем имитацию освещения объекта.

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

Модель Фонга - не единственная модель отражения света, которая есть и которую можно использовать в WebGL. Существуют различные техники создания освещения. Однако она довольно популярная и позволяет учесть различные аспекты при освещении. Ну а теперь перейдем к основной программе на javascript.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850