Горячее
Лучшее
Свежее
Подписки
Сообщества
Блоги
Эксперты
Войти
Забыли пароль?
или продолжите с
Создать аккаунт
Я хочу получать рассылки с лучшими постами за неделю
или
Восстановление пароля
Восстановление пароля
Получить код в Telegram
Войти с Яндекс ID Войти через VK ID
Создавая аккаунт, я соглашаюсь с правилами Пикабу и даю согласие на обработку персональных данных.
ПромокодыРаботаКурсыРекламаИгрыПополнение Steam
Пикабу Игры +1000 бесплатных онлайн игр
Нарисуй Удар - это смесь двух жанров: рисование и драки. Вы играете за одну из балерин, которые сражаются друг против друга в смертельном танце.

Испытайте новый игровой опыт в захватывающей игре “Нарисуй Удар” прямо сейчас!

Нарисуй Удар

Драки, На ловкость, Для мальчиков

Играть

Топ прошлой недели

  • AlexKud AlexKud 40 постов
  • unimas unimas 13 постов
  • hapaevilya hapaevilya 2 поста
Посмотреть весь топ

Лучшие посты недели

Рассылка Пикабу: отправляем самые рейтинговые материалы за 7 дней 🔥

Нажимая кнопку «Подписаться на рассылку», я соглашаюсь с Правилами Пикабу и даю согласие на обработку персональных данных.

Спасибо, что подписались!
Пожалуйста, проверьте почту 😊

Помощь Кодекс Пикабу Команда Пикабу Моб. приложение
Правила соцсети О рекомендациях О компании
Промокоды Биг Гик Промокоды Lamoda Промокоды МВидео Промокоды Яндекс Директ Промокоды Отелло Промокоды Aroma Butik Промокоды Яндекс Путешествия Постила Футбол сегодня
0 просмотренных постов скрыто
38
andreibelianin
andreibelianin
1 год назад
TECHNO BROTHER

Ответ на пост «Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году?»⁠⁠1

Когда я учился в 9 классе, а это был вроде 2003 год, начали заниматься программированием на информатике, хотя я начал изучать его чуть раньше. Нам объясняли, как на бейсике отрисовать точку, круг, квадрат, и прописав несколько координат, линиями нарисовать многоугольник в виде разных лодочек. Меня это так увлекло, ведь мы на алгебре как раз строили графики, а задав нужную функцию, вывести точки в виде графика, нет ничего проще. В общем первой моей программой был "решебник" для проверки верности моих построений графиков. Потом втянулся, нажал на кнопочку, нарисована ось координат с необходимым масштабом и штрих отметками, перерисовка при изменении масштабов окна и т.д. были, конечно проблемы с тормозами, ведь если точки рисовать достаточно плотно друг к другу, происходит эффект анимации рисования, а если точки редко, отрисовка быстрая, но приблизительная. В общем довел эту программу практически до уровня примитивного графического редактора, можно сказать даже некое подобие графического движка, способного выводить необходимые 2д модели, сохраняя их в файле и пользоваться ими в дальнейшем как спрайтами. Сделал на нем даже две игры, лабиринт, по которому бегает колобок, и пятнашки. Когда учитель информатики увидел мои работы, он конечно мне отлично поставил за будущие года, так как я там ничего нового больше не узнаю, но в то же время предложил заниматься со мной факультативно. Хоть он мне и сказал, что я изобрел велосипед, и все это уже реализовано в DX, но меня преисполняла гордость, что допер до этого сам. Тем не менее, я купил книжку по директу, и тут понеслось. Как же изменилось качество графики, в основном из за смены буферов. Затем разобрался с фотошопом и 3dsmax. Вуаля, и первая полноценная 3d игра, где все тот же лабиринт из рельс и вагонетки, в которой помимо стандартного прохождения, нужно еще переключать стрелки в нужном порядке, чтобы собирать золото. В общем игра не слишком захватывающая, но играл в нее чаще, чем в КС. Эх как же жаль что исходников не сохранилось.

Показать полностью
[моё] Опрос Горячее Ништяки Программирование Net 3D графика Игры Леталки Пилот Самолет Fw-190 Directx Gamedev Без звука Ответ на пост Текст
6
516
monobogdan
monobogdan
Посты о ремонте и моддинге ретрогаджетов.
TECHNO BROTHER
1 год назад

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году?⁠⁠1

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Осторожно: Несмотря на кажущуюся сложность статьи о разработке целой 3D-игры с нуля, я постарался систематизировать и упростить материал так, чтобы понятно было любому заинтересованному читателю, даже если вы далеки от программирования в целом!

Статьи о разработке инди-игр — это всегда интересно. Но разработка чего-то абсолютно с нуля, без каких-либо движков или фреймворков — ещё интереснее! Почти всю свою жизнь, буквально с 13-14 лет меня тянет пилить какие-нибудь прикольные 3D-демки и игрушки. Ещё на первом курсе ПТУ я написал небольшую демку с 3D-вертолетиками по сети и идея запилить какие-нибудь прикольные леталки не покидала меня по сей день! Спустя 6 лет, в 22 года я собрался с силами и решил написать небольшую аркадную демку про баталии на самолетиках, да так, чтобы работало аж на видеокартах из 90-х — NVidia Riva 128 и 3DFX Voodoo 3! Интересно, как происходит процесс разработки игры с нуля — от первого «тридэ» треугольника, до работающей на реальном железе демки? Тогда добро пожаловать под кат!

❯ Мотивация


Друзья! Вижу, что вам очень заходит моя постоянная рубрика о том, как работали графические ускорители из 90-х «под капотом», где мы не только разбираем их архитектуру, но и пишем демки на их собственных графических API. Мы уже успели с вами рассмотреть 3Dfx Voodoo, S3 ViRGE и мобильный PowerVR MBX и, думаю, теперь пришло время рассмотреть инструменты для разработчиков игр под Windows из 90-х. Про «старый» OpenGL рассказывать смысла не вижу — до сих пор многие новички учатся по материалам с glBegin/glEnd и FFP (Fixed Function Pipeline), а спецификацию с описанием первой версии API можно найти прямо на сайте Khronos. Зато про «старый» DirectX информации в сети очень мало и большинство документации уже потёрли даже из MSDN, хотя в нём было много чего интересного!

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Вероятно читатель спросит — зачем пилить что-то для компьютеров 90-х годов, если большинство таких машин (к сожалению) отправились на цветмет и «никто в своем уме» не будет ими пользоваться? Ну, ретро-компьютинг и программирование демок — это, во-первых, всегда интересно. Среди моих подписчиков довольно много ребят, которые ещё учатся в школе, а уже натаскали с барахолок Pentium III или Pentium IV и GeForce 4 MX440 и сидят, балдеют и играют в замечательные игрушки из нулевых на таких машинах с по настоящему трушным опытом, да и я сам таким был и остаюсь по сей день. Вон, мне даже dlinyj скидывал свои девайсы в личку, а я сидел и слюни пускал. Так что факт остаётся фактом — ретро-компьютинг становится всё более и более популярен — что не может не радовать!

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

