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

Программирование шейдеров на HLSL. (4 стр)

Модели освещения.

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

Рассмотрим все подробнее, создадим простую модель освещения — равномерное освещение.

1. Равномерное освещение.

Равномерное освещение (ambient lighting) обеспечивает постоянное начальное освещение для всей сцены. Оно освещает все вершины объектов одинаково, потому что не зависит ни от каких других факторов освещения. Это самый простой и быстрый тип освещения, но при этом дает наименее реалистичный результат. Формула для вычисления этой модели освещения так же очень проста, т.к. там всего одна арифметическая операция — умножение. Для ее вычисления достаточно перемножить цвет материала на интенсивность освещения.

Iambient=ka×Ia

Вершинный шейдер для расчета равномерной модели освещения:

//Произведение мировой, видовой и проекционной матриц.
float4x4 mat_mvp;
// Входящие данные.
struct VS_INPUT_STRUCT
{
  float4 position: POSITION;
};
// Исходящие данные.
struct VS_OUTPUT_STRUCT
{
  float4 position: POSITION;
};

VS_OUTPUT_STRUCT main (VS_INPUT_STRUCT In_struct)
{
  VS_OUTPUT_STRUCT Out_struct;
  Out_struct.position = mul (mat_mvp, In_struct.position);
  return Out_struct;
}

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

float4 ambient_color;     // Цвет материала.
float  ambient_intensity; // Интенсивность цвета.

float4 main(): COLOR0
{
    // Возвратим Результат из формулы AI*AC. 
    return ambient_color * ambient_intensity;                            
}

Здесь тоже нет ничего сложного, просто возвращаем компилятору произведение вектора, на какое то число в данном случае ambient_intensity. Делает он это так:

ambient_intensity * ambient_color.x;

ambient_intensity * ambient_color.y;
ambient_intensity * ambient_color.z;
ambient_intensity * ambient_color.w;

Модель равномерного освещения:

Изображение

Для того чтобы вычислить бликовый и диффузный компоненты света, необходимо найти три вектора:
• Нормаль N к фрагменту.
• Видовой вектор V — вектор, который направлен на наблюдателя.
• Позицию источника света L.

Изображение

Углы между этими векторами составляют интенсивность освещения.

2. Диффузная модель освещения.

Диффузная модель освещения (diffuse lighting model) — модель освещения, которая зависит от положения источника освещения и от объектной нормали поверхности. Поскольку излучение света одинаково во всех направлениях, видовой вектор не имеет значения, т.е. v=0. Такой метод требует большего вычисления, так как изменяется для каждой вершины объекта, однако неплохо затеняет объекты и придает им объем. Свет падает, не заполняя всю поверхность одинаковым цветом (как в случае с раномерным освещением), а создается впечатление, что, свет направлен на какую либо поверхность.

Изображение

Если вектор позиции источника освещения перпендикулярен поверхности, то никакой матовости не будет наблюдаться, потому что интенсивность света зависит от угла α. Для расчета диффузной модели освещения используется формула (по закону Ламберта):

Idiffuse=kd×Id×(NL)

Подведем итоги – создадим шейдеры для расчета освещения.

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

float4x4 mat_mvp;  //Произведение мировой, видовой и проекционной матриц.
float4x4 mat_world; //Мировая матрица.
float4   vec_light; //Позиция источника света

struct VS_INPUT_STRUCT //Входящие данные
{
    float4 position: POSITION;
    float3 normal:   NORMAL;
};

struct VS_OUTPUT_STRUCT //Исходящие данные
{
     float4 position: POSITION;
     float3 light:    TEXCOORD0;
     float3 normal:   TEXCOORD1;
};

VS_OUTPUT_STRUCT main(VS_INPUT_STRUCT In_struct)
{
    VS_OUTPUT_STRUCT Out_struct;
    //Вычисляем позицию вершины.
    Out_struct.position = mul(mat_mvp,In_struct.position);
    //Сохраняем позицию источника для передачи во фрагментный шейдер в виде
    //3D текстурных координат.
    Out_struct.light    = vec_light;
    //Рассчитываем нормаль поверхности и сохраняем для фрагментного шейдера.
    Out_struct.normal   = normalize(mul(mat_world,In_struct.normal));
    //*под словом “сохраняем” имеется ввиду посылание данных в растеризатор,
    //а только потом в вершинный шейдер.
    return Out_struct;
}

