Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/issues/13020)
- Fixed unexpected outline artifacts around billboards [#13038](https://github.com/CesiumGS/cesium/issues/13038)
- Fixed image alignment in large billboard collections [#13042](https://github.com/CesiumGS/cesium/issues/13042)

#### Additions :tada:

Expand Down
71 changes: 41 additions & 30 deletions packages/engine/Source/Renderer/TextureAtlas.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,22 +193,21 @@ Object.defineProperties(TextureAtlas.prototype, {
});

/**
* Get the texture coordinates for reading the associated image in shaders.
* TODO
* @param {number} index The index of the image region.
* @param {BoundingRectangle} [result] The object into which to store the result.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @private
* @example
* const index = await atlas.addImage("myImage", image);
* const rectangle = atlas.computeTextureCoordinates(index);
* const rectangle = atlas.computeImageCoordinates(index);
* BoundingRectangle.pack(rectangle, bufferView);
*/
TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
TextureAtlas.prototype.computeImageCoordinates = function (index, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("index", index, 0);
//>>includeEnd('debug');

const texture = this._texture;
const rectangle = this._rectangles[index];

if (!defined(result)) {
Expand All @@ -224,11 +223,6 @@ TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
return result;
}

const atlasWidth = texture.width;
const atlasHeight = texture.height;

const width = rectangle.width;
const height = rectangle.height;
let x = rectangle.x;
let y = rectangle.y;

Expand All @@ -240,10 +234,36 @@ TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
y += parentRectangle.y;
}

result.x = x / atlasWidth;
result.y = y / atlasHeight;
result.width = width / atlasWidth;
result.height = height / atlasHeight;
result.x = x;
result.y = y;
result.width = rectangle.width;
result.height = rectangle.height;

return result;
};

/**
* Get the texture coordinates for reading the associated image in shaders.
* @param {number} index The index of the image region.
* @param {BoundingRectangle} [result] The object into which to store the result.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @private
* @example
* const index = await atlas.addImage("myImage", image);
* const rectangle = atlas.computeTextureCoordinates(index);
* BoundingRectangle.pack(rectangle, bufferView);
*/
TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
result = this.computeImageCoordinates(index, result);

const texture = this._texture;
const atlasWidth = texture.width;
const atlasHeight = texture.height;

result.x /= atlasWidth;
result.y /= atlasHeight;
result.width /= atlasWidth;
result.height /= atlasHeight;

return result;
};
Expand Down Expand Up @@ -364,26 +384,17 @@ TextureAtlas.prototype._resize = function (context, queueOffset = 0) {
toPack.push(queue[i]);
}

// At minimum, the texture will need to scale to accommodate the largest width and height
width = Math.max(maxWidth, width);
height = Math.max(maxHeight, height);

if (!context.webgl2) {
width = CesiumMath.nextPowerOfTwo(width);
height = CesiumMath.nextPowerOfTwo(height);
}

