Skip to content

Commit

Permalink
Merge pull request #11784 from CesiumGS/voxel-pickbuffer
Browse files Browse the repository at this point in the history
Add Scene._pickVoxelCoordinate to report voxel tile and sample numbers
  • Loading branch information
ggetz authored Jan 29, 2024
2 parents a005c07 + e716ff8 commit 2b59f05
Show file tree
Hide file tree
Showing 13 changed files with 762 additions and 655 deletions.
5 changes: 5 additions & 0 deletions packages/engine/Source/Core/PixelFormat.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,11 @@ PixelFormat.alignmentInBytes = function (pixelFormat, pixelDatatype, width) {

/**
* @private
* @param {PixelFormat} pixelFormat The pixel format.
* @param {PixelDatatype} pixelDatatype The pixel datatype.
* @param {Number} width The width of the texture.
* @param {Number} height The height of the texture.
* @returns {TypedArray} The typed array.
*/
PixelFormat.createTypedArray = function (
pixelFormat,
Expand Down
10 changes: 10 additions & 0 deletions packages/engine/Source/Renderer/Context.js
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,16 @@ Context.prototype.endFrame = function () {
}
};

/**
* @private
* @param {object} readState An object with the following properties:
* @param {number} [readState.x=0] The x offset of the rectangle to read from.
* @param {number} [readState.y=0] The y offset of the rectangle to read from.
* @param {number} [readState.width=gl.drawingBufferWidth] The width of the rectangle to read from.
* @param {number} [readState.height=gl.drawingBufferHeight] The height of the rectangle to read from.
* @param {Framebuffer} [readState.framebuffer] The framebuffer to read from. If undefined, the read will be from the default framebuffer.
* @returns {Uint8Array|Uint16Array|Float32Array|Uint32Array} The pixels in the specified rectangle.
*/
Context.prototype.readPixels = function (readState) {
const gl = this._gl;

Expand Down
5 changes: 5 additions & 0 deletions packages/engine/Source/Scene/FrameState.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ function FrameState(context, creditDisplay, jobScheduler) {
* @type {object}
* @property {boolean} render <code>true</code> if the primitive should update for a render pass, <code>false</code> otherwise.
* @property {boolean} pick <code>true</code> if the primitive should update for a picking pass, <code>false</code> otherwise.
* @property {boolean} pickVoxel <code>true</code> if the primitive should update for a voxel picking pass, <code>false</code> otherwise.
* @property {boolean} depth <code>true</code> if the primitive should update for a depth only pass, <code>false</code> otherwise.
* @property {boolean} postProcess <code>true</code> if the primitive should update for a per-feature post-process pass, <code>false</code> otherwise.
* @property {boolean} offscreen <code>true</code> if the primitive should update for an offscreen pass, <code>false</code> otherwise.
Expand All @@ -199,6 +200,10 @@ function FrameState(context, creditDisplay, jobScheduler) {
* @default false
*/
pick: false,
/**
* @default false
*/
pickVoxel: false,
/**
* @default false
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/Source/Scene/GlobeTranslucencyState.js
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,7 @@ GlobeTranslucencyState.prototype.pushDerivedCommands = function (
isBlendCommand,
frameState
) {
const picking = frameState.passes.pick;
const picking = frameState.passes.pick || frameState.passes.pickVoxel;
if (picking && isBlendCommand) {
// No need to push blend commands in the pick pass
return;
Expand Down
36 changes: 36 additions & 0 deletions packages/engine/Source/Scene/PickFramebuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function PickFramebuffer(context) {
this._width = 0;
this._height = 0;
}

PickFramebuffer.prototype.begin = function (screenSpaceRectangle, viewport) {
const context = this._context;
const { width, height } = viewport;
Expand All @@ -50,6 +51,12 @@ PickFramebuffer.prototype.begin = function (screenSpaceRectangle, viewport) {

const colorScratch = new Color();

/**
* Return the picked object rendered within a given rectangle.
*
* @param {BoundingRectangle} screenSpaceRectangle
* @returns {object|undefined} The object rendered in the middle of the rectangle, or undefined if nothing was rendered.
*/
PickFramebuffer.prototype.end = function (screenSpaceRectangle) {
const width = defaultValue(screenSpaceRectangle.width, 1.0);
const height = defaultValue(screenSpaceRectangle.height, 1.0);
Expand Down Expand Up @@ -113,6 +120,34 @@ PickFramebuffer.prototype.end = function (screenSpaceRectangle) {
return undefined;
};

/**
* Return voxel tile and sample information as rendered by a pickVoxel pass,
* within a given rectangle.
*
* @param {BoundingRectangle} screenSpaceRectangle
* @returns {TypedArray}
*/
PickFramebuffer.prototype.readVoxelInfo = function (screenSpaceRectangle) {
const width = defaultValue(screenSpaceRectangle.width, 1.0);
const height = defaultValue(screenSpaceRectangle.height, 1.0);

const context = this._context;
const pixels = context.readPixels({
x: screenSpaceRectangle.x,
y: screenSpaceRectangle.y,
width: width,
height: height,
framebuffer: this._fb.framebuffer,
});

// Read the center pixel
const halfWidth = Math.floor(width * 0.5);
const halfHeight = Math.floor(height * 0.5);
const index = 4 * (halfHeight * width + halfWidth);

return pixels.slice(index, index + 4);
};

PickFramebuffer.prototype.isDestroyed = function () {
return false;
};
Expand All @@ -121,4 +156,5 @@ PickFramebuffer.prototype.destroy = function () {
this._fb.destroy();
return destroyObject(this);
};

export default PickFramebuffer;
95 changes: 89 additions & 6 deletions packages/engine/Source/Scene/Picking.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,22 @@ let scratchRectangle = new BoundingRectangle(
const scratchPosition = new Cartesian2();
const scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0);

/**
* Returns an object with a <code>primitive</code> property that contains the first (top) primitive in the scene
* at a particular window coordinate or undefined if nothing is at the location. Other properties may
* potentially be set depending on the type of primitive and may be used to further identify the picked object.
* <p>
* When a feature of a 3D Tiles tileset is picked, <code>pick</code> returns a {@link Cesium3DTileFeature} object.
* </p>
* @param {Scene} scene
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {number} [width=3] Width of the pick rectangle.
* @param {number} [height=3] Height of the pick rectangle.
* @returns {object} Object containing the picked primitive.
*/
Picking.prototype.pick = function (scene, windowPosition, width, height) {
//>>includeStart('debug', pragmas.debug);
if (!defined(windowPosition)) {
throw new DeveloperError("windowPosition is undefined.");
}
Check.defined("windowPosition", windowPosition);
//>>includeEnd('debug');

scratchRectangleWidth = defaultValue(width, 3.0);
Expand Down Expand Up @@ -301,6 +312,80 @@ Picking.prototype.pick = function (scene, windowPosition, width, height) {
return object;
};

/**
* Returns an object with information about the voxel sample rendered at
* a particular window coordinate. Returns <code>undefined</code> if there is no
* voxel at that position.
*
* @param {Scene} scene
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {number} [width=3] Width of the pick rectangle.
* @param {number} [height=3] Height of the pick rectangle.
* @returns {object|undefined} Object containing the picked primitive.
*/
Picking.prototype.pickVoxel = function (scene, windowPosition, width, height) {
//>>includeStart('debug', pragmas.debug);
Check.defined("windowPosition", windowPosition);
//>>includeEnd('debug');

scratchRectangleWidth = defaultValue(width, 3.0);
scratchRectangleHeight = defaultValue(height, scratchRectangleWidth);

const { context, frameState, defaultView } = scene;
const { viewport, pickFramebuffer } = defaultView;

scene.view = defaultView;

viewport.x = 0;
viewport.y = 0;
viewport.width = context.drawingBufferWidth;
viewport.height = context.drawingBufferHeight;

let passState = defaultView.passState;
passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);

const drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(
scene,
windowPosition,
scratchPosition
);

scene.jobScheduler.disableThisFrame();

scene.updateFrameState();
frameState.cullingVolume = getPickCullingVolume(
scene,
drawingBufferPosition,
scratchRectangleWidth,
scratchRectangleHeight,
viewport
);
frameState.invertClassification = false;
frameState.passes.pickVoxel = true;
frameState.tilesetPassState = pickTilesetPassState;

context.uniformState.update(frameState);

scene.updateEnvironment();

scratchRectangle.x =
drawingBufferPosition.x - (scratchRectangleWidth - 1.0) * 0.5;
scratchRectangle.y =
scene.drawingBufferHeight -
drawingBufferPosition.y -
(scratchRectangleHeight - 1.0) * 0.5;
scratchRectangle.width = scratchRectangleWidth;
scratchRectangle.height = scratchRectangleHeight;
passState = pickFramebuffer.begin(scratchRectangle, viewport);

scene.updateAndExecuteCommands(passState, scratchColorZero);
scene.resolveFramebuffers(passState);

const voxelInfo = pickFramebuffer.readVoxelInfo(scratchRectangle);
context.endFrame();
return voxelInfo;
};

function renderTranslucentDepthForPick(scene, drawingBufferPosition) {
// PERFORMANCE_IDEA: render translucent only and merge with the previous frame
const { defaultView, context, frameState, environmentState } = scene;
Expand Down Expand Up @@ -357,9 +442,7 @@ Picking.prototype.pickPositionWorldCoordinates = function (
}

//>>includeStart('debug', pragmas.debug);
if (!defined(windowPosition)) {
throw new DeveloperError("windowPosition is undefined.");
}
Check.defined("windowPosition", windowPosition);
if (!scene.context.depthTexture) {
throw new DeveloperError(
"Picking from the depth buffer is not supported. Check pickPositionSupported."
Expand Down
32 changes: 29 additions & 3 deletions packages/engine/Source/Scene/Scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -1849,10 +1849,12 @@ function getOccluder(scene) {

/**
* @private
* @param {FrameState.Passes} passes
*/
Scene.prototype.clearPasses = function (passes) {
passes.render = false;
passes.pick = false;
passes.pickVoxel = false;
passes.depth = false;
passes.postProcess = false;
passes.offscreen = false;
Expand Down Expand Up @@ -2111,6 +2113,7 @@ function executeCommand(command, scene, context, passState, debugFramebuffer) {
const passes = frameState.passes;
if (
!passes.pick &&
!passes.pickVoxel &&
!passes.depth &&
scene._hdr &&
defined(command.derivedCommands) &&
Expand Down Expand Up @@ -2289,7 +2292,7 @@ function executeCommands(scene, passState) {
uniformState.updatePass(Pass.ENVIRONMENT);

const passes = frameState.passes;
const picking = passes.pick;
const picking = passes.pick || passes.pickVoxel;
const environmentState = scene._environmentState;
const view = scene._view;
const renderTranslucentDepthForPick =
Expand Down Expand Up @@ -3315,7 +3318,10 @@ function updateShadowMaps(scene) {
const length = shadowMaps.length;

const shadowsEnabled =
length > 0 && !frameState.passes.pick && scene.mode === SceneMode.SCENE3D;
length > 0 &&
!frameState.passes.pick &&
!frameState.passes.pickVoxel &&
scene.mode === SceneMode.SCENE3D;
if (shadowsEnabled !== frameState.shadowState.shadowsEnabled) {
// Update derived commands when shadowsEnabled changes
++frameState.shadowState.lastDirtyTime;
Expand Down Expand Up @@ -3379,7 +3385,7 @@ function updateAndClearFramebuffers(scene, passState, clearColor) {
const view = scene._view;

const passes = scene._frameState.passes;
const picking = passes.pick;
const picking = passes.pick || passes.pickVoxel;
if (defined(view.globeDepth)) {
view.globeDepth.picking = picking;
}
Expand Down Expand Up @@ -3961,6 +3967,26 @@ Scene.prototype.pick = function (windowPosition, width, height) {
return this._picking.pick(this, windowPosition, width, height);
};

/**
* Returns an object with cordinates of the voxel sample rendered at
* a particular window coordinate. Returns <code>undefined</code> if there is no
* voxel at that position.
*
* @private
*
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {number} [width=3] Width of the pick rectangle.
* @param {number} [height=3] Height of the pick rectangle.
* @returns {object|undefined} Object containing information about the voxel.
*/
Scene.prototype._pickVoxelCoordinate = function (
windowPosition,
width,
height
) {
return this._picking.pickVoxel(this, windowPosition, width, height);
};

/**
* Returns the cartesian position reconstructed from the depth buffer and window position.
* The returned position is in world coordinates. Used internally by camera functions to
Expand Down
2 changes: 2 additions & 0 deletions packages/engine/Source/Scene/VoxelPrimitive.js
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Render the primitive
const command = frameState.passes.pick
? this._drawCommandPick
: frameState.passes.pickVoxel
? this._drawCommandPickVoxel
: this._drawCommand;
command.boundingVolume = shape.boundingSphere;
frameState.commandList.push(command);
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/Source/Scene/VoxelTraversal.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ function VoxelTraversal(
*/
VoxelTraversal.prototype.findKeyframeNode = function (megatextureIndex) {
return this._keyframeNodesInMegatexture.find(function (keyframeNode) {
return (keyframeNode.megatextureIndex = megatextureIndex);
return keyframeNode.megatextureIndex === megatextureIndex;
});
};

Expand Down
23 changes: 23 additions & 0 deletions packages/engine/Source/Scene/buildVoxelDrawCommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,17 @@ function buildVoxelDrawCommands(primitive, context) {
// Compile shaders
const shaderBuilderPick = shaderBuilder.clone();
shaderBuilderPick.addDefine("PICKING", undefined, ShaderDestination.FRAGMENT);
const shaderBuilderPickVoxel = shaderBuilder.clone();
shaderBuilderPickVoxel.addDefine(
"PICKING_VOXEL",
undefined,
ShaderDestination.FRAGMENT
);
const shaderProgram = shaderBuilder.buildShaderProgram(context);
const shaderProgramPick = shaderBuilderPick.buildShaderProgram(context);
const shaderProgramPickVoxel = shaderBuilderPickVoxel.buildShaderProgram(
context
);
const renderState = RenderState.fromCache({
cull: {
enabled: true,
Expand Down Expand Up @@ -98,6 +107,14 @@ function buildVoxelDrawCommands(primitive, context) {
drawCommandPick.shaderProgram = shaderProgramPick;
drawCommandPick.pickOnly = true;

// Create the pick voxels draw command
const drawCommandPickVoxel = DrawCommand.shallowClone(
drawCommand,
new DrawCommand()
);
drawCommandPickVoxel.shaderProgram = shaderProgramPickVoxel;
drawCommandPickVoxel.pickOnly = true;

// Delete the old shader programs
if (defined(primitive._drawCommand)) {
const command = primitive._drawCommand;
Expand All @@ -109,9 +126,15 @@ function buildVoxelDrawCommands(primitive, context) {
command.shaderProgram =
command.shaderProgram && command.shaderProgram.destroy();
}
if (defined(primitive._drawCommandPickVoxel)) {
const command = primitive._drawCommandPickVoxel;
command.shaderProgram =
command.shaderProgram && command.shaderProgram.destroy();
}

primitive._drawCommand = drawCommand;
primitive._drawCommandPick = drawCommandPick;
primitive._drawCommandPickVoxel = drawCommandPickVoxel;
}

export default buildVoxelDrawCommands;
Loading

0 comments on commit 2b59f05

Please sign in to comment.