И пиксельный шейдер:

float diffuse_intensity;
float4 diffuse_color;

float4 main(float3 light:  TEXCOORD0, float3 normal: TEXCOORD1):COLOR0
{
    return diffuse_color * diffuse_intensity * dot(normal,light);
}

Диффузная модель освещения:

Изображение

3.Бликовая модель освещения.

Сложно, представить такую модель освещения не увидев ее. А на самом деле эту модель мы можем увидеть почти везде. Например, чисто отполированную (круговыми движениями) прямую металлическую поверхность, направив на нее источник света, и посмотрев под неким углом, который не перпендикулярен поверхности. В результате мы увидим блики на поверхности, которые существенно увеличивают реалистичность изображения. Эти блики являются отражением источника света от поверхности. В этой модели освещения помимо векторов позиции источника освещения и нормали (как в случае с диффузной моделью освещения) используются еще два вектора: видовой вектор и вектор отражения. Бликовую модель освещения (specular lighting model) предложил Буи-Туонг Фонг.

Изображение

Угол между видовым вектором и вектором отражения – β. Чем больше угол β, тем ярче бликовое освещение. Поэтому бликовая модель освещения вычисляется по следующей формуле:

Ispecular=ks×Is×(VR)n

где R=reflect (–norm(V), N)
n — коэффициент яркости свечения.

Зависимость яркости свечения от угла β:

Изображение

С ростом параметра n отражение становиться все более бликовым и все более концентрируется вдоль направления вектора отражения R.

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

float4x4 mat_mvp;      //Произведение мировой, видовой и проекционной матриц.
float4x4 mat_world;    //Мировая матрица.
float4   vec_light;    //Позиция источника света
float3   vec_view_pos; //Видовой вектор
float4   vec_eye;      //Позиция наблюдателя

struct VS_INPUT_STRUCT //Входящие данные
{
    float4 position: POSITION;
    float3 normal:   NORMAL;
};

struct VS_OUTPUT_STRUCT //Исходящие данные
{
    float4 position:  POSITION;
    float3 light:     TEXCOORD0;
    float3 normal:    TEXCOORD1;
    float3 view:      TEXCOORD2;
};

VS_OUTPUT_STRUCT main(VS_INPUT_STRUCT In_struct)
{
    VS_OUTPUT_STRUCT Out_struct;
    //Трансформируем позицию вершины
    Out_struct.position = mul(mat_mvp,In_struct.position);    
    //Сохраняем позицию источника для передачи в пиксельный шейдер в виде
    //3D текстурных координат.
    Out_struct.light = vec_light; 
    //Рассчитываем нормаль поверхности и сохраняем для пиксельного шейдера.
    Out_struct.normal = normalize(mul(mat_world,In_struct.normal));  
    //Вычисляем видовой вектор и сохраняем для пиксельного шейдера.
    Out_struct.view = vec_eye - vec_view_pos;

    return Out_struct;
}

Необязательно вычислять видовой вектор в шейдере, можно вычислить в программе и занести в вершинный шейдер. Для этого нужно инвертировать видовую матрицу и умножить на вектор D3DXVECTOR4(0.0,0.0,0.0,1.0) — позиция при которой вектор перпендикулярен поверхности (смотрит на нас). Выглядит это так:

D3DXMATRIXA16 mat_temp,mat_view_inverse;

D3DXVECTOR4  view_pos;
mat_temp = mat_world * mat_view;
D3DXMatrixInverse(&mat_view_inverse,NULL,&mat_temp);
D3DXVec4Transform(&view_pos,(D3DXVECTOR4*)&D3DXVECTOR4(0.0f,0.0f,0.0f,1.0f));

Пиксельный шейдер:

float4 specular_color;
float4 specular_intensity;

struct PS_INPUT_STRUCT
{
    float3 light:  TEXCOORD0;
    float3 normal: TEXCOORD1;
    float3 view:   TEXCOORD2;
};