А во-вторых — это челлендж для самого себя! Посмотреть на то, как делали игры «деды» и попытаться запилить что-то самому, не забыв об этом написать статью и снять интересное видео в попытке донести это как можно большему числу читателей и зрителей! Конечно сам DirectX6 в целом значительно проще DX12, но некоторые техники весьма заковыристые и для достижения оптимальной производительности приходится пользоваться хаками. Ну а почему именно леталки? Потому что, наверное, хотел бы когда-нибудь полетать :)

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Игру я решил писать на C#. Кому-то решение может показаться странным, но я уже не раз говорил, что это мой любимый язык, а при определенной сноровке — программы на нем работают даже под Windows 98. В качестве основного API для игры я выбрал DirectX 6, который вышел 7 августа 1998 года — за 3 года до моего рождения :)

Перед тем как что-то начинать делать, нужно определиться с тем, что нам нужно для нашей 3D-игры:

  • Графический движок или рендерер, работающий на базе Direct3D. В его задачи входит отрисовка геометрии, работа с освещением и материалами, отсечение моделей, находящихся вне поле зрения глаз, генерация ландшафтов из карт высот и т. п. Собственно, в нашем конкретном случае это графическим движком назвать сложно — никакого полноценного графа (иерархической структуры, как в Unity) сцены нет, толковой анимации тоже, зато есть довольно продвинутая система материалов :)

  • Звуковой движок на базе DirectSound. Здесь всё по классике: программный 3D-звук с эффектами типа «виу» и «вжух» с загрузкой звуковых дорожек из wav-файлов. Никакого стриминга звука с кольцевыми буферами и ogg/mp3 здесь не нужно!

  • Подсистема ввода, которая представляет из себя «получить состояние кнопки на клавиатуре» и «получить позицию курсора» :)

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




Как известно, в самолёте всё зависит от винта! Ну, или в нашем случае, от 3D-движка — поэтому предлагаю рассмотреть архитектуру нашего рендерера и заложить первые кирпичики в нашу 3D-игру!

❯ Графический движок


Поскольку C# — управляемый язык и напрямую дёргать COM-интерфейсы формально не может, а готовых обёрток для DirectX 6 по понятным причинам нет, мне пришлось писать свою. Простыми словами, обёртка обеспечивает слой совместимости между нативными библиотеками, написанными на C++ и управляемым кодом, написанном на C#/VB и т.п. Благо в мире .NET есть такое замечательное, но увы, забытое расширение плюсов, как С++/CLI, которое позволяет прозрачно смешивать нативный код и «байткод» .NET, благодаря которому разработка пошла значительно быстрее.

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Любой графический движок начинается с создания окна и инициализации контекста графического API (инициализации видеокарты, если простыми словами) для рисования в это самое окно. В случае Direct3D6 всё интереснее тем, что фактически здесь уже был свой аналог современного DXGI (DirectX Graphics Infrastructure — библиотека для управления видеокартами, мониторами в системе), который назывался DirectDraw. Изначально DDraw использовался для аппаратного ускорения графики на VGA 2D-акселеллераторах — тех самых S3 ViRGE и Oak Technology и предназначался в основном для операций блиттинга (копирования картинки в картинку), но в D3D ему выделили функции управления видеопамятью и поэтому они очень тесно связаны.

Инициализация начинается с создания так называемой первичной поверхности (которая будет отображаться на экран) и заднего буфера (в который будет рисоваться само изображение), или в терминологии современных API — Swap-chain.

DDSURFACEDESC rtDesc;
memset(&rtDesc, 0, sizeof(rtDesc));
rtDesc.dwSize = sizeof(rtDesc);
rtDesc.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
rtDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;
rtDesc.dwWidth = Width;
rtDesc.dwHeight = Height;
Guard(ddraw->CreateSurface(&rtDesc, &sSurf, 0));
Guard(sSurf->QueryInterface(IID_IDirectDrawSurface4, (LPVOID*)&sSurf4));

Теперь у нас есть окно, куда можно что-нибудь нарисовать!

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Но 3D мы пока рисовать не можем — ведь контекста D3D у нас всё ещё нет, благо создаётся он очень просто. Единственный момент: Z-буфер нужно создать перед созданием устройства, иначе работать он не будет.

DDSURFACEDESC zbufDesc;
memset(&zbufDesc, 0, sizeof(zbufDesc));
zbufDesc.dwSize = sizeof(zbufDesc);
zbufDesc.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
zbufDesc.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | DDSCAPS_VIDEOMEMORY;
memcpy(&zbufDesc.ddpfPixelFormat, Window::zBufferFormat, sizeof(zbufDesc.ddpfPixelFormat));
zbufDesc.dwWidth = Width;
zbufDesc.dwHeight = Height;

IDirectDrawSurface* zTemp;
IDirectDrawSurface4* zSurface;
Guard(ddraw->CreateSurface(&zbufDesc, &zTemp, 0));
Guard(zTemp->QueryInterface(IID_IDirectDrawSurface4, (LPVOID*)&zSurface));

// Attach Z-Buffer to backbuffer
Guard(d3dSurface->AddAttachedSurface(zSurface));
Guard(d3d->CreateDevice(IID_IDirect3DHALDevice, surf, &device, 0));

Мы уже на полпути перед тем как нарисовать первый тридэ-треугольник: осталось лишь объявить структуру вершины и написать обёртки над… Begin/End! Да, в Direct3D когда-то тоже была концепция из OpenGL, а связана она с тем, что в видеокартах тех лет вершины передавались не буферами, а по одному, уже трансформированные. Подробнее об этом можно почитать в моей статье о S3 ViRGE:

public value struct Vertex
{
public:
float X, Y, Z;
float NX, NY, NZ;
D3DCOLOR Diffuse;
float U, V;
};

...

Vertex[] v = new Vertex[3];
v[0] = new Vertex()
{
X = 0,
Y = 0,
Z = 0,
U = 0,
V = 0
};
v[1] = new Vertex()
{
X = 1,
Y = 0,
Z = 0,
U = 1,
V = 0
};
v[2] = new Vertex()
{
X = 1,
Y = 1,
Z = 0,
U = 1,
V = 1
};

dev.BeginScene();
dev.Begin(PrimitiveType.TriangleList, Device.VertexFormat);
dev.Vertex(v[0]);
dev.Vertex(v[1]);
dev.Vertex(v[2]);
dev.End();
dev.EndScene();

