Если вы когда-либо задумывались о создании видео, содержащего компьютерную анимацию, эта статья для вас. Я предполагаю, что у вас уже есть код, или вы можете написать код, создающий некое изображение в виде двумерного массива пикселей. Неважно, что это за изображение, если оно помещается в массив в памяти.
Если вы сможете добраться до этой точки, в этой статье я проведу вас по оставшемуся пути. Шаг за шагом вы научитесь создавать видеофайлы в формате MP4. В итоге вы получите файл, подходящий для загрузки на YouTube, например, как этот:
План
Вот перечень шагов для создания такого видео:
- Написать код на C++, производящий изображение в массиве в памяти. Я использую простой генератор изображений множества Мандельброта, но, повторюсь, это может быть что угодно.
- Использовать LodePNG с открытым кодом для сохранения изображения на диск в формате PNG.
- Код повторяет первые два шага сотни раз, создавая по одному кадру за раз. Каждый PNG-файл содержит один неподвижный кадр видеоклипа.
- На Windows или Linux конвертируем серию PNG-изображений в MP4-видео, используя ffmpeg.
Изображения множества Мандельброта
Множество Мандельброта — один из самых знаменитых фрактальных объектов. Видео выше постепенно увеличивает приближение множества Мандельброта с 1 до 100 миллионов раз. Видео занимает 30 секунд, скорость воспроизведения 30 кадров в секунду, в общей сложности 900 быстро воспроизводимых изображений. Замечательно, что вся эта красота возникает при повторении маленькой формулы:
Функция Mandelbrot
в исходном файле mandelzoom.cpp повторяет эту формулу до тех пор, пока либо комплексное значение z не выйдет за пределы круга радиусом в 2 раза больше исходного, либо n (счетчик повторений) не достигнет максимального предела. Итоговое значение n определяет цвет определенного пикселя на экране.
static int Mandelbrot(double cr, double ci, int limit)
{
int count = 0;
double zr = 0.0;
double zi = 0.0;
double zr2 = 0.0;
double zi2 = 0.0;
while ((count < limit) && (zr2 + zi2 < 4.001))
{
double tzi = 2.0*zr*zi + ci;
zr = zr2 - zi2 + cr;
zi = tzi;
zr2 = zr*zr;
zi2 = zi*zi;
++count;
}
return count;
}
Взгляните на функцию Palette
, чтобы увидеть, как счетчик повторений конвертируется в значения красного, зеленого и синего цветов.
Создание изображения в памяти
Функция GenerateZoomFrames
производит серию PNG-файлов. Каждый PNG-файл содержит изображение множества Мандельброта с другим увеличением. Разрешение изображений — 1280 на 720, стандартное для HD-видео.
static int GenerateZoomFrames(const char *outdir, int numframes, double xcenter, double ycenter, double zoom)
{
try
{
// Создаем буфер видеокадров с разрешением 720p (1280x720).
const int width = 1280;
const int height = 720;
VideoFrame frame(width, height);
const int limit = 16000;
double multiplier = pow(zoom, 1.0 / (numframes - 1.0));
double denom = 1.0;
for (int f = 0; f < numframes; ++f)
{
// Рассчитываем действительный и мнимый диапазон
// значений для каждого кадра.
// Приближение экспоненциально.
// В первом кадре масштаб таков, что меньший габарит
// (высота) занимает 4 единицы от нижней части кадра
// до верхней.
// В последнем кадре масштаб равен количеству единиц,
// разделенному на 'приближение'.
double ver_span = 4.0 / denom;
double hor_span = ver_span * (width - 1.0) / (height - 1.0);
double ci_top = ycenter + ver_span/2.0;
double ci_delta = ver_span / (height - 1.0);
double cr_left = xcenter - hor_span/2.0;
double cr_delta = hor_span / (width - 1.0);
for (int x=0; x < width; ++x)
{
double cr = cr_left + x*cr_delta;
for (int y=0; y < height; ++y)
{
double ci = ci_top - y*ci_delta;
int count = Mandelbrot(cr, ci, limit);
PixelColor color = Palette(count, limit);
frame.SetPixel(x, y, color);
}
}
// Создаем PNG-файл для вывода, имя в формате
// "outdir/frame_12345.png".
char number[20];
snprintf(number, sizeof(number), "%05d", f);
std::string filename = std::string(outdir) + "/frame_" + number + ".png";
// Сохраняем видеокадр как PNG-файл.
int error = frame.SavePng(filename.c_str());
if (error)
return error;
printf("Wrote %sn", filename.c_str());
// Увеличиваем приближение для следующего кадра.
denom *= multiplier;
}
return 0;
}
catch (const char *message)
{
fprintf(stderr, "EXCEPTION: %sn", message);
return 1;
}
}
Сохранение изображения в PNG-файл
Класс VideoFrame
может быть полезен для приложения генератора видео. Он отображает один кадр и знает, как сохранить его в PNG-файл. Функция GenerateZoomFrames
использует VideoFrame
для создания каждого кадра видео фрактального приближения.
class VideoFrame
{
private:
int width;
int height;
std::vector<unsigned char> buffer;
public:
VideoFrame(int _width, int _height)
: width(_width)
, height(_height)
, buffer(4 * _width * _height, 255)
{}
void SetPixel(int x, int y, PixelColor color)
{
int index = 4 * (y*width + x);
buffer[index] = color.red;
buffer[index+1] = color.green;
buffer[index+2] = color.blue;
buffer[index+3] = color.alpha;
}
int SavePng(const char *outFileName)
{
unsigned error = lodepng::encode(outFileName, buffer, width, height);
if (error)
{
fprintf(stderr, "ERROR: lodepng::encode returned %un", error);
return 1;
}
return 0;
}
};
Функция-член VideoFrame::SetPixel
должна вызываться приложением для каждого пикселя каждого кадра. Вы передаете структуру PixelColor
, которая определяет красный, зеленый и синий компоненты пикселя. Эти значение — целые числа в диапазоне от 0 до 255.
PixelColor
также содержит альфа-значение в диапазоне от 0 до 255, отображающее прозрачность пикселя. Для видео приложений следует всегда устанавливать это значение 255, что означает, что пиксель полностью непрозрачен. Я включил альфа-значение для универсальности, в случае, если вам захочется сгенерировать PNG-файлы с прозрачными областями.
Использование ffmpeg для создания видеоклипа
Я включил баш-скрипт run
, автоматизирующий весь процесс создания программы mandelzoom из исходного кода, ее запуск и конвертирование полученных 900 PNG-файлов в файл видеоклипа с именем zoom.mp4
. Последний шаг выполняется запуском программы ffmpeg. Вот скрипт run
. он содержит несколько полезных комментариев с пояснениями к аргументам командной строки для ffmpeg.
#!/bin/bash
Fail()
{
echo "FATAL($0): $1"
exit 1
}
# Компилируем исходный код для mandelzoom. Оптимизируем для
# скорости.
echo "Building C++ code..."
g++ -Wall -Werror -Ofast -o mandelzoom mandelzoom.cpp lodepng.cpp || Fail "Error building C++ code."
# Уничтожаем папку "movie" и ее содержимое, если она существует.
rm -rf movie || Fail "Error deleting movie directory."
# Создаем новую пустую папку "movie" для хранения файлов вывода.
mkdir movie || Fail "Error creating movie directory."
# Запускаем генератор mandelbrot. Он создает все PNG-файлы вывода.
./mandelzoom movie $((30*30)) -0.74498410019 -0.13523854817 1.0e8 ||
Fail "Error running Mandelbrot Zoom program."
# Конвертируем PNG-файлы в видеофайл zoom.mp4.
# Пояснение к аргументам командной строки ffmpeg:
# "-r 30" означает 30 кадров в секунду.
# "-f image2" : конвертирует серию кадров в видео.
# "-s 1280x720" указывает размеры итогового видео в пикселях.
# "-i movie/frame_%05d.png" задает имена png-файлов, которые будут
# использоваться в качестве входных данных.
# "-vcodec libx264": библиотека кодеков, источник:
# https://www.videolan.org/developers/x264.html
# "-crf 15" : коэффициент постоянной скорости, определяющий степень # сжатия с потерями. https://trac.ffmpeg.org/wiki/Encode/H.264
# "-pix_fmt yuv420p" определяет, как цвета кодируются в mp4-файле.
# https://en.wikipedia.org/wiki/YUV
# "zoom.mp4" - имя итогового файла.
ffmpeg -r 30 -f image2 -s 1280x720 -i movie/frame_%05d.png -vcodec libx264 -crf 15 -pix_fmt yuv420p zoom.mp4 ||
Fail "Error in ffmpeg."
echo "Created movie zoom.mp4"
exit 0
И это все! Репозиторий mandelzoom
с исходным кодом можно найти здесь.
Еще один пример
Я упоминал, что этот метод можно использовать для создания любого видео, при условии, что вы сможете написать код, создающий каждый кадр. Я закончу статью другим моим видео. Оно основано на простой трассировке лучей, которую я создал для моей книги Основы трассировки лучей. Код в этом видео работает так же, как и код выше: он создает серии PNG-файлов, которые затем ffmpeg конвертирует в MP4-файл.
Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming