План работы
  Настройка 3dmax
  Моделирование
    Mesh edit
    Surface Tools
  Анимация
    Biped
    Physique
    Animation
  Маппинг модели
  Shaders
  Компилирование
    Тэги
    Экспорт
    Конфигурация
    Тестирование
  Звуки
  Бот-файлы
  Советы
  Утилиты
  Модели
  Любимые модели
 

 


| Новости | Quake 3: Модели | Карты | Warcraft 3 | Лит.творчество | Гостевая |
Shaders

Итак, собственная модель игрока скомпилирована и доведена до совершенства. Однако, внешний вид готовой модели можно еще значительно улучшить.

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

Чем-то shaders напоминают редактор материалов в 3DSMAX. С помощью shaders можно добиться имитации практически любой металлической поверхности: хромированные, зеркальные поверхности; матовые, мягко отражающие металлы и т.д. Более того, без shaders не обойтись при создании реалистичного неба, жидкостей, анимационных последовательностей (взрыв, пламя и т.п.), световых конусов (типа volume light), телепортов и множества других эффектов. Таким образом, при использовании shaders создателям моделей и дизайнерам уровней открываются новые возможности по представлению поверхностей и созданию неповторимых спецэффектов.
 
Что такое shader?
 
Итак, shader - это небольшой скрипт, в котором содержатся параметры текстуры, которые, в свою очередь, определяют внешний вид поверхности в игровой среде. Файлы shader'ов имеют расширение *.shader и являются обычными текстовыми файлами, т.е. могут редактироваться в любом текстовом редакторе. Стандартные shaders находятся в папке ы. Этой же иерархии надо придерживаться и при запаковке собственного пака, т.е. помещать свои shaders в папку scripts.
 
Что необходимо знать для создания собственных shaders
 
Файл с описанием параметров (или атрибутов - кому как нравится) поверхности (далее просто - shader) содержит название (заголовок) shader и тело - параметры поверхности. В заголовок shader обычно записывают путь и имя текстуры, над которой проводятся операции. Здесь главное чтобы у вас не получилось двух разных shader'ов с одинаковыми именами: в процессе загрузки игры shader, загруженный движком раньше, нивелируется shader'ом, загруженным позднее. Без этого, однако, не обойтись в тех случаях, когда вы хотите заменить стандартный shader на собственный, но об этом поговорим ниже.

Тело скрипта, как и в любом языке программирования, имеет переменные и константы, и заключается в фигурные скобки. Однако, никаких, даже минимальных, навыков программирования для создания shader'ов с нуля вам не потребуется. Главное требование - синтаксиса shader'ов необходимо строго придерживаться, иначе, если будут появляться ошибки, shader не будет работать.
Ниже я приведу структуру простенького shader для значка рокет- ланчера и дам пояснения:

icons/iconw_rocket
{
nopicmip
{
map icons/iconw_rocket.tga
blendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
}
}


Первая строка - название shader. Как видно, название дублирует полный (начиная с корня архива pak0.pk3) путь обрабатываемой текстуры и ее имя, только без расширения. Далее открываются фигурные скобки и записываются основные ключевые слова, отвечающие за параметры поверхности в целом, т.н. глобальные параметры - следы от пуль остаются/не остаются, clipping есть/нет, какие использовать звуки шагов по поверхности и т.д. Их (глобальные параметры) всегда необходимо ставить в самое начало. После открытия вторых фигурных скобок идет, собственно, описание атрибутов текстуры, которое начинается со слова map и указания полного пути к текстуре. Заметьте, пути прописываются с прямой косой "/", а не с обратной, как принято. После описания всех параметров все скобки должны быть закрыты. Количество открытых скобок всегда равно количеству закрытых. В связи с этим на первых порах здесь часто допускают ошибки, поэтому не забывайте об этом.

Заметки или пояснения начинаются вспомагательными символами - двумя прямыми косыми "//" (без кавычек), после чего любой текст в этой строке в процессе загрузки игры пропускается движком. Заметки не закрываются, т.е. следующая строка и вообще любая другая, не начинающаяся символами "//" движком будет обрабатываться.
 
Инструментарий
 