И вот, у нас есть первый треугольник! Читатель может спросить — а где же здесь игра и причём здесь треугольники, мы же не на уроке геометрии… Дело в том, что вся 3D-графика в современных играх строится из треугольников. Любая моделька на экране — это набор из маленьких примитивов, которые в процессе рисования на экран подвергаются процессу трансформации — преобразованию из мировых координат (то есть абсолютной позиции в мире) сначала в координаты камеры (таким образом, при движении камеры, на самом деле двигаются объекты вокруг камеры), а затем и в экранные координаты, где происходит перспективное деление и каждый треугольник начинает выглядеть как трёхмерный…

Таким образом, из тысяч треугольников можно описать самые разные объекты — от трёхмерной модели моих любимых «жигулей», до персонажей.

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Но если сейчас нарисовать самолетик, то он будет исключительно белым, без намёка на освещение или детали. А для его «раскрашивания» служат текстуры — специальные изображения, подогнанные под текстурные координаты геометрии, которые помогают дополнить образ 3D-моделей деталями: асфальт на дороге, трава на земле, дверная карты в жигулях…

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

И вот с текстурами ситуация в D3D6 не менее интересная и очень похожа на современные GAPI: нам необходимо сначала создать текстуру в системной памяти (ОЗУ) и только затем скопировать её в видеопамять. Причём форматов текстур не слишком много. Я выбрал RGB565 (16-битный), хотя есть поддержка и форматов со сжатием — тот-же S3TC.

bool hasMips = mipCount > 1; // If texture has more than 1 mipmap, then create surface as complex, if not - then as single-level.

DDSURFACEDESC2 desc;
memset(&desc, 0, sizeof(desc));
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_TEXTURESTAGE | DDSD_CKSRCBLT;
desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE | (hasMips ? (DDSCAPS_MIPMAP | DDSCAPS_COMPLEX) : 0);
desc.ddsCaps.dwCaps2 = DDSCAPS2_TEXTUREMANAGE;
desc.ddckCKSrcBlt.dwColorSpaceHighValue = 0;
desc.ddckCKSrcBlt.dwColorSpaceLowValue = 0;
memcpy(&desc.ddpfPixelFormat, DXSharp::Helpers::Window::opaqueTextureFormat, sizeof(desc.ddpfPixelFormat));
desc.dwWidth = Width = width;
desc.dwHeight = Height = height;

IDirectDrawSurface4* surf;
IDirect3DTexture2* tex;

IDirectDraw4* dd2;
window->ddraw->QueryInterface(IID_IDirectDraw4, (LPVOID*)&dd2);

Guard(dd2->CreateSurface(&desc, &surf, 0));
Guard(surf->QueryInterface(IID_IDirect3DTexture2, (LPVOID*)&tex));

А чтобы её использовать, нужно «сказать» об этом видеокарте с помощью биндинга текстуры к текстурному юниту. Те, у кого были в свое время 3dfx Voodoo, наверняка поймут, о чём я :)

Guard(device->SetTexture(stage, tex->texture));

И вот у нас уже есть треугольник с текстурой! Осталось лишь домножить его матрицы трансформации, перспективную матрицу…

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Реализуем простенький загрузчик моделей из формата SMD (GoldSrc, Half-Life или CS1.6), который грузит статичные модельки без скиннинга, а также загрузчик текстур из bmp и вот — мы уже имеем 3D-модельку самолёта с текстурой.

for(int i = 0; i < smd.Triangles.Count; i++)
{
uint c = new Color(255, 255, 255, 255).GetRGBA();

for (int j = 0; j < 3; j++)
vert[i * 3 + j] = new Vertex()
{
X = smd.Triangles[i].Verts[j].Position.X,
Y = smd.Triangles[i].Verts[j].Position.Y,
Z = smd.Triangles[i].Verts[j].Position.Z,
U = smd.Triangles[i].Verts[j].UV.X,
V = smd.Triangles[i].Verts[j].UV.Y,
NX = smd.Triangles[i].Verts[j].Normal.X,
NY = smd.Triangles[i].Verts[j].Normal.Y,
NZ = smd.Triangles[i].Verts[j].Normal.Z,
Diffuse = c
};
}

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

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

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

  • Sky-sphere, которая заключается в том, что небо представляет из себя полусферу с наложенной поверх текстурой неба в специальном формате. Такую полусферу очень часто крутят вокруг своей оси по оси Y, создавая эффект плывущих облаков. И получается вполне себе симпатичное анимированное небо. Иные варианты включают в себя многослойные реализации, где крутится могут лишь облака, когда статичные элементы фона остаются на месте.

  • Skybox — здесь суть простая, вокруг камеры рисуется «коробка» с вывернутыми в обратную сторону треугольниками, на которых рисуется текстура одной из сторон панорамы с выключенной записью в Z-буфер. Получается не только симпатично, но ещё и быстрее Skysphere на слабом железе, правда скайбоксы обычно статичным. Скайбоксы можно найти почти везде: например, в Counter-Strike, Half-Life.

    На скриншоте ниже можно увидеть пример скайбокса:

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост



Я выбрал скайбоксы. Реализация — проще пареной репы:

materials[0].Texture = TextureLoader.LoadFromImage(string.Format("{0}{1}_bk.bmp", Path, name));
materials[1].Texture = TextureLoader.LoadFromImage(string.Format("{0}{1}_ft.bmp", Path, name));
materials[2].Texture = TextureLoader.LoadFromImage(string.Format("{0}{1}_lf.bmp", Path, name));
materials[3].Texture = TextureLoader.LoadFromImage(string.Format("{0}{1}_rt.bmp", Path, name));
materials[4].Texture = TextureLoader.LoadFromImage(string.Format("{0}{1}_up.bmp", Path, name));
materials[5].Texture = TextureLoader.LoadFromImage(string.Format("{0}{1}_dn.bmp", Path, name));

....

Engine.Current.Graphics.DrawMesh(mesh, 0, 6, v, new Vector3(0, 0, 0), new Vector3(1, 1, 1), materials[1]); // Forward
Engine.Current.Graphics.DrawMesh(mesh, 6, 12, v, new Vector3(0, 0, 0), new Vector3(1, 1, 1), materials[3]); // Right
Engine.Current.Graphics.DrawMesh(mesh, 12, 18, v, new Vector3(0, 0, 0), new Vector3(1, 1, 1), materials[0]); // Back
Engine.Current.Graphics.DrawMesh(mesh, 18, 24, v, new Vector3(0, 0, 0), new Vector3(1, 1, 1), materials[2]); // Left
Engine.Current.Graphics.DrawMesh(mesh, 24, 30, v, new Vector3(0, 0, 0), new Vector3(1, 1, 1), materials[4]); // Left

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

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

