ПрограммированиеСтатьиГрафика

Освещение с использованием фотометрического профиля

Автор:

Фотометрический профиль (ФП) описывает распределение интенсивности света точечного источника, полученные с помощью соответствующего измерительного оборудования. Используя данные ФП можно добиться эффекта симуляции сложных теней и нестандартного затухания света.
 

1. Немного теории
2. Фотометрический профиль
3. Запаковка в текстуру
4. Заключение
5. Дополнительные материалы по теме

1. Немного теории

photometric profile | Освещение с использованием фотометрического профиля

В основе лежит Фотометрия — научная дисциплина, на основании которой производятся количественные измерения энергетических характеристик поля излучения. Первый из законов фотометрии — закон обратных квадратов (Inverse-square law) был впервые сформулирован Иоганнном Кеплером в 1604 году:

Inverse square law | Освещение с использованием фотометрического профиля

\(E = \frac{I}{r^2}\cos i\)

Где \(E\) — освещенность, \(r\) — расстояние от источника освещения до объекта, \(I\) — сила света точечного источника, \(i\) — угол падения лучей относительно нормали к поверхности.

2. Фотометрический профиль

Существуют два распространенных формата для хранения фотометрических данных о распределении интенсивности света: IES и EULUMDAT. Оба формата — обычные ASCII файлы.

Пример файла в формате IES:

IESNA:LM-63-2002
[TEST]BALLABS TEST NO.  14397.0
[TESTLAB] BUILDING ACOUSTICS & LIGHTING LABORATORIES, INC
[ISSUEDATE] 03-DEC-2008
[MANUFAC] LeoMoon Studios
[LUMINAIRE] 1/100W CLEAR ED17 LAMP 6"DIA 38.25"TALL WHITE BOLLARD   
[MORE] 4.5"DIA SPECULAR CONE REFLECTOR w/5.875"TALL ACRYLIC   
[MORE] CYLINDER LENS                                           
[LUMCAT] OSA6R-100PSMH120-AC                       
[LAMPCAT] M90 MH100W/U/PS   
TILT=NONE
1 9000 1 35 1 1 1 -0.49 -0.49 0.406
1 1 100
0 5 10 15 20 25 30 35 40 45 50 55 60 62.5 65 67.5 70 72.5 75 77.5 80 82.5 85 87.5 90 95 105 115 125 135 145 155 165 175 180
0
0.00 12.00 87.00 182.00 167.00 300.00 655.00 944.00 822.00 703.00 614.00 565.00 487.00 438.00 372.00 309.00 262.00 216.00 182.00 142.00 111.00 83.00 61.00 42.00 25.00 19.00 14.00 14.00 15.00 20.00 33.00 36.00 27.00 4.00 0.00

IES — формат, разработанный Светотехническим Обществом Северной Америки (Illuminating Engineering Society of North America, IESNA) для электронной передачи фотометрических данных световых приборов между разными светотехническими компьютерными программами. Является наиболее популярным форматом для хранения фотометрических данных в Северной Америке и широко используется в Европе.

EULUMDAT — Европейский стандарт и де-факто промышленный стандарт описания фотометрических данных в Европе.

Оба формата описывают интенсивность освещения при различных углах и используют сферическую систему координат, именуемую как «Фотометрическая сеть» или «Фотометрическая паутина».

Фотометрический профиль в программе IESViewer:

Photometric profile in IESViewer | Освещение с использованием фотометрического профиля

Фотометрический профиль может быть легко применен в компьютерной графике к точечному источнику освещения или прожектору, в том числе и в реалтайм, если данные об интенсивности света агрегировать в lookup текстуру.

Также его можно использовать двумя разными способами, например, если нормализовать интенсивность света от 0 до 1, то ФП можно использовать как маску для источника освещения, таким образом, ФП лишь создаст дополнительную аттенуацию (затухание). Второй вариант применения - когда данными о интенсивности света будет являться только фотометрический профиль, для этого необходимо нормализованные значение интенсивности умножить на максимальную интенсивность до нормализации.

3. Запаковка в текстуру

Мы можем просэмплировать ФП по горизонтали от 0 до 360 градусов, а по вертикали от 0 до 180, восполняя недостающую информацию линейной интерполяцией, таким образом получив 2D текстуру. В данном случае при расчете в шейдере нам придется учитывать не только направление источника (spot direction), но и поворот вокруг этого направления.

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

Псевдокод:

float MaxIntensity = 0;
float IntensityData[TextureWidth];
unsigned char TextureData[TextureWidth];
for ( int x = 0 ; x < TextureWidth ; x++ ) {
   float Angle = (float)x / ( TextureWidth - 1 ) * PI;
   IntensityData[ x ] = PhotometricData.SampleVerticalAngle( Angle );
   MaxIntensity = std::max( MaxIntensity, IntensityData[ x ] );
}
float Normalizer = MaxIntensity > 0.0f ? 1.0f / MaxIntensity : 1.0f;
for ( int x = 0 ; x < TextureWidth ; x++ ) {
    TextureData[ x ] = clamp( std::pow( IntensityData[ x ] * Normalizer, 1.0f / 2.2f ) * 255, 0, 255 );
}