Для создания собственных shader'ов кроме Блокнота™ :) вам понадобится любой графический редактор, желательно Photoshop. Где его брать, думаю, рассказывать вам не надо :). Также необходимо знать такие основные операции: работа со слоями, альфа каналами, использование различных методов выделения. Желательно знать и представлять себе, что дают различные типы смешивания каналов (как преобразуются RGB составляющие текстуры), т.н. blending modes (в Photoshop и в shader'ах они почти идентичны, просто в shader'ах их меньше) для быстрого получения нужной комбинации, хотя, подбор методом научного тыка тоже приветствуется - иногда получаются неожиданные и довольно привлекательные комбинации.

Также для работы пригодятся школьные знания синусов, турбулентностей и пр. - это я думаю всем известно; и опять - эксперименты только приветствуются.

Ну а после разбора по пальцам нескольких несложных примеров мы познакомимся с удобной программой для просмотра, создания и редактирования shader'ов QAse. Скачать ее можно по адресу http://www.bpeers.com/software/q3ase/q3ase.zip (513 KB).

Вообще говоря, shader - вопрос очень большой и достоин написания отдельного руководства. Но, так как данный ресурс посвещен в основном моделингу, мы и рассмотрим в основном shader, которые можно применить к моделям игроков, оружию и всяческим item'ам. Если многим вопрос окажется интересен, в последствии эту тему можно будет расширить.
 
Создание текстуры и shader'а для стандартной модели рокет-ланчера
 
Итак, все уже наверняка уяснили, какая крутая штука shader :) и, поэтому думаю, пора попробовать создать shader самому. Чтобы не усложнять задачу, возьмем стандартную модель рокет-ланчера и попробуем к ней прикрутить shader. К такому предмету, как металлический рокет, по смыслу, т.е. целесообразно будет добавить металлического блеска. Сделать это можно с использованием дополнительной текстуры, которая будет выступать в качестве или простого блика или отражения (имитации отражения) окружающего игрового пространства. По ходу дела мы рассмотрим еще несколько полезных нюансов, которые вам обязательно пригодятся.

Для начала нам потребуется вытащить стандартную текстуру рокет-ланчера из pak0.pk3 в папке baseq3. Находится она по адресу pak0.pk3\models\weapons2\rocketl\rocketl.jpg. Маппинг этой модели осуществлялся с помощью двух текстур, поэтому вытаскиваем заодно и rocketl2.jpg да и саму модель rocketl.md3, которая нам пригодится позднее. Для удобства рекомендую выделить указанные файлы и задать путь для распаковки, таким образом файлы распакуются с сохранением существующих путей. В результате получиться что-то типа C:\myrocketlauncher\models\weapons2\rocketl\*.*. Эта операция облегчит в дальнейшем запаковку всего нами наредактированного в новый pak.

Создаем в папке myrocketlauncher папку и называем ее scripts. В ней создаем txt файл и переименовываем его в как_угодно.shader, например myrocketlauncher.shader. Как угодно потому, что стандартный рокет не имеет shader и нам нечего перекрывать. Не забывайте о прямой косой "/" при прописывании заголовков и путей. Далее я приведу текст самого скрипта с пояснениями:

models/weapons2/rocketl/rocketl //заголовок
{
{ map models/weapons2/rocketl/specular.jpg //
путь к текстуре блика
tcGen environment //координаты текстуры
rgbGen identity //
tcMod scroll 0.02 0.02 //здесь задаются геометрические искривления текстуры, в данном
случае - смещение
}
{ map models/weapons2/rocketl/rocketl.tga //
путь к обрабатываемой текстуре
blendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA //параметры смешивания текстуры с текстурой блика
rgbGen identitylighting//искривления, пульсации, взаимодействие с местными источниками освещения и т.д., подробнее рассмотрим ниже
}
}
models/weapons2/rocketl/rocketl2 //
Записываем все то же, что и для 1-й текстуры
{
{ map models/weapons2/rocketl/specular.jpg
tcGen environment
rgbGen identity
tcMod scroll 0.02 0.02
}
{ map models/weapons2/rocketl/rocketl2.tga
blendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
rgbGen identitylighting
}
}


Объясню значение параметров, которые записаны в этом примере.

