<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>OpenGL</title>
    <description/>
    <link>https://tproger.ru/tag/opengl</link>
    <atom:link href="https://tproger.ru/tag/opengl/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sun, 04 Oct 2026 01:47:48 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>OpenGL</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>WebGL-портфолио Susurrus: акварельный 3D-мир на Three.js и Kuwahara-шейдере</title>
      <link>https://tproger.ru/translations/webgl-portfolio-susurrus-akvarelnyj-3d-mir-na-three-js-i-kuwah</link>
      <comments>https://tproger.ru/translations/webgl-portfolio-susurrus-akvarelnyj-3d-mir-na-three-js-i-kuwah?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/webgl-portfolio-susurrus-akvarelnyj-3d-mir-na-three-js-i-kuwah</guid>
      <description><![CDATA[<p>Перевод case-study Wei Xianyao о Susurrus — WebGL-сцене на React Three Fiber, построенной вокруг Kuwahara-шейдера. Отражающая вода, ScrollControls, физика.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/webgl-portfolio-susurrus-akvarelnyj-3d-mir-na-three-js-i-kuwah">WebGL-портфолио Susurrus: акварельный 3D-мир на Three.js и Kuwahara-шейдере</a>»</p>]]></description>
      <category><![CDATA[Инструменты]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[Компьютерная графика]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Фронтенд]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 06 May 2026 05:59:52 GMT</pubDate>
      <content:encoded><![CDATA[<p>Чем это могло бы быть, если бы 3D-сцена в браузере вела себя как акварельный рисунок — текучая, отзывчивая, интерактивная? Дизайнер Wei Xianyao (Weisdevice) <a href="https://tympanus.net/codrops/2026/04/24/susurrus-crafting-a-cozy-watercolor-world-with-three-js-and-shaders/">опубликовал case-study</a> на Codrops о проекте <b>Susurrus</b>: WebGL-сцена, целиком построенная вокруг одного Kuwahara-шейдера в постпроцессинге, с отражающей водой, физикой и звуком. Перевод материала.</p><p>Главный приём — Kuwahara-фильтр как единственный пасс постпроцессинга. Всё остальное (отражения, частицы, реакция на скролл, физика появления хлеба по клику) встроено вокруг него и подгоняется под его эстетику.</p><p><b>Концепция.</b> Забытая мельница на воде, рендерится как акварель, но полноценно 3D и интерактивна. Вода, ветер, сцена — единое атмосферное пространство. Под мирным фасадом скрыт сюрреалистический намёк: подводная мельница бесконечно производит хлеб, хотя зерно давно ушло.</p><p><b>Стек.</b> React, React Three Fiber, Drei, React Three Rapier, Howler.js, TypeScript, WebGL, HTML/SCSS.</p><p><b>Главный приём — Kuwahara-шейдер.</b> Несмотря на сильно обработанный вид, это единственный пасс постпроцессинга. Сцена строится вокруг него, в том числе на этапе моделей в Blender — чтобы видеть итоговый эффект сразу.</p><p><b>Отражающая вода — три шага.</b> MeshReflectorMaterial в низком разрешении (для производительности), кастомный шейдер сверху для деталей и анимации воды, тюнинг освещения.</p><p><b>Reveal Effect.</b> ScrollControls контролирует параметр uProgress, шейдер применён к ScreenQuad. Это компактный способ реализовать разворачивающуюся при скролле интро-сцену.</p><h2>Концепция: уют, которого не бывает</h2><p>Wei работает в свободном режиме: не использует Figma и подобные инструменты на личных проектах, идёт от концепта в голове. Susurrus родился как «эхо в сознании»: дом, плавающий на отражающей воде, с Kuwahara-шейдером, наложенным как пост-процессинг — чтобы получить «3D-веб-впечатление как картина».</p><p>Интерфейс при этом намеренно прост — фокус на 3D-контенте. В сцене присутствуют два поэта, но смысловое ядро — атмосфера и эмоция, переданные через визуал и звук. Само слово «susurrus» (шёпот, шорох) задаёт тон: слова намеренно невнятны, важнее — настроение.</p><p>При ближнем рассмотрении сцена становится тревожной: подводная мельница бесконечно производит хлеб, хотя зерна давно нет. Это тихая метафора несуществующего комфорта — то, что выглядит решённым, но к чему присматриваться не стоит.</p><h2>Kuwahara-шейдер: ядро всего проекта</h2><p>Весь визуальный стиль Susurrus построен на Kuwahara-фильтре. Это единственный пасс постпроцессинга — всё остальное собрано вокруг него. Wei поставил его на этап раньше, чем закончил 3D-модели в Blender: так можно видеть итоговый эффект и подгонять модели под шейдер, а не получить сюрприз в финале.</p><p>При исследовании автор нашёл несколько подходов, опираясь в основном на разбор Maxime Heckel о Kuwahara-фильтре и painterly-шейдинге. На основе этих референсов Wei сделал упрощённую реализацию:</p><ul><li>Не использовал TensorPass, чтобы пасс остался простым (хотя он усиливает детализацию).</li><li>Использовал отличающийся подход на vertex-стадии, по сравнению со стандартными реализациями.</li></ul><p>Стандартный vertex для Kuwahara обычно выглядит так:</p><p>Версия Wei проще:</p><p>Wei не применяет полную модель-вью-проекционную матрицу (MVP), чтобы немного ускорить вычисления. Цена компромисса — эффект слабее, когда камера подходит близко, но в Susurrus камера не приближается к моделям анимацией, поэтому это ОК.</p><h2>Отражающая вода в три шага</h2><p>Авторская «грязная» реализация воды:</p><ul><li>MeshReflectorMaterial в акварельном стиле. Из-за визуальной обработки разрешение можно ставить очень низким — это сохраняет производительность на мобильных и десктопе.</li><li>Кастомный шейдер на отдельной плоскости поверх MeshReflectorMaterial — добавляет детали и анимацию воды.</li><li>Освещение подкручено так, чтобы вода казалась живее, не плоской и не скучной.</li></ul><h2>Reveal Effect для интро-сцены</h2><p>Wei использовал ScrollControls для контроля параметра uProgress, который подаётся в reveal-шейдер. Шейдер применён к ScreenQuad — это удобный способ реализовать эффект «открытия» сцены при скролле.</p><h2>Звук, физика и адаптивность</h2><p><b>Звуковые эффекты.</b> Через react-howler: разные звуковые взаимодействия по hover/click. Эффекты интегрированы с фоновой музыкой, чтобы получился lo-fi-стиль — «картина, которая может петь».</p><p><b>Физика.</b> Главный интерактивный элемент Susurrus — Spawning Bread on Click: щелчок порождает физический объект (хлеб), который падает в сцену. На реализации стоит React Three Rapier.</p><p><b>Адаптивность.</b> Стандартная для автора цель — мобильная совместимость и плавная производительность на mobile и более старых устройствах.</p><h2>Выводы</h2><p>Susurrus — пример того, как один точно выбранный шейдер может задать стиль всему проекту: от моделей в Blender до анимации воды и интерактивности. Идея Wei — начать с Kuwahara-фильтра ещё до финальных моделей — практичная: видишь итоговый эффект сразу и не тратишь время на проработку деталей, которые шейдер всё равно сгладит.</p><p>Для тех, кто работает с Three.js / React Three Fiber, в случае Susurrus есть три прямых заимствования: компактный паттерн ScrollControls + ScreenQuad для reveal-сцен, дешёвая реализация отражающей воды через MeshReflectorMaterial низкого разрешения + кастомный шейдер сверху, и выбор пасса постпроцессинга на старте проекта, а не в финале.</p><p>Источник: <a href="https://tympanus.net/codrops/2026/04/24/susurrus-crafting-a-cozy-watercolor-world-with-three-js-and-shaders/">Susurrus: Crafting a Cozy Watercolor World with Three.js and Shaders — Codrops</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Space Invaders «с нуля» — Часть 3: создаём клон игры с минимумом зависимостей</title>
      <link>https://tproger.ru/articles/space-invaders--s-nulya----chast-3--sozdayom-klon-igry-s-minimumom-zavisimostej</link>
      <comments>https://tproger.ru/articles/space-invaders--s-nulya----chast-3--sozdayom-klon-igry-s-minimumom-zavisimostej?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/space-invaders--s-nulya----chast-3--sozdayom-klon-igry-s-minimumom-zavisimostej</guid>
      <description><![CDATA[<p>В третьей части серии «Space Invaders с нуля» мы переходим от основ к геймплею: добавляем игрока и рои пришельцев, вводим анимацию спрайтов и делаем игровой цикл на фиксированном шаге времени с V-sync. Пошагово разбираем, как структурировать данные и оживить игровую сцену на C++.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/space-invaders--s-nulya----chast-3--sozdayom-klon-igry-s-minimumom-zavisimostej">Space Invaders «с нуля» — Часть 3: создаём клон игры с минимумом зависимостей</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Игра]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 16 Oct 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это перевод <a href="https://nicktasios.nl/posts/space-invaders-from-scratch-part-3.html">статьи</a> автора Nick Tasios. Мы уже публиковали <a href="https://tproger.ru/articles/space-invaders--s-nulya----chast-1--sozdayom-okno">перевод первой части</a> с настройками окна и контекста и <a href="https://tproger.ru/articles/space-invaders--s-nulya----chast-2--nastraivaem-wejdery-opengl-i-risuem-sprajt-priwelca">перевод второй части</a> с настройкой шедеров и отрисовкой спрайта. В этой части создадим клон классической аркадной игры Space Invaders на C++, используя минимум зависимостей.</p><p>В этой части мы сделаем несколько ключевых шагов:</p><ul><li>реализуем игровой цикл с фиксированным временным шагом,</li><li>добавим игрока и пришельцев,</li><li>и, наконец, добавим анимацию спрайтов.</li></ul><p>Этот этап превращает наш базовый прототип в настоящую игру — с управлением, движением врагов и визуальной динамикой. Код третьей части можно посмотреть в<a href="https://github.com/Grieverheart/space_invaders/blob/2d5007b62ef044f9e47b8660d26810d9ea6de636/main.cpp"> репозитории на Github</a>.</p><h2>Добавление игрока и роя пришельцев</h2><p>Прежде чем добавить игрока и рой пришельцев, создадим два агрегата данных — то есть структуры (struct):</p><p>И у структуры игрока, и у структуры пришельца есть координаты позиции x и y, заданные в пикселях относительно нижнего левого угла окна. В структуру Player также добавляется поле с количеством жизней игрока.</p><p>В классических аркадных играх Space Invaders существует три типа пришельцев, которые отличаются только своими спрайтами. Этот параметр мы кодируем в поле type.</p><p>Кроме того, мы вводим отдельную структуру, предназначенную для хранения всех переменных, связанных с состоянием игры.</p><p>Это включает ширину и высоту игры в пикселях, объект игрока и массив пришельцев, выделяемый динамически.</p><p>Как и ранее, мы добавляем спрайт для игрока, закодированный в виде растрового изображения (bitmap).</p><p>Затем мы создаём и инициализируем структуру Game.</p><p>Мы задаём количество пришельцев равное 55 — как в оригинальной аркадной игре, — даём игроку 3 жизни и размещаем его рядом с нижней центральной частью экрана. Затем инициализируем позиции пришельцев, выбрав для них разумные координаты.</p><p>Наконец, в основном игровом цикле мы отрисовываем игрока и всех пришельцев.</p><p>Получаем вот такой результат:</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-10-16/b18012d2-4d3e-4faf-81e9-02f622b2ce40.png" alt="" /></figure><p>Это уже начинает напоминать Space Invaders, но пока всё довольно статично!</p><h2>Анимация спрайтов</h2><p>Чтобы сделать игру более динамичной, нам, конечно, нужно реализовать ввод от игрока, но также требуется способ анимировать спрайты. Анимация спрайтов в видеоиграх достигается заменой текущего спрайта последовательностью спрайтов один за другим. Поэтому мы создаём структуру данных для хранения различной информации об анимации спрайта.</p><p>Структура SpriteAnimation по сути представляет собой массив Sprite. Здесь мы используем тип «указатель на указатель» для хранения спрайтов, чтобы их можно было шарить (совместно использовать). Если хочется повысить эффективность, спрайты можно упаковать в атласы (spritesheets). Кроме того, мы добавляем флаг, указывающий, нужно ли зацикливать анимацию или воспроизводить её лишь один раз, интервал между последовательными кадрами и время, проведённое в текущем экземпляре анимации. Зная число кадров и желаемую длительность анимации, интервал между кадрами легко вычислить. Ниже мы вводим дополнительный спрайт для нашего инопланетянина…</p><p>…и создаём двухкадровую анимацию, используя два спрайта пришельца.</p><p>Обратите внимание: чтобы упростить задачу, мы измеряем длительность кадра и время в игровых циклах — то есть в количестве итераций игрового цикла. Чтобы это работало корректно, нужно зафиксировать частоту кадров. Для простой игры, подобной нашей, можно использовать V-sync — опцию, при которой обновления видеокарты синхронизируются с частотой обновления монитора.</p><p>Большинство современных мониторов имеют частоту обновления 60 Гц, что означает обновление экрана 60 раз в секунду. Включив V-sync, мы получим частоту кадров 60 FPS или кратное значение, синхронизированное с частотой дисплея.</p><p>Однако есть и недостаток: на мониторах с более высокой частотой обновления, например, 120 Гц или 240 Гц, игра будет работать быстрее. Чтобы включить V-sync, вызывается функция GLFW:</p><p>В конце каждого кадра мы обновляем все анимации, увеличивая значение времени.</p><p>Если анимация дошла до конца, мы либо удаляем её, либо сбрасываем время обратно в 0, если это зацикленная анимация.</p><p>Обратите внимание, что на данный момент у нас есть только одна анимация, которую нужно обновлять.</p><p>В основном цикле, где происходит отрисовка пришельцев, мы изменяем цикл рисования так, чтобы отображался соответствующий кадр анимации.</p><p>Нужный кадр вычисляется на основе времени, прошедшего с начала анимации, и длительности одного кадра.</p><p>Чтобы сделать процесс более интересным, мы также добавляем простое движение игрока, введя переменную, которая управляет направлением его движения…</p><p>…и обновляем положение игрока в конце каждого кадра в зависимости от значения этой переменной.</p><p>Условия if выполняют простую проверку столкновений спрайта игрока с границами игрового поля, гарантируя, что игрок остаётся внутри этих границ.</p><p>Выше вы можете видеть анимированный GIF с результатом.</p><h2>Заключение</h2><p>В этом посте мы создали несколько структур (struct), чтобы логически сгруппировать данные для игры, игрока и пришельцев. Ещё важнее то, что мы заложили основу для анимации спрайтов. Для этой простой версии Space Invaders мы используем фиксированные игровые циклы, задав постоянную частоту кадров с помощью включения вертикальной синхронизации (V-sync). Это позволяет нам выполнять анимацию спрайтов в игровых циклах, а не в реальном времени.</p><p>Единственное, что остаётся сделать, прежде чем игра станет играбельной, — реализовать обработку пользовательского ввода. Источников ввода может быть много, но в нашем случае мы ограничимся клавиатурой.</p>]]></content:encoded>
    </item>
    <item>
      <title>Space Invaders «с нуля» — Часть 2: настраиваем шейдеры OpenGL и рисуем спрайт пришельца</title>
      <link>https://tproger.ru/articles/space-invaders--s-nulya----chast-2--nastraivaem-wejdery-opengl-i-risuem-sprajt-priwelca</link>
      <comments>https://tproger.ru/articles/space-invaders--s-nulya----chast-2--nastraivaem-wejdery-opengl-i-risuem-sprajt-priwelca?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/space-invaders--s-nulya----chast-2--nastraivaem-wejdery-opengl-i-risuem-sprajt-priwelca</guid>
      <description><![CDATA[<p>Как написать собственный клон Space Invaders на C++ с помощью OpenGL: создание буфера, работа с шейдерами, текстурами и спрайтами. Подробное руководство для начинающих гейм-девелоперов и энтузиастов графики.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/space-invaders--s-nulya----chast-2--nastraivaem-wejdery-opengl-i-risuem-sprajt-priwelca">Space Invaders «с нуля» — Часть 2: настраиваем шейдеры OpenGL и рисуем спрайт пришельца</a>»</p>]]></description>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Боты]]></category>
      <category><![CDATA[Игра]]></category>
      <category><![CDATA[1C]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 09 Oct 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это перевод <a href="https://nicktasios.nl/posts/space-invaders-from-scratch-part-2.html">статьи</a> автора Nick Tasios. <a href="https://tproger.ru/articles/space-invaders--s-nulya----chast-1--sozdayom-okno">Перевод первой части</a> мы уже публиковали: там настроили окно и контекст. Во второй части настроим шейдеры OpenGL, чтобы отрисовать спрайт пришельца! Спрайт (sprite) — это двумерное графическое изображение, которое используется для отображения объектов в играх и интерфейсах. Если проще — картинка персонажа, предмета или эффекта, которая выводится на экран поверх фона. Код для действий этой части можно посмотреть в <a href="https://github.com/Grieverheart/space_invaders/blob/10c9f24eec222d66a22ee1868ae598a6803eea1d/main.cpp">репозитории на GitHub</a>.</p><h2>Рендеринг на стороне CPU</h2><p>GPU отлично справляется с обработкой больших объёмов данных, но программировать графический процессор сложнее, чем CPU. Для простого клона Space Invaders, который я создаю здесь, проще выполнять всю отрисовку на центральном процессоре (CPU), используя буфер — участок памяти, представляющий собой пиксели игрового экрана. Затем этот буфер можно передать на GPU в виде текстуры и вывести на экран компьютера.</p><p>Буфер имеет определённую ширину и высоту. Мы представляем каждый пиксель в виде значения типа uint32_t, что позволяет хранить четыре 8-битных компонента цвета для каждого пикселя. В нашем случае мы будем использовать только 24 бита — по 8 бит для красного (red), зелёного (green) и синего (blue) каналов. Можно возразить, что логичнее было бы использовать uint8_t, но я применяю 32-битное значение, потому что так проще работать с индексами при обращении к элементам массива. Чтобы использовать эти типы целых чисел с фиксированной разрядностью, необходимо подключить стандартный заголовочный файл:</p><p>Чтобы упростить определение цветов в виде значений типа uint32_t, мы создадим следующую функцию:</p><p>Эта функция устанавливает три старших байта (24 бита) в значения r, g и b соответственно.</p><p>Оставшиеся 8 младших битов заполняются значением 255 — хотя, как упоминалось ранее, альфа-канал (прозрачность) в нашем случае не используется. Далее мы создаём функцию, которая очищает буфер, заполняя его указанным цветом.</p><p>Эта функция проходит по всем пикселям буфера и устанавливает для каждого заданный цвет.</p><p>Теперь, в главной функции (main), мы инициализируем буфер — то есть создаём область памяти, где будут храниться все пиксели экрана:</p><p>Этот код создаст буфер с шириной buffer_width и высотой buffer_height, а также заполнит его цветом clear_color, то есть зелёным.</p><h2>Шейдеры OpenGL</h2><p>После того как мы создали буфер, нам нужно настроить OpenGL, чтобы иметь возможность вывести его содержимое на экран. В современном OpenGL большая часть обязанностей, которые раньше выполнял драйвер, теперь ложится на разработчика. Он должен сам писать небольшие программы, которые выполняются на видеокарте (GPU) — такие программы называются шейдерами (shaders).</p><p>OpenGL определяет конвейер рендеринга — последовательность этапов, через которые проходит изображение перед выводом на экран. Шейдеры могут выполняться на разных стадиях этого конвейера.</p><p>Старый OpenGL иногда называют fixed pipeline — «фиксированный конвейер», поскольку его этапы были заранее определены и не могли быть изменены программистом.</p><p>В современном OpenGL почти все этапы программируемы, и основные среди них два:</p><ul><li>Vertex Shader (вершинный шейдер) — обрабатывает данные вершин, обычно выполняет трансформацию объектов в координаты экрана.</li><li>Fragment Shader (фрагментный шейдер) — работает с пикселями, полученными после растеризации. Он определяет их цвет, глубину и, при необходимости, трафаретные значения (stencil).</li></ul><p>Эти два шейдера — минимально необходимые для работы любого рендеринга в OpenGL.</p><p>Хотя с помощью шейдеров можно создавать невероятно сложные визуальные эффекты, в этом проекте мы построим два простых шейдера, которые просто выведут содержимое нашего буфера (из предыдущего шага) на экран.</p><p>Обычно для отображения изображения нужно создать квад (quad), который покрывает весь экран. Однако существует известный трюк — можно сгенерировать полноэкранный треугольник, не передавая в шейдер вообще никаких вершинных данных.</p><p>Для фрагментного шейдера нам просто нужно считать значение из текстуры буфера и вывести результат этого выборочного чтения.</p><p>Обратите внимание, что выходное значение вершинного шейдера TexCoord теперь используется как вход для фрагментного шейдера. Хотя вершинному шейдеру не требуется передавать какие-либо данные о вершинах, мы всё же должны указать, что будем рисовать три вершины. Для этого создаётся объект вершинного массива (VAO).</p><p>Если упростить, VAO (Vertex Array Object) — это структура в OpenGL, которая хранит формат вершинных данных вместе с самими данными вершин. Наконец, оба шейдера нужно скомпилировать в код, понятный GPU, а затем связать их в единый шейдерный программный объект.</p><p>В приведённом выше фрагменте кода шейдерная программа сначала создаётся с помощью функции <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glCreateProgram.xhtml">glCreateProgram</a>. Отдельные шейдеры создаются функцией <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glCreateShader.xhtml">glCreateShader</a> и компилируются с помощью <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glCompileShader.xhtml">glCompileShader</a>. После этого они присоединяются к программе через glAttachShader, после чего сами объекты шейдеров можно удалить.</p><p>Программа связывается вызовом <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glLinkProgram.xhtml">glLinkProgram</a>. Во время компиляции OpenGL выводит различную информацию — аналогично тому, как это делает компилятор C++. Однако, чтобы получить эти сообщения, их нужно перехватывать вручную. Для этого созданы две простые функции: validate_shader и validate_program.</p><h2>Текстура буфера</h2><p>Для передачи данных изображения на GPU используется текстура OpenGL. Как и в случае с VAO, текстура также объект, который вместе с данными изображения содержит информацию о формате этих данных. Сначала текстура создаётся с помощью функции <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGenTextures.xhtml">glGenTextures</a>.</p><p>Затем задаём формат изображения, а также некоторые стандартные параметры, определяющие поведение выборки текстуры.</p><p>Здесь мы указываем, что изображение должно использовать 8-битный RGB-формат для внутреннего представления текстуры. Последние три параметра вызова <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml">glTexImage2D</a> определяют формат пикселей данных, которые мы передаём в текстуру: каждый пиксель находится в формате RGBA и представлен четырьмя беззнаковыми 8-битными целыми числами.</p><p>Два первых вызова <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glTexParameter.xhtml">glTexParameteri</a> сообщают GPU не применять фильтрацию (сглаживание) при чтении пикселей. Два последних указывают, что при попытке чтения за пределами текстуры будет использоваться значение на границе.</p><p>Теперь нам нужно привязать текстуру к uniform-переменной sampler2D во фрагментном шейдере. В OpenGL существует несколько текстурных единиц, к которым может быть привязан uniform. Мы получаем расположение uniform-переменной в шейдере (его можно рассматривать как своего рода «указатель») с помощью <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGetUniformLocation.xhtml">glGetUniformLocation</a> и устанавливаем её на текстурную единицу 0 с помощью вызова <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glUniform.xhtml">glUniform1i</a>.</p><h2>Отображение буфера</h2><p>Наконец, мы настроили всё необходимое, чтобы вывести буфер на экран. Непосредственно перед запуском игрового цикла мы отключаем тест глубины и привязываем созданный ранее объект вершинного массива (VAO).</p><p>Если теперь вызвать:</p><p>То содержимое созданного окна должно отобразиться зелёным цветом.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-10-08/c93c2af0-fd33-483f-a745-434e6836636e.png" alt="" /></figure><h2>Отрисовка спрайта</h2><p>В предыдущем посте мы уже создали окно и вывели на экран цвет. Заканчивать эту часть на том же этапе было бы скучно — так что теперь давайте что-нибудь нарисуем.</p><p>Сначала я определяю простой спрайт:</p><p>Это просто участок данных, размещённых в куче, вместе с указанием ширины и высоты спрайта. Сам спрайт представлен в виде битовой карты (bitmap) — то есть каждый пиксель кодируется одним битом, где значение 1 означает, что пиксель спрайта включён (“on”).</p><p>Далее мы создаём функцию, которая отрисовывает спрайт в буфере с заданным цветом.</p><p>Функция просто проходит по пикселям спрайта и рисует активные (“on”) пиксели по указанным координатам, если они находятся в пределах границ буфера.</p><p>Альтернативно можно представить спрайт так же, как и буфер — то есть каждый пиксель хранится как 32-битное RGBA-значение, где альфа-канал используется для прозрачности или смешивания.</p><p>В главной функции (main) мы создаём спрайт пришельца (alien sprite).</p><p>Чтобы нарисовать красный спрайт в позиции (112, 128), вызываем:</p><p>Чтобы подготовиться к следующим шагам, мы очищаем буфер и рисуем спрайт на каждом кадре игрового цикла. Для обновления текстуры OpenGL вызываем функцию <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glTexSubImage2D.xhtml">glTexSubImage2D</a>.</p><p>Если скомпилировать и запустить финальный код из этого поста, то вы увидите красную текстуру в центре зелёного фона.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-10-08/9fa3aee4-f307-4890-9603-a857e74c1bad.png" alt="" /></figure><p>В этом посте мы подготовили всё необходимое, чтобы отрисовывать спрайты на CPU и выводить их в окно, созданное ранее. В современном OpenGL это требует определённого объёма работы — мы затронули такие темы, как объекты вершинных буферов, шейдеры и текстуры.</p><p>Теперь, когда базовые функции отрисовки готовы, можно переходить к более интересной части — программированию игровой логики. Разберём это в следующей статье.</p>]]></content:encoded>
    </item>
    <item>
      <title>Space Invaders «с нуля» — Часть 1, создаём окно</title>
      <link>https://tproger.ru/articles/space-invaders--s-nulya----chast-1--sozdayom-okno</link>
      <comments>https://tproger.ru/articles/space-invaders--s-nulya----chast-1--sozdayom-okno?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Даровская Маша]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/space-invaders--s-nulya----chast-1--sozdayom-okno</guid>
      <description><![CDATA[<p>Старт серии по созданию клона Space Invaders на C++: настраиваем окно и контекст OpenGL 3.3 с GLFW и GLEW, собираем проект и запускаем первый «красный» кадр.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/space-invaders--s-nulya----chast-1--sozdayom-okno">Space Invaders «с нуля» — Часть 1, создаём окно</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[HTML]]></category>
      <category><![CDATA[macOS]]></category>
      <category><![CDATA[Игра]]></category>
      <category><![CDATA[Xcode]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[1C]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 02 Oct 2025 12:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это перевод <a href="https://nicktasios.nl/posts/space-invaders-from-scratch-part-1.html">статьи</a> автора Nick Tasios.</p><p>Автор написал клон классической аркады Space Invaders на C++, опираясь всего на пару зависимостей. В этой части мы подготовим окно и контекст OpenGL 3.3, используя GLFW и GLEW — это единственные внешние библиотеки, которые понадобятся на старте.</p><h2>Что такое Space Invaders</h2><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-10-01/491e2f60-9c59-4936-bd1e-0932e65000be.png" alt="" /></figure><p>Space Invaders — аркадная игра 1978 года. Это 2D-шутер с горизонтальным управлением: игрок двигает пушку вдоль нижней границы экрана и стреляет по строю инопланетян. За каждого сбитого врага начисляются очки. Периодически по верхней части экрана пролетает НЛО — если сбить, получите бонусные очки. По мере уничтожения инопланетян игра ускоряется. Враги тоже стреляют случайно, приближаясь к низу экрана; попадание по игроку отнимает жизнь. Пушку частично прикрывают бункеры, но они постепенно разрушаются как под выстрелами врагов, так и самим игроком. После зачистки волны появляется новая, а бункеры восстанавливаются. Игра заканчивается, если бункеры полностью разрушены, инопланетяне достигли низа экрана или у игрока закончились жизни.</p><h3>Постановка целей</h3><p>Важно определить цели до начала проекта. Мы не собираемся дотошно воссоздавать оригинал — сделаем «space-invaders-like» прототип с базовыми элементами. В геймдеве обычно сначала собирают «грубый» прототип, чтобы проверить ядро механик, а «полировать» будем позже.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-10-01/721c66cf-7153-45fb-90a7-655561da422e.png" alt="" /></figure><p>В прототипе нужны:</p><ul><li>управляемая игроком пушка;</li><li>волны инопланетян, которые постепенно движутся к пушке;</li><li>стрельба у обеих сторон.</li></ul><p>НЛО и бункеры на первом этапе опустим (их несложно добавить потом).</p><p>Почти любую игру можно разложить на базовые элементы (очень советую <a href="https://www.youtube.com/watch?v=zyVTxGpEO30">доклад</a> Raph Koster). В Space Invaders это движение и стрельба (а значит — детекция столкновений). Даже простой клон поможет прокачать понимание геймлупа, коллизий и правил игры.</p><p>Поехали!</p><h2>Hello Window</h2><p>Окно можно создать по-разному: нативные API (Cocoa/X11/WinAPI) или кроссплатформенные библиотеки (Qt, GLFW). Нативный путь даёт полный контроль, но ради простоты и кроссплатформенности возьмём GLFW: лёгкая, простая C-API.</p><p>Подключим заголовки (стандартный ввод/вывод и GLFW):</p><p>Это откроет окно 640×480 с заголовком Space Invaders и контекстом OpenGL. Два последних параметра glfwCreateWindow — монитор для фуллскрина и «шаринг» контекста между окнами. При неудаче вызываем glfwTerminate(). Важно: нужно «привязать» контекст текущему потоку (glfwMakeContextCurrent), чтобы последующие вызовы OpenGL применялись к нему.</p><p>По умолчанию версия контекста не гарантируется — попросим минимум 3.3 Core (задать до создания окна):</p><p>Почему нужен загрузчик функций OpenGL. OpenGL — это спецификация; реализация зависит от GPU/драйвера/ОС. Множество функций нужно загружать в рантайме. Делать это вручную неудобно, поэтому используют лоадеры. Здесь — GLEW (можно и GLAD, но в статье выбран GLEW).</p><p>Важно: подключаем GLEW до glfw3.h:</p><h2>Игровой цикл (Game loop)</h2><p>Если запустить код сейчас, окно мигнёт и программа завершится. Нужен бесконечный game loop, где мы обрабатываем ввод, обновляем состояние и рисуем кадр:</p><ul><li>Буферы. Современный OpenGL рисует в «задний» буфер, а «передний» отображается на экране. glfwSwapBuffers() меняет их местами.</li><li>События. glfwPollEvents() вынимает накопившиеся события (клавиатура, мышь, закрытие окна).</li><li>Выход. glfwWindowShouldClose() станет true, если пользователь нажал «крестик».</li></ul><p>В конце корректно освобождаем ресурсы:</p><h2>Компиляция</h2><p>Ниже — команды из оригинала (C++11), плюс современные примечания.</p><p>Обратите внимание, что позже мы будем использовать некоторые функции C++11, поэтому компилируем с помощью -std=c++11. В обоих случаях убедитесь, что у вас установлен GLFW 3. В Linux, в зависимости от вашего дистрибутива, вы можете использовать менеджер пакетов. Например, в Ubuntu GLFW можно установить с помощью следующей команды:</p><p>На Mac OS X автор предпочитает использовать <a href="https://brew.sh/">Homebrew</a>:</p><p>Возможно, <a href="https://www.monocilindro.com/2017/02/14/how-to-install-glfw-library-on-visual-studio-c-2015/">эта статья</a> поможет вам настроить проект GLFW в Visual Studio.</p><p>Если всё собрано успешно, вы увидите красное окно с заголовком Space Invaders.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-10-01/49e56b4a-e080-4074-91b4-b289af58fba1.png" alt="" /></figure><h2>Итоги</h2><p>Даже просто «поднять окно» с контекстом OpenGL на C++ — задача не из быстрых, несмотря на помощь GLFW. Мы пока ничего не рисуем; настройка простейшего рендера в современном OpenGL тоже требует подготовки. Хорошая новость — делать это придётся один раз, а дальше вы переиспользуете базу в следующих частях (шейдеры, VBO/VAO, спрайты, коллизии и т. д.).</p><p>Вторая часть статьи — <a href="https://tproger.ru/articles/space-invaders--s-nulya----chast-2--nastraivaem-wejdery-opengl-i-risuem-sprajt-priwelca">здесь</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Linux полностью портировали на Mac M1/M2 — работают даже игры через Proton</title>
      <link>https://tproger.ru/news/--linux-polnostyu-portirovali-na-mac-m1-m2---rabotayut-dazhe-igry-cherez-proton</link>
      <comments>https://tproger.ru/news/--linux-polnostyu-portirovali-na-mac-m1-m2---rabotayut-dazhe-igry-cherez-proton?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Булат Яббаров]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/--linux-polnostyu-portirovali-na-mac-m1-m2---rabotayut-dazhe-igry-cherez-proton</guid>
      <description><![CDATA[<p>Linux теперь полноценно работает на Mac с M1/M2: Asahi Linux реализовал драйверы для OpenGL 4.6, Vulkan 1.4 и запуск игр через Proton</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/--linux-polnostyu-portirovali-na-mac-m1-m2---rabotayut-dazhe-igry-cherez-proton">Linux полностью портировали на Mac M1/M2 — работают даже игры через Proton</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Linux]]></category>
      <category><![CDATA[Apple]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[Windows]]></category>
      <category><![CDATA[Wi-Fi]]></category>
      <category><![CDATA[NVIDIA]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 27 Aug 2025 08:36:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>Спустя почти пять лет после выхода первых Mac на чипах Apple Silicon, Linux наконец-то полноценно <a href="https://rosenzweig.io/blog/asahi-gpu-part-n.html">работает</a> на M1 и M2 — с графическим ускорением, поддержкой <b>Vulkan</b> и запуском Windows-игр через <b>Proton</b>.</p><p>За проектом стоит команда Asahi Linux. Ключевую роль в успехе сыграли разработчики, собравшие полноценный стек драйверов с нуля — без помощи Apple и без официальной документации.</p><h2>От треугольника к OpenGL 4.6</h2><p>Работа началась в декабре 2020 года — всего через несколько недель после релиза M1. Разработчик Кристиан Шалонски, ранее работавший над драйвером <b>Panfrost</b> (для GPU Mali), увлекся идеей и начал реверс-инжиниринг графической части Apple Silicon.</p><blockquote>Сначала я просто нарисовал треугольник. В 3D-графике, если ты можешь нарисовать треугольник — ты можешь все.</blockquote><p>Потом появился полноценный компилятор шейдеров, поддержка OpenGL. Еще позже — сложнейшая эмуляция геометрии и тесселяции шейдеров.</p><p>В январе 2024 года стек получил <b>официальную сертификацию OpenGL 4.6</b>.</p><h2>Vulkan, Direct3D и Proton</h2><p>Следом Шалонски собрал Vulkan-драйвер и уже через несколько недель получил <b>Vulkan 1.3</b>, а позже — <b>Vulkan 1.4</b> с поддержкой делимых текстур. Это стало финальной точкой: <b>через Proton теперь запускаются Windows-игры с поддержкой Direct3D 11/12</b>.</p><p><b>Да, вы не ослышались: на Mac с M1 можно запускать игры для Windows через Proton от Valve. И они работают.</b></p><p>Также команда обеспечила поддержку <b>OpenCL 3.0</b> (благодаря Karol Herbst) и <b>OpenGL ES 3.2</b>, обеспечив совместимость с мобильными и встраиваемыми приложениями.</p><h2>Полноценный десктоп на Linux</h2><p>Сегодня на Asahi Linux для Mac M1/M2 работают:</p><ul><li>Wi-Fi, Bluetooth, аудио;</li><li>видеоускорение;</li><li>полнодоступный терминал и графическая среда;</li><li>поддержка Wayland и X11;</li><li>графические API: OpenGL, Vulkan, OpenCL;</li><li>игры через <b>Proton</b> и <b>Wine</b>.</li></ul><p>Все это — <b>с открытым кодом и в мейнлайне Mesa</b>.</p><h2>Почему это важно</h2><p>Apple по-прежнему не дает официального доступа к документации GPU. Это делает реализацию открытого драйвера <b>большим инженерным достижением</b>, сопоставимым с реверсом графики NVIDIA до появления Nouveau.</p><p>Кроме того, это еще один шаг к <b>альтернативной экосистеме на базе Linux даже на закрытом железе Apple</b>. Проект может вдохновить:</p><ul><li>разработчиков игр, заинтересованных в кросс-платформенности;</li><li>линуксоидов, использующих Mac в качестве основного железа;</li><li>вендоров и дистрибутивы, которые могут внедрить поддержку Apple Silicon без сторонних патчей.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Apple объявила о прекращении поддержки OpenGL и OpenCL в macOS</title>
      <link>https://tproger.ru/news/macos-deprecates-opengl-opencl</link>
      <comments>https://tproger.ru/news/macos-deprecates-opengl-opencl?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рамис Ганиев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/news/macos-deprecates-opengl-opencl</guid>
      <description><![CDATA[<p>Apple прекращает поддержку OpenGL и OpenCL в macOS. Разработчикам рекомендовано перейти на Metal API, старые приложения продолжат работать без гарантий.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/news/macos-deprecates-opengl-opencl">Apple объявила о прекращении поддержки OpenGL и OpenCL в macOS</a>»</p>]]></description>
      <category><![CDATA[Apple]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[Компьютерная графика]]></category>
      <category><![CDATA[macOS]]></category>
      <category><![CDATA[Новости]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 05 Jun 2018 12:51:24 GMT</pubDate>
      <content:encoded><![CDATA[<p>Обновленная macOS 10.14 Mojave <a href="https://developer.apple.com/macos/whats-new/">перестала поддерживать</a> фреймворки <a href="https://ru.wikipedia.org/wiki/OpenGL">OpenGL</a> и <a href="https://ru.wikipedia.org/wiki/OpenCL">OpenCL</a>. Причинами отказа стали отсутствие обновленных версий и желание Apple продвигать собственный <a href="https://developer.apple.com/metal/">API Metal</a>.</p><h3>Apple без OpenGL</h3><p>macOS по-прежнему будет запускать приложения с устаревшими фреймворками, но не гарантирует стабильность их работы. Apple порекомендовала разработчикам перейти на собственный API и использовать шейдеры Metal и Metal Performance. Инструкция по переносу кода в Metal представлена в <a href="https://developer.apple.com/documentation/metal/mixing_metal_and_opengl_rendering_in_a_view">документации</a>.</p><p>Компания поддерживала OpenGL с 90-х годов. Инструмент стал популярен среди разработчиков благодаря открытому исходному коду и кроссплатформенности. Его преемник <a href="https://ru.wikipedia.org/wiki/Vulkan_(API)">Vulkan API</a> был представлен в 2015 году, но macOS его не поддерживает. В свою очередь, Metal API не является кроссплатформенным.</p><p>Несмотря на отсутствие поддержки Vulkan API, разработчики могут использовать часть его преимуществ. Для этого была создана библиотека MoltenVK, <a href="https://tproger.ru/news/moltenvk-vulkan-on-metal/">представленная </a>в феврале 2018 года.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как написать своего сапёра на Java за 15 минут</title>
      <link>https://tproger.ru/articles/java-15-mins-minesweeper</link>
      <comments>https://tproger.ru/articles/java-15-mins-minesweeper?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Пётр Соковых]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/java-15-mins-minesweeper</guid>
      <description><![CDATA[<p>Создание сапёра на Java: отдельный класс GUI хранит графические элементы, определяет элемент под кликом и управляет функциями OpenGL.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/java-15-mins-minesweeper">Как написать своего сапёра на Java за 15 минут</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Разработка игр]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 27 Apr 2016 23:36:47 GMT</pubDate>
      <content:encoded><![CDATA[<h3>Нам понадобятся:</h3><ul><li>15 минут свободного времени;</li><li>Настроенная рабочая среда, т.е. <a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html">JDK</a> и IDE (например <a href="http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/mars2">Eclipse</a>);</li><li>Библиотека <a href="https://web.archive.org/web/20170708054959/https://www.lwjgl.org/download">LWJGL</a> (версии 2.x.x) для работы с Open GL. Обратите внимание, что для LWJGL версий выше 3 потребуется написать код, отличающийся от того, что приведён в статье;</li><li>Иконки для клеток, т.е. цифры, флаг, неверно поставленный флаг, мина, взорвавшаяся мина и закрытое поле. Можно чисто символически нарисовать самому, или скачать <a href="https://www.dropbox.com/s/trjdm0qr88cwguj/res.zip?dl=0">использовавшиеся при написании статьи</a>.</li></ul><h3>Работа с графикой</h3><p>Для работы с графикой создадим отдельный класс — GUI. От него нам потребуется хранение всех графических элементов управления (т.е. полей клеток), определение элемента, по которому пришёлся клик и передача ему управления, вывод графических элементов на экран и управление основными функциями OpenGL.</p><p>Благо класс GUI будет взаимодействовать с графическими элементами, нам нужно создать интерфейс (писать классы сетки, клеток и прочую механику пока рано), который определит, что это такое. Логика подсказывает, что у графического элемента должны быть:</p><ul><li>Внешний вид (текстура);</li><li>Координаты;</li><li>Размеры (ширина и высота);</li><li>Метод, который по переданным координатам клика определит, попал ли клик по элементу;</li><li>Метод, который обработает нажатие на элемент.</li></ul><p>Таким образом, пишем:</p><p>В GUI должны храниться ячейки поля. Создадим для этих целей двумерный массив:</p><p>GUI должен передавать клики элементам, которые он содержит. Вычислить адрес клетки, по которой кликнули, нетрудно:</p><p>Теперь разберёмся с основными функциями OpenGL. Во-первых, нам нужна инициализация.</p><p>На этом мы подробно останавливаться не будем, т.к. изучение LWJGL не входит в наши сегодняшние планы. Во-вторых, нам нужно обновлять изображение на экране:</p><p>И, наконец, нам нужно это изображение вообще рисовать. Для этого пора закончить enum Sprite. Его элементы будут представлять из себя обёртку для текстуры с удобочитаемыми именами.</p><p>Теперь мы можем написать метод для GUI, который будет рисовать элементы:</p><p>Нам осталось только сделать единый метод для инициализации графики, и остальное мы будем писать в основном управляющем классе, возвращаясь сюда, только чтобы внести незначительные изменения.</p><h3>Ячейки</h3><p>Создадим класс Cell, реализующий интерфейс GUIElement. В методах getWidth() и getHeight() вернём константу, для координат придётся создать поля, которые будут инициализироваться конструктором. Так же конструктором будем передавать состояние клетки: “-1”, если это мина, “-2”, если это взорванная мина, число мин поблизости в остальных случаях. Для этой цели можно было бы использовать enum, но число мин удобнее передавать как integer, имхо. Итак, конструктор:</p><p>Ещё два поля — boolean isMarked и boolean isHidden будут отвечать за то, отметили ли клетку флажком, и открыли ли её. По умолчанию оба флага выставлены на false.</p><p>Разберёмся с методом getSprite().</p><p>В случае, если на кнопку нажали, нам снова необходимо рассмотреть несколько простейших случаев:</p><p>Чтобы при поражении клетки можно было вскрыть, добавим метод:</p><p>Для более удобной реализации генератора добавьте ещё и этот метод:</p><h3>Обработка ответов от клеток</h3><p>Вернёмся к методу GUI.receiveClick(). Теперь мы не можем просто вернуть результат назад, т.к. если результат выполнения — единица, то нам нужно открыть соседние ячейки, а в главный управляющий класс вернуть уже ноль, в знак того, что всё прошло корректно.</p><h3>Пишем генератор</h3><p>Задачка эта не сложнее, чем создать массив случайных boolean-величин. Идея следующая — для каждой ячейки матрицы мы генерируем случайное число от 0 до 100. Если это число  меньше 15, то в этом месте записываем в матрицу мину (таким образом, шанс встретить мину — 15%). Записав мину, мы вызываем у всех клеток вокруг метод incNearMines(), а для тех ячеек, где клетка ещё не создана храним значение в специальном массиве.</p><h3>Главный управляющий класс и ввод</h3><p>Создадим класс Main, в нём входной метод — public static void main(String[] args). Этот метод должен будет делать всего две вещи: вызывать инициализацию GUI и циклически вызывать рабочие методы (input(), GUI.draw() и GUI.update()), пока не получит сигнал закрытия.</p><p>Здесь нам не хватает метода input(), займёмся им.</p><p>Метод GUI.gameover() будет просто вызывать метод show() у каждой клетки, показывая таким образом всё поле:</p><p>Запускаем: <a href="https://media.tproger.ru/uploads/2016/03/gameplay.png"></a></p><p>Готово!</p><p>UPD: исходники выложены на <a href="https://github.com/DoKel/tprogersMinesweeper">GitHub</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Создание движка для 3D-рендеринга на Java</title>
      <link>https://tproger.ru/translations/how-to-make-a-3d-render-engine-in-java</link>
      <comments>https://tproger.ru/translations/how-to-make-a-3d-render-engine-in-java?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Иван Бирюков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/how-to-make-a-3d-render-engine-in-java</guid>
      <description><![CDATA[<p>Простой движок трёхмерного рендеринга по силам одному разработчику при наличии компьютера и времени, а заодно объясняет устройство больших движков.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/how-to-make-a-3d-render-engine-in-java">Создание движка для 3D-рендеринга на Java</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[OpenGL]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 10 Mar 2016 20:38:44 GMT</pubDate>
      <content:encoded><![CDATA[<p>Современные движки для 3D-рендеринга, использующиеся в играх и мультимедиа, поражают своей сложностью в плане математики и программирования. Соответственно, результат их работы превосходен.</p><p>Многие разработчики ошибочно полагают, что создание даже простейшего 3D-приложения с нуля требует нечеловеческих знаний и усилий. К счастью, это не совсем так. Более того, при наличии компьютера и свободного времени, можно создать нечто подобное самостоятельно. Давайте взглянем на процесс разработки нашего собственного движка для 3D-рендеринга.</p><p>Итак, для чего же это всё нужно? Во-первых, создание движка для 3D-рендеринга поможет понять, как же работают современные движки изнутри. Во-вторых, сам движок при желании можно использовать и в своём собственном приложении, не прибегая к вызову внешних зависимостей. В случае с Java это значит, что вы можете создать своё собственное приложение для просмотра 3D-изображений без зависимостей (далёких от API Java), которое будет работать практически везде и уместится в 50 КБ!</p><p>Само собой, если вы хотите создать какое-нибудь большое 3D-приложение с плавной анимацией, вам лучше использовать OpenGL/WebGL. Однако, имея базовое представление о том, как устроены подобные движки, работа с более сложными движками будет казаться в разы проще.</p><p>В этой статье я постараюсь объяснить базовый 3D-рендеринг с ортографической проекцией, простую треугольную растеризацию (процесс, обратный векторизации), Z-буферизацию и плоское затенение. Я не буду заострять своё внимание на таких вещах, как оптимизация, текстуры и разные настройки освещения — если вам это нужно,  попробуйте использовать более подходящие для этого инструменты, вроде OpenGL (существует множество библиотек, позволяющих вам работать с OpenGL, даже используя Java).</p><p>Примеры кода будут на Java, но сами идеи могут, разумеется, быть применены для любого другого языка по вашему выбору.</p><p>Довольно болтать — давайте приступим к делу!</p><h3>GUI</h3><p>Для начала, давайте поместим хоть что-нибудь на экран. Для этого, я буду использовать простое приложение, в котором будет отображаться наше отрендеренное изображение и два скроллера для вращения.</p><p>Результат должен выглядеть вот так:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/1-1.png" alt="" /></figure><p>Теперь давайте добавим некоторые модели — вершины и треугольники. Вершина — это просто структура для хранения наших трёх координат (X, Y и Z), а треугольник соединяет вместе три вершины и содержит их цвет.</p><p>Здесь я буду считать, что X означает перемещение влево-вправо, Y — вверх-вниз, а Z будет глубиной (так, что ось Z перпендикулярна вашему экрану). Положительная Z будет означать «ближе к пользователю».</p><p>В качестве примера я выбрал тетраэдр как простейшую фигуру, о которой вспомнил — нужно всего 4 треугольника, чтобы описать её.</p><figure><img src="https://media.tproger.ru/uploads/2016/03/3.gif" alt="" /></figure><p>Код также будет достаточно простым — мы просто создаём 4 треугольника и добавляем их в ArrayList:</p><p>В результате мы получим фигуру, центр которой находится в начале координат (0, 0, 0), что довольно удобно, так как мы будем вращать фигуру относительно этой точки.</p><p>Теперь давайте добавим всё это на экран. Сперва мы не будем добавлять возможность вращения и просто отрисуем каркасное представление фигуры. Так как мы используем ортографическую проекцию, это довольно просто — достаточно убрать координату Z и нарисовать наши треугольники.</p><p>Заметьте, что сейчас я совершил все преобразования до отрисовки треугольников. Это сделано, чтобы для того, что бы поместить наш центр (0, 0, 0) в центр экрана — по умолчанию начало координат находится в левом верхнем углу экрана. После компиляции вы должны получить:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/4-1.png" alt="" /></figure><p>Вы можете не поверить, но это наш тетраэдр в ортогональной проекции, честно!</p><p>Теперь нам нужно добавить вращение. Для этого мне нужно будет немного отойти от темы и поговорить об использовании матриц и о том, как с их помощью достичь трёхмерной трансформации 3D-точек.</p><p>Существует много путей манипулировать 3D-точками, но самый гибкий из них — это использование матричного умножения. Идея заключается в том, чтобы показать точки в виде вектора размера 3×1, а переход — это, собственно, домножение на матрицу размера 3×3.</p><p>Возьмём наш входной вектор A:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/1-3.png" alt="" /></figure><p>И умножим его на так называемую матрицу трансформации T, чтобы получить в итоге выходной вектор B:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/2-4.png" alt="" /></figure><p>Например, вот как будет выглядеть трансформация, если мы умножим на 2:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/3-2.png" alt="" /></figure><p>Вы не можете описать любую возможную трансформацию, используя матрицы размера 3×3 — например, если переход происходит за пределы пространства. Вы можете использовать матрицы размера 4×4, делая перекос в 4D-пространство, но об этом не в этой статье.</p><p>Трансформации, которые нам пригодятся здесь — масштабирование и вращение.</p><p>Любое вращение в 3D-пространстве может быть выражено в 3 примитивных вращениях: вращение в плоскости XY, вращение в плоскости YZ и вращение в плоскости XZ. Мы можем записать матрицы трансформации для каждого из данных вращений следующим путём:</p><ul><li>Матрица вращения XY:</li></ul><figure><img src="https://media.tproger.ru/uploads/2016/03/4-3.png" alt="" /></figure><ul><li>Матрица вращения YZ:</li></ul><figure><img src="https://media.tproger.ru/uploads/2016/03/5-3.png" alt="" /></figure><ul><li>Матрица вращения XZ:</li></ul><figure><img src="https://media.tproger.ru/uploads/2016/03/6-1.png" alt="" /></figure><p>И вот здесь начинается магия: если вам нужно сначала совершить вращение точки в плоскости XY, используя матрицу трансформации T1, и затем совершить вращение этой точки в плоскости YZ, используя матрицу трансформации T2, то вы можете просто умножить T1 на T2  и получить одну матрицу, которая опишет всё вращение:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/7-1.png" alt="" /></figure><p>Это очень полезная оптимизация — вместо того, чтобы постоянно считать вращения на каждой точке, мы заранее считаем одну матрицу и затем используем её.</p><p>Что ж, довольно страшной математики, давайте вернёмся к коду. Создадим служебный класс Matrix3, который будет обрабатывать перемножения  типа “матрица-матрица” и “вектор-матрица”:</p><p>Теперь можно и оживить наши скроллеры вращения. Горизонтальный скроллер будет контролировать вращение влево-вправо (XZ), а вертикальный скроллер будет контролировать вращение вверх-вниз (YZ).</p><p>Давайте создадим нашу матрицу вращения:</p><p>Вам также будет нужно добавить слушателей на скроллеры, чтобы обеспечить обновление изображения, когда вы будете тянуть их вверх-вниз или вправо-влево.</p><p>Как вы, наверное, уже заметили, вращение вверх-вниз ещё не работает. Добавим эти строки в код:</p><p>До сих пор мы отрисовывали только каркасное представление нашей фигуры. Теперь давайте заполним его чем-нибудь. Для этого нам нужно сначала растеризовать треугольник — представить его в виде пикселей на экране.</p><p>Я буду использовать очень простой, но крайне неэффективный метод — растеризация через барицентрические координаты. Настоящие 3D-движки используют растеризацию, задействуя железо компьютера, что очень быстро и эффективно, но мы не можем использовать нашу видеокарту, так что будем делать всё вручную, через код.</p><p>Идея заключается в том, чтобы посчитать барицентрическую координату для каждого пикселя, который может лежать внутри нашего треугольника и исключить те, что вне его пределов. Следующий фрагмент содержит алгоритм. Обратите внимание на то, как мы стали напрямую обращаться к пикселям изображения.</p><p>Довольно много кода, но теперь у нас есть цветной тетраэдр на экране.</p><figure><img src="https://media.tproger.ru/uploads/2016/03/2-3.png" alt="" /></figure><p>Если вы поиграетесь с демкой, то вы заметите, что не всё сделано идеально — например, синий треугольник всегда выше других. Так происходит потому, что мы отрисовываем наши треугольники один за другим. Синий здесь — последний, поэтому он отрисовывается поверх других.</p><p>Чтобы исправить это, давайте рассмотрим Z-буферизацию. Идея состоит в том, чтобы в процессе растеризации создать промежуточный массив, который будет хранить в себе расстояние до последнего видимого элемента на каждом из пикселей. Делая растеризацию треугольников, мы будем проверять, меньше ли расстояние до пикселя, чем расстояние до предыдущего, и закрашивать его только в том случае, если он находится поверх других.</p><p>Теперь видно, что у нашего тетраэдра есть одна белая сторона:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/3-3.png" alt="" /></figure><p>Вот мы и получили работающий движок для 3D-рендеринга!</p><p>Но и это ещё не конец. В реальном мире восприятие какого-либо цвета меняется в зависимости от положения источников света — если на поверхность падает лишь небольшое количество света, то она видится более темной.</p><p>В компьютерной графике мы можем достичь подобного эффекта с помощью так называемого «затенения» — изменения цвета поверхности в зависимости от угла наклона и расстояния относительно источника света.</p><p>Простейшая форма затенения — это плоское затенение. Этот способ учитывает только угол между поверхностью, нормаль и направление источника света. Вам всего лишь нужно найти косинус угла между двумя векторами и умножить цвет на получившееся значение. Такой подход очень прост и эффективен, поэтому он часто используется для высокоскоростного рендеринга, когда наиболее продвинутые технологии затенения слишком неэффективны.</p><p>Для начала нам нужно посчитать вектор нормали для нашего треугольника. Если у нас есть треугольник ABC, мы можем посчитать его вектор нормали, рассчитав векторное произведение векторов AB и AC и поделив получившийся вектор на его длину.</p><p>Векторное произведение — это бинарная операция на двух векторах, которые определены в 3D пространстве вот так:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/8.png" alt="" /></figure><p>Вот так выглядит визуальное представление того, что делает наше векторное произведение:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/5-1.png" alt="" /></figure><p>Теперь нам нужно посчитать косинус между нормалью треугольника и направлением света. Для упрощения будем считать, что наш источник света расположен прямо за камерой на каком-либо расстоянии (такая конфигурация называется «направленный свет») — таким образом, наш источник света будет находиться в точке (0, 0, 1).</p><p>Косинус угла между векторами можно посчитать по формуле:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/9.png" alt="" /></figure><p>Где ||A|| — длина вектора, а числитель — скалярное произведение векторов A и B:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/10.png" alt="" /></figure><p>Обратите внимание на то, что длина вектора направления света  равна 1, так же, как и длина нормали треугольника (мы уже нормализовали это). Таким образом, формула просто превращается в это:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/11.png" alt="" /></figure><p>Заметьте, что только Z компонент направления света не равен нулю, так что мы можем просто всё упростить:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/12.png" alt="" /></figure><p>В коде это всё выглядит тривиально:</p><p>Мы опускаем знак результата, потому что для наших целей нам не важно, какая сторона треугольника смотрит на камеру. В реальном приложении вам нужно будет отслеживать это и применять затенение соответственно.</p><p>Теперь, получив наш коэффициент затенения, мы можем применить его к цвету нашего треугольника. Простой вариант будет выглядеть как-то так:</p><p>Код будет давать нам некоторые эффекты затенения, но спадать они будут куда быстрее, чем нам нужно. Так происходит, потому что в Java используется спектр цветов sRGB.</p><p>Так что нам нужно конвертировать каждый цвет в линейный формат, применить затенение и затем конвертировать обратно. Реальный переход из sRGB к линейному RGB — довольно трудоёмкий процесс, так что я не буду выполнять полный перечень задач здесь. Вместо этого, я сделаю нечто приближенное к этому.</p><p>И теперь мы видим, как наш тетраэдр оживляется. У нас есть работающий движок для 3D рендеринга с цветами, освещением, затенением, и заняло это около 200 строк кода — неплохо!</p><p>Вот небольшой бонус для вас — вы можете быстро создать фигуру, приближенную к сфере из своего тетраэдра. Этого можно достичь путём разбивания каждого треугольника на 4 маленьких и «надувая».</p><p>Вот что должно у вас получиться:</p><figure><img src="https://media.tproger.ru/uploads/2016/03/0.png" alt="" /></figure><p>Я бы закончил эту статью, порекомендовав одну занимательную книгу: <a href="http://www.amazon.com/Math-Primer-Graphics-Development-Edition/dp/1568817231">“Основы 3D-математики для графики и разработки игр”</a>. В ней вы можете найти детальное объяснение процесса рендеринга и математики в этом процессе. Её стоит прочитать, если вам интересны движки для рендеринга.</p>]]></content:encoded>
    </item>
  </channel>
</rss>