opengl — как реализовать рендеринг портала

Я пытался реализовать что-то вроде игры 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 что-то вроде этого (простите за глупые рисунки, я ленивый): введите здесь описание изображения

Затем, после шага 2:
введите здесь описание изображения

Затем я начинаю со второго портала:
введите здесь описание изображения

Но теперь, между шагами 1 и 2, я говорю трафарету рисовать только там, где биты равны 1; поскольку буфер теперь очищен, я потерял след единиц первого портала, поэтому, если часть кадра второго портала находится за предыдущим кадром, единицы второго кадра все равно будут отрисованы с комнатой второго портала, независимо от буфер глубины. Примерно как на этом изображении: введите здесь описание изображения

Не знаю, правильно ли я объяснил...


person Beriol    schedule 09.07.2016    source источник


Ответы (1)


Прошло некоторое время. Поскольку ответов не будет (вероятно), я пишу об обходном пути, который использую сейчас.

Я попытался решить проблему, используя различные функции трафарета и не очищая буфер трафарета при рендеринге нового портала; идея заключалась в том, чтобы использовать тот факт, который я знал, глядя на буфер трафарета, где отрисовывались предыдущие порталы.

В конце концов, мне это не удалось; в примерах, которые я разместил в своем исходном вопросе, один из двух порталов всегда заслонял части другого.

Итак, я решил сделать что-то более простое: я проверяю, виден ли портал, а затем рисую его точно так же, как в коде, который я разместил в вопросе (поэтому каждый раз очищая буфер трафарета).

В псевдокоде я делаю так:

for(var i = 0; i < PORTALS_NUMBER; i++) 
{
    // I get the normal to the portal's frame and its position
    var normal = mPortalFramesNormals[i];
    var framePos = mPortalFrames[i].GetPosition();

    // I compute the scalar product between the normal and the direction vector between the camera's position and the frame's position
    var dotProduct = normal * (currentCameraPosition - framePos);

    // If the dot product is 0 or positive, the portal is visible
    if(dotProduct >= 0)
    {
        // I render the portal
        DrawPortal(mPortalFrames[i], mPortalRooms[i]);
    }
}

glDisable(GL_STENCIL_TEST);

// Now I draw the portals' frames in the depth buffer, so they don't get overwritten by other objects in the scene
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);

for(var i = 0; i < PORTALS_NUMBER; i++)
{
    mPortalFrames[i].Draw();
}

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

В моем случае, когда мне нужно отобразить всего 4 портала, расположенных в виде куба (поэтому я знаю, что одновременно видны не более 2 порталов), и только одна сторона кадра отображает портал, а другой нет , работает отлично.

Я знаю, что, вероятно, есть более элегантный способ сделать это, используя только буфер трафарета, но сейчас это работает для меня.

Если кто-то знает лучший способ сделать это, мне все еще интересно узнать!

EDIT: я узнал о других версиях функций трафарета (например, glStencilFuncSeparate, glStencilOpSeparate, glStencilMaskSeparate), которые позволяют делать разные вещи с задней и передней гранями. Кажется, это то, что мне нужно, чтобы решить проблемы, которые я описал, но я не смогу попробовать это в ближайшее время. Я просто указываю это на случай, если кто-то, у кого есть такая же проблема, забредет сюда.

person Beriol    schedule 19.07.2016