Мы проходимся по всей картинке и строим сетку треугольников, где высота определяется именно соседними пикселями на этой самой карте высот. На практике это выглядит так:

for (int i = 1; i < bmp.Width - 1; i++)
{
for(int j = 1; j < bmp.Height - 1; j++)
{
float baseX = (float)i * XZScale;
float baseZ = (float)j * XZScale;

// Transform vertices
verts[vertOffset] = new DXSharp.D3D.Vertex()
{
X = baseX,
Y = ((float)bmp.GetPixel(i, j).R / 255.0f) * YScale,
Z = baseZ,
U = 0,
V = 1 * TextureScale,
NY = 1
};
verts[vertOffset + 2] = new DXSharp.D3D.Vertex()
{
X = baseX,
Y = ((float)bmp.GetPixel(i, j + 1).R / 255.0f) * YScale,
Z = baseZ + XZScale,
U = 0,
V = 0,
NY = 1
};
verts[vertOffset + 1] = new DXSharp.D3D.Vertex()
{
X = baseX + XZScale,
Y = ((float)bmp.GetPixel(i + 1, j + 1).R / 255.0f) * YScale,
Z = baseZ + XZScale,
U = 1 * TextureScale,
V = 0,
NY = 1
};
verts[vertOffset + 3] = new DXSharp.D3D.Vertex()
{
X = baseX,
Y = ((float)bmp.GetPixel(i, j).R / 255.0f) * YScale,
Z = baseZ,
U = 0,
V = 1 * TextureScale,
NY = 1
};
verts[vertOffset + 4] = new DXSharp.D3D.Vertex()
{
X = baseX + XZScale,
Y = ((float)bmp.GetPixel(i + 1, j).R / 255.0f) * YScale,
Z = baseZ,
U = 1 * TextureScale,
V = 1 * TextureScale,
NY = 1
};
verts[vertOffset + 5] = new DXSharp.D3D.Vertex()
{
X = baseX + XZScale,
Y = ((float)bmp.GetPixel(i + 1, j + 1).R / 255.0f) * YScale,
Z = baseZ + XZScale,
U = 1 * TextureScale,
V = 0,
NY = 1
};

vertOffset += 6;
}
}

А результат — такой! Это самый простой кейс с Terrain'ом: в реальных играх, где ландшафт достаточно большой, его обычно бьют на так называемые патчи и дальние участки ландшафта упрощают с помощью специальных алгоритмов. Таким образом построены ландшафтры, например, в TES Skyrim.

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

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

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Этот способ даёт возможность использовать всего лишь две текстуры за один проход, в современных играх используется сплат-маппинг, позволяющий использовать более 4х-текстур за один проход!

Context.SetTextureStageState(1, (int)TextureStageState.AlphaOp, (int)TextureStageOp.Modulate);
Context.SetTextureStageState(1, (int)TextureStageState.AlphaArg1, (int)TextureArgument.Texture);
Context.SetTextureStageState(1, (int)TextureStageState.AlphaArg2, (int)TextureArgument.Texture);

Context.SetTextureStageState(0, (int)TextureStageState.ColorOp, (int)TextureStageOp.SelectArg1);
Context.SetTextureStageState(0, (int)TextureStageState.ColorArg1, (int)TextureArgument.Texture);
Context.SetTextureStageState(0, (int)TextureStageState.ColorArg2, (int)TextureArgument.Texture);

Context.SetTextureStageState(1, (int)TextureStageState.ColorOp, (int)TextureStageOp.BlendDiffuseAlpha);
Context.SetTextureStageState(1, (int)TextureStageState.ColorArg1, (int)TextureArgument.Texture);
Context.SetTextureStageState(1, (int)TextureStageState.ColorArg2, (int)TextureArgument.Current);

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Но тем не менее, выглядит вполне прикольно. Однако текстуры вдали выглядят слишком грубо и отдают пикселями. Ретро-стайл скажете вы? Согласен, но фильтрация и мипмаппинг здесь необходимы! Мип-маппинг — это техника, которая делит большую текстуру на несколько небольших разного размера. Каждый размер называется mip-уровнем и в два раза меньше прошлого: таким образом, у текстуры 256x256 9 уровней: 256x256, 128x128, 64x64 и так до 1x1. Мой самопальный конвертер текстур в собственный формат заранее «запекает» все мип-уровни, дабы быстро грузить текстуры с медленных HDD, а линейная фильтрация с мипмаппингом позволяет сгладить текстуры вдали, дабы они не резали глаза:

device->SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTFP_LINEAR);
device->SetTextureStageState(0, D3DTSS_MINFILTER, D3DFILTER_LINEAR);
device->SetTextureStageState(0, D3DTSS_MAGFILTER, D3DFILTER_LINEAR);

device->SetTextureStageState(1, D3DTSS_MIPFILTER, D3DTFP_LINEAR);
device->SetTextureStageState(1, D3DTSS_MINFILTER, D3DFILTER_LINEAR);
device->SetTextureStageState(1, D3DTSS_MAGFILTER, D3DFILTER_LINEAR);

Ну и давайте же посадим немного деревьев на наш ландшафт! Для этого я добавил псевдослучайное добавление деревьев и кустов при генерации геометрии ландшафта:

if (rand.Next(0, 32) % 8 == 0)
foliageBatches.Add(new FoliagePlacement()
{
Mesh = foliage[rand.Next(0, foliage.Length)],
Position = new Vector3(baseX, ((float)bmp.GetPixel(i, j).R / 255.0f) * YScale, baseZ)
});

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Упс, наши деревья — черные! А всё потому, что у них нет альфа-канала, благодаря которому видеокарта может отделить прозрачные пиксели текстуры от непрозрачных. Полноценный альфа-блендинг (полупрозрачность) здесь слишком дорогой, поэтому приходится использовать технику, называемую колоркеями (Color key). Техника очень схожая с Chromakey, благодаря которым вырезают фон из видео, но чуть попроще (тем, что цвет прозрачности фиксированный, без Threshold). У нас есть определенный цвет, который считается прозрачным и не используется во всей картинке. Нередко это Magenta, в моём случае — полностью чёрный:

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Включаем колоркей и наслаждаемся прозрачными деревьями на фоне ландшафта!

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Ой-ой, а FPS то успел просесть с 1.000 до 50 из-за большого количества DIP'ов (и не очень хорошей работе современных GPU с старыми гапи). Время оптимизаций! Пока что нам хватит обычного Frustum culling'а, также известного как «отсечение по пирамиде видимости». Суть алгоритма простая: из матрицы вида и проекции строятся 6 плоскостей, каждая из которых описывает одну из сторон системы координат: левая, правая, верхняя, нижняя, ближняя и дальняя. Таким образом, делая обычную проверку нахождения точки в World-space и одной из плоскостей, мы можем отсечь невидимую глазам геометрию и не тратить ресурсы GPU и CPU на отрисовку невидимой геометрии:

public void Calculate(Matrix viewProj)
{
float[] items = viewProj.Items;
Planes[0] = new Vector4(items[3] - items[0], items[7] - items[4], items[11] - items[8], items[15] - items[12]);
Planes[0].Normalize();
Planes[1] = new Vector4(items[3] + items[0], items[7] + items[4], items[11] + items[8], items[15] + items[12]);
Planes[1].Normalize();
Planes[2] = new Vector4(items[3] + items[1], items[7] + items[5], items[11] + items[9], items[15] + items[13]);
Planes[2].Normalize();
Planes[3] = new Vector4(items[3] - items[1], items[7] - items[5], items[11] - items[9], items[15] - items[13]);
Planes[3].Normalize();

Planes[4] = new Vector4(items[3] - items[2], items[7] - items[6], items[11] - items[10], items[15] - items[14]);
Planes[4].Normalize();
Planes[5] = new Vector4(items[3] + items[2], items[7] + items[6], items[11] + items[10], items[15] + items[14]);
Planes[5].Normalize();
}

// Allocation-less
public bool IsPointInFrustum(float x, float y, float z)
{
foreach(Vector4 v in Planes)
{
if (v.X * x + v.Y * y + v.Z * z + v.W <= 0)
return false;
}

return true;
}

public bool IsSphereInFrustum(float x, float y, float z, float radius)
{
foreach (Vector4 v in Planes)
{
if (v.X * x + v.Y * y + v.Z * z + v.W <= -radius)
return false;
}

return true;
}

Затем проверяем, находится ли сфера внутри каждой из 6 плоскостей и если нет, то не рисуем геометрию вообще:

if (mesh.Radius > 0 && !Camera.IsSphereVisible(position, mesh.Radius))
return;

С учётом всех оптимизацией, получаем 17-20 кадров на этом GPU что можно считать… весьма неплохим результатом, учитывая что всё ещё есть куда оптимизировать!

❯ Звук


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


Инициализация контекста DSound начинается с создания primary-буфера, который выступает в роли микшера перед отправкой звука на аудио-карту. Создаётся он довольно легко:

BufferDescription desc = new BufferDescription();
desc.Flags = BufferFlags.PrimaryBuffer | BufferFlags.Control3D;

primaryBuffer = Context.CreateSoundBuffer(desc);

После этого, в самом простом случае (без стриминга звука) нам достаточно лишь выгрузить PCM-поток на аудио-карту и начать его играть:

public WaveBuffer(WaveFormat fmt, byte[] pcmData)
{
BufferDescription desc = new BufferDescription();
desc.BufferBytes = (uint)pcmData.Length;
desc.Flags = BufferFlags.ControlDefault |BufferFlags.Software;
desc.Format = fmt;

buffer = Engine.Current.Sound.Context.CreateSoundBuffer(desc);
IntPtr data = buffer.Lock();
Marshal.Copy(pcmData, 0, data, pcmData.Length);
buffer.Unlock();

buffer.Play();
}

И всё! Да, вот так легко. BufferFlags.Software заменяется на Hardware, если необходимо аппаратное ускорение.

❯ Ввод


Пожалуй, это самая простая часть нашей статьи :) Как я уже говорил ранее, никакого особого функционала от модуля обработки ввода не нужно, лишь получать состояние кнопок — и с этим справляется лишь один метод…

[DllImport("user32.dll")]
static extern short GetAsyncKeyState(Keys vKey);

public static bool GetKeyState(Keys key)
{
return (GetAsyncKeyState(key) & 0x8000) != 0;
}


Ну что ж, основа готова, давайте перейдем к реализации самого геймплея!

❯ Пилим геймплей


Сначала нам нужно реализовать логику полёта нашего самолётика. В целом, в нашем конкретном кейсе всё просто — для поворотов используем углы Эйлера (лень было писать класс для кватерниона), считаем Forward-вектор (вектор, указывающий на направление прямо) и просто крутим повороты по оси X и Y в нужную сторону, прибавляя к позиции самолетика Forward вектор, умноженный на скорость полёта. Правда, с таким подходом есть некоторые проблемы: выполнить петлю не получится, поскольку Forward-вектор всегда смотрит именно прямо и не учитывает обратную направленность по оси X.

Rotation.X += -v * (YawSpeed * Engine.Current.DeltaTime);
Rotation.Y += h * (YawSpeed * Engine.Current.DeltaTime);

Rotation.Z = MathUtils.Lerp(Rotation.Z, 35 * -h, 4.0f * Engine.Current.DeltaTime);

Vector3 fw = GetForward();
Position.X += fw.X * (Speed * Engine.Current.DeltaTime);
Position.Y += fw.Y * (Speed * Engine.Current.DeltaTime);
Position.Z += fw.Z * (Speed * Engine.Current.DeltaTime);

Мы с вами хотим, чтобы камера всегда следила за нашим самолётиком. Для этого нужно взять Forward-вектор объекта и умножить каждую его компоненту на дальность от источника камеры. Эдакая бомж-версия lookat, правда с кучей ограничений, как минимум с Gimbal lock (потерей одной из осей поворота), а чтобы камера казалась плавной и придавала динамичности игре — мы делаем EaseIn/EaseOut эффект путём неправильного использования формулы линейной интерполяции :)

Vector3 forward = GetForward();
// Adjust camera
Engine.Current.Graphics.Camera.Position = new Vector3(Position.X + (forward.X * -12.0f),
Position.Y + (forward.Y * -12.0f) + 4.0f, Position.Z + (forward.Z * -12.0f));
Engine.Current.Graphics.Camera.Rotation.Y = MathUtils.Lerp(Engine.Current.Graphics.Camera.Rotation.Y, Rotation.Y + (yaw * 30), 3.0f * Engine.Current.DeltaTime);
Engine.Current.Graphics.Camera.Rotation.X = MathUtils.Lerp(Engine.Current.Graphics.Camera.Rotation.X, Rotation.X + (pitch * 5), 3.0f * Engine.Current.DeltaTime);
Engine.Current.Graphics.Camera.MarkUpdated();

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Ну, летать мы с вами уже можем… да, сильно по аркадному, но всё же :) Пришло время реализовать каких-нибудь соперников, а именно вражеские самолёты! Вообще, реализация нормального ИИ на самолетах, тем более в симуляторах — задачка очень нетривиальная, поскольку боты будут либо читерить, используя не те рычаги, что использует игрок, либо тупить и играть будет не сильно интересно. Вон, что «Варгейминг», что «Гайдзины» крутые в этом плане — я б ниасилил нормальных ботов для мультиплеерного симулятора или даже аркады :))

