Уроки Коммент.

Создание игр » DirectX 9, Featured » Per-pixel lighting (Попиксельное освещение)

Per-pixel lighting (Попиксельное освещение)

Per-piхel lighting в DOOM IIIPer-piхel lighting или Pixel shader lighting это общее название для техник, позволяющих рассчитывать цвет, яркость, освещение, блики и т.д. для каждого видимого пикселя того объекта, который мы рендерим. В старых играх освещения либо не было вообще, либо оно было статическим и, лишь в очень редких случаях, освещённость объектов рассчитывалось повертексно (для каждой вершины модели). Но, с ростом мощностей видео-карт, стало возможным использовать более точные методы расчёта освещения, в том числе и попиксельного освещения моделей. Такие, давно уже привычные нас вещи, как рендер теней от объектов, нормали, красивые блики, отражения и преломления, практически невозможно нормально реализовать без использования per-piхel lighting.

Per-pixel Phong lighting

Попробуем разобраться как запрограммировать достаточно простую модель освещения, которая называется Phong shading, либо Phong lighting. Эта модель освещения, иногда с некоторыми доделками, а иногда без них, применяется в большинстве компьютерных игр. Например, вот результат применения этой модели для освещения персонажей в Half-Life 2: Episode One (подробнее смотрите тут):
Phong Per-pixel lighting

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

Но, помимо того, что поверхность освещается источником света, она так же ещё и отражает часть этого света и, тем самым, получается эффект, который мы называем спекулярными бликами (см. картинку выше). И положение и яркость этих бликов уже, естественно, очень даже зависят от положения нашей камеры, т.е. вектора нашего взгляда. Этот вектор вычисляется отдельно для каждого pixel’а экарана, т.е. per-pixel, поскольку мы рассматриваем per-pixel освещение.

Однако нам повезло – часть работы за нас может сделать сама видео-карта. В ней есть так называемые интерполяторы – специальные… назовём их “штуки”, что бы не усложнять ))) Так вот, эти интерполяторы занимаются как раз тем, что интерполируют значения. Если мы рисуем на экране треугольник и, скажем, каждая его вершина имеет свой уникальный цвет, нормаль и текстурную координату, то интерполяторы занимаются тем, что они рассчитываю значения этих параметров (цвет, нормаль, текст.координаты) для каждого пиксела этого треугольника.

Соответственно, точно так же мы можем передать в эти интерполяторы и вектор нашего взгляда, рассчитанный к каждому вертексу модели, а для каждого пиксела (per pixel т.е.) – рассчитают эти интерполяторы. Поскольку работают они как раз как промежуточное звено между вертексным и пиксельным шейдером, мы можем даже не передавать наш вектор взгляда из программы, а рассчитывать его непосредственно в vertex shader. Точно так же мы можем поступить и с расчётом вектора падения света (сейчас я веду речь про точечный источник света).

Per-pixel lighting Phong vertex shader

Исходный код vertex shader совсем не сложен. По крайней мере не сильно сложнее, чем мы использовали ранее для рендера спрайтов:

// light position
float4 vLight;

// vertex transformations world
float4x4 mWorld;
// vertex transformation view/projection
float4x4 mViewProjection;

struct VS_OUTPUT
{
	float4 Pos  : POSITION;
	float2 tc: TEXCOORD0;
	float3 normal: TEXCOORD1;
	float3 light: TEXCOORD2;
	float3 view: TEXCOORD3;
};

VS_OUTPUT main(float3 Pos  : POSITION0, float3 Normal: NORMAL2,
	float2 tc: TEXCOORD0)
{
	VS_OUTPUT Out;

	// tranform vertex
	float4 pos = mul(float4(Pos, 1), mWorld);
	Out.Pos = mul(pos, mViewProjection);
	Out.light = vLight.xyz - pos.xyz;
	Out.view = float3(0, 0, 1);// наш вектор взгляда

	float3 normal = mul(float4(Normal, 1), mWorld).xyz;
	normal -= mul(float4(0,0,0, 1), mWorld).xyz;
	Out.normal = normal;

	Out.tc = tc;

	return Out;
}

Per-pixel lighting Phong pixel shader

Пиксельный шейдер вообще элементарен. Если вы знакомы с основами геометрии и знаете что такое скалярное произведение векторов и как вычислить вектор отражения, то больше объяснять тут и нечего:

sampler2D tex0 : register(s0); // текстура для pixel shader
 
// основная функция pixel shader'а
float4 pixel_shader_main(float2 tc:TEXCOORD0, float3 normal: TEXCOORD1,
	float3 light: TEXCOORD2, float3 view: TEXCOORD3) : COLOR                
{
	float3 n = normalize(normal);
	float3 l = normalize(light);
	float3 v = normalize(view);
	float3 r = reflect(l, n);
	float fNdotL = saturate(dot(n, l));
	return ( tex2D(tex0, tc)*(fNdotL+.2) + pow(dot(r, v), 8)*fNdotL );
}

