Вторник, 21.11.2017, 05:32
Главная » Статьи » Painkiller » Прочее

PainKiller: Технологии Рендера Графики
Итак, почему же PainKiller. Причины схожи с причинами, которые меня побудили взглянуть на FlatOut. Хоть и игра уже немного старая, картиночка, выдаваемая игрой, мне очень нравится. Учитывая великолепную скорость отрисовки, рендер заслуживает всяческих похвал.

Итак, реверсил я на максимальных настройках графики ("insane" в параметрах конфиграции) , на видеокарте GF6600.

Рендер игрового кадра состоит из следующих этапов:

* Если на уровне есть вода, то рисуется рефлекшин мапа. Никаких упрощений геометрии и шейдеров, по сравнению с основным проходом (их детальное описание см. дальше), я не заметил. Рисуется все, что видно в главную камеру. Т.е. теоретически можем поиметь эффект внезапного исчезновения/появления отражений невидимых в камеру объектов. Практически же, я пытался поймать эту ситуацию (не долго правдаJ). Не поймал.

* Тени для монстров. Для каждого используется RTT (render target texture) A8R8G8B8 128х128.

* Блурим эти текстуры каждую по 2 раза простым 4 tap фильтром, используя смещения координат.

Получаем:


* Монстрики. Используется хардварный скининг. Точно количество костей не знаю, но учитывая что vs.1.1, смею предположить что около 20. Модель содержит, естественно, больше, поэтому она разбивается на несколько сабмешей, по костям. Количество влияний на вершину – 4. Скиненные персонажи рисуются в 1 проход, поэтому освещаются сразу.

Поддерживаются, похоже, только directional источники, коих может быть до 5 штук. Пиксельного шейдера нет. Освещение посчитанное в vs, умножается на диффузную текстуру (которая, кстати, очень качественная).
Текстура монстрика (оригинальный размер 1024x1024):



Шейдер для монстров

* Первый проход на объекты. FFP + простые шейдеры (1.1).
При отрисовке земли используется 2 диффузные текстуры с рисунком камешков и травички, которые блендятся по заданной маске. Также используется лайтмапа.
Шейдеры для земли

Пример маски блендинга для земли:

Пример лайтмапы для земли:



При отрисовке всех остальных объектов используется лайтмапа и 1 диффузная текстура.
Для ближней геометрии используется также текстура с деталями.
Шейдеры для объектов

Лайтмапа для объектов (оригинальный размер 1024x1024):


* Вода. Первый проход.
Сложные шейдеры, до конца не осилил :) Приводить их не буду.
Большое количество входных параметров. Vs – 56 инструкций, ps – 31 инструкция. В этом проходе накладываем reflections, анимируя её используя 2 dudv карты. Волнение геометрии создаем анимацией вершин в vs. Я вообще обнаружил повертексную анимацию, только после того, как шейдер проанализировал, и увидел, что там меняется позиция вершины. Так думал, что вода плоская.

* Второй проход для воды.
Освещаем её 4-мя направленными (directional) источниками повертексно.
Шейдеры для второго прохода для воды

В арсенале PainKiller’а есть еще один тип воды. Без честных отражений, с использованием лайтмап. Такой тип используется на уровнях, где воды много.
Например в локации C5L1_City_On_Water. Первый же тип используется на первом уровне аддона Battle Out Of Hell: C6L1_Orphanage. Думаю, что мощный редактор позволяет по-разному варьировать параметры воды, чтобы получить нужный визуальный стиль.

* Небо.
До 4-х слоев. Компоненты трехслойного неба:
полусфера, внутри чашечка в зените, потом опять полусфера. Эти слои блендятся стандартным альфаблендом (srcalpha, invsrcalpha), и используют шейдеры точно такие же как при рендеринге земли. Неиспользованные слои (вспомним, что при рендеринге земли используется 2 текстуры для комбинирования) заменяются dummy текстурой.

Пример текстур неба:
Оригинальный размер 2048х512



Оригинальный размер 1024x1024

Оригинальный размер 1024x1024


* Второй проход на сцену – освещение. Тут все что земля, что остальные статические объекты рисуются одинаково. Освещение осуществляется от одного точечного источника, попиксельно. Весь процесс достаточно хитрый. Для учета расстояния используется текстура аттенюации такого содержания:



В вертексном шейдере мы вычисляем расстояние от источника света до вершины. Поделив полученное расстояние на дистанцию влияния источника, мы пакуем вектор так, что все расстояния на которые влияет источник попадают в диапазон [-1,1]. Скейлим дальше в [0,1]. Получили текстурные координаты для лукапа в текстуру аттенюации (так как address mode установлен в clamp, то все лукапы за пределами текстуры будут происходить по ее границе). В нашем примере используется простой кружочек, что означает простое постепенное ослабление освещения с расстоянием. Но задав, например тот же кружочек, но инвертировав его цвет, мы получим освещёнными дальние объекты, но неосвещённые ближние. В общем вариантов масса.
Шейдеры для освещения статики

Основные стетйты тендера установлены в:
Depth write = false
Srcblend = one
Dstblend = one

Т.е. получаем блендинг pixel color + incoming color

* Третий проход на статику (только если включены тени). Рисуем геометрию, проективно накладывая текстуры с тенюшками от монстриков. Ничего особо интересного, разве что, тени еще больше разблуриваются (2 лукапа на текстуру).

* Партиклы. Используются динамические VB/IB. FFP.
* Оружие. Sm30. Normal mapping от нескольких (я видел 2) directional источников освещения. Тут все стандартно, за исключением того, что normal map’ы у них не в tangent, a в model space. Просто источники они тоже поворачивают в model space. Анимации оружия скинтся хардварно (на вертекс влияет 2 кости)
Normal map оружия (оригинальный размер 1024х1024):

Specular mask хранится в альфа канале normal map’ы (оригинальный размер 1024х1024)

Диффузная карта (оригинальный размер 1024х1024):


Шейдеры для оружия

* Downsample картики, с небольшим color remap’ом, для выделения ярких областей.

* Separable Gaussian blur. Горизонтальный, вертикальный, 13х13. Получили bloom текстуру.
Шейдер horizontal pass’a
Блум текстура:




* Блендинг с блум тектурой. FFP. Блендим по формуле: blurred текстура + текстура сцены.
Полученный результат отлично выглядит на динамическом небе.

* UI. Ингейм интерфейса в painkiller почти нет. Думаю именно по этому он сделан так ужасно J Рисуется поэлементно, группировок никаких нет. Зато в startup menu разработчики постарались. Особенно убило окно конфигурации кнопок. Несколько скринов из под NVPerfHUD’а:




3 раза перетиреть почти весь экран, причем такими маленькими квадратиками, это надо уметь :) 2000 дипов.