Вычисляем угол между позицией самолетика соперника и позицией игрока и интерполируем текущий угол по оси Y: получается вполне плавно, правда в нормальных играх ещё и компенсируют эффект «плаванья» вокруг игрока по синусоиде. Для подъёма и спуска по вертикали просто берём абсолютную величину выше/ниже:

float angle = (float)Math.Atan2(Game.Current.Player.Position.X - Position.X, Game.Current.Player.Position.Z - Position.Z);
float vert = MathUtils.Clamp(Position.Y - Game.Current.Player.Position.Y, -1, 1);
Rotation.X = MathUtils.Lerp(Rotation.X, vert * 35, 1.5f * Engine.Current.DeltaTime);

float prevY = Rotation.Y;
Rotation.Y = MathUtils.Lerp(Rotation.Y, angle * MathUtils.RadToDeg, 1.5f * Engine.Current.DeltaTime);
float diffY = Rotation.Y - prevY > 0 ? 1 : -1;
Rotation.Z = MathUtils.Lerp(Rotation.Z, 15 * -diffY, 4.0f * Engine.Current.DeltaTime);

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

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

Ну что ж, демка у нас есть и в этот раз я подготовился получше, чем в статье про 3dfx Voodoo: я собрал целых два тестовых стенда и попросил у подписчиков потестировать демку на своих машинах с диковинным железом из 90-х и нулевых годов. Железо у нас такое:

  • Процессор: Celeron 600MHz Coppermine

  • ОЗУ: 192Mb SDRAM 133MHz

  • GPU: Asus GeForce 4 MX420

  • ОС: WinXP SP3

Сам написал, сам полетал: как и зачем я разработал 3D-игру с нуля под компьютеры из 90-х в 2024 году? Опрос, Горячее, Ништяки, Программирование, Net, 3D графика, Игры, Леталки, Пилот, Самолет, Fw-190, Directx, Gamedev, Видео, Без звука, Длиннопост

На Win98 я так и не смог нормально накатить драйвера на MSDC (Mass Storage Device Class — «флэшки»), поэтому «считерил» и поставил WinXP. Изначально я планировал ставить Win2000 — но там .NET 2.0 работает с косяками (при том что этот же самый .NET работает на Win98!).

❯ Тесты


Давайте же посмотрим, как демка идёт на трушном железе. Для наглядности, я решил записать видео.

Переходим к интегрированной графике, а именно к EEEPC 701 4G с Intel GMA 900 на борту! Те, кто знают что такое GMA, понимают насколько эти встройки не приспособлены для игр. Несмотря на наличие поддержки вторых шейдеров, из-за отсутствия аппаратного вершинного конвейера чип ничего не тянет. Но моя игрушка — исключение и она работает на удивление очень даже неплохо! 15-20 кадров точно есть и это при том что есть куда оптимизировать!

А дальше у нас идут тесты от подписчиков в Telegram-канале, которым я скинул билд и пригласил потестить демку на ретро-железе. Первый тест от читателя на ноутбуке с Pentium III и редкой встройкой Trident CyberBlade XP показал весьма неплохой результат — 15-20 кадров:

Дальше тот же читатель, имя которое он просил не раскрывать, потестил демку на ATI Rage M6 — очень и очень бодрый GPU, который выдает стабильные 20-25-30 кадров!

❯ Заключение


Вот такая демка, мини-игрушка у меня получилось. Да, весьма примитивненько, зато прикольно, запилено за пару дней и можно полетать на виртуальных самолетиках. Также у меня есть Telegram-канал, куда я публикую различные мысли связанные с подручным ремонтом, моддингом и программированием под гаджеты прошлых лет, а также публикую туда ссылки на новые статьи и видео! Найти исходный код демки вы можете на моём Github.

Понравилась статья? Пишите своё мнение в комментариях, я старался :)

Статья подготовлена при поддержке TimeWeb.Cloud. Подписывайтесь на меня и @Timeweb.Cloud, чтобы не пропускать новые статьи каждую неделю!

Статья...
Всего голосов:
Показать полностью 20 4 1
[моё] Опрос Горячее Ништяки Программирование Net 3D графика Игры Леталки Пилот Самолет Fw-190 Directx Gamedev Видео Без звука Длиннопост
95
278
monobogdan
monobogdan
Посты о ремонте и моддинге ретрогаджетов.
TECHNO BROTHER
1 год назад

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One⁠⁠

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

В своей жизни я обожаю как минимум три вещи: это C# (как и .NET в целом), интересное железо и одноплатные компьютеры. В Embedded-системах на Linux обычно принято писать код на C/C++ для решения чувствительных к производительности задач и интерпретируемых Lua/Python для быстрого прототипирования, которые стали популярны в встраиваемых устройствах сравнительно недавно. Однако о нативной разработке под одноплатники на C# практически ничего не слышно и я решил исправить это недоразумение! В сегодняшнем материале: рассмотрим, какие платформы .NET нам доступны на одноплатниках, научимся работать с GPIO и SPI в юзерспейсе, а также напишем практическое приложение, которое реализовывает драйвер дисплея и выводит на экран определенное изображение.

❯ Предисловие


Одноплатники уже давно вошли в повседневную жизнь многих DIY-щиков, сисадминов и людей, которые интересуются мини-компьютерами. Казалось бы, одну и ту же задачу можно решить несколькими методами на самых разных языках: кто-то предпочитает писать нативный код на тех же плюсах, а особо прожженные — на Plain-C и ассемблере, стараясь получить максимальную производительность, а кто-то хочет сразу перейти к реализации своего устройства не заморачиваясь с подробным изучением того, как чип работает «под капотом» и какие шины существуют, ограничиваясь использованием готовых библиотек.

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Но я лично очень люблю C# за его максимальную гибкость, позволяющую оптимизировать некоторые обращения к памяти путем получения прямых указателей на данные, умеет в удобные темплейты, а также имеет механизм для маршаллинга (прямой импорт функций из библиотек, возможность создать нативный трамплин на управляемый делегат, возможность быстрого копирования из unmanaged в managed окружение и т. п.). Потому всегда думал: почему бы его не использовать в своих embedded-проектах на базе одноплатников?

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Сейчас .NET можно накатить на большинство современных одноплатников, за исключением самых слабых с 64Мб ОЗУ «бутербродом» на чипе (AllWinner F1C100s, AllWinner V3s, некоторые MStar и т. п.). Доступно два рантайма, которые предлагают разные профили и соответственно, разный функционал.

  • dotnet — официальный рантайм, который реализует профиль .NET Core (ой, простите, так уже не модно, теперь это просто .NET). Предоставляет весь современный базовый функционал дотнета вкупе с современными версиями самого C#, но в нём нет, например, Windows Forms для UI (если вы используете полноценные «иксы» и GTK), и System.Drawing для обработки графики и отрисовки текста. Это эталонная реализация дотнета и его можно без проблем накатить на любой одноплатник, для которого есть достаточно свежий Linux.

  • Mono — альтернативная реализация .NET Framework для Linux, ранее активно использовалась в Unity. В отличии от .NET Core, может работать и на более старых одноплатниках на прошлых версиях дистрибутивов Linux, в том числе и самой первой Raspberry Pi. Считается более медленной, чем dotnet, зато имеет значительно большую функциональность, почти идентичную фреймворку на Windows.