Параметр tcGen назначает координаты для текстуры (почти так же, как и в редакторе материалов MAX'а). В данном случае этому параметру присвоено значение environment; таким образом, будет создана иллюзия отражения от предмета окружающей среды. Значению identity отвечают обычные координаты, т.е. в игре такая текстура будет отображаться обычным образом. То же самое действие и у значения base. И вообще, если текстуру не предполагается использовать для environment mapping'а, параметр tcGen можно вовсе не прописывать. Еще одно значение, которое может принимать этот параметр - lightmap - специфическое значение, надо сказать, но иногда используется создателями моделей как маска, а также картостроителями.

Параметр tcMod искажает уже примененные координаты несколькими методами и может принимать значения scale - масштабирование, rotate - поворот, scroll - смещение, stretch - искажение координат с помощью функций sin, square и т. д.

Параметр blendFunc определяет, как текстуры будут смешиваться между собой. Немного в этом разобраться можно в Photoshop'е, меняя blending modes для различных слоев. Для этого загрузите две любых картинки и, скопировав любую из них в буфер обмена, вставьте как новый слой в другую картинку. Теперь можете перебирать blending modes (в панели layers) созданного слоя и понаблюдать за происходящим.

В нашем случае параметр blendFunc принимает значение GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA. Отложим рассмотрение построения этого монстра на потом, а пока будем использовать как есть, или лучше более упрощенно - blend. Кроме того, имеются еще 2 метода смешивания, которые могут быть названы одним коротким словом без применения смешивающих формул - это add и filter. Однако и за этими названиями скрываются те же формулы, просто эти 3 метода, как наиболее часто используемые, решили дополнительно обозвать коротким словом, имеющим смысловую нагрузку. Различные методы смешивания проще рассматривать в программе QAse, что я и собираюсь сделать ниже.

Итак, "blendFunc blend" заставляет текстуры смешиваться по альфа-каналу. Чтобы это работало, основная текстура должна содержать этот самый альфа-канал. Кто знает, что это такое, может смело пропускать этот абзац. Для остальных немного поясню: прозрачность, или альфа-канал - это дополнительный 8-ми битный канал, который содержит информацию о прозрачности, и как отдельный слой представляет собой обычную черно-белую картинку (256 градаций серого - 8 бит). Каждый пиксель альфа-канала может принимать значения от 0 (черный) до 255 (белый). Абсолютно черный цвет (значение 0) обозначает полную прозрачность, а абсолютно белый (значение 255) - полностью непрозрачный. Таким образом, при сплошной заливке альфа-канала 50% серым текстура будет наполовину прозрачной. А под пикселом, имеющим такое же значение подмешиваемая текстура соответственно будет видна наполовину. К форматам, которые поддерживают альфа-каналы относятся: TIFF, PDF, TGA, PICT, Pixar, ну и понятное дело Photoshop PSD. При использовании альфа-каналов открываются широченные возможности, основной из которых является маскирование и изолирование ненужных участков изображения. По своей сути альфа-канал - маска, и в этом его основное предназначение.

В старых играх для обозначения прозрачности ипользовались различные методы. Например, применялись нестандартные цвета - lime, magenta и др. - которыми заполняли предполагаемые прозрачные области (кто рипал и просматривал ресурсы старых игрушек, знают о чем речь).



Градации, т.е. различные уровни прозрачности были невозможны. Использовался и более продвинутый метод - маска создавалась в отдельном файле, что было хоть и качественно, но не очень удобно. В Q3A альфа-каналы хранятся вместе с текстурой в одном файле, т.е. текстуры сохранены в формате, который поддерживает альфа-каналы - TGA. В этом формате, как и в большинстве других, поддерживающих альфа-каналы, информация о прозрачности записывается в отдельном 8-ми битном канале. При битовости картинки 24 (~16 миллионов цветов) добавляется 8 бит на альфа-канал и в результате получается 32-х битовая картинка. Главное преимущество использования этого формата - удобство: в одном файле одновременно содержатся и текстура и информация о прозрачности. Недостаток - увеличение размеров, что, в общем-то, не существенно при нынешних объемах винтов.

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

Кто уже работал с масками в Photoshop'е и знает, как создать текстуру, содержащую альфа-канал, может переходить к дальнейшему ковырянию рокет-ланчера, а для тех, кто сталкивается с этим впервые, я рассмотрю небольшой пример создания собственной текстуры с альфа-каналом.
 
Создание текстуры с альфа-каналом
   
  Для начала посмотрим, как выглядит стандартная текстура с альфа-каналом в Photoshop'е. Для этого:
 
[1]
Открываем pak0.pk3 и по адресу pak0.pk3\models\powerups\armor\ вытаскиваем newred.tga. Это текстура красной брони, как вы уже могли догадаться. В ACDSee она будет выглядеть так:

Рис. 1

В Photoshop'е эта же текстура будет выглядеть так:

Рис. 2

Такая маска готовится по ходу рисования текстуры с использованием различных инструментов редактора, поэтому в точности воспроизвести альфа-канал рис.2, взяв за основу текстуру без альфа-канала, как на рис.1, будет тяжело. Однако можно добиться некоторого сходства. Попробуем это сделать.
 
[2]
В ACDSee конвертируем текстуру в BMP.
 
[3]
Открываем ее в Photoshop'е и выделяем инструментом лассо области, где по смыслу краска стерлась с металла:

Рис. 3

Совет: чтобы выделенный участок, если он неоднократно должен пригодиться позже, заново потом не выделять, полезно сейчас сохранить его на отдельном слое в виде залитой 1-м цветом области и скрыть:

Рис. 4
   
[4]
Копируем Background слой. Для этого просто перетаскиваем этот слой на иконку Create a new layer, как показано на рисунке:

 
[5]
Инвертируем выделение, нажав комбинацию клавиш Ctrl+Shift+I и добавляем к слою маску:

 
[6]
В выпадающем меню маски выбираем Применить маску:

 
[7]
Если сделать невидимым Background слой, то у нас должна получиться примерно такая картинка:

 
[8]
Зададим теперь прозрачность этого слоя равной 95%:

 
[9]
Вернемся к заранее выделенной области. Выделяем слой по прозрачности (Сtrl+click на слое в панели Layers) и, предварительно выбрав слой Background, комбинацией клавиш Ctrl+J создаем новый слой.

После этого располагаем его над всеми остальными слоями. Выглядеть текстура после всего должна примерно так:

   
[10]
Аналогичные операции (кроме краски - ее нет на этой текстуре) проводим с rocketl2.jpg.

   
[11]
Теперь займемся текстурой с бликом. Достаем из pak0.pk3 по адресу pak0.pk3\textures\sfx specular.jpg.



В таком виде блик нам не подойдет, он не будет смотреться на такой модели, как рокет-ланчер. Поэтому, взяв эту текстуру за основу, мутим блик несколько другой конфигурации:



С такой текстурой рокет-ланчер будет поблескивать более убедительно.
   
[12]
Сохраняем готовые текстуры в папку C:\myrocketlauncher\models\weapons2\rocketl\ с именами rocketl.tga и rocketl2.tga, помещаем туда же наш specular.jpg.

Пожалуй, дальше Photoshop нам уже не пригодится. Однако, полученные нами альфа-каналы выглядят несколько грубовато, поэтому, для доведения их до более изящного вида, рекомендуется ещё немного повозиться со слоями и поработать аэрографом. Вот как это получилось у меня:

   
[13]
Пора переходить к написанию shader. Открываем в блокноте созданный где-то в начале рассмотрения этого примера пустой файл myrocketlauncher.shader и заносим туда следующие строки:

models/weapons2/rocketl/rocketl
{
{
map models/weapons2/rocketl/specular.jpg
tcGen environment
rgbGen identity
}
{
map models/weapons2/rocketl/rocketl.tga
blendfunc blend
rgbGen lightingDiffuse
}
}
models/weapons2/rocketl/rocketl2
{
{
map models/weapons2/rocketl/specular.jpg
tcGen environment
rgbGen identity
}
{
map models/weapons2/rocketl/rocketl2.tga
blendfunc blend
rgbGen lightingDiffuse
}
}


Все значения были рассмотрены выше, поэтому осианавливаться на них уже не буду.

   
[14]
Сохраняем, закрываем и запаковываем все папки с помощью WinZip'а или WinRAR'а (в формате zip) например в myrocket.pk3. Обязательно сохраняйте такую же иерархию папок, как и в pak0.pk3. Помещаем myrocket.pk3 в папку Q3A - baseq3. Мой вариант myrocket.pk3 предлагаю взять здесь (157 KB).
   
[15]
Запускаем Quake 3 Arena и наблюдаем результат.
   
  Дальнейшие пути совершенствования внешнего вида модели основываются на подборе оптимального метода смешивания текстур, т.е. манипуляциях с параметром blendFunc, подборе такой текстуры блика, с которой отблески и отражения будут выглядеть наиболее убедительно и, главное, к месту, и на дальнейшем совершенствовании текстур и их прозрачности.

Например, попробуйте заменить значение параметра blendFunc с blend на gl_one gl_one_minus_src_alpha и посмотрите, должно выглядеть более реалистично. Рассмотреть различные значения blendFunc и быстро выбрать из них нужный удобнее будет в программе Q3Ase, о которой я расскажу далее.

Ну и как итог, предлагаю оценить еще один вариант рокет-ланчера с дырками от пуль на корпусе, который можно взять здесь (139 KB). В этом случае я немного доработал саму текстуру - совместил некоторые части текстуры рокет-ланчера из полного Q3A и из Q3Test и использовал другую текстуру с бликом.

А еще оцените вот это (431 bytes)- очень любопытно получилось :)
   
