Я пытался реализовать что-то вроде игры Antichamber (точнее, этот трюк показан ниже ) за прошедшую неделю:
Вот видео того, чего я надеюсь достичь (хотя это было сделано с помощью Unreal Engine 4; я его не использую): https://www.youtube.com/watch?v=Of3JcoWrMZs
Я искал лучший способ сделать это и узнал о буфере трафарета. Между этой статьей и этот код (функция "drawPortals()"), который я нашел в Интернете, мне удалось почти реализовать его.
Он хорошо работает с одним порталом в другую комнату (не проходной портал, то есть вы не можете пройти через него и телепортироваться в другую комнату). В моем примере я рисую портал в простую квадратную комнату со сферой внутри; за порталом есть еще одна сфера, которую я использовал для проверки корректности работы буфера глубины и отрисовки ее за порталом:
Проблемы возникают, когда я добавляю другой портал рядом с этим. В этом случае мне удается правильно отобразить другой портал (освещение выключено, но сфера справа другого цвета, чтобы показать, что это другая сфера):
Но если я поверну камеру так, что первый портал должен быть нарисован поверх второго, то глубина первого станет неправильной, и второй портал будет нарисован поверх первого, вот так:
в то время как это должно быть что-то вроде этого:
Итак, это проблема. Я, вероятно, делаю что-то не так с буфером глубины, но я не могу найти что.
Мой код для части рендеринга примерно таков:
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
// First portal
glPushMatrix();
// Disable writing to the color and depht buffer; disable depth testing
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
glDisable(GL_DEPTH_TEST);
// Make sure that the stencil always fails
glStencilFunc(GL_NEVER, 1, 0xFF);
// On fail, put 1 on the buffer
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
// Enable writing to the stencil buffer
glStencilMask(0xFF);
// Clean the buffer
glClear(GL_STENCIL_BUFFER_BIT);
// Finally draw the portal's frame, so that it will have only 1s in the stencil buffer; the frame is basically the square you can see in the pictures
portalFrameObj1.Draw();
/* Now I compute the position of the camera so that it will be positioned at the portal's room; the computation is correct, so I'm skipping it */
// I'm going to render the portal's room from the new perspective, so I'm going to need the depth and color buffers again
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
// Disable writing to the stencil buffer and enable drawing only where the stencil values are 1s (so only on the portal frame previously rendered)
glStencilMask(0x00);
glStencilFunc(GL_EQUAL, 1, 0xFF);
// Draw the room from this perspective
portalRoomObj1.Draw();
glPopMatrix();
// Now the second portal; the procedure is the same, so I'm skipping the comments
glPushMatrix();
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
glDepthMask(GL_FALSE);
glDisable(GL_DEPTH_TEST);
glStencilFunc(GL_NEVER, 1, 0xFF);
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
glStencilMask(0xFF);
glClear(GL_STENCIL_BUFFER_BIT);
portalFrameObj2.Draw();
/* New camera perspective computation */
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
glStencilMask(0x00);
glStencilFunc(GL_EQUAL, 1, 0xFF);
portalRoomObj2.Draw();
glPopMatrix();
// Finally, I have to draw the portals' frames once again but this time on the depth buffer, so that they won't get drawn over; first off, disable the stencil buffer
glDisable(GL_STENCIL_TEST);
// Disable the color buffer
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glClear(GL_DEPTH_BUFFER_BIT);
// Draw portals' frames
portalFrameObj1.Draw();
portalFrameObj2.Draw();
// Enable the color buffer again
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
/* Here I draw the rest of the scene */
ОБНОВЛЕНИЕ
Мне удалось выяснить, в чем проблема, но я до сих пор не могу ее решить. На самом деле это связано не с буфером глубины, а с трафаретом.
В основном, способ, которым я рисую первый портал, таков: 1) Заполните биты кадра портала в буфере трафарета единицами; снаружи портала только 0 2) Нарисуйте комнату портала, где трафарет имеет 1 (чтобы он рисовался на портале фрейма
И я повторяю это для второго портала.
Для первого портала я получаю на шаге 1 что-то вроде этого (простите за глупые рисунки, я ленивый):
Затем я начинаю со второго портала:
Но теперь, между шагами 1 и 2, я говорю трафарету рисовать только там, где биты равны 1; поскольку буфер теперь очищен, я потерял след единиц первого портала, поэтому, если часть кадра второго портала находится за предыдущим кадром, единицы второго кадра все равно будут отрисованы с комнатой второго портала, независимо от буфер глубины. Примерно как на этом изображении:
Не знаю, правильно ли я объяснил...