В сегодняшней статье мы будем писать программу на C# для OrangePi One, которая должна инициализировать дисплей из юзерспейса и выводить на него определенные данные. В качестве профиля используем .NET Framework 4 (да, я порой старомоден), а одноплатником выступит OrangePi One в стоковой конфигурации ядра, без правок devicetree, где по умолчанию у нас доступен spidev без аппаратных чипселектов, доступ к GPIO из /sys/ и i2cdev.

❯ Настраиваем окружение


Для начала нам нужен образ системы для нашего одноплатника. Какой — выбирать вам. Для большинства устройств на чипсетах AllWinner доступны образы с ядром 3.x, которые более стабильны, но не используют devicetree и не входят в мейнлайн и 5.x, так называемый мейнлайн, но там всё ещё есть некоторые нюансы. Я выбрал Ubuntu Xenial с ядром 5.3.5.

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Теперь самое время накатить рантайм, что мы и делаем командой:

apt-get install mono-all

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

mono assembly.exe

Давайте же перейдём к фактической реализации нашей программы и узнаем как работать с периферией устройства!

❯ GPIO


Начинаем с GPIO или «ногодрыга». В Linux есть удобный интерфейс, позволяющий экспортировать пины общего назначения в юзерспейс и рулить ими прямо из sysfs, в том числе и из терминала! Для реализации софтварного SPI или быстрого опроса цифровых пинов такой способ не подойдет — слишком большой оверхед, но для моргания светодиодами, обработки кнопок или… программного ногодрыга чипселектом — вполне подойдет :)

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Как я и говорил выше, GPIO сначала нужно сделать видимым в sysfs — т. е. экспортировать, путём записи номера нужного пина в «файл» /sys/class/gpio/export. Посчитать ID нужного пина можно с помощью простой формулы: (позиция буквы в алфавите — 1) * 32 + номер пина. То есть, для PA10 ID будет 10. При ошибке, системный вызов close выбросит ошибку, а поток в C# — IOException.

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

После этого, по пути /sys/class/gpio/gpio10/ появится директория с файлами direction, куда нужно записать направление нашего пина («in» — ввод, «out» — вывод) и value, куда мы будем записывать или читать значение пина. Реализовать управление пином можно так:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Да, всё так просто! Мигалка светодиодом в нашем случае будет выглядеть так:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Переходим к чему посложнее, а именно к SPI из всё того-же юзерспейса!

❯ SPI


Для управления SPI нам потребуется вызов ioctl, который позволяет отправлять устройству различные пакеты с описанием команд. Для этого нам пригодится PInvoke:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Для каждой аппаратной шины SPI создаётся одно устройство spidev. В случае OrangePi One, по умолчанию экспортирована только одна шина (поскольку и SPI-контроллер на гребенке лишь один) — spidev0.0. Для начала открываем наше устройство для записи:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Драйвер spidev работает по принципу транзакций — вы посылаете IOCTL с запросом SPI_IOC_MESSAGE (в оригинале это макрос с возможностью послать сразу несколько транзакций в драйвер) и указателем на структуру spi_ioc_transfer с описанием отправляемых или получаемых данных, а драйвер уже сам решает что и когда отправить, при этом вызов ioctl — блокирующий, то есть управление в поток вернется только когда драйвер завершит работу. Но есть нюанс — драйвер SPI у чипсетов AllWinner не может отправлять более 128-байт (на AllWinner A10/A13 — 64-байт) данных за транзакцию, поэтому большой массив данных придётся разбивать на несколько мелких:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Уже в шоке от обилия указателей в коде на шарпе? :) Надеюсь, комментарии помогут вам разобраться.

Тоже самое и для чтения данных с шины, только вместо txBufPointer — rxBufPointer.

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Пример работы прост до безобразия:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Имея GPIO и SPI уже можно переходить к реализации чего-то более конкретного!

❯ Дисплей


В качестве дисплея я буду использовать стандартную дешёвую 2.4" матрицу с разрешением 240x320 и контроллером ST7789 с интерфейсом SPI. Для использования дисплея с питанием 3.3В нужно поставить перемычку на позиции J1, как показано на фото ниже.

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Для подключения такого дисплея, достаточно всего лишь 4 (5, если нужен чипселект) сигнальные линии на 40-пиновой гребенке RPi One, плюс один для ШИМ (если нужно регулировать подсветку) и два на питание. Обратите внимание, что лучше сдуть гребенку и паяться к одноплатнику напрямую — у меня из-за китайских дюпонтов постоянно помехи на дисплее и мусор на шине.

Схема подключения:

VCC -> 3.3V

GND -> Масса

CS -> PA9

RESET - PA10

D/C - PA20

MOSI - PC0

SCK - PC2

LED -> 3.3V

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Начинаем с подготовки необходимых GPIO. Для управления дисплеем всегда нужен аппаратный RESET и D/C (бит команда/данные). Чипселект необязателен (его можно кинуть на массу), если это будет единственное устройство на шине, однако в случае ST7789 почему-то в таком случае нужно использовать SPI MODE 3.

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Переходим к реализации коммуникации с дисплеем. Здесь всё просто — ставим CS в низкий уровень, начиная транзакцию, устанавливаем D/C в низкий уровень в случае команды, либо высокий в случае данных и отправляем байт контроллеру, после чего устанавливаем чипселект обратно в высокий уровень.

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Теперь дисплей нужно инициализировать. Здесь нужно сконфигурировать регистры контроллера дисплея для установки режима адресации, цветности и порядка байт в пикселях (BGR или RGB).

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Если всё сделано правильно — то после этого вы должны увидеть «мусор» на дисплее, поскольку состояние ОЗУ не определено после подачи питания на контроллер (но при сбросе содержимое DRAM останется на месте).

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

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

После этого, достаточно лишь непрерывно слать изображение на контроллер дисплея и всё будет работать!

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Поскольку ни один формат изображений не соответствовал моим требованиям (RGB565, без выравнивания), я быстренько накостылил конвертер в самопальный:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