float4 main(PS_INPUT_STRUCT In):COLOR0
{
  float power =16;
  float3 reflect_vec=reflect(-normalize(In.view),In.normal);
  return specular_color*specular_intensity*pow(dot(reflect_vec, In.light),power);
}

Бликовая модель освещения:

Изображение

Модификация бликового освещения по Блинну.

Джим Блинн придумал альтернативный способ вычисления бликового освещения, который устраняет дорогие вычисления над вектором отражения. Он ввел промежуточный вектор, который является средним значением между видовым вектором и вектором позиции источника освещения: H=(L+V)/(|L+V|)

Общая формула имеет вид:

Iblin_specular=kb_s×Ib_s×(NH)n

Поэтому фрагментный шейдер уже будет такой:

float4 main(PS_INPUT_STRUCT In_struct):COLOR0
{
  float3 H=normalize(In.light+In.view);
  float n = 16;
  return specular_color*specular_intensity*pow(dot(In.normal,H),n);
}

Ускорение вычисления яркости свечения.

Шлик предложил замену степени n. Пусть скалярное произведение равно D: D=(NH)n, тогда по его способу яркость свечения будет вычисляться следующим образом:
Изображение

Пиксельный шейдер:

float4 main(PS_INPUT_STRUCT In_struct):COLOR0
{
  float3 H=normalize(In.light+In.view);
  float n = 16;
  float D=dot(In.normal,H);
  return specular_color*specular_intensity*D/(n-D*n+D);
}

Сравнительные графики степенных законов:

Изображение

На самом деле физический смысл бликового отражения света намного сложнее, чем предполагается в модели освещения Фонга. В более реалистичной модели Is зависит от длины волны l и от угла падения света (NL). Такая зависимость называется коэффициентом Френеля.

Комбинирование компонентов освещения.

Теперь мы можем сложить три модели освещения (постоянное, диффузное и бликовое), чтобы получить суммарное количество света I, получаемое глазом:
Изображение

Пиксельный шейдер теперь будет такой:

struct PS_INPUT_STRUCT
{
    float3 light:  TEXCOORD0;
    float3 normal: TEXCOORD1;
    float3 view:   TEXCOORD2;
};

float4 ambient_color;     // Цвет материала.
float  ambient_intensity; // Интенсивность цвета.

float  diffuse_intensity;
float4 diffuse_color;

float4 specular_color;
float4 specular_intensity;

float n;

float4 main(PS_INPUT_STRUCT In_struct):COLOR0
{
  float3 H=normalize(In.light+In.view);
  float n = 16;
  float D= dot(In.normal,H);
  return ambient_color * ambient_intensity +
  diffuse_color * diffuse_intensity*dot(In.normal, In.light) +
  specular_color*specular_intensity*D/(n-D*n+D);
}

Реалистичное освещение на основе Кука-Торренса

В более реальных моделях освещения основное внимание уделяется на  распределение энергии падающего света. Часть ее поглощается материалом и превращается в тепло, другая часть рассеивается в виде диффузного света, третья часть задает поверхности бликовую освещенность. Поэтому для различных материалов разделение падающего света происходит по-разному, и зависит оно от:
• Функции распределения нормалей
• Затенения и экранирования
• Коэффициента Френеля

Функция распределение нормалей

Эта функция описывает возможное отклонение нормали к поверхности от идеальной нормали N. Чем более эта функция пологая, тем большие отклонения допустимы и тем большей величины пятно отраженного блика. Необходимые нормали расположены вдоль вектора L+V и видимы в направлении V и находятся под углом (HN) к зрителю. Кук и Торренс использовали формулу распределения  Бекмана:

Изображение

где d=HN
m – степень шероховатости объекта. 0.2f – гладкая поверхность, 0.6f – шероховатая. По умолчанию ставят 0.3f.

График распределения Бекмана:

Изображение

Затенение и экранирование

В модели Кука–Торренса учитываются также и такие эффекты, как затенение (shadowing) и экранирование (masking), которые определяют интенсивность бликовой составляющей.

Неэкранированный свет равен:
Изображение

