-
Notifications
You must be signed in to change notification settings - Fork 2
Rendering Advanced
This page covers advanced rendering techniques for AtumVR. Make sure you're familiar with Rendering Basics first.
VR headsets have areas at the edges of each lens that the user cannot see. Rendering to these areas wastes GPU resources. AtumVR provides access to the hidden area mesh that you can use for stencil-based optimization.
float[] leftHiddenArea = vrRenderer.getHiddenAreaVertices(EyeType.LEFT);
float[] rightHiddenArea = vrRenderer.getHiddenAreaVertices(EyeType.RIGHT);The returned array contains triangle vertices in the format [x1, y1, x2, y2, x3, y3, ...] in pixel coordinates. These triangles represent the areas that should NOT be rendered.
You can use this data to create a stencil buffer that prevents rendering to hidden areas:
- At the start of each eye render, draw the hidden area triangles to the stencil buffer
- Set stencil test to fail where hidden area was drawn
- Render your scene normally - pixels in hidden area will be skipped
This optimization can significantly improve performance, especially on complex scenes.
While XRTexture works for most cases, you can create custom texture classes for more control.
public class CustomVRTexture extends XRTexture {
private int depthBuffer;
public CustomVRTexture(int width, int height, int textureId, int index) {
super(width, height, textureId, index);
}
@Override
public XRTexture init() {
super.init();
// Add custom depth buffer
depthBuffer = GL30.glGenRenderbuffers();
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthBuffer);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER,
GL30.GL_DEPTH24_STENCIL8, width, height);
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER,
GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depthBuffer);
return this;
}
@Override
public void destroy() {
GL30.glDeleteRenderbuffers(depthBuffer);
super.destroy();
}
}Then in your renderer:
@Override
protected XRTexture createTexture(int width, int height, int textureId, int index) {
return new CustomVRTexture(width, height, textureId, index);
}int width = vrRenderer.getResolutionWidth();
int height = vrRenderer.getResolutionHeight();The resolution is determined by the VR runtime based on the headset capabilities. AtumVR automatically uses the recommended resolution.
AtumVR requests swapchain images in this order of preference:
-
GL_SRGB8_ALPHA8- sRGB with alpha (best quality) -
GL_SRGB8- sRGB without alpha -
GL_RGB10_A2- 10-bit RGB -
GL_RGBA16F- 16-bit float RGBA -
GL_RGBA8- fallback
You can customize this by overriding getSwapChainFormats() in your XRProvider:
@Override
public List<Integer> getSwapChainFormats() {
return List.of(
GL21.GL_SRGB8_ALPHA8,
GL11.GL_RGBA8
);
}For complex rendering pipelines, you may need multiple passes per eye.
Instead of rendering directly to the swapchain framebuffer, render to your own framebuffer first:
@Override
public void updateEyeTexture(@NotNull EyeType eyeType) {
// Pass 1: Render scene to intermediate buffer
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, myIntermediateFramebuffer);
renderScene(eyeType);
// Pass 2: Post-processing
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, myPostProcessFramebuffer);
applyPostProcessing();
// Final pass: Copy to VR framebuffer
int vrFramebuffer = (eyeType == EyeType.LEFT)
? getVrRenderer().getTextureLeftEye().getFrameBufferId()
: getVrRenderer().getTextureRightEye().getFrameBufferId();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, vrFramebuffer);
drawFullscreenQuad(myPostProcessTexture);
}- Always end each eye's rendering with the final result in the VR framebuffer
- The VR runtime expects the swapchain image to contain the final rendered frame
- Be mindful of performance - VR requires high framerates (typically 72-120 FPS)