blob: 74df7885ff446b6cfb26adc2e60f53b761e8dc72 [file] [log] [blame]
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.ar.core.examples.java.common.samplerender.arcore;
import android.media.Image;
import android.opengl.GLES30;
import com.google.ar.core.Coordinates2d;
import com.google.ar.core.Frame;
import com.google.ar.core.examples.java.common.samplerender.Framebuffer;
import com.google.ar.core.examples.java.common.samplerender.Mesh;
import com.google.ar.core.examples.java.common.samplerender.SampleRender;
import com.google.ar.core.examples.java.common.samplerender.Shader;
import com.google.ar.core.examples.java.common.samplerender.Texture;
import com.google.ar.core.examples.java.common.samplerender.VertexBuffer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.HashMap;
/**
* This class both renders the AR camera background and composes the a scene foreground. The camera
* background can be rendered as either camera image data or camera depth data. The virtual scene
* can be composited with or without depth occlusion.
*/
public class BackgroundRenderer {
private static final String TAG = BackgroundRenderer.class.getSimpleName();
// components_per_vertex * number_of_vertices * float_size
private static final int COORDS_BUFFER_SIZE = 2 * 4 * 4;
private static final FloatBuffer NDC_QUAD_COORDS_BUFFER =
ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
private static final FloatBuffer VIRTUAL_SCENE_TEX_COORDS_BUFFER =
ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
static {
NDC_QUAD_COORDS_BUFFER.put(
new float[] {
/*0:*/ -1f, -1f, /*1:*/ +1f, -1f, /*2:*/ -1f, +1f, /*3:*/ +1f, +1f,
});
VIRTUAL_SCENE_TEX_COORDS_BUFFER.put(
new float[] {
/*0:*/ 0f, 0f, /*1:*/ 1f, 0f, /*2:*/ 0f, 1f, /*3:*/ 1f, 1f,
});
}
private final FloatBuffer cameraTexCoords =
ByteBuffer.allocateDirect(COORDS_BUFFER_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
private final Mesh mesh;
private final VertexBuffer cameraTexCoordsVertexBuffer;
private Shader backgroundShader;
private Shader occlusionShader;
private final Texture cameraDepthTexture;
private final Texture cameraColorTexture;
private Texture depthColorPaletteTexture;
private boolean useDepthVisualization;
private boolean useOcclusion;
private float aspectRatio;
/**
* Allocates and initializes OpenGL resources needed by the background renderer. Must be called
* during a {@link SampleRender.Renderer} callback, typically in {@link
* SampleRender.Renderer#onSurfaceCreated()}.
*/
public BackgroundRenderer(SampleRender render) {
cameraColorTexture =
new Texture(
render,
Texture.Target.TEXTURE_EXTERNAL_OES,
Texture.WrapMode.CLAMP_TO_EDGE,
/*useMipmaps=*/ false);
cameraDepthTexture =
new Texture(
render,
Texture.Target.TEXTURE_2D,
Texture.WrapMode.CLAMP_TO_EDGE,
/*useMipmaps=*/ false);
// Create a Mesh with three vertex buffers: one for the screen coordinates (normalized device
// coordinates), one for the camera texture coordinates (to be populated with proper data later
// before drawing), and one for the virtual scene texture coordinates (unit texture quad)
VertexBuffer screenCoordsVertexBuffer =
new VertexBuffer(render, /* numberOfEntriesPerVertex=*/ 2, NDC_QUAD_COORDS_BUFFER);
cameraTexCoordsVertexBuffer =
new VertexBuffer(render, /*numberOfEntriesPerVertex=*/ 2, /*entries=*/ null);
VertexBuffer virtualSceneTexCoordsVertexBuffer =
new VertexBuffer(render, /* numberOfEntriesPerVertex=*/ 2, VIRTUAL_SCENE_TEX_COORDS_BUFFER);
VertexBuffer[] vertexBuffers = {
screenCoordsVertexBuffer, cameraTexCoordsVertexBuffer, virtualSceneTexCoordsVertexBuffer,
};
mesh =
new Mesh(render, Mesh.PrimitiveMode.TRIANGLE_STRIP, /*indexBuffer=*/ null, vertexBuffers);
}
/**
* Sets whether the background camera image should be replaced with a depth visualization instead.
* This reloads the corresponding shader code, and must be called on the GL thread.
*/
public void setUseDepthVisualization(SampleRender render, boolean useDepthVisualization)
throws IOException {
if (backgroundShader != null) {
if (this.useDepthVisualization == useDepthVisualization) {
return;
}
backgroundShader.close();
backgroundShader = null;
this.useDepthVisualization = useDepthVisualization;
}
if (useDepthVisualization) {
depthColorPaletteTexture =
Texture.createFromAsset(
render,
"models/depth_color_palette.png",
Texture.WrapMode.CLAMP_TO_EDGE,
Texture.ColorFormat.LINEAR);
backgroundShader =
Shader.createFromAssets(
render,
"shaders/background_show_depth_color_visualization.vert",
"shaders/background_show_depth_color_visualization.frag",
/*defines=*/ null)
.setTexture("u_CameraDepthTexture", cameraDepthTexture)
.setTexture("u_ColorMap", depthColorPaletteTexture)
.setDepthTest(false)
.setDepthWrite(false);
} else {
backgroundShader =
Shader.createFromAssets(
render,
"shaders/background_show_camera.vert",
"shaders/background_show_camera.frag",
/*defines=*/ null)
.setTexture("u_CameraColorTexture", cameraColorTexture)
.setDepthTest(false)
.setDepthWrite(false);
}
}
/**
* Sets whether to use depth for occlusion. This reloads the shader code with new {@code
* #define}s, and must be called on the GL thread.
*/
public void setUseOcclusion(SampleRender render, boolean useOcclusion) throws IOException {
if (occlusionShader != null) {
if (this.useOcclusion == useOcclusion) {
return;
}
occlusionShader.close();
occlusionShader = null;
this.useOcclusion = useOcclusion;
}
HashMap<String, String> defines = new HashMap<>();
defines.put("USE_OCCLUSION", useOcclusion ? "1" : "0");
occlusionShader =
Shader.createFromAssets(render, "shaders/occlusion.vert", "shaders/occlusion.frag", defines)
.setDepthTest(false)
.setDepthWrite(false)
.setBlend(Shader.BlendFactor.SRC_ALPHA, Shader.BlendFactor.ONE_MINUS_SRC_ALPHA);
if (useOcclusion) {
occlusionShader
.setTexture("u_CameraDepthTexture", cameraDepthTexture)
.setFloat("u_DepthAspectRatio", aspectRatio);
}
}
/**
* Updates the display geometry. This must be called every frame before calling either of
* BackgroundRenderer's draw methods.
*
* @param frame The current {@code Frame} as returned by {@link Session#update()}.
*/
public void updateDisplayGeometry(Frame frame) {
if (frame.hasDisplayGeometryChanged()) {
// If display rotation changed (also includes view size change), we need to re-query the UV
// coordinates for the screen rect, as they may have changed as well.
frame.transformCoordinates2d(
Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
NDC_QUAD_COORDS_BUFFER,
Coordinates2d.TEXTURE_NORMALIZED,
cameraTexCoords);
cameraTexCoordsVertexBuffer.set(cameraTexCoords);
}
}
/** Update depth texture with Image contents. */
public void updateCameraDepthTexture(Image image) {
// SampleRender abstraction leaks here
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, cameraDepthTexture.getTextureId());
GLES30.glTexImage2D(
GLES30.GL_TEXTURE_2D,
0,
GLES30.GL_RG8,
image.getWidth(),
image.getHeight(),
0,
GLES30.GL_RG,
GLES30.GL_UNSIGNED_BYTE,
image.getPlanes()[0].getBuffer());
if (useOcclusion) {
aspectRatio = (float) image.getWidth() / (float) image.getHeight();
occlusionShader.setFloat("u_DepthAspectRatio", aspectRatio);
}
}
/**
* Draws the AR background image. The image will be drawn such that virtual content rendered with
* the matrices provided by {@link com.google.ar.core.Camera#getViewMatrix(float[], int)} and
* {@link com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)} will
* accurately follow static physical objects.
*/
public void drawBackground(SampleRender render) {
render.draw(mesh, backgroundShader);
}
/**
* Draws the virtual scene. Any objects rendered in the given {@link Framebuffer} will be drawn
* given the previously specified {@link OcclusionMode}.
*
* <p>Virtual content should be rendered using the matrices provided by {@link
* com.google.ar.core.Camera#getViewMatrix(float[], int)} and {@link
* com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)}.
*/
public void drawVirtualScene(
SampleRender render, Framebuffer virtualSceneFramebuffer, float zNear, float zFar) {
occlusionShader.setTexture(
"u_VirtualSceneColorTexture", virtualSceneFramebuffer.getColorTexture());
if (useOcclusion) {
occlusionShader
.setTexture("u_VirtualSceneDepthTexture", virtualSceneFramebuffer.getDepthTexture())
.setFloat("u_ZNear", zNear)
.setFloat("u_ZFar", zFar);
}
render.draw(mesh, occlusionShader);
}
/** Return the camera color texture generated by this object. */
public Texture getCameraColorTexture() {
return cameraColorTexture;
}
/** Return the camera depth texture generated by this object. */
public Texture getCameraDepthTexture() {
return cameraDepthTexture;
}
}