Незатененный свет равен:
Изображение

Тогда финальный множитель G равен:

G=min(1, Gm, Gs)

Изображение

Коэффициент Френеля

Этот коэффициент определяет долю отраженного света и задается функцией:

Изображение

где f — угол падения, косинус, которого равен c=(NH)
n — показатель преломления материала, g=sqrt(n2+c2–1)

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

F=Rs+(1–Rs)×(1–EN)n
где Rs — бликовое отражение,E — вектор наблюдателя,N — нормаль из карты нормалей.

Такая аппроксимация не учитывает соответственно f и n компоненты, но имеет степень n, с увеличением которой можно добиться не плохих результатов. Шлик использовал n=5.

Комбинирование всех множителей

Торренс и Спэрроу объединили эти множители и вывели формулу для подсчета бликового света:
Изображение

Знаменатель NV введен для регулирования интенсивности света.

Изображение

Общая формула для расчета количества света такая:
Изображение

Пришлось формулу немного упростить, так как в оригинале каждый компонент освещения (кроме бликового) умножается еще и на F(0, n). Сделано это из-за использования аппроксимации Шлика.

Пиксельный шейдер этого эффекта:

float roughness;
sampler SAMP_COLOR;
sampler SAMP_BUMP;

struct PS_INPUT 
{
  float2 T : TEXCOORD0;
  float3 L : TEXCOORD1;
  float3 V : TEXCOORD2; 
  float3 H : TEXCOORD3;
};

float4 main(PS_INPUT IN): COLOR0
{
  float3 N = tex2D(SAMP_BUMP,IN.T);
  N = normalize(2.0f * N - 1.0f);
  IN.L = normalize(IN.L);
  IN.V = normalize(IN.V);
  IN.H = normalize(IN.H);

  float r2 = roughness * roughness;

  float exponent = -(1-dot(N,IN.H) * dot(N,IN.H))/(dot(N,IN.H) * dot(N,IN.H)*r2);

  float D = pow(2.71,exponent) /  (r2*dot(N,IN.H) * dot(N,IN.H)*dot(N,IN.H) * dot(N,IN.H));

  float F = pow(1 - dot(N,IN.V),5);

  float G = min(1, min(2.0f * dot(N,IN.H) / dot(IN.V,IN.H) * 
    dot(N,IN.L), 2.0f * dot(N,IN.H) / dot(IN.V,IN.H) * dot(N,IN.V)));

  float4 Spec = max(0.0f,(D*F*G) / (dot(N,IN.V) * 3.14));
  float4 Diff = max(0.0f,dot(N,IN.L));

  return tex2D(SAMP_COLOR,IN.T) * (Diff + Spec);
}

И вершинный шейдер:

float4x4 view_proj_matrix: register(c0);
float4x4 view_matrix;
float4 light_vec;
float4x4 inv_view_matrix;
float4 view_position;

struct VS_INPUT
{
  float4 mPosition : POSITION0; 
  float3 mNormal : NORMAL; 
  float2 mCoord : TEXCOORD0; 
  float3 mTangent : TANGENT; 
  float3 mBinormal : BINORMAL;
};

struct VS_OUTPUT
{
  float4 P: POSITION0;
  float2 T : TEXCOORD0; 
  float3 L : TEXCOORD1;
  float3 V : TEXCOORD2; 
  float3 H : TEXCOORD3;
};

VS_OUTPUT main(const VS_INPUT IN) 
{
  VS_OUTPUT OUT;
  OUT.P = mul(view_proj_matrix,IN.mPosition);
  OUT.T = IN.mCoord;
  OUT.L = float3(dot(light_vec,IN.mTangent),dot(light_vec,IN.mNormal),dot(light_vec,IN.mBinormal));
  OUT.V = float3(dot(view_position,IN.mTangent),
     dot(view_position,IN.mNormal),dot(view_position,IN.mBinormal));
  OUT.H = (OUT.L + OUT.V);
  return OUT;
}
Страницы: 1 2 3 4 5 6 7 Следующая »

#3D, #Direct3D, #DirectX, #HLSL, #шейдеры

1 июня 2004 (Обновление: 19 июня 2024)

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