Phong Per-pixel lighting: результаты

Per-pixel lightingО том, насколько всё получилось, судите сами. Мне кажется, что вполне даже нормально. Когда мы, в следующих уроках, более подробно разберём темы расчёта спекулярных бликов, учёта карт нормалей, другие алгоритмы расчёта освещенности поверхности – картинка станет ещё заметно лучше. Хотя и сейчас, как мне кажется, получился вовсе даже не “полный отстой” )) Главное, мы научились делать per-pixel освещение, а то, какие формулы в него подставить – это уже вопрос второй, мы всегда сможем использовать именно те формулы, которые больше подходят нам в каждом конкретном случае.

Как обычно, прилагаю исходники и демку.

»crosslinked«

Ещё по этой теме:




Раздел: DirectX 9, Featured · Теги: Pixel shader, Shader

8 комментариев на "Per-pixel lighting (Попиксельное освещение)"
  1. Antony пишет:

    Вершинные шейдеры получаются менее накладными для видеоустройства, чем пиксельные – это верно? Т.к. вершин обычно намного меньше в сцене, чем пикселей.

    У нас в структуре вершины в вершинном шейдере есть вектор взгляда float3 view: TEXCOORD3;
    Но он же общий для всех вершин, обязательно его вносить? Это не понимаю.

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

    Как связаны структуры вершины в коде на С++ и в вершинном шейдере, они могут и не совпадать? Какая “главнее”?

    Наши стейты, которые задаются в коде (SetRenderState(), SetSamplerState()) могу быть указаны в коде шейдеров, чтобы их не указывать в коде С++? Если да, то как быстрее и профессиональнее?

    Извиняюсь за столько, возможно, дурацких вопросов :) Очень хочется всё знать и уметь)

    И спасибо большое за статью!

    P.S. Не хочется показаться наглым, но было бы удобнее разделить код 2d и 3d игры, пусть 2d развивается во своей линии, а 3d – по своей.

  2. Antony пишет:

    И ещё забыл – где надо писать код шейдеров, а то для студии это просто текстовик без форматировани и подсветки типов?

  3. ArchiDevil пишет:

    Код для шейдеров пишу в Notepad++, подсветку сам настроил руками (ключевые слова, всё такое), это недолго и удобно.

    Ссылка в самом начале на сайт валв ведет в никуда, надо слэш в конце убрать.

    Тема отличная, автору огромное спасибо, побольше бы именно такого направления.

  4. Вячеслав пишет:

    Antony, вершинные шейдеры выгодны тем, что vertex shader выполняется 1 раз на 1 вертекс, в то время, как пиксельный – для каждого пиксела. Соответственно, работы для пиксельных шейдеров получается в десятки раз меньше. По этой причине желательно большую часть работы перекладывать на вершинные шейдеры, а в пиксельные пихать только то, что вынести в вертексные никак не получится.

    Касательно структур и деклараций – это тема для одного из ближайших уроков. Постараюсь объяснить максимально подробно и понятно.

    Стейты не могут быть указаны в шейдерах, если не используется система эффектов DirectX (которая просто эмулирует то, что они задаются в шейдерах).

    Касательно подсветки: я сам использую http://nshader.codeplex.com/ – очень удобная штука, подсвечивающая код шейдера непосредственно в среде Visual Studio.

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

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

  5. Antony пишет:

    Наберусь терпения тогда :)
    Спасибо.

  6. Antony пишет:

    Ещё раз дико извиняюсь! Решил потестить exe на другой машине на предмет запуска.Для этого в release настройках обоих проектов (либы графического движка и игры) в code generation -> Runtime Library установил Multi-threading (/MT).
    И проект отказался собираться, вываливая вот такой лог (показываю часть, чтобы не мусорить):

    2>MSVCRT.lib(MSVCR100.dll) : error LNK2005: ___iob_func already defined in LIBCMT.lib(_file.obj)
    2>MSVCRT.lib(MSVCR100.dll) : error LNK2005: __errno already defined in LIBCMT.lib(dosmap.obj)
    2>MSVCRT.lib(MSVCR100.dll) : error LNK2005: _realloc already defined in LIBCMT.lib(realloc.obj)
    2>MSVCRT.lib(MSVCR100.dll) : error LNK2005: _getc already defined in LIBCMT.lib(fgetc.obj)
    2>MSVCRT.lib(MSVCR100.dll) : error LNK2005: _isspace already defined in

    2>C:\Visual Studio 2010\Projects\Spaceship\Release\Spaceship.exe : fatal error LNK1169: one or more multiply defined symbols found

    Можно это как-нибудь побороть?

  7. Вячеслав пишет:

    Antony, что бы это побороть, надо собрать все либы (луа, луабинд, буст, движок) с данной опцией компиляции, только после этого всё слинкуется нормально. Хотя, насколько помню, буст у нас уже собран с поддержкой /MT.

  8. Antony пишет:

    Спасибо, всё пересобрал и всё заработало.

Оставить комментарий

*

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>