// Determine by what factor the texture need to be scaled by at minimum
const areaDifference = areaQueued;
let scalingFactor = 1.0;
while (areaDifference / width / height >= 1.0) {
scalingFactor *= 2.0;
// At minimum, atlas must fit its largest input images. Texture coordinates are
// compressed to 0–1 with 12-bit precision, so use power-of-two size to align pixels.
width = CesiumMath.nextPowerOfTwo(Math.max(maxWidth, width));
height = CesiumMath.nextPowerOfTwo(Math.max(maxHeight, height));

// Resize by one dimension
// Iteratively double the smallest dimension until atlas area is (approximately) sufficient.
while (areaQueued >= width * height) {
if (width > height) {
height *= scalingFactor;
height *= 2;
} else {
width *= scalingFactor;
width *= 2;
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/engine/Source/Scene/Billboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,16 @@ Billboard._updateClamping = function (collection, owner) {
updateFunction(scratchCartographic);
};

/**
* Get the image coordinates for reading the loaded texture in shaders.
* @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @private
*/
Billboard.prototype.computeImageCoordinates = function (result) {
return this._imageTexture.computeImageCoordinates(result);
};

/**
* Get the texture coordinates for reading the loaded texture in shaders.
* @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.
Expand Down
87 changes: 33 additions & 54 deletions packages/engine/Source/Scene/BillboardCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ const SDF_INDEX = Billboard.SDF_INDEX;
const SPLIT_DIRECTION_INDEX = Billboard.SPLIT_DIRECTION_INDEX;
const NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;

const scratchTextureSize = new Cartesian2();

let attributeLocations;

const attributeLocationsBatched = {
positionHighAndScale: 0,
positionLowAndRotation: 1,
compressedAttribute0: 2, // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates
compressedAttribute0: 2, // pixel offset, translate, horizontal origin, vertical origin, show, direction, image coordinates (px)
compressedAttribute1: 3, // aligned axis, translucency by distance, image width
compressedAttribute2: 4, // image height, color, pick color, size in meters, valid aligned axis, 13 bits free
eyeOffset: 5, // 4 bytes free
Expand All @@ -79,11 +81,11 @@ const attributeLocationsBatched = {
const attributeLocationsInstanced = {
direction: 0,
positionHighAndScale: 1,
positionLowAndRotation: 2, // texture offset in w
compressedAttribute0: 3,
positionLowAndRotation: 2,
compressedAttribute0: 3, // image lower-left coordinates (px) in w
compressedAttribute1: 4,
compressedAttribute2: 5,
eyeOffset: 6, // texture range in w
eyeOffset: 6,
scaleByDistance: 7,
pixelOffsetScaleByDistance: 8,
compressedAttribute3: 9,
Expand Down Expand Up @@ -323,6 +325,10 @@ function BillboardCollection(options) {
u_atlas: () => {
return this.textureAtlas.texture;
},
u_atlasSize: () => {
const { width, height } = this.textureAtlas.texture;
return Cartesian2.fromElements(width, height, scratchTextureSize);
},
u_highlightColor: () => {
return this._highlightColor;
},
Expand Down Expand Up @@ -939,8 +945,6 @@ function writePositionScaleAndRotation(
}
}

const scratchCartesian2 = new Cartesian2();

const UPPER_BOUND = 32768.0; // 2^15

const LEFT_SHIFT16 = 65536.0; // 2^16
Expand Down Expand Up @@ -1004,22 +1008,18 @@ function writeCompressedAttrib0(
billboardCollection._allVerticalCenter &&
verticalOrigin === VerticalOrigin.CENTER;

let bottomLeftX = 0;
let bottomLeftY = 0;
let width = 0;
let height = 0;
// Compute image coordinates and size, in pixels. Coordinates are from lower-left of texture atlas.Z
let imageX = 0;
let imageY = 0;
let imageWidth = 0;
let imageHeight = 0;
if (billboard.ready) {
const imageRectangle = billboard.computeTextureCoordinates(
scratchBoundingRectangle,
);

bottomLeftX = imageRectangle.x;
bottomLeftY = imageRectangle.y;
width = imageRectangle.width;
height = imageRectangle.height;
billboard.computeImageCoordinates(scratchBoundingRectangle);
imageX = scratchBoundingRectangle.x;
imageY = scratchBoundingRectangle.y;
imageWidth = scratchBoundingRectangle.width;
imageHeight = scratchBoundingRectangle.height;
}
const topRightX = bottomLeftX + width;
const topRightY = bottomLeftY + height;

let compressed0 =
Math.floor(
Expand Down Expand Up @@ -1056,52 +1056,46 @@ function writeCompressedAttrib0(
compressed1 += upperTranslateY;
compressed2 += lowerTranslateY;

scratchCartesian2.x = bottomLeftX;
scratchCartesian2.y = bottomLeftY;
const compressedTexCoordsLL =
AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.x = topRightX;
const compressedTexCoordsLR =
AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.y = topRightY;
const compressedTexCoordsUR =
AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.x = bottomLeftX;
const compressedTexCoordsUL =
AttributeCompression.compressTextureCoordinates(scratchCartesian2);
// Compress image coordinates (px), integers 0-2^12 from lower-left of atlas. Avoid
// `AttributeCompression.compressTextureCoordinates` for lossless pixel values.
const compressedImageLL = imageX * LEFT_SHIFT12 + imageY;
const compressedImageLR = (imageX + imageWidth) * LEFT_SHIFT12 + imageY;
const compressedImageUR =
(imageX + imageWidth) * LEFT_SHIFT12 + imageY + imageHeight;
const compressedImageUL = imageX * LEFT_SHIFT12 + imageY + imageHeight;

if (billboardCollection._instanced) {
i = billboard._index;
writer(i, compressed0, compressed1, compressed2, compressedTexCoordsLL);
writer(i, compressed0, compressed1, compressed2, compressedImageLL);
} else {
i = billboard._index * 4;
writer(
i + 0,
compressed0 + LOWER_LEFT,
compressed1,
compressed2,
compressedTexCoordsLL,
compressedImageLL,
);
writer(
i + 1,
compressed0 + LOWER_RIGHT,
compressed1,
compressed2,
compressedTexCoordsLR,
compressedImageLR,
);
writer(
i + 2,
compressed0 + UPPER_RIGHT,
compressed1,
compressed2,
compressedTexCoordsUR,
compressedImageUR,
);
writer(
i + 3,
compressed0 + UPPER_LEFT,
compressed1,
compressed2,
compressedTexCoordsUL,
compressedImageUL,
);
}
}
Expand Down Expand Up @@ -1255,23 +1249,8 @@ function writeEyeOffset(
);

if (billboardCollection._instanced) {
scratchCartesian2.x = 0;
scratchCartesian2.y = 0;

if (billboard.ready) {
const imageRectangle = billboard.computeTextureCoordinates(
scratchBoundingRectangle,
);

scratchCartesian2.x = imageRectangle.width;
scratchCartesian2.y = imageRectangle.height;
}

const compressedTexCoordsRange =
AttributeCompression.compressTextureCoordinates(scratchCartesian2);

i = billboard._index;
writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, compressedTexCoordsRange);
writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
} else {
i = billboard._index * 4;
writer(i + 0, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
Expand Down
11 changes: 11 additions & 0 deletions packages/engine/Source/Scene/BillboardTexture.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,17 @@ BillboardTexture.prototype.setImageSubRegion = function (index, subRegion) {
this.dirty = true;
};

/**
* Get the image coordinates for reading the loaded texture in shaders.
* @private
* @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BillboardTexture.prototype.computeImageCoordinates = function (result) {
const atlas = this._billboardCollection.textureAtlas;
return atlas.computeImageCoordinates(this._index, result);
};

/**
* Get the texture coordinates for reading the loaded texture in shaders.
* @private
Expand Down
27 changes: 16 additions & 11 deletions packages/engine/Source/Shaders/BillboardCollectionVS.glsl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
uniform vec2 u_atlasSize;
uniform float u_threePointDepthTestDistance;
#ifdef INSTANCED
in vec2 direction;
Expand Down Expand Up @@ -47,6 +48,7 @@ const float SHIFT_LEFT3 = 8.0;
const float SHIFT_LEFT2 = 4.0;
const float SHIFT_LEFT1 = 2.0;

const float SHIFT_RIGHT16 = 1.0 / 65536.0;
const float SHIFT_RIGHT12 = 1.0 / 4096.0;
const float SHIFT_RIGHT8 = 1.0 / 256.0;
const float SHIFT_RIGHT7 = 1.0 / 128.0;
Expand Down Expand Up @@ -146,16 +148,10 @@ void main()
float show = floor(compressed * SHIFT_RIGHT2);
compressed -= show * SHIFT_LEFT2;

#ifdef INSTANCED
vec2 textureCoordinatesBottomLeft = czm_decompressTextureCoordinates(compressedAttribute0.w);
vec2 textureCoordinatesRange = czm_decompressTextureCoordinates(eyeOffset.w);
vec2 textureCoordinates = textureCoordinatesBottomLeft + direction * textureCoordinatesRange;
#else
#ifndef INSTANCED
vec2 direction;
direction.x = floor(compressed * SHIFT_RIGHT1);
direction.y = compressed - direction.x * SHIFT_LEFT1;

vec2 textureCoordinates = czm_decompressTextureCoordinates(compressedAttribute0.w);
#endif

float temp = compressedAttribute0.y * SHIFT_RIGHT8;
Expand All @@ -172,10 +168,19 @@ void main()
translate.y -= UPPER_BOUND;
translate.y *= SHIFT_RIGHT2;

temp = compressedAttribute1.x * SHIFT_RIGHT8;
float temp2 = floor(compressedAttribute2.w * SHIFT_RIGHT2);
// TODO(donmccurdy): This is billboard size (px) on screen, not image size (px) in atlas?
float imageWidth = floor(compressedAttribute1.x * SHIFT_RIGHT8);
float imageHeight = floor(compressedAttribute2.w * SHIFT_RIGHT2);
vec2 imageSize = vec2(imageWidth, imageHeight);

vec2 imageSize = vec2(floor(temp), temp2);
float imageOffsetX = floor(compressedAttribute0.w * SHIFT_RIGHT12);
float imageOffsetY = compressedAttribute0.w - (imageOffsetX * SHIFT_LEFT12);
vec2 textureCoordinates = vec2(imageOffsetX + 0.5, imageOffsetY + 0.5) / u_atlasSize.xy;

#ifdef INSTANCED
vec2 textureCoordinatesRange = imageSize.xy / u_atlasSize.xy; // TODO(donmccurdy): Needs -1.0 offset?
textureCoordinates += direction * textureCoordinatesRange;
#endif

#ifdef FS_THREE_POINT_DEPTH_CHECK
float labelHorizontalOrigin = floor(compressedAttribute2.w - (temp2 * SHIFT_LEFT2));
Expand Down Expand Up @@ -328,7 +333,7 @@ void main()
if (lengthSq < (u_threePointDepthTestDistance * u_threePointDepthTestDistance) && (enableDepthCheck == 1.0)) {
float depthsilon = 10.0;
vec2 depthOrigin;
// Horizontal origin for labels comes from a special attribute. If that value is 0, this is a billboard - use the regular origin.
// Horizontal origin for labels comes from a special attribute. If that value is 0, this is a billboard - use the regular origin.
// Otherwise, transform the label origin to -1, 0, 1 (right, center, left).
depthOrigin.x = floor(compressedAttribute2.w - (temp2 * SHIFT_LEFT2));
depthOrigin.x = czm_branchFreeTernary(depthOrigin.x == 0.0, origin.x, depthOrigin.x - 2.0);
Expand Down
Loading