Using multiview rendering
此示例提供了GL_OVR_multiview和GL_OVR_multiview2扩展以及它们如何用于提高虚拟现实用例的性能。
Introduction
多视图渲染允许绘制调用同时渲染到数组纹理的多个层。 顶点着色器可以知道它正在写入哪个图层,因此每个图层的渲染结果可能不同。 这对于虚拟现实应用非常有用,其中需要从两个不同位置渲染相同的场景。
多视图渲染示例。
警告 为了使用多视图渲染,需要GL_OVR_multiview扩展。 但是,此扩展是有限的,因此只有输出gl_Position可以取决于视图索引。 扩展GL_OVR_multiview2删除了此限制,使其更有用,因为照明可能取决于相机的位置。 本教程只需要GL_OVR_multiview扩展,因为我们只根据示例代码中的视图索引更改gl_Position输出。 扩展GL_OVR_multiview_multisampled_render_to_texture也很有用,因为它允许多视图渲染到多重采样纹理。使用多视图渲染还会限制使用几何和曲面细分着色器。
What is multiview rendering and why is it useful?
虚拟现实应用程序需要从不同视角渲染两次所有场景,以创建深度幻觉。通过简单地使用不同的视图和透视矩阵渲染所有内容来完成此操作并不是最佳选择,因为它需要多次设置大致相同的绘制调用。 GL_OVR_multiview扩展通过允许一次绘制调用渲染到数组纹理的多个纹理图层来解决此问题,从而消除了设置多个绘制调用的开销。然后,为附加的数组纹理中的每个纹理图层调用顶点和片段着色器一次,并且可以访问变量gl_ViewID_OVR,该变量可用于为每个图层选择视图相关的输入。使用这种机制,可以使用视图和投影矩阵的数组而不是单个矩阵,并且着色器可以使用gl_ViewID_OVR为每个图层选择正确的矩阵,允许一个绘制调用从多个眼睛位置渲染而几乎没有开销。使用分层几何着色器渲染到只有一个绘制调用的多个图层也可以使用分层几何着色器完成,但与使用多视图扩展相比,这会产生更大的开销,因为几何着色器对性能要求很高,而多视图是一个固定的功能解决方案,它允许许多与几何着色器相比的内部优化。
Setting up a multiview framebuffer object
在使用扩展之前,应该检查它是否可用,这可以通过以下代码完成,该代码需要GL_OVR_multiview扩展。 如果需要,类似的代码可用于检查GL_OVR_multiview2或GL_OVR_multiview_multisampled_render_to_texture扩展。
const GLubyte* extensions = GL_CHECK(glGetString(GL_EXTENSIONS));
char * found_extension = strstr ((const char*)extensions, "GL_OVR_multiview");
if (NULL == found_extension)
{
LOGI("OpenGL ES 3.0 implementation does not support GL_OVR_multiview extension.\n");
exit(EXIT_FAILURE);
}
复制代码
但是,即使支持扩展,glFramebufferTextureMultiviewOVR功能也可能无法在GL标头中使用。 如果是这种情况,可以使用eglGetProc访问函数,如下面的代码所示。
typedef void (*PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei);
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR glFramebufferTextureMultiviewOVR;
glFramebufferTextureMultiviewOVR =
(PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR)eglGetProcAddress ("glFramebufferTextureMultiviewOVR");
if (!glFramebufferTextureMultiviewOVR)
{
LOGI("Can not get proc address for glFramebufferTextureMultiviewOVR.\n");
exit(EXIT_FAILURE);
}
复制代码
如果此调用成功,则glFramebufferTextureMultiviewOVR可用作普通gl函数。
以下代码设置了一个帧缓冲对象,用于渲染到多视图。 颜色附件和深度附件都是具有2层的数组纹理,并且这两个层将通过此帧缓冲对象上使用的每个绘制调用进行渲染。 重要的是所有附件具有相同数量的层,否则帧缓冲不会完成。 framebuffer对象必须绑定到GL_DRAW_FRAMEBUFFER,否则glFramebufferTextureMultiviewOVR调用将给出INVALID_OPERATION错误。 同样重要的是,为framebuffer对象设置的视图数量与绘制时当前着色器程序中声明的视图数量相匹配,否则绘制调用将给出INVALID_OPERATION错误。
bool setupFBO(int width, int height)
{
// Create array texture
GL_CHECK(glGenTextures(1, &frameBufferTextureId));
GL_CHECK(glBindTexture(GL_TEXTURE_2D_ARRAY, frameBufferTextureId));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CHECK(glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, 2));
/* Initialize FBO. */
GL_CHECK(glGenFramebuffers(1, &frameBufferObjectId));
/* Bind our framebuffer for rendering. */
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBufferObjectId));
/* Attach texture to the framebuffer. */
GL_CHECK(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
frameBufferTextureId, 0, 0, 2));
/* Create array depth texture */
GL_CHECK(glGenTextures(1, &frameBufferDepthTextureId));
GL_CHECK(glBindTexture(GL_TEXTURE_2D_ARRAY, frameBufferDepthTextureId));
GL_CHECK(glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH_COMPONENT24, width, height, 2));
/* Attach depth texture to the framebuffer. */
GL_CHECK(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
frameBufferDepthTextureId, 0, 0, 2));
/* Check FBO is OK. */
GLenum result = GL_CHECK(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER));
if (result != GL_FRAMEBUFFER_COMPLETE)
{
LOGE("Framebuffer incomplete at %s:%i\n", __FILE__, __LINE__);
/* Unbind framebuffer. */
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
return false;
}
return true;
}
复制代码
Using multiview in shaders to render to several layers
以下代码显示用于多视图渲染的着色器。只有顶点着色器包含多视图特定代码。它启用GL_OVR_multiview扩展并将布局中的num_views变量设置为2.此数字需要与使用glFramebufferTextureMultiviewOVR附加到帧缓冲区的视图数相同。着色器接收一组视图投影矩阵(视图和投影矩阵相乘),而不仅仅是一个,并通过使用gl_ViewID_OVR索引来选择要使用的矩阵,gl_ViewID_OVR给出要渲染的当前纹理图层的索引。这允许我们为不同的层具有不同的相机位置和投影,使得可以通过一次绘制调用从VR应用中的两个眼睛位置进行渲染。在这种情况下只有一个模型矩阵,因为模型不会针对不同的层移动。在这种情况下,只有gl_Position受gl_ViewID_OVR值的影响,这意味着此着色器仅需要GL_OVR_multiview扩展而不是GL_OVR_multiview2扩展。为了也基于gl_ViewID_OVR(或其他顶点输出)改变法线,将需要GL_OVR_multiview2。
#version 300 es
#extension GL_OVR_multiview : enable
layout(num_views = 2) in;
in vec3 vertexPosition;
in vec3 vertexNormal;
uniform mat4 modelViewProjection[2];
uniform mat4 model;
out vec3 v_normal;
void main()
{
gl_Position = modelViewProjection[gl_ViewID_OVR] * vec4(vertexPosition, 1.0);
v_normal = (model * vec4(vertexNormal, 0.0f)).xyz;
}
#version 300 es
precision mediump float;
in vec3 v_normal;
out vec4 f_color;
vec3 light(vec3 n, vec3 l, vec3 c)
{
float ndotl = max(dot(n, l), 0.0);
return ndotl * c;
}
void main()
{
vec3 albedo = vec3(0.95, 0.84, 0.62);
vec3 n = normalize(v_normal);
f_color.rgb = vec3(0.0);
f_color.rgb += light(n, normalize(vec3(1.0)), vec3(1.0));
f_color.rgb += light(n, normalize(vec3(-1.0, -1.0, 0.0)), vec3(0.2, 0.23, 0.35));
f_color.a = 1.0;
}
复制代码
该程序可以与任何其他程序相同的方式设置。 必须将viewProjection矩阵设置为矩阵阵列,如下面的代码所示。 在该示例中,投影矩阵是相同的,但是对于VR,通常对每只眼睛使用不同的投影矩阵。 本教程后面的示例将呈现为2层以上,并且每层将使用不同的透视矩阵,这就是此处存在多个透视矩阵的原因。 在这种情况下,相机位置在x方向上设置为-1.5和1.5,两者都看着场景的中心。
/* M_PI_2 rad = 90 degrees. */
projectionMatrix[0] = Matrix::matrixPerspective(M_PI_2, (float)width / (float)height, 0.1f, 100.0f);
projectionMatrix[1] = Matrix::matrixPerspective(M_PI_2, (float)width / (float)height, 0.1f, 100.0f);
/* Setting up model view matrices for each of the */
Vec3f leftCameraPos = {-1.5f, 0.0f, 4.0f};
Vec3f rightCameraPos = {1.5f, 0.0f, 4.0f};
Vec3f lookAt = {0.0f, 0.0f, -4.0f};
Vec3f upVec = {0.0f, 1.0f, 0.0f};
viewMatrix[0] = Matrix::matrixCameraLookAt(leftCameraPos, lookAt, upVec);
viewMatrix[1] = Matrix::matrixCameraLookAt(rightCameraPos, lookAt, upVec);
modelViewProjectionMatrix[0] = projectionMatrix[0] * viewMatrix[0] * modelMatrix;
modelViewProjectionMatrix[1] = projectionMatrix[1] * viewMatrix[1] * modelMatrix;
multiviewModelViewProjectionLocation = GL_CHECK(glGetUniformLocation(multiviewProgram, "modelViewProjection"));
multiviewModelLocation = GL_CHECK(glGetUniformLocation(multiviewProgram, "model"));
/* Upload matrices. */
GL_CHECK(glUniformMatrix4fv(multiviewModelViewProjectionLocation, 2, GL_FALSE, modelViewProjectionMatrix[0].getAsArray()));
GL_CHECK(glUniformMatrix4fv(multiviewModelLocation, 1, GL_FALSE, modelMatrix.getAsArray()));
复制代码
在绑定多视图帧缓冲对象时使用此程序渲染的任何内容都将从不同视角渲染到两个纹理图层,而无需进行多次绘制调用。 将VR场景渲染为每只眼睛的单独图层后,现在需要将结果渲染到屏幕上。 这可以通过将纹理绑定并将其渲染为2D数组纹理来轻松完成。 对于VR应用程序,可以设置两个视口,并且对于每个视口,相关的纹理图层将呈现给屏幕。 这可以是纹理对屏幕的简单blitting,也可以在显示纹理之前对纹理进行过滤或其他后处理操作。 由于纹理是一个数组,纹理采样操作需要一个vec3纹理坐标,其中最后一个坐标索引到数组中。 为了使每个绘图调用选择不同的层,可以提供具有层索引的统一,如下面的片段着色器中所示。