Загрузчик такого формата выглядит так:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

А фактическое использование — так:

Пишем программы на C# для одноплатников: что, как и где на примере Orange Pi One Своими руками, Гаджеты, Orange Pi, Raspberry Pi, Программирование, Net, Код, Embedded, Ништяки, Туториал, Умный дом, Дисплей, Длиннопост

❯ Заключение

Как мы видим, писать программы для одноплатников на C# отнюдь не сложно и можно пользоваться всеми приятными фишками языка. Часть кода из этой статьи выдрана из моего сайд-проекта, о котором хочу рассказать вам в ближайшее время — поэтому местами код совсем не причесан, но надеюсь — всё было понятно :)

Также у меня есть канал в Telegram, куда я выкладываю посты с тематикой DIY, ремонта и моддинга, а также программирования под гаджеты прошлых лет и вовремя ссылки на новые статьи.

Показать полностью 22
[моё] Своими руками Гаджеты Orange Pi Raspberry Pi Программирование Net Код Embedded Ништяки Туториал Умный дом Дисплей Длиннопост
29
Партнёрский материал Реклама
specials
specials

Сколько нужно времени, чтобы уложить теплый пол?⁠⁠

Точно не скажем, но в нашем проекте с этим можно справиться буквально за минуту одной левой!

Попробовать

Ремонт Теплый пол Текст
user8633032
1 год назад

Играю по сети в игры любые⁠⁠

телеграм дискорд kogabakamaru

Особенно дырябло и ымть норт!

RPG Игры Net Мультиплеер Telegram TikTok Discord Текст
3
7
Sharah35
2 года назад

Baza.net запихнули так в гофру и сдали канал связи⁠⁠

Baza.net запихнули так в гофру и сдали канал связи Безопасность, Провайдер, Рукожоп, Baza, Net
Показать полностью 1
Безопасность Провайдер Рукожоп Baza Net
4
7
JavNikTool
2 года назад
Настольные игры

Моя первая программа⁠⁠

Привет пикабу! с недавнего времени я начал изучать c# и .net, а совсем недавно мои загребущие рученьки добрались и до wpf, поэтому я, как любитель настольных игр, решил сделать программку для игры в древний ужас. Изначально программа задумывалась для случайного определения сыщиков и древнего перед партией (уж больно я люблю рандомить, но лень заморачиваться бросками монеток и прочим колхозом), но в итоге "разрослась" и до случайного выбора карты прелюдий.
Собственно воть, делюсь с вами сея "шедевром". Т.к это мой первый проект, то просьба судить строго)
Ну и по классике, чукча не дизайнер...

https://github.com/JavNikTool/fullRandomEH
Запускается из \bin\Release\net6.0-windows

[моё] Настольные игры Говард Филлипс Лавкрафт Программирование Net Hobbygames Текст
17
Аноним
Аноним
2 года назад
Типичный программист

Поиск разработчиков⁠⁠

Доброго, возникла потребность в разработчике и стандартными методами как то не решается, может быть кто сможет подсказать место для поиска c# разработчика? стандартные сайты вакансий, тг каналы с теми же вакансиями, но под современным соусом и платно можно не предлагать. спасибо

[моё] IT Рекрутинг Net Программирование Текст
26
Партнёрский материал Реклама
specials
specials

Разбираетесь в укладке теплого пола лучше, чем профи?⁠⁠

Проверьте, насколько вы круты в монтаже, и порадуйте котика.

Кот Ремонт Текст
12
Mistred
2 года назад

Самописный переключатель треков для Windows⁠⁠

Очень люблю слушать музыку на пк во время работы через приложение "Яндекс.Музыка" (не реклама), но меня раздражает что у нее нет мини виджета по переключению музыки, или нужно открывать приложение, или использовать кнопки на клавиатуре, коих к сожалению нет на раб ноуте.

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

Первый виджет является обычным вызываемым окном через панель значков винды, который показывает название трека, исполнителя и обложку и 3 кнопки, две их которых отвечают за переключение треков, и одна соответственно pause\play.

Самописный переключатель треков для Windows Компьютер, Windows, Csharp, Visual Studio, Яндекс, Windows 10, Net, Dotnet, Яндекс Музыка, Программирование, Github, Приложение, Программист, Google, Длиннопост

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

Самописный переключатель треков для Windows Компьютер, Windows, Csharp, Visual Studio, Яндекс, Windows 10, Net, Dotnet, Яндекс Музыка, Программирование, Github, Приложение, Программист, Google, Длиннопост
Самописный переключатель треков для Windows Компьютер, Windows, Csharp, Visual Studio, Яндекс, Windows 10, Net, Dotnet, Яндекс Музыка, Программирование, Github, Приложение, Программист, Google, Длиннопост

Вот ссылка на гитхаб https://github.com/Mistreds/MusicWPF. Readmi к проекту честно писать лень, он элементарный, запустил и пользуйся. Самораспаковывающиеся архивы где Release, MusicWidget виджет для раб стола, MusicControl, всплывающий виджет для панели значков

Но есть несколько но, проект написан на C# в среде  .Net 6, для переключения треков используется win sdk, так что проект поддерживается только на windows 10 (возможно и windows 11, я не проверял).

Проверенные плееры:

-Яндекс музыка  приложение (работает)

- Яндекс музыка браузерная версия (работает только play\pause и получает название треков без обложки)

-Youtube (когда играет в плейлистах работает переключение, а так получает название, автора канала и обложку видео)

-AIMP (не работает)

-Windows media player (не знаю кто им пользуется, но не работает)

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

Самописный переключатель треков для Windows Компьютер, Windows, Csharp, Visual Studio, Яндекс, Windows 10, Net, Dotnet, Яндекс Музыка, Программирование, Github, Приложение, Программист, Google, Длиннопост

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

Показать полностью 4
[моё] Компьютер Windows Csharp Visual Studio Яндекс Windows 10 Net Dotnet Яндекс Музыка Программирование Github Приложение Программист Google Длиннопост
30
Посты не найдены
О нас
О Пикабу Контакты Реклама Сообщить об ошибке Сообщить о нарушении законодательства Отзывы и предложения Новости Пикабу Мобильное приложение RSS
Информация
Помощь Кодекс Пикабу Команда Пикабу Конфиденциальность Правила соцсети О рекомендациях О компании
Наши проекты
Блоги Работа Промокоды Игры Курсы
Партнёры
Промокоды Биг Гик Промокоды Lamoda Промокоды Мвидео Промокоды Яндекс Директ Промокоды Отелло Промокоды Aroma Butik Промокоды Яндекс Путешествия Постила Футбол сегодня
На информационном ресурсе Pikabu.ru применяются рекомендательные технологии