Начнём с того, что я обзавелся идеей оптимизировать процесс рисования для компьютеров без игровой видеокарты. Для встроенной карты не так важно сколько видеопамяти занимает приложение, сколько сама мощность этой карты. Насчет памяти для встроенной карты я кстати ещё не в курсе, но помню, что она может выделять её из CPU.
Так вот. Я хотел рендерить сцену один раз с двумя источниками света и применять запечённые текстуры уже в обычном рендеринге без просчета света каждый раз. Если добавиться новый источник света, то просчитаем опять везде свет и дальше рисуем запечённые текстуры.
Итак начнём. У меня движок ещё сырой, так что не ругайте за код, так как я бывает многое переписываю, потому что не учитывал некоторые аспекты того, что требуется.
Первым делом, чтобы проверить, что всё точно работает, я написал вот такой код.
for (int indx_z = 0; indx_z < height; indx_z++) {
for (int indx_x = 0; indx_x < width; indx_x++) {
struct obj_model *obj = obj_model_init (game, scene, RES_OBJECTS, TILE_OBJECT, SHADER_BACKED_TEXTURE_MODEL);
obj->model->texture = sprite_init_with_texture_for_object_optimized (game, RES_SPRITES, FLOOR_RHOMB_PNG_TEXTURE);
#if 1
obj_model_set_optimized_shader (obj, SHADER_TEXTURE_WITH_LIGHT_POINT_OPTIMIZED);
В цикле создается модель. Приставку назвал optimized, так как не нашел для неё другого лучшего названия. Здесь мы создаем модель и присваиваем ей обычный шейдер SHADER_BACKED_TEXTURE_MODEL, а в отпмизации указываем SHADER_TEXTURE_WITH_LIGHT_POINT_OPTIMIZED.
Сам шейдер оптимизации выглядит так.
#version 450 core
layout (location = 0) in vec3 dpos;
layout (location = 1) in vec2 dtex_coord;
layout (location = 2) in vec3 dnormal;
out vec2 tex_coord;
out vec3 normal;
out vec3 frag_pos;
uniform mat4 model;
uniform mat4 transform;
void main ()
{
vec4 texture_coord = vec4 (dtex_coord.x, dtex_coord.y, 1.0, 1.0);
gl_Position = model * texture_coord;
tex_coord = dtex_coord;
normal = dnormal;
frag_pos = vec3 (transform * vec4 (dpos, 1.0));
}
~
~
~
~
Как вы можете видеть, вместо того, чтобы рисовать модель по вершинам, я создаю точку вершины у текстуры и рисую её на экран. Для неё нужен ortho, который находится в model такой.
ortho (sp->ortho, 0.f, g->screen->aspect / 2.f, 1.f, 0.f, -1.0, 10.f);
Я поставил источник света и вот что получилось. Там горят два источника света.

Как видно, я применил glViewport (0, 0, 1024, 1024) так как размер текстуры именно должен быть таким. Куда падает свет тоже видно. Теперь уберём один источник света.

Так как это сработало, то я теперь могу считать буфер экрана в массив и загрузить этот массив в текстуру этой модели. Посмотрим что будет.
Текстура у меня перевернута оказалась, так что я меня в шейдере (1.0 - dtex_coord.y) и получаем.

Теперь стало больше похоже на правду.
В инициализации я создал фреймбуфер.
off_screen = framebuffer_init (1024, 1024, LINEAR_INTERPOLATION, 4);
Теперь осталось нарисовать в этот фреймбуфер
Я сделал такой код, чтобы рисовать, но вышло не то, что хотел.
if (is_once) {
glBindFramebuffer (GL_FRAMEBUFFER, off_screen->fbo);
uint32_t tex_width = 1024;
glDisable (GL_DEPTH_TEST);
glViewport (0, 0, tex_width, tex_width);
uint32_t total_bytes = total_floor_tiles * 4;
if (buf == NULL) {
buf = malloc (tex_width * tex_width * 4);
}
for (int i = 0; i < total_floor_tiles; i++) {
obj_model_render_optimized (scene, tile[i], NULL, NULL, light);
glReadPixels (0, 0, tex_width, tex_width, GL_RGBA, GL_UNSIGNED_BYTE, buf);
graphics_fill_texture_by_buf (tile[i]->model->texture, 1024, buf);
}
glEnable (GL_DEPTH_TEST);
glBindFramebuffer (GL_FRAMEBUFFER, 0);
is_once = 0;
glViewport (0, 0, game->screen->w, game->screen->h);
}
В graphics_fill_texture_by_buf вот такой код.
void
graphics_fill_texture_by_buf (struct sprite *sp0, uint32_t width, uint8_t *buf)
{
glBindTexture (GL_TEXTURE_2D, sp0->textures0[0]);
glTexSubImage2D (GL_TEXTURE_2D,
0,
0,
0,
sp0->tex_width,
sp0->tex_height,
GL_RGBA,
GL_UNSIGNED_BYTE,
buf
);
glBindTexture (GL_TEXTURE_2D, 0);
}
Текстура перевёрнута. Значит надо её теперь повернуть. В шейдере меняем на вот так.
#version 450 core
layout (location = 0) in vec3 dpos;
layout (location = 1) in vec2 dtex_coord;
layout (location = 2) in vec3 dnormal;
out vec2 tex_coord;
out vec3 normal;
out vec3 frag_pos;
uniform mat4 model;
uniform mat4 transform;
void main ()
{
vec4 texture_coord = vec4 (dtex_coord.x, dtex_coord.y, 1.0, 1.0);
gl_Position = model * texture_coord;
tex_coord = dtex_coord;
normal = dnormal;
frag_pos = vec3 (transform * vec4 (dpos, 1.0));
}
Почти всё сработало, но почему-то выглядит как-то скошенным, как будто текстура неполностью нарисована.

Я поменял ширину ortho в текстуре, которую рисуем на экран.
struct sprite *
sprite_init_with_texture_for_object_optimized (struct game *g, uint32_t type, uint32_t texture)
{
struct sprite *sp = malloc (sizeof (struct sprite));
memset (sp, 0, sizeof (struct sprite));
mat4x4_diag (sp->model, 1.f);
mat4x4_diag (sp->scale, 1.f);
translate (sp->transform, 0.f, 0.f, 0.f);
ortho (sp->ortho, 0.f, 1.f, 1.f, 0.f, -1.0, 10.f);
sp->shader = NULL;
sp->textures0 = NULL;
sp->g = g;
sprite_set_texture (sp, type, texture);
return sp;
}
И вот результат.

Теперь у нас каждая текстура имеет свой набор света и это работает. Я показал, что текстуры теперь можно запекать. Далее можно оптимизировать и сделать один пол, а не так как у меня. У меня каждый тайл пола рисуется отдельно, это я буду менять. Главное, что теперь такое можно запустить компьютере без игровой видеокарты и запечь текстуру один раз. А если добавить ещё один источник света, то заного просто перезапечь текстуры и всё. А потом рисовать запечёнными текстурами.
Комментарии (14)

Atom735
30.11.2025 23:05А это типа одна общая текстура, дифуз+свет?

xverizex Автор
30.11.2025 23:05Извините, я не понял вопрос. Объясню немного сначала. Есть оригинальная текстура. Её мы используем для совмещения со светом. Она же и рисуется в другой фреймбуфер. Потом берём эту текстуру и сохраняем в текстуру под другим id и вот эту текстуру, которую мы сохранили, теперь рисуем без просчета каждый раз света и не обращая внимания на нормали.
Таким образом в игре я могу динамически размещать свет в разных местах кофейни и когда поставлю новый свет, то только в этот момент произойдет просчет света. Запекуться текстуры с новыми данными и теперь будет запечённая текстура, которую опять можно рисовать без просчета света.
Так можно и с тенями работать думаю. Один раз прорисовать динамически тени, а потом рисуешь новую текстуру с тенью.
Это позволяет строить домик с освещением (якобы) и наслаждаться картинкой даже на встроенной карте (думаю).

xverizex Автор
30.11.2025 23:05Только пока каждая стеночка и пол отдельные. Но я написал в конце туториала, что буду менять на обычный большой по площади пол.

Jijiki
30.11.2025 23:05могу посоветовать, тут на хабре есть обзор Doom3 там есть некоторые концепции, которые по началу могут быть не интересными, но они очень тонкие

xverizex Автор
30.11.2025 23:05Да, если цель другая, то и метод другой понадобиться. У меня в игре всё же в помещении дело происходит и нужно устанавливать в каждой комнате освещение. Световых источников может быть до 20 кажется. Объекты отбрасывающие тень запекаются. Только пока отбрасывание тени у меня не реализовано.

Jijiki
30.11.2025 23:05понял вас ну если вы сделаете 2 света с тригерным(я подправил думал не интересно), смотрите, получается вы хотите светить почти как в реале или у вас задумка с тёмнотой? может можно как-то обыграть как в первых версиях вова, тут главное не реалистичное сходство же, или вы хотите гиперреализм? там в зданиях ао может будет нужен кстати вот тут помогут запекания наверно, но с 3д я не вкурсе, я представил как в 2д из пляжа светлой теплой гаммы, перешли в теплое здание с деревянными стенами и теплым светом
я всегда себе представлял, что надо просто придать тон локации и перенести его в здание, ну да могут быть факелы там
тоесть подмешивать частички получается в свет это нагрузно вроде, хотя если рисовать частички на слоях обьектов вроде получаеся, ну да тогда надо смотреть
pythonopengl_game_engine_update_4_reflections/ вот еще что видел
в третьей серии у него четко прям видны тени

Armitage
30.11.2025 23:05Спасибо, я сам сейчас начал осваивать игровой движок и было полезно узнать про такой прием, он в целом нередко раньше встречался в играх, пока не было лучей
xverizex Автор
Да за что минусы то? Вы хоть одну статью покажите, где эта тема освещается.
andreymal
Я не минусовал, но lightmap'ы были ещё тридцать лет назад в первом Quake
xverizex Автор
Да и я осветил эту тему, потому что сначала не понимал как это сделать. Самое интересное, что про это никто почти не пишет и я не догадывался насколько это просто в реализации, пока не додумался до этого варианта. Код квейка не изучал и поэтому не знал об этом.
Ладно, спасибо за общение.
Я посмотрел в книге learnopengl по теме lightmap и обнаружил, что lightmap имеется ввиду, когда несколько разных материалов в одной текстуре, а у меня не это. У меня запекание baking texture. Или я опять неправильно понял? Просто в моём варианте можно один раз запечь со светом текстуры и потом без света рисовать текстуры как будто текстура со светом.
Возможно вы ошиблись с терминологией или я просто не понимаю что такое настоящее lightmap. Но вот описание lightmap.
andreymal
Это бесполезная трата огромного количества памяти (и скорее всего производительности, потому что процесс чтения текстур из памяти тоже не бесплатный), поэтому lightmap является отдельной сжатой текстурой
Ну и ещё использование отдельной текстуры позволяет нормально анимировать текстуры без поломки освещения (например, сделать бегущую строку путём сдвига uv-координат) и динамически менять яркость отдельных лампочек без перезапекания
diakin
Да один минус-то всего, что так переживать! ) Я поставил плюс, стало по нулям, все норм )
xverizex Автор
Спасибо.