Использование параметра rgbGen
 
Параметр rgbGen чаще всего используется для создания мигающих эффектов, пульсаций и текстурных конвульсий :). Для этого используются волновые функции sin, triangle, square, noise, и т.д. Не следует путать rgbGen с параметром tcGen - параметр rgbGen преобразует RGB данные текстуры, а не данные о системе координат, как в случае с tcGen.

В том случае, когда rgbGen не используется для создания пульсаций, он может принимать значения identityLighting, identity или lightingDiffuse.
 
Как работает параметр rgbGen
 

Кроме просто текстуры, т. е. ее RGB данных, в shader'ах используется еще т.н. VertexColor. Данные RGB текстуры умножаются на VertexColor, который может принимать значения от 0 до 1 и может изменяться во времени по волновым функциям, о которых я говорил выше. Проще говоря, VertexColor пульсирует от значения 0 к 1 и обратно по указанной волновой функции и умножает на себя RGB данные текстуры, кроме того, текстуры уже смешиваются друг с другом по какому-либо типу blendFunc.

При значениях rgbGen identityLighting и identity VertexColor равен 1, т.е. RGB данные текстуры умножаются на 1, а умножениие на 1, как вы знаете, ничего не меняет. В игре это выглядит так, как-будто модель освещена равномерно со всех сторон, ни теней, ни бликов нет. В случае с lightingDiffuse любые источники освещения в игре взаимодействуют с данными RGB текстуры и на моделях получаются тени и блики такими, какими мы их привыкли видеть, т.е. есть shading. С параметром rgbGen, равным identity, отображается броня, различные powerups, многие объекты на карте и т.д. Определить разницу между lightingDiffuse и identity проще визуально, а не на словах, для чего предлагаю загрузить эти 2 пака, в которых содержится измененный shader для модели Razor Patriot, и просмотреть их, после чего все сразу станет понятно: pak9patriot1.pk3 (4.31 KB) pak9patriot2.pk3 (4.30 KB).

