\documentclass[a4paper, final]{article} \usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта \usepackage[T2A]{fontenc} \usepackage[utf8]{inputenc} \usepackage[russian]{babel} \usepackage{ragged2e} \usepackage{algorithmic} \usepackage{amsmath} \usepackage{multicol} \usepackage{nccmath} \usepackage{tikz} \usepackage{wrapfig} \usepackage{caption} \usepackage{tabularx} \usepackage{array} \usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry} \usepackage{ragged2e} %для растягивания по ширине \usepackage{setspace} %для межстрочного интервала \usepackage{moreverb} %для работы с листингами \usepackage{indentfirst} % для абзацного отступа \usepackage{moreverb} %для печати в листинге исходного кода программ \renewcommand\verbatimtabsize{4\relax} \renewcommand\listingoffset{0.2em} \renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице \usepackage[font=small, singlelinecheck=false, justification=raggedleft, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы %\usepackage[dvips]{graphicx} % Для вставки графических изображений %\usepackage{color} %% это для отображения цвета в коде %\usepackage{xcolor} % цвета \usepackage{listingsutf8} \usepackage{hyperref}% для гиперссылок \usepackage{enumitem} %для перечислений \usepackage{pdflscape} %для pdf \usepackage{pdfpages} %для pdf \definecolor{apricot}{HTML}{FFF0DA} \definecolor{mygreen}{rgb}{0,0.6,0} \definecolor{string}{HTML}{B40000} % цвет строк в коде \definecolor{comment}{HTML}{008000} % цвет комментариев в коде \definecolor{keyword}{HTML}{1A00FF} % цвет ключевых слов в коде \definecolor{morecomment}{HTML}{8000FF} % цвет include и других элементов в коде \definecolor{captiontext}{HTML}{FFFFFF} % цвет текста заголовка в коде \definecolor{captionbk}{HTML}{999999} % цвет фона заголовка в коде \definecolor{bk}{HTML}{FFFFFF} % цвет фона в коде \definecolor{frame}{HTML}{999999} % цвет рамки в коде \definecolor{brackets}{HTML}{B40000} % цвет скобок в коде \setlist[enumerate,itemize]{leftmargin=1.2cm} \hypersetup{colorlinks, pdftitle={Лабораторная №3}, pdfauthor={Тищенко А. А.}, allcolors=[RGB]{010 090 200}} % подгружаемые языки — подробнее в документации listings \lstloadlanguages{bash} % включаем кириллицу и добавляем кое−какие опции %\lstset{language =[LaTeX] TeX, % выбираем язык по умолчанию %extendedchars=true , % включаем не латиницу %escapechar = | , % |«выпадаем» в LATEX| %frame=tb , % рамка сверху и снизу %commentstyle=\itshape , % шрифт для комментариев %stringstyle =\bfseries} % шрифт для строк \textheight=24cm % высота текста \textwidth=16cm % ширина текста \oddsidemargin=0pt % отступ от левого края \topmargin=-1.5cm % отступ от верхнего края \parindent=24pt % абзацный отступ \parskip=0pt % интервал между абзацами \tolerance=2000 % терпимость к "жидким" строкам \flushbottom % выравнивание высоты страниц \addto\captionsrussian{\def\refname{\hspace{1.15cm} Список литературы}} \begin{document} % начало документа \lstset{ language=Python, % Язык кода по умолчанию morekeywords={*,...}, % если хотите добавить ключевые слова, то добавляйте % Цвета keywordstyle=\color{keyword}\ttfamily\bfseries, %stringstyle=\color{string}\ttfamily, stringstyle=\ttfamily\color{red!50!brown}, commentstyle=\color{comment}\ttfamily, morecomment=[l][\color{morecomment}]{\#}, % Настройки отображения breaklines=true, % Перенос длинных строк basicstyle=\ttfamily\footnotesize, % Шрифт для отображения кода backgroundcolor=\color{bk}, % Цвет фона кода frame=lrb,xleftmargin=\fboxsep,xrightmargin=-\fboxsep, % Рамка, подогнанная к заголовку rulecolor=\color{frame}, % Цвет рамки tabsize=3, % Размер табуляции в пробелах % Настройка отображения номеров строк. Если не нужно, то удалите весь блок numbers=left, % Слева отображаются номера строк stepnumber=1, % Каждую строку нумеровать numbersep=5pt, % Отступ от кода numberstyle=\small\color{black}, % Стиль написания номеров строк % Для отображения русского языка extendedchars=true, literate={Ö}{ {\"O} }1 {~}{ {\textasciitilde} }1 {а}{ {\selectfont\char224} }1 {б}{ {\selectfont\char225} }1 {в}{ {\selectfont\char226} }1 {г}{ {\selectfont\char227} }1 {д}{ {\selectfont\char228} }1 {е}{ {\selectfont\char229} }1 {ё}{ {\"e} }1 {ж}{ {\selectfont\char230} }1 {з}{ {\selectfont\char231} }1 {и}{ {\selectfont\char232} }1 {й}{ {\selectfont\char233} }1 {к}{ {\selectfont\char234} }1 {л}{ {\selectfont\char235} }1 {м}{ {\selectfont\char236} }1 {н}{ {\selectfont\char237} }1 {о}{ {\selectfont\char238} }1 {п}{ {\selectfont\char239} }1 {р}{ {\selectfont\char240} }1 {с}{ {\selectfont\char241} }1 {т}{ {\selectfont\char242} }1 {у}{ {\selectfont\char243} }1 {ф}{ {\selectfont\char244} }1 {х}{ {\selectfont\char245} }1 {ц}{ {\selectfont\char246} }1 {ч}{ {\selectfont\char247} }1 {ш}{ {\selectfont\char248} }1 {щ}{ {\selectfont\char249} }1 {ъ}{ {\selectfont\char250} }1 {ы}{ {\selectfont\char251} }1 {ь}{ {\selectfont\char252} }1 {э}{ {\selectfont\char253} }1 {ю}{ {\selectfont\char254} }1 {я}{ {\selectfont\char255} }1 {А}{ {\selectfont\char192} }1 {Б}{ {\selectfont\char193} }1 {В}{ {\selectfont\char194} }1 {Г}{ {\selectfont\char195} }1 {Д}{ {\selectfont\char196} }1 {Е}{ {\selectfont\char197} }1 {Ё}{ {\"E} }1 {Ж}{ {\selectfont\char198} }1 {З}{ {\selectfont\char199} }1 {И}{ {\selectfont\char200} }1 {Й}{ {\selectfont\char201} }1 {К}{ {\selectfont\char202} }1 {Л}{ {\selectfont\char203} }1 {М}{ {\selectfont\char204} }1 {Н}{ {\selectfont\char205} }1 {О}{ {\selectfont\char206} }1 {П}{ {\selectfont\char207} }1 {Р}{ {\selectfont\char208} }1 {С}{ {\selectfont\char209} }1 {Т}{ {\selectfont\char210} }1 {У}{ {\selectfont\char211} }1 {Ф}{ {\selectfont\char212} }1 {Х}{ {\selectfont\char213} }1 {Ц}{ {\selectfont\char214} }1 {Ч}{ {\selectfont\char215} }1 {Ш}{ {\selectfont\char216} }1 {Щ}{ {\selectfont\char217} }1 {Ъ}{ {\selectfont\char218} }1 {Ы}{ {\selectfont\char219} }1 {Ь}{ {\selectfont\char220} }1 {Э}{ {\selectfont\char221} }1 {Ю}{ {\selectfont\char222} }1 {Я}{ {\selectfont\char223} }1 {\{}{ { {\color{brackets}\{} } }1 % Цвет скобок { {\} }{ { {\color{brackets}\} } } }1 % Цвет скобок } } % НАЧАЛО ТИТУЛЬНОГО ЛИСТА \thispagestyle{empty} \begin{center} \normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ \\ ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО \\ ОБРАЗОВАНИЯ\\ «Санкт-Петербургский политехнический университет Петра Великого»} \normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt] \normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt] \normalsize{Направление: 02.03.01 Математика и компьютерные науки}\\ \hfill \break \hfill \break \hfill \break \hfill \break \hfill \break \hfill \break \normalsize{Отчет о выполнении лабораторной работы №3 по дисциплине}\\[3pt] \normalsize{«Алгоритмические основы компьютерной графики»}\\[3pt] \normalsize{по теме:}\\[3pt] \normalsize{«Визуализация физического процесса»}\\[10pt] \end{center} \hfill \break \hfill \break \hfill \break \hfill \break \begin{tabular}{lcrl} \!\!\!Студент, & \hspace{2cm} & & \\ \!\!\!группы 5130201/20101 & \hspace{2.13cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\ \!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Курочкин М. А. \\\\ &&\hspace{5cm} \end{tabular} \begin{flushright} <<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025 г. \end{flushright} \hfill \break \hfill \break \begin{center} \small{Санкт-Петербург, 2025} \end{center} \newpage %Содержание% выключаем отображение номера для этой страницы % КОНЕЦ ТИТУЛЬНОГО ЛИСТА \newpage \tableofcontents % \newpage % \addcontentsline{toc}{section}{Введение} % \newpage % \subsection*{Введение} % Компьютерная графика служит мощным средством для отображения физических явлений, обеспечивая возможность их визуализации и изучения динамических свойств. Одним из значимых направлений в этой области является визуализация жидкостей, которая позволяет имитировать реальные физические явления, в частности, движение воды. % Процедурные методы визуализации занимают особую нишу среди подходов к созданию графики, поскольку их принцип основан на применении математических алгоритмов и физических закономерностей. В отличие от традиционного подхода, требующего ручной работы над каждым кадром, процедурные методы автоматизируют генерацию изменений, что делает их оптимальными для воспроизведения таких сложных систем, как: % \begin{itemize} % \item Деформация и течение потоков. % \item Искажение изображений через водную поверхность. % \end{itemize} % Настоящая работа посвящена визуализации процесса течения воды. В качестве основы для создания визуализации был использован фрагмент видеозаписи течения воды из крана, что дает возможность сравнить результаты цифровой визуализации с поведением жидкости в естественных условиях. % \vspace{5pt} % Ключевые аспекты реализации: % \begin{itemize} % \item \textbf{Динамика во времени}\\ % Течение жидкости — это непрерывный процесс, в котором форма и объем потока постоянно меняются. Для достижения реалистичности требуется последовательная и поэтапная обработка генерации на различных участках струи. % \item\textbf{ Упрощенный подход к визуализации}\\ % Несмотря на то, что в действительности движение жидкостей регулируется сложными законами гидродинамики, в данной работе основное внимание уделяется внешней, визуальной стороне процесса. Такой подход позволяет добиться высокой степени достоверности изображения без необходимости выполнения сложных вычислений. % \end{itemize} % Основная цель работы — создание реалистичной анимации процесса течения воды из крана, демонстрирующей ключевые физические свойства вещества при минимальных упрощениях. \newpage \section{Постановка задачи} Дано: Видеоролик, демонстрирующий физический процесс --- течение воды из крана. На записи также видно формирование и падение нескольких капель с головки крана. Один из кадров данного процесса показан на рисунке \hyperref[pic1]{1}.\par Цель работы: программными средствами визуализировать наблюдаемый на видеозаписи процесс. \vspace{5pt} Для достижения цели требуется выполнить следующие шаги: \vspace{-10pt} \begin{enumerate} \item Изучить видеоматериал и выделить ключевые стадии динамики водного потока. \item Разработать алгоритмы и подходы для визуальной реконструкции процесса. \item Реализовать визуализацию с применением графических библиотек, обеспечив: \begin{enumerate} \item естественное отображение колебаний и искажений водной струи; \item достоверную анимацию падения отдельных капель с учётом их прозрачности и размеров; \item возможность параметрической настройки характеристик струи (амплитуда колебаний, скорость появления капель). \end{enumerate} \end{enumerate} \begin{figure}[h] \centering{ \includegraphics[width=80mm]{pic1.png} \caption{\centering{{Кадр видеофрагмента реального процесса}}} } \label{pic1} \end{figure}\par \newpage \section{Описание физического процесса} Видео иллюстрирует физический процесс: течение воды из крана, который нобходимо было воспроизвести средствами процедурной анимации. Видео имеет разрешение $1600 \times 913$ пикселей, этого достаточно, чтобы заметить даже небольшие детали процесса, которые необходимо учесть при визуализации: \begin{itemize} \item \textbf{Динамика струи воды:} \begin{itemize} \item В начальный момент из крана выходит непрерывный поток воды, формирующий вытянутую струю. \item Струя не является идеально ровной --- под действием колебаний и турбулентности она искажается, создавая небольшую рябь на границах струи. \item Наблюдается постепенное движение воды влево и вправо, всего наблюдается 4 наиболее заметных и несколько небольших колебаний. \end{itemize} \item \textbf{Образование капель:} \begin{itemize} \item На видео наблюдается процесс формирования и падения двух капель. \item Капли имеют разный размер и форму, а также формируются в разных местах и с разной скоростью на головке крана. \item После формирования капли очень быстро падают вниз. Падение капель видно лишь на нескольких кадрах из-за небольшой частоты кадров видеозаписи. \end{itemize} \item \textbf{Влияние фонового изображения:} \begin{itemize} \item Вода частично прозрачна, поэтому за струёй виден фон. \item Фоновые объекты, видимые через струю, сильно искажаются из-за преломления света в воде. \end{itemize} \end{itemize} \newpage \section{Общая структура визуализации} \textit{Фон и окружение:} Для большего соответствия исходному видеоролику используется фоновое изображение, поверх которого накладывается визуализируемая струя воды. Это позволяет интегрировать анимацию в контекст сцены и повысить реализм изображения. \textit{Струя воды:} Основной элемент визуализации --- поток воды из крана, основные положения которой формируются по контурам, заданным масками. Для каждого шага анимации контуры интерполируются, что обеспечивает плавное изменение формы струи во времени. \textit{Дополнительные эффекты:} Во время течения воды, появляются капли, которые отрываются от верхней границы струи и движутся вниз, усиливая динамику сцены. \subsection{Подход к формированию струи} Визуализация строится на {геометрических контурах}, которые задаются масками. Каждая маска определяет форму и положения струи в пространстве в определенный момент времени. Плавность перехода между масками обеспечивается интерполяцией контуров. На форму добавляются синусоидальные колебания и случайные шумовые сдвиги, что позволяет создать характерную «живую» динамику воды. Для придания глубины и текстуры поток заливается цветным градиентом, плавно переходящим от более тёмных тонов к светлым. \subsubsection{Маски формы струи} Каждая маска задаёт положение струи на определённой фазе. Пример такой маски приведен на \hyperref[pic2]{рисунке 2} и \hyperref[pic3]{рисунке 3}. Последовательность масок определяет динамику движения струи влево и вправо. \begin{figure}[h!] \begin{multicols}{2} \hfill \includegraphics[width=100mm]{pic2.png} \caption{\centering{Начальная маска положения струи}} \label{pic2} \hfill \includegraphics[width=100mm]{pic3.png} \caption{\centering{Маска сдвинутого положения струи}} \label{pic3} \end{multicols} \end{figure} \subsubsection{Интерполяция между масками} При переходе от одной маски к другой вычисляются промежуточные контуры, которые подвергаются волновым и шумовым искажениям. Это позволяет струе двигаться и «колыхаться», имитируя естественное течение воды. \subsection{Динамические эффекты} \textbf{Искажения:} Для отрисовки бликов и неоднородной структуры потока под саму струю была подложена картинка бликов, изображенная на \hyperref[pic4]{рисунке 4}. Для получения эффекта размытия на подложку струи накладывается горизонтальное синусоидальное смещение строк изображения, что создаёт эффект размытости и движения. \textbf{Градиентная заливка:} Поток закрашивается набором полигонов с плавным изменением цвета и прозрачности, формируя характерное изменение цвета и прозрачности воды по времени ее стекания из крана. \textbf{Формирование капель:} На верхнем крае струи периодически появляются капли. Капли визуализируются как вытянутые эллипсы с частичной прозрачностью. \begin{figure}[h] \centering{ \includegraphics[width=100mm]{pic4.png} \caption{\centering{{Подложка под струю, имитирующая блики и отражения окружающей среды}}} } \label{pic4} \end{figure}\par % \begin{figure}[h] % \centering{ % \includegraphics[width=80mm]{pic5.png} % \caption{\centering{{Пример падения капли}}} % } % \label{pic5} % \end{figure}\par \subsection{Алгоритм анимации и физические эффекты} \begin{itemize} \item На каждом кадре выбираются начальный и конечный контур струи и вычисляется их интерполяция. \item К координатам контуров добавляются синусоидальные колебания и случайный шум. \item По получённым точкам строится маска, которая ограничивает область видимости подложки. \item Внутренняя область струи окрашивается с использованием цветового градиента. \item На верхнем крае струи (где она вытекает из под крана) периодически формируются капли. После форимрования капли падают вниз и скрываются за границей экрана. \end{itemize} Таким образом, реализованная структура визуализации позволяет имитировать динамику потока воды из крана с учётом геометрических деформаций, колебаний и отрыва капель, что делает анимацию достаточно реалистичной. \newpage \section{Процесс визуализации} Визуализация воспроизводит процесс течения воды из крана с последующим формированием струи и отдельных капель. Здесь применён геометрический подход: поток описывается через набор контуров, получаемых из масок и подвергаемых интерполяции и искажениям. Такая схема позволяет имитировать естественные колебания, разрывы и изменения формы водного потока. \subsection{Последовательность процесса визуализации} \subsubsection{Формирование и динамика струи} \begin{itemize} \item \textbf{Начальное появление:} Струя формируется на основе исходной маски (\hyperref[pic2]{рисунок 2}), которая задаёт начальную геометрию потока. \item \textbf{Изменение формы:} При переходе между масками используется интерполяция контуров. Дополнительно применяются синусоидальные колебания и шумовые искажения, что создаёт эффект подвижной струи с характерными колебаниями и неровностями. \item \textbf{Генерация капель:} На верхнем крае потока периодически появляются отдельные капли, которые движутся вниз под действием гравитации. Их размер и прозрачность варьируются, что делает анимацию более естественной. \end{itemize} \subsubsection{Визуальные эффекты} \begin{itemize} \item \textbf{Прозрачность и цвет:} Поток и капли визуализируются как полупрозрачные объекты с градиентной заливкой, что позволяет передать глубину, световые переходы и эффект преломления. \item \textbf{Фоновое изображение:} На задний план накладывается статическое фото, что усиливает реалистичность сцены и создаёт контекст окружающей среды. \item \textbf{Искажения:} Кадры подвергаются горизонтальному синусоидальному смещению, которое усиливает впечатление движения и дрожания струи. \end{itemize} \subsubsection{Завершение анимации} \begin{itemize} \item \textbf{Окончание симуляции:} Визуализация продолжается до достижения последней маски либо заданного числа кадров. Каждый кадр сохраняется для последующего формирования видеоряда. \end{itemize} \subsection{Используемые технологии и библиотеки} Python --- основной язык программирования. \subsubsection{Используемые библиотеки} \begin{itemize} \item \textbf{Pygame} --- для создания окна, загрузки изображений, построения анимации и работы с масками. \begin{itemize} \item \texttt{pygame.init()} --- инициализирует все основные модули Pygame. \item \texttt{pygame.display.set\_mode()} --- создаёт окно приложения и поверхность для отрисовки. \item \texttt{pygame.image.load()} --- загружает изображения фона и масок. \item \texttt{pygame.Surface()} --- создаёт поверхности с поддержкой прозрачности, используемые для наложения эффектов. \item \texttt{pygame.draw.polygon()} и \texttt{pygame.draw.ellipse()} --- для отрисовки контура струи и капель воды. \item \texttt{pygame.display.flip()} --- обновляет окно, показывая отрисованный кадр. \item \texttt{pygame.time.Clock()} --- управляет частотой кадров анимации. \item \texttt{pygame.event.get()} --- отслеживает события (например, закрытие окна). \item \texttt{pygame.quit()} --- завершает работу программы и освобождает ресурсы. \end{itemize} \item \textbf{NumPy} --- для преобразования массива пикселей поверхности в формат, совместимый с записью видео. \item \textbf{Imageio} --- для записи последовательности кадров анимации в файл \texttt{.mp4}. \item \textbf{math} --- для вычисления синусоидальных искажений струи. \item \textbf{random} --- для внесения случайных колебаний в контуры и траектории капель. \end{itemize} \subsection{Основные числовые параметры} \begin{itemize} \item \textit{Размер окна:} определяется автоматически по загруженному изображению фона (например, $400 \times 600$ пикселей). \item \textit{Частота кадров:} \texttt{FPS = 60} --- обеспечивает плавность анимации. \item \textit{Цвета струи:} \begin{itemize} \item Начальный цвет: \texttt{(55, 50, 45, 100)} --- тёмно-серый полупрозрачный. \item Конечный цвет: \texttt{(180, 180, 180, 50)} --- светло-серый с большей прозрачностью. \end{itemize} \item \textit{Анимация:} \begin{itemize} \item \texttt{animation\_speed = 0.005} --- скорость перехода между фазами. \item \texttt{amplitude = 2} --- амплитуда волнового искажения контура. \item \texttt{frequency = 0.05} --- частота волны. \item \texttt{t += 9} --- параметр времени, задающий движение искажения. \end{itemize} \item \textit{Капли воды:} \begin{itemize} \item Размер: \texttt{18} пикселей. \item Скорость падения: \texttt{35} пикселей за кадр. \item Цвет: полупрозрачный бело-голубой \texttt{(60, 55, 55, 30)}. \item Частота появления: \texttt{drop\_frequency = 1}. \end{itemize} \item \textit{Фазы анимации:} \begin{itemize} \item Используются 4 маски (\texttt{mask\_1.png} ... \texttt{mask\_4.png}), определяющие форму струи на каждом этапе. \item Интерполяция контуров плавно изменяет форму между фазами. \item Переключение фаз происходит при завершении цикла интерполяции. \end{itemize} \end{itemize} \vspace{15pt} Таким образом, разработанная программа выполняет визуализацию движения водной струи с использованием масок, градиентной заливки и реалистичных капель, обеспечивая плавную анимацию и экспорт результата в видеофайл. \newpage \section{Результаты работы} Ниже на рисунках 5-10 приведено сравнение кадров из реального видеоролика и собственной реализации, в различные моменты времени. \begin{figure}[h!] \begin{multicols}{2} \hfill \includegraphics[width=80mm]{pic6.png} \caption{\centering{Начальный момент оригинального видео}} \label{pic6} \hfill \includegraphics[width=80mm]{pic7.png} \hfill \caption{\centering{Начальный момент реализации}} \label{pic7} \end{multicols} \end{figure} \begin{figure}[h!] \begin{multicols}{2} \hfill \includegraphics[width=80mm]{pic8.png} \caption{\centering{Серединный момент оригинального видео}} \label{pic8} \hfill \includegraphics[width=80mm]{pic9.png} \hfill \caption{\centering{Серединный момент реализации}} \label{pic9} \end{multicols} \end{figure} \begin{figure}[h!] \begin{multicols}{2} \hfill \includegraphics[width=80mm]{pic10.png} \caption{\centering{Конечный момент оригинального видео}} \label{pic10} \hfill \includegraphics[width=80mm]{pic11.png} \hfill \caption{\centering{Конечный момент реализации}} \label{pic11} \end{multicols} \end{figure} \newpage \hfill \newpage \addcontentsline{toc}{section}{Заключение} \section*{Заключение} В ходе выполнения лабораторной работы был проведён анализ видеозаписи, отображающей физический процесс вытекания воды из крана и её дальнейшего движения. На основе изучения динамики струи были определены ключевые особенности: постепенное изменение формы потока, появление колебаний, формирование капель и влияние случайных искажений на контур. Для воспроизведения наблюдаемого эффекта была реализована программная визуализация на языке Python. Визуализация основана на интерполяции масок, описывающих форму струи на различных этапах, а также на применении дополнительных эффектов, таких как волновое искажение, градиентная заливка и генерация падающих капель. В технической части использованы следующие приёмы: \begin{enumerate} \item Применение альфа-канала для создания эффекта прозрачности воды; \item Использование масок для задания формы струи и плавной смены фаз; \item Добавление случайных искажений, формирующих реалистичные колебания потока; \item Генерация отдельных капель, усиливающих правдоподобность анимации; \item Экспорт последовательности кадров в видеофайл с помощью библиотеки \texttt{imageio}. \end{enumerate} Разработанная визуализация позволяет достоверно отобразить процесс формирования и движения струи воды, включая её деформацию и каплеобразование. Программная реализация на базе библиотек \texttt{Pygame}, \texttt{NumPy}, \texttt{imageio}, \texttt{math} и \texttt{random} обеспечила гибкость настройки и высокую наглядность результата. Созданная программа может быть использована как демонстрационный инструмент при изучении явлений гидродинамики, а также как основа для дальнейших экспериментов по визуализации жидкостей в компьютерной графике. Полный исходный код представлен в приложении~A. \newpage \newpage \newpage \lstset{ backgroundcolor=\color{white}, frame=single } \newpage \addcontentsline{toc}{section}{Приложение А. Код реализации программы} \addcontentsline{toc}{subsection}{А.1 waterflow\_visualization.py} \section*{Приложение А. Код реализации программы} \subsection*{А.1 waterflow\_visualization.py} \begin{lstlisting}[language=Python, caption={Визуализация течения воды из крана}] import pygame import math import random import imageio import numpy as np pygame.init() # --- Класс для капель воды --- class WaterDrop: def __init__(self, x, y): self.x = x self.y = y self.size = 18 self.speed = 35 # Прозрачный бело-голубой цвет (альфа 120) self.color = (60, 55, 55, 30) self.ellipse_width = self.size self.ellipse_height = self.size * 1.5 def update(self): self.y += self.speed def draw(self, screen): drop_rect = pygame.Rect( self.x - self.ellipse_width / 2, self.y - self.ellipse_height / 2, self.ellipse_width, self.ellipse_height, ) # Временная поверхность с альфой drop_surface = pygame.Surface( (self.ellipse_width, self.ellipse_height), pygame.SRCALPHA ) # Рисуем каплю на этой поверхности pygame.draw.ellipse(drop_surface, self.color, (0, 0, self.ellipse_width, self.ellipse_height)) # Накладываем на экран screen.blit(drop_surface, drop_rect.topleft) def is_offscreen(self, screen_height): return self.y > screen_height # --- Настройка экрана и загрузка ресурсов --- try: temp_background_image = pygame.image.load("waterflow_background.png") width, height = temp_background_image.get_size() use_background = True except pygame.error as e: print(f"Error loading background image: {e}") width, height = 400, 600 use_background = False screen = pygame.display.set_mode((width, height)) if use_background: background_image = temp_background_image.convert() clock = pygame.time.Clock() masks = [] try: mask1_image = pygame.image.load("mask_1.png").convert_alpha() mask2_image = pygame.image.load("mask_2.png").convert_alpha() mask3_image = pygame.image.load("mask_3.png").convert_alpha() mask4_image = pygame.image.load("mask_4.png").convert_alpha() masks = [mask1_image, mask2_image, mask3_image, mask4_image] except pygame.error as e: print(f"Error loading mask images: {e}") pygame.quit() exit() # --- Вырезаем базовую подложку из отдельной картинки --- if use_background: base_mask = pygame.image.load("moving_image_4.png").convert_alpha() stream_base = pygame.Surface((width, height), pygame.SRCALPHA) stream_base.blit(background_image, (0, 0)) stream_base.blit(base_mask, (0, 0), special_flags=pygame.BLEND_RGBA_MULT) # --- ФУНКЦИИ --- def distort_only_stream(base, mask, t): """Размывает и искажает подложку по маске""" w, h = base.get_size() distorted = pygame.Surface((w, h), pygame.SRCALPHA) for y in range(0, h, 2): offset = int(5 * math.sin(0.05 * y + t * 0.1)) # сдвиг строки distorted.blit(base, (offset, y), (0, y, w, 2)) # применяем маску струи distorted.blit(mask, (0, 0), special_flags=pygame.BLEND_RGBA_MULT) return distorted def get_stream_contours(mask_surface): left_contour = [] right_contour = [] rows_data = {} for y in range(mask_surface.get_height()): left_x = -1 right_x = -1 for x in range(mask_surface.get_width()): if mask_surface.get_at((x, y))[0] < 50: if left_x == -1: left_x = x right_x = x if left_x != -1: rows_data[y] = (left_x, right_x) for y in sorted(rows_data.keys()): left_x, right_x = rows_data[y] left_contour.append((left_x, y)) right_contour.append((right_x, y)) return left_contour, right_contour def make_stream_mask(left, right, width, height): mask_surface = pygame.Surface((width, height), pygame.SRCALPHA) polygon_points = left + right[::-1] pygame.draw.polygon(mask_surface, (255, 255, 255, 255), polygon_points) return mask_surface def interpolate_countours( start_left_contour_, start_right_contour_, end_left_contour_, end_right_contour_ ): interpolated_left = [] interpolated_right = [] min_len_left = min(len(start_left_contour_), len(end_left_contour_)) min_len_right = min(len(start_right_contour_), len(end_right_contour_)) for i in range(min_len_left): start_x, start_y = start_left_contour_[i] end_x, end_y = end_left_contour_[i] interp_x = start_x + (end_x - start_x) * animation_progress wave1 = amplitude * math.sin(frequency * start_y + t) wave2 = (amplitude / 2) * math.sin(2 * frequency * start_y + 1.5 * t) noise = random.uniform(-0.5, 0.5) x_offset = wave1 + wave2 + noise interpolated_left.append((interp_x + x_offset, start_y)) for i in range(min_len_right): start_x, start_y = start_right_contour_[i] end_x, end_y = end_right_contour_[i] interp_x = start_x + (end_x - start_x) * animation_progress wave1 = amplitude * math.sin(frequency * start_y + t) wave2 = (amplitude / 2) * math.sin(2 * frequency * start_y + 1.5 * t) noise = random.uniform(-0.5, 0.5) x_offset = wave1 + wave2 + noise interpolated_right.append((interp_x + x_offset, start_y)) return interpolated_left, interpolated_right # --- Контуры масок --- contours = [] for mask in masks: left, right = get_stream_contours(mask) contours.append([left, right]) # --- Параметры --- start_color = (55, 50, 45, 100) end_color = (180, 180, 180, 50) animation_progress = 0 animation_speed = 0.005 amplitude = 2 frequency = 0.05 t = 0 reverse = False phase = 1 drops = [] FPS = 60 drop_frequency = 1 drop_counter = 0 writer = imageio.get_writer("animation_with_blur.mp4", fps=FPS) # --- Главный цикл --- running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # --- Отрисовка фона --- if use_background: screen.blit(background_image, (0, 0)) else: screen.fill((255, 255, 255)) # Интерполяция контура струи interpolated_left, interpolated_right = interpolate_countours( contours[0][1], contours[0][0], contours[phase][1], contours[phase][0] ) stream_mask = make_stream_mask(interpolated_left, interpolated_right, width, height) # --- Размытая и искажённая подложка --- distorted_stream = distort_only_stream(stream_base, stream_mask, t) screen.blit(distorted_stream, (0, 0)) # --- Цветная заливка для струи --- polygon_points = interpolated_left + interpolated_right[::-1] if interpolated_left and interpolated_right: stream_surface = pygame.Surface((width, height), pygame.SRCALPHA) num_points = len(interpolated_left) for i in range(num_points - 1): r = int(start_color[0] + (end_color[0] - start_color[0]) * (i / num_points) ** 0.6) g = int(start_color[1] + (end_color[1] - start_color[1]) * (i / num_points) ** 0.6) b = int(start_color[2] + (end_color[2] - start_color[2]) * (i / num_points) ** 0.6) a = int(start_color[3] + (end_color[3] - start_color[3]) * (i / num_points) ** 0.2) points = [ interpolated_left[i], interpolated_left[i + 1], interpolated_right[i + 1], interpolated_right[i], ] pygame.draw.polygon(stream_surface, (r, g, b, a), points) screen.blit(stream_surface, (0, 0)) coef = 3 if phase == 0 or phase == 1 else 1.5 # --- Анимация прогресса --- animation_progress += animation_speed if not reverse else animation_speed * coef if animation_progress >= 1.0 or animation_progress <= 0.0: animation_speed *= -1 if animation_progress <= 0.0: phase = min(3, phase + 1) drop_counter +=1 reverse = not reverse animation_progress = max(0.0, min(1.0, animation_progress)) t += 9 if drop_counter >= drop_frequency: last_point_y = min(p[1] for p in polygon_points) + 25 bottom_points_x = [p[0] for p in polygon_points if p[1] == last_point_y] if bottom_points_x: spawn_x = sum(bottom_points_x) / len(bottom_points_x) drops.append(WaterDrop(spawn_x + random.randint(-5, 5), last_point_y)) drop_counter = 0 new_drops_list = [] for drop in drops: drop.update() drop.draw(screen) if not drop.is_offscreen(height): new_drops_list.append(drop) drops = new_drops_list pygame.display.flip() # --- Запись кадра --- frame = pygame.surfarray.array3d(screen) frame = np.swapaxes(frame, 0, 1) writer.append_data(frame) clock.tick(FPS) writer.close() pygame.quit() \end{lstlisting} \end{document}