* Demon mode. Кто не играл, это такой режим, когда собираешь 66 душ, и превращаешься в бессмертного психа-убийцу. :)
Процесс рендеринга таков:
Рисуем сцену нормально в RTT. Затем из нее делаем ч.б. картинку (способ стандартный: dp3 pixel, float3(0.3, 0.59, 0.11). Потом рисуем монстров специальным шейдером, который используя нормали и градиентную текстуру выделяет границу объекта красным цветом. И, напоследок, используя dudv bump map, вносим в картинку искажения. Для получения motion blur эффекта блендим предыдущий и текущий кадр используя весовые коэффициенты.
Demon mode shader

Градиентная текстура:

Получаем вот такую картинку:


Вот собственно и всё.

Теперь общие сведения по рендеру.
1. Дипов мало. В аутдорах до 1000 (редко больше, но бывает). В индорах обычно меньше. Иногда сильно. При таком количестве проходов, это очень хороший показатель.
2. Сортировка по материалам и текстурам хорошая. D3DXEffect не используется.
3. Арт просто супер. Эффекты там где надо. Все хорошо настроены и сбалансированы. Дизайн уровней великолепен. Текстуры сделаны очень качественно. Полетайте в редакторе по C6L2_LoonyPark, например. Просто загляденье.
4. Загрузка видеопамяти 100 – 120 MB (у видеокарты на борту 128). AGP – 6 MB. Чувствуется умелая рука настройщика.
5. LOD’ов геометрии нет. Есть удаление glow партиклов по расстоянию.

Мои мини выводы (большинство перекликается с выводами по FlatOut2):
1. Первое, что надо продумать в рендере, это минимизация DP calls. PainKiller наглядно показал, что с малым количеством DP больше простора для применения интересных и разнообразных алгоритмов (которые могут потребовать несколько проходов рендера). Если для одного прохода требуется больше 1000 DP, то многопроходным алгоритмам, скорее всего, дорога закрыта.
2. Не надо брезговать предрасчетом освещения. Не подходят лайтмапы? Рассмотрите использование ambient occlusion. Никакие реалтайм алгоритмы не дадут вам того-же качества при таком малом потреблении ресурсов.
3. Арт и хорошая дизайнерская работа решает с огромной силой. Эти компоненты не просто делают игру красивее, но и позволяют применить более простые алгоритмы визуализации без ущерба для картинки, что разгружает рендер. Получается, что хороший арт ускоряет игру! Во как! J
4. Чтобы был хороший дизайн, надо давать хорошие рычаги управления этим дизайном (это не из исследования рендера, это после ознакомления с редактором и структурирования своих мыслей, после этого исследования). Методы управления по типу: «сказать программисту, чтобы исправил параметры материала там и там», не работают. Знаю по собственному опыту. Признаю, важность этого момента я недооценивал.



Раздел с комментированными шейдерами.
Шейдер для монстров
vs_1_1
dcl_position v0
dcl_normal v1
dcl_texcoord v2
dcl_blendweight v3
dcl_blendindices v4
dp4 oT0.x, v2, c24
dp4 oT0.y, v2, c25
dp4 oT1.x, v2, c24
dp4 oT1.y, v2, c25
dp3 r0.w, v3, c8.z
add r0.w, c8.z, -r0.w
mad r1, v4.zyxw, c8.y, c8.x
mov a0.x, r1.x
m3x3 r5.xyz, v1, c0[a0.x] // Скининг позиции по первой кости
m4x3 r6.xyz, v0, c0[a0.x] // Скининг нормали
mul r3, r5.xyzz, v3.x // Блендим по весу кости
mul r4, r6.xyzz, v3.x
mov a0.x, r1.y
m3x3 r5.xyz, v1, c0[a0.x] // тут и дальше аналогично по 2-4 костям.
m4x3 r6.xyz, v0, c0[a0.x]
mad r3, r5.xyzz, v3.y, r3
mad r4, r6.xyzz, v3.y, r4
mov a0.x, r1.z
m3x3 r5.xyz, v1, c0[a0.x]
m4x3 r6.xyz, v0, c0[a0.x]
mad r3, r5.xyzz, v3.z, r3
mad r4, r6.xyzz, v3.z, r4
mov r4.w, c8.z
m4x4 r0, r4, c0
mov oPos, r0
mad oFog, r0.z, c9.y, c9.x
mov r4, c11.xyzz
mov r0.w, c10.w
dp3 r0.x, r3, c13 // n dot l
dp3 r0.y, r3, c14 // n dot h
lit r1, r0 // посчитали освещение от directional источника. Тут всего 1 источник, но может быть больше. Считается аналогично.
mad oD0, r1.y, c12, r4
mul r5, r1.z, c12
mul oD1, r5, c10

ps – ffp.

Шейдеры для земли:
vs_1_1
dcl_position v0
dcl_texcoord v1
dcl_texcoord1 v2
m4x4 r0, v0, c0
mov oPos, r0
mad oFog, r0.w, c9.y, c9.x
dp4 oT0.x, v1, c24
dp4 oT0.y, v1, c25
dp4 oT1.x, v1, c27
dp4 oT1.y, v1, c28
mov oT2.xy, v2.xyyy
mov oT3.xy, v2.xyyy

// approximately 12 instruction slots used

ps_1_1
tex t0 // ground texture 1
tex t1 // ground texture 2
tex t2 // blend map
tex t3 // light map
mul r0.xyz, t0, 1-t2 // Темные регионы на маске блендинга соответсвуют t0
+ mul r0.w, t0.w, 1-t2.w // Зачем тут параллелить альфа и вектор пайпы? Не пойму.
mad r0.xyz, t1, t2, r0 // Светлые регионы - t1. Результат сложили
+ mad r0.w, t1.w, t2.w, r0.w // Опять…
mul_x2 r0.xyz, r0, t3 // * на лайтмапу и * 2
+ mul r0.w, r0.w, t3.w // альфу на 2 не умножаем.

// approximately 7 instruction slots used (4 texture, 3 arithmetic)

Шейдеры для объектов:
vs_1_1
dcl_position v0
dcl_texcoord v1
dcl_texcoord1 v2
m4x4 r0, v0, c0
mov oPos, r0
mad oFog, r0.z, c9.y, c9.x // считаем туманчик
dp4 oT0.x, v1, c24 // тут какой-то рескейлинг. Присутствует во всех шейдерах. Глубоко не вникал.
dp4 oT0.y, v1, c25
dp4 oT1.x, v1, c27
dp4 oT1.y, v1, c28
mov oT2, v2

// approximately 11 instruction slots used
На момент теста в константах было вот что.
// c24 – 20, 0, 0, 0
// c25 – 0, 20, 0, 0
// c27 – 20, 0, 0, 0
// c28 – 0, 20, 0, 0

ps – FFP
t0 – base
t1 – detail – для ближних объектов этого нет.
t2 – lightmap

формула блендинга текстур: (base + detail – 0.5) * lightmap * 2

Шейдеры для второго прохода для воды:
vs_1_1
dcl_position v0
dcl_normal v1
dcl_texcoord v2
m4x4 r0, v0, c0
mov oPos, r0
mad oFog, r0.z, c9.y, c9.x
dp4 oT0.x, v2, c24
dp4 oT0.y, v2, c25
dp4 oT1.x, v2, c27
dp4 oT1.y, v2, c28
mov r4, c11.xyzz // r4.xyzw = 0.125
mov r0.w, c10.w
m3x3 r3.xyz, v1, c4 // во view space
dp3 r0.x, r3, c13 // n dot l
dp3 r0.y, r3, c14 // n dot h
lit r1, r0 // осветили. После операции r1.y = diffuse, r1.z = specular
mad r4, r1.y, c12, r4 // в c12 хранится цвет источника света
mul r5, r1.z, c12 // для спекуляра тоже самое.
dp3 r0.x, r3, c16 // дальше аналогично накапливаем diffuse и specular contribution для еще 3-х источников.
dp3 r0.y, r3, c17
lit r1, r0
mad r4, r1.y, c15, r4
mad r5, r1.z, c15, r5
dp3 r0.x, r3, c19
dp3 r0.y, r3, c20
lit r1, r0
mad r4, r1.y, c18, r4
mad r5, r1.z, c18, r5
dp3 r0.x, r3, c22
dp3 r0.y, r3, c23
lit r1, r0
mad oD0, r1.y, c21, r4
mad r5, r1.z, c21, r5
mul oD1, r5, c10

// approximately 36 instruction slots used

ps – ffp

Шейдеры для освещения статики
vs_1_1
dcl_position v0
dcl_texcoord v1
dcl_normal v3
m4x4 r0, v0, c0
mov oPos, r0
mad oFog, r0.w, c9.y, c9.x
add r0, v0, -c13 // world pos – light pos
mul r1, r0, c12.w // В с12.w коэффициент дальности освещения источником. После умножения все расстояния на которые влияет источник, попадут в диапазон [-1, 1]
mad r1, r1, c8.w, c8.w // Пакуем вектор [-1, 1]->[0, 1]
mov oT0.xy, r1.xyyy // xy записали в tc0
mov oT1.x, r1.z // z – в tc1
mov oT1.y, c8.w // c8.w = 0.5, получаем эмуляцию 1D лукапа по горизонтальной линии в центре текстуры.
mov oT2, -r0 // записали vertex to light vector
dp4 oT3.x, v1, c33 // учитывая, что c33 1000, а с34 0100, эти 2 строки очень хитрый метод записать mov oT3.xy, v1. Возможно тут сделано с прицелом на анимацию текстурных координат.
dp4 oT3.y, v1, c34
mov oD0, c12
mad oD1, v3, c8.w, c8.w // запаковали нормаль в цвет

// approximately 17 instruction slots used

ps_1_1
tex t0 // Кружочек, снаружи непрозрачный, внутри прозрачный. А так белый.
tex t1 // Тот же кружочек
tex t2 // normalized vertex to light vector from normalization cubemap
tex t3 // diffuse texture
mul r0.xyz, t3.w, v0 // diffuse color * base texture alpha
dp3_sat r1, v1_bx2, t2_bx2 // n dot l
mul r0.xyz, r0, r1
+ add r0.w, 1-t0.w, -t1.w // Тут получаем аттенюацию по расстоянию, используя наш кружочек. t0.w – аттенюация по xy координатам, t1.w по z.
mul r0.xyz, r0, r0.w // Применили вычисленную аттенюацию к цвету.
+ mul r0.w, r1.w, r0.w
mul_x2 r0.xyz, r0, t3 // добавили diffuse texture

// approximately 9 instruction slots used (4 texture, 5 arithmetic)

Шейдеры для оружия
// Registers:
//
// Name Reg Size
// ------------ ----- ----
// GClipMat c0 4
// GFogParams c9 1
// GLightDir0 c13 1
// GHalfDir0 c14 1
// GLightDir1 c16 1
// GHalfDir1 c17 1
// GSkinBones c27 69

vs_3_0
def c4, 765.005859, 1, 0, 0
dcl_position v0
dcl_texcoord v1
dcl_blendweight v2
dcl_blendindices v3
dcl_position o0
dcl_fog o1.x
dcl_texcoord o2.xy
dcl_texcoord1 o3.xy
dcl_texcoord2 o4.xyz
dcl_texcoord3 o5.xyz
dcl_texcoord4 o6.xyz
dcl_texcoord5 o7.xyz
mul r0.xy, c4.x, v3.zyzw
mova a0.xy, r0
dp4 r1.x, v0, c27[a0.x] // Скининг
dp4 r1.y, v0, c28[a0.x]
dp4 r1.z, v0, c29[a0.x]
dp4 r2.x, v0, c27[a0.y]
dp4 r2.y, v0, c28[a0.y]
dp4 r2.z, v0, c29[a0.y]
lrp r0.xyz, v2.x, r1, r2 // 2 кости, блендим по весам.
mov r0.w, c4.y
dp4 o0.x, r0, c0 // x * wvp
dp4 o0.y, r0, c1 // y * wvp
mov r4.xyz, c28[a0.x] // Тут немножко хитро. Так как используется не tangent space normal map, а model space, то параметры источников света (dir, half) перед установкой в константы переводится в это пространство. Затем поворачиваем вектора в соответствии с матрицами костей.
mul r3.xyz, r4, c13.y // y в animated model space
mul r1.xyz, r4, c14.y
mov r2.xyz, c27[a0.x]
mad r5.xyz, r2, c13.x, r3 // x
mad r3.xyz, r2, c14.x, r1
mov r1.xyz, c29[a0.x]
mad o4.xyz, r1, c13.z, r5 // o4 - light vector 0 в animated model space
mad o5.xyz, r1, c14.z, r3 // o5 – half vector 0 в animated model space
mul r3.xyz, r4, c16.y
mul r4.xyz, r4, c17.y
mad r3.xyz, r2, c16.x, r3
mad r2.xyz, r2, c17.x, r4
mad o6.xyz, r1, c16.z, r3 // o6 – light vector 1 в animated model space
mad o7.xyz, r1, c17.z, r2 // o7 – half vector 1 в animated model space
dp4 r1.z, r0, c2 // z * wvp
dp4 r1.w, r0, c3 // w * wvp
mad o1.x, r1.z, c9.y, c9.x // fog
mov o0.zw, r1
mov o2.xy, v1 // tc0 pass through
mov o3.xy, v1

// approximately 33 instruction slots used


// Name Reg Size
// ------------- ----- ----
// GLightEnable0 b0 1
// GLightEnable1 b1 1
// GAmbientColor c0 1
// GLightColor0 c1 1
// GLightColor1 c2 1
// ColorSampler s0 1
// NormalSampler s1 1

ps_3_0
def c3, 2, -1, 10, 0
dcl_texcoord v2.xy
dcl_texcoord1 v3.xy
dcl_texcoord2 v4.xyz
dcl_texcoord3 v5.xyz
dcl_texcoord4 v6.xyz
dcl_texcoord5 v7.xyz
dcl_2d s0
dcl_2d s1
texld_pp r0, v2, s0
texld r1, v3, s1
if b0
mad_pp r3.xyz, c3.x, r1, c3.y // распаковали нормаль из NM
nrm_pp r2.xyz, v4 // нормализировали light vector 0
nrm_pp r1.xyz, v5 // нормализировали half vector 0
dp3_sat_pp r2.w, r3, r2 // n dot l
dp3_sat_pp r2.z, r3, r1 // n dot h
pow_pp r1.z, r2.z, c3.z // посчитали спекуляр
mul_pp r2.xyz, r2.w, c1 // учли цвет источника
mul_pp r1.xyz, r1.z, r2 // учли спекуляр
if b1 // для второго тоже самое
nrm_pp r4.xyz, v7
nrm_pp r2.xyz, v6
dp3_sat_pp r4.w, r3, r4
dp3_sat_pp r2.z, r3, r2
pow_pp r3.w, r4.w, c3.z
mul_pp r2.xyz, r2.z, c2
mad_pp r1.xyz, r3.w, r2, r1
mad_pp r2.xyz, r2.w, c1, r2
endif
add r2.xyz, r2, c0 // добавили амбиент
mul r0.xyz, r0, r2 // добавили диффузную текстуру
mad_pp oC0.xyz, r1.w, r1, r0 // в альфа канале normal map’ы- specular mask
mov_pp oC0.w, r0.w
else
mul_pp oC0.xyz, r0, c0
mov_pp oC0.w, r0.w
endif

// approximately 45 instruction slots used (2 texture, 43 arithmetic)

Шейдер horizontal pass’a
vs_1_1
dcl_position v0
dcl_texcoord v1
add oT1.xy, v1, c20 // Посмещали в стороны текстурные координаты
add oT2.xy, v1, c21
add oT3.xy, v1, c22
add oT4.xy, v1, -c20
add oT5.xy, v1, -c21
add oT6.xy, v1, -c22
mov oPos, v0
mov oT0.xy, v1

ps_2_0
dcl t0.xy
dcl t1.xy
dcl t2.xy
dcl t3.xy
dcl t4.xy
dcl t5.xy
dcl t6.xy
dcl_2d s0
texld_pp r6, t1, s0 // 7 семплов используя TC посчитанные в VS
texld_pp r5, t0, s0
texld_pp r4, t2, s0
texld_pp r3, t3, s0
texld_pp r2, t4, s0
texld_pp r1, t5, s0
texld_pp r0, t6, s0
mul r6, r6, c1
mad_pp r5, r5, c0, r6 // Аккумулируем с учетом весов.
mad_pp r4, r4, c2, r5
mad_pp r3, r3, c3, r4
mad_pp r2, r2, c1, r3
mad_pp r1, r1, c2, r2
mad_pp r6, r0, c3, r1
add r5.xy, t0, c7 // Посчитали координаты еще 6 семплов.
add r4.xy, t0, c8 // Можно было заюзать все 4 компонента у oTx регистров в VS.
add r3.xy, t0, c9
add r2.xy, t0, -c7
add r1.xy, t0, -c8
add r0.xy, t0, -c9
texld_pp r5, r5, s0
texld_pp r4, r4, s0
texld_pp r3, r3, s0
texld_pp r2, r2, s0
texld_pp r1, r1, s0
texld_pp r0, r0, s0
mad_pp r5, r5, c4, r6
mad_pp r4, r4, c5, r5
mad_pp r3, r3, c6, r4
mad_pp r2, r2, c4, r3
mad_pp r1, r1, c5, r2
mad_pp r0, r0, c6, r1
mov_pp oC0, r0

// approximately 33 instruction slots used (13 texture, 20 arithmetic)

Demon mode shader
ps_1_1
tex t0 // Семпл из оригинальной текстуры
texbem t1, t0 // Эффект мембраны, который возникает когда стреляешь в demon mode, реализуется твиками bumpenvmat текстурного стейджа.
tex t2 // Используем текстуру с предыдущего кадра, чтобы получить плавающий эффект.
mul r0, t1, c0
mad r0, t2, c1, r0 // Блендим текущий и предыдущий кадр по весам. Получаем motion blur like эффект.

Категория: Прочее | Добавил: UnSlayeR (19.05.2013)
Просмотров: 1381 | Комментарии: 3 | Теги: Технологии Рендера Графики, графика, Рендер | Рейтинг: 4.6/9
Всего комментариев: 3
3 Gektor   (16.06.2014 22:18)
Отличный материал. Хоть и не смыслю ничего в этом, приятно было почитать.

1 BlooDFloweR   (19.05.2013 15:47)
Подправил картинки

2 Helldecadent   (10.08.2013 16:04)
Благодарю

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск
Мини-Профиль
Здравствуйте, гость. Войдите на сайт или зарегистрируйтесь.

E-mail:
Пароль:
ГАМАЗАВР
Друзья сайта

Персональный Сайт
Статистика

Онлайн всего: 3
Гостей: 3
Пользователей: 0