pak9patriot1.pk3 - с параметром identity, pak9patriot2.pk3 с параметром lightingDiffuse. Помещайте их в папку baseq3 не одновременно, а по очереди, иначе будет работать лишь один shader. Все отчетливо увидеть можно сделав вид от 3-го лица и нажимая на кнопочку gesture. Побегайте по уровню, забегите в неосвещенные места и наооборот и вы сразу увидите механизм работы этого параметра.

Рекомендации по просмотру и тестированию shader'ов смотрите ниже.

Хочу еще отметить, что стандартный shader для рассматриваемой мной модели реализован правильнее с точки зрения физики освещения. Но выглядит, на мой взгляд, менее эффектно мной предложенного, естественно в варианте pak9patriot2.pk3.

 
  Рекомендации по тестированию shader'ов
   
  Дам несколько рекомендаций по просмотру и тестированию shader'ов. Просматривать модель советую не в меню выбора модели - она не видна полностью и ее нельзя покрутить, а на загруженном уровне. Для этого выберите модель и загрузите любой уровень из консоли с командой /devmap q3dm17, где вместо q3dm17, понятное дело, можете прописать свой уровень. Когда загрузится, выполните команду /cg_thirdperson 1 (def: 0) - получите вид от третьего лица. Менять угол обзора можно командой /cg_thirdpersonangle 90 (def: 0), где вместо 90 выставляется любой угол. Расстояние до модели (при виде от третьего лица) меняется командой /cg_thirdpersonrange 80 (def: 40). Все - бегайте, крутитесь и наблюдайте. Для удобства настоятельно рекомендую забиндить (выполняется командой /bind "клавиша" команда) на разные клавиши несколько углов обзора, несколько расстояний и вход/выход из вида от третьего лица.

В инете имеется множество конфигов, специально написанных для более удобного тестирования и просмотра модели. Например, один из них можно взять здесь. Можете посмотреть на этот конфиг и посмотреть, как он работает, а можете и сами написать, что, как видите, не очень то и сложно сделать.

Еще очень помогает любопытная команда /timescale 0.2 (def: 1), которая регулирует скорость игры. При значении меньше единицы игра замедляется, больше единицы - ускоряется. Замедлите скорость игры и от вас не ускользнут мельчайшие детали, вы сможете заметить недочеты и просто полюбоваться моделью.
   
  Пример использования параметра rgbGen
   
  Для закрепления изложенного по rgbGen попробуем добавить свечение к злобным маленьким красным глазкам модели Ranger'а. Для этого:
   
[1]
Я подготовил такую текстуру:

   
[2]
Эта текстура будет смешиваться со стандартной текстурой головы r_head.tga с помощью параметра blendFunc по методу add. Это значит, что RGB данные нарисованной мной текстуры будут прибавляться к RGB данным стандартной текстуры: места, заполненные черным цветом, у которого все составляющие RGB равны нулю, при прибавлении себя к данным стандартной текстуры никакого эффекта не дадут и визуально на вид стандартной текстуры не повлияют, а красный цвет будет прибавляться и даст нужный нам эффект. В Photoshop'е имеется аналогичный метод смешивания, но называется иначе - Color Dodge.

Итак, создаем текстовый файл и заносим в него следующее:

models/players/ranger/red_h
{
{
map models/players/ranger/red_h2.tga

}
{
map models/players/ranger/red_h.tga
blendfunc add

}
}

   
[3]
Для создания эффекта мигания (или подсвечивания) воспользуемся параметром rgbGen wave sin 0 1 0 0.4. Значение wave означает, что для придания эффекта используется волновая функция. Значение sin конкретизирует, какая именно функция. Далее идут 4 блока цифр, которые разделяются между собой пробелами. Значения 0 1 0 0.4 соответственно означают точка отправки (или х0), амплитуда, фаза и количество повторений в секунду (т.е. частота).

При таких значениях, подобранных мной экспериментальным путем, глазки не будут бешено светиться и конвульсивно мигать, как неисправная лампа, а будут периодически плавно подсвечиваться, примерно как у модели Grunt Stripe. Однако, и тут возможны варианты. Достаточно хотя бы поменять функцию с sin на noise, cube, ну и так далее, тут движок предоставляет широкое поле для экспериментов.

Заносим строку rgbGen wave sin 0 1 0 0.4 следующей под строкой с путем для 1-й текстуры (red_h2.tga).
   
[4]
Добавляем после всех параметров второй текстуры rgbGen lightingDiffuse. Все!
Окончательный текст shader будет выглядеть так:

models/players/ranger/red_h
{
{
map models/players/ranger/red_h2.tga
rgbGen wave sin 0 1 0 0.4
}
{
map models/players/ranger/red_h.tga
blendfunc add
rgbGen lightingDiffuse
}
}
   
[5]
Сохраняем, пакуем, смотрим что получилось.
   
  Готовый вариант можно взять здесь (2.48 KB).
 
 

Евгений Шлыков

   
   
Использование программы QAse
Hosted by uCoz