Достаточно использовать half-float текстуру, но можно и обойтись восьмибитной текстурой (8 бит на пиксель). Для этого я бы рекомендовал запаковать интенсивность в sRGB-пространство, воспользовавшись аппроксимацией gamma 2.2. Обратите внимание, в приведенном коде, где нормализованная интенсивность возводится в степень 1/2.2. Подробно на sRGB пространстве мы останавливаться не будем, поскольку информации предостаточно в общедоступных источниках.

Размер текстуры (TextureWidth) подбирается под ваши требования. В большинстве случаев достаточно текстуры шириной 256 пискселей.

В следующей строке происходит получение средней горизонтальной интенсивности для заданного вертикального угла.

IntensityData[ x ] = PhotometricData.SampleVerticalAngle( Angle )

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

Псевдокод:

float CPhotometricData::SampleVerticalAngle( float Angle ) {
  // Ищем пару вертикальных углов между которыми находится Angle
  int AngleIndex = -1;
  for ( int i = 0 ; i < m_NumVerticalAngles - 1 ; i++ ) {
    if ( Angle >= m_VerticalAngles[i] && Angle < m_VerticalAngles[i+1] ) {
       AngleIndex = i;
       break;
    }
  }
  if ( AngleIndex == -1 ) {
    return 0.0f;
  }

  // Рассчитываем среднюю горизонтальную интенсивность найденных вертикальных углов
  float Intensity0 = 0.0f;
  float Intensity1 = 0.0f;
  for ( int i = 0 ; i < m_NumHorizontalAngles - 1 ; i++ ) {
    Intensity0 += m_IntensityTable[ i ][ AngleIndex ];
    Intensity1 += m_IntensityTable[ i ][ AngleIndex + 1 ];
  }
  Intensity0 /= m_NumHorizontalAngles;
  Intensity1 /= m_NumHorizontalAngles;

  // Вычисляем значение для интерполяции интенсивности между двумя углами
  float Delta = m_VerticalAngles[AngleIndex+1] - m_VerticalAngles[AngleIndex];
  float Fract = Delta > 0.0f ? ( Angle - m_VerticalAngles[AngleIndex] ) / Delta : 0.0f

  return Intensity0 * ( 1.0f - Fract ) + Intensity1 * Fract;
}

Код для шейдера (GLSL) будет следующий

sampler1D PhotometricLookup;

...

// Косинус угла между нормализованными векторами
// от источника к объекту и направлением источника освещения (spot direction)
float LdotD = dot( L, D );

// Получаем координату в lookup текстуру
float sampleCoord = acos(LdotD) * (1.0 / PI);

// Считываем нормализованную интенсивность, распаковываем из sRGB в RGB
float normalizedIntensity = pow( texture( PhotometricLookup, sampleCoord ).r, 2.2 );

// Восстанавливаем исходную интенсивность.
// При использовании ФП в качестве маски, умножение на maxIntensity не требуется.
float I = normalizedIntensity * maxIntensity;

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

Изменим вычисление угла в цикле при упаковке в 1D текстуру:

for ( int x = 0 ; x < TextureWidth ; x++ ) {
   float NdotL = (float)x / ( TextureWidth - 1 ) * 2.0f - 1.0f;
   float Angle = std::acos( NdotL );
   IntensityData[ x ] = PhotometricData.SampleVerticalAngle( Angle );
   MaxIntensity = max( MaxIntensity, IntensityData[ x ] );
}

И внесем корректировку в шейдер при вычислении текстурной координаты:

// Получаем координату в lookup текстуру, переводим NdotL из диапазона [-1,+1] к [0,1]
float sampleCoord = LdotD * 0.5 + 0.5;

Таким образом мы избавились от вычисления дорогостоящего арккосинуса в шейдере, заменив на одну mad инструкцию для приведения диапазона [-1,+1] к [0,1].

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

4. Заключение

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

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

Photometric profile | Освещение с использованием фотометрического профиля

5. Дополнительные материалы по теме

1. Описание формата iesna-lm-63-1995:
http://expertunion.ru/normyi-osvescheniya/iesna-lm-63-1995-opisan… mata-ies.html

2. Ian Ashdown. Parsing The IESNA LM-63 Photometric Data File. 1998.
http://lumen.iee.put.poznan.pl/kw/iesna.txt.

3. Photometric Data for Lamps
https://www.usa.lighting.philips.com/support/support/literature/p… tometric-data

3 июня 2020 (Обновление: 28 авг 2020)

Комментарии [25]