Модель отражения Блинна–Фонга , также называемая модифицированной моделью отражения Фонга, представляет собой модификацию модели отражения Фонга, разработанную Джимом Блинном . [ 1]
Модель затенения Блинна–Фонга используется в конвейере фиксированных функций OpenGL и Direct3D (до Direct3D 10 и OpenGL 3.1) и применяется к каждой вершине по мере ее прохождения по графическому конвейеру ; значения пикселей между вершинами по умолчанию интерполируются с помощью затенения Гуро , а не более затратного с точки зрения вычислений затенения Фонга . [2]
При затенении по Фонгу необходимо постоянно пересчитывать скалярное произведение между наблюдателем ( V ) и лучом от источника света ( L ), отраженным ( R ) от поверхности.
Если вместо этого вычислить средний вектор между векторами наблюдателя и источника света,
можно заменить на , где — нормализованная нормаль поверхности. В приведенном выше уравнении и — нормализованные векторы, а — решение уравнения , где — матрица Хаусхолдера , которая отражает точку в гиперплоскости, содержащую начало координат и имеющую нормаль
Это скалярное произведение представляет собой косинус угла, который составляет половину угла, представленного скалярным произведением Фонга, если V , L , N и R лежат в одной плоскости. Это соотношение между углами остается приблизительно верным, когда векторы не лежат в одной плоскости, особенно когда углы малы. Угол между N и H поэтому иногда называют углом половины пути.
Учитывая, что угол между срединным вектором и нормалью к поверхности, скорее всего, будет меньше угла между R и V, используемого в модели Фонга (если только поверхность не рассматривается под очень крутым углом, для которого он, скорее всего, будет больше), и поскольку Фонг использует показатель степени, можно установить так, чтобы он был ближе к предыдущему выражению.
Для фронтально освещенных поверхностей (зеркальные отражения на поверхностях, обращенных к зрителю) приведут к зеркальным бликам, которые очень близко соответствуют соответствующим отражениям Фонга. Однако, в то время как отражения Фонга всегда круглые для плоской поверхности, отражения Блинна-Фонга становятся эллиптическими, когда поверхность рассматривается под крутым углом. Это можно сравнить со случаем, когда солнце отражается в море близко к горизонту, или когда далекий уличный свет отражается в мокром тротуаре, где отражение всегда будет гораздо более протяженным по вертикали, чем по горизонтали. [3]
Кроме того, хотя ее можно рассматривать как приближение к модели Фонга, она создает более точные модели эмпирически определенных функций распределения двунаправленной отражательной способности , чем Фонг для многих типов поверхностей. [4]
Метод Блинна-Фонга будет быстрее, чем метод Фонга, в случае, когда зритель и свет рассматриваются как очень удаленные, например, приближающиеся или находящиеся на бесконечности. Это касается направленного света и ортографических/изометрических камер. В этом случае вектор середины пути не зависит от положения и кривизны поверхности просто потому, что вектор середины пути зависит от направления на положение зрителя и направления на положение источника света, которые по отдельности сходятся на этом удаленном расстоянии, поэтому вектор середины пути можно считать в этом случае постоянным. поэтому его можно вычислить один раз для каждого источника света, а затем использовать для всего кадра или даже пока свет и точка обзора остаются в том же относительном положении. То же самое не относится к методу Фонга, в котором используется вектор отражения, который зависит от кривизны поверхности и должен быть пересчитан для каждого пикселя изображения (или для каждой вершины модели в случае вершинного освещения). В 3D-сценах с перспективными камерами такая оптимизация невозможна.
Этот пример на языке High-Level Shading Language представляет собой метод определения рассеянного и зеркального света от точечного источника света. Пропускаются структура света, положение поверхности в пространстве, вектор направления взгляда и нормаль поверхности. Возвращается структура освещения;
Ниже также необходимо зафиксировать определенные скалярные произведения на нуль в случае отрицательных ответов. Без этого свет, направленный от камеры, обрабатывается так же, как и свет, направленный к ней. Для зеркального расчета неправильное «гало» света, отражающееся от краев объекта и от камеры, может казаться таким же ярким, как свет, напрямую отраженный к камере.
структура Освещение { float3 Рассеянное ; float3 Зеркальное ; }; struct PointLight { float3 position ; float3 diffusionColor ; float diffusionPower ; float3 specularColor ; float specularPower ; }; Освещение GetPointLight ( PointLight light , float3 pos3D , float3 viewDir , float3 normal ) { Освещение OUT ; if ( light.diffusePower > 0 ) { float3 lightDir = light.position - pos3D ; //3D положение поверхности в пространстве float distance = length ( lightDir ) ; lightDir = lightDir / distance ; // = normalize( lightDir ) ; distance = distance * distance ; //Интенсивность рассеянного света. Насыщаем, чтобы оставаться в диапазоне 0-1. float NdotL = dot ( normal , lightDir ); float diffusionIntensity = saturate ( NdotL ); // Рассчитаем рассеянный свет, учитывая цвет света, мощность и затухание OUT . Diffuse = diffusionIntensity * light . diffusionColor * light . diffusionPower / distance ; //Вычислить половину вектора между вектором освещения и вектором вида. float3 H = normalize ( lightDir + viewDir ); //Интенсивность зеркального света float NdotH = dot ( normal , H ); float specularIntensity = pow ( saturate ( NdotH ), specularHardness ); // Суммируем зеркальный свет , разложив его на множители OUT.Specular = specularIntensity * light.specularColor * light.specularPower / distance ; } return OUT ; }
Этот пример на языке шейдеров OpenGL состоит из двух файлов кода, или шейдеров . Первый из них — это так называемый вершинный шейдер , реализующий затенение Фонга , которое используется для интерполяции нормали поверхности между вершинами. Второй шейдер — это так называемый фрагментный шейдер , реализующий модель затенения Блинна–Фонга для определения рассеянного и зеркального света от точечного источника света.
Этот вершинный шейдер реализует затенение Фонга :
атрибут vec3 inputPosition ; атрибут vec3 inputNormal ; равномерный mat4 проекция , вид модели , нормальныйMat ; изменяющийся vec3 normalInterp ; изменяющийся vec3 vertPos ; void main ( ) { gl_Position = проекция * modelview * vec4 ( inputPosition , 1.0 ); vec4 vertPos4 = modelview * vec4 ( inputPosition , 1.0 ); vertPos = vec3 ( vertPos4 ) /vertPos4.w ; normalInterp = vec3 ( normalMat * vec4 ( inputNormal , 0.0 ) ) ; }
Этот фрагментный шейдер реализует модель затенения Блинна–Фонга [5] и гамма-коррекцию :
точность средняяp float ; в vec3 normalInterp ; в vec3 vertPos ; равномерный режим int ; const vec3 lightPos = vec3 ( 1.0 , 1.0 , 1.0 ) ; const vec3 lightColor = vec3 ( 1.0 , 1.0 , 1.0 ); const float lightPower = 40.0 ; const vec3 ambientColor = vec3 ( 0.1 , 0.0 , 0.0 ); const vec3 diffusionColor = vec3 ( 0.5 , 0.0 , 0.0 ); const vec3 specColor = vec3 ( 1.0 , 1.0 , 1.0 ); const float shininess = 16.0 ; const float screenGamma = 2.2 ; // Предположим, что монитор откалиброван в цветовом пространстве sRGB пустая основная () { vec3 normal = normalize ( normalInterp ); vec3 lightDir = lightPos - vertPos ; расстояние с плавающей точкой = dot ( lightDir , lightDir ); lightDir = normalize ( lightDir ); float lambertian = max ( dot ( lightDir , normal ), 0.0 ); float specular = 0.0 ; если ( ламбертиан > 0,0 ) { vec3 viewDir = нормализовать ( - vertPos ); // это blinn phong vec3 halfDir = normalize ( lightDir + viewDir ); float specAngle = max ( dot ( halfDir , normal ), 0.0 ); specular = pow ( specAngle , shininess ); // это phong (для сравнения) if ( mode == 2 ) { vec3 reflectDir = reflect ( - lightDir , normal ); specAngle = max ( dot ( reflectDir , viewDir ), 0.0 ); // обратите внимание, что показатель степени здесь другой specular = pow ( specAngle , shininess / 4.0 ); } } vec3 colorLinear = ambientColor + diffusionColor * lambertian * lightColor * lightPower / distance + specColor * specular * lightColor * lightPower / distance ; // применяем гамма-коррекцию (предполагаем, что ambientColor, diffusionColor и specColor // линеаризованы, т. е. не содержат гамма-коррекции) vec3 colorGammaCorrected = pow ( colorLinear , vec3 ( 1.0 / screenGamma )); // используем гамма-скорректированный цвет во фрагменте gl_FragColor = vec4 ( colorGammaCorrected , 1.0 ); }
Цвета ambientColor , diffusionColor и specColor не должны подвергаться гамма-коррекции . Если это цвета, полученные из файлов изображений с гамма-коррекцией ( JPEG , PNG и т. д.), их необходимо линеаризовать перед работой с ними, что делается путем масштабирования значений каналов до диапазона [0, 1] и повышения их до значения гаммы изображения, которое для изображений в цветовом пространстве sRGB можно принять равным примерно 2,2 (хотя для этого конкретного цветового пространства простое степенное соотношение является лишь приближением фактического преобразования ). Современные графические API имеют возможность выполнять эту гамма-коррекцию автоматически при выборке из текстуры или записи в буфер кадра . [6]