Compare commits

...

3 Commits

Author SHA1 Message Date
Don McCurdy eecd3b43b1 additional fixes for billboard image coords
deploy / deploy (push) Waiting to run Details
2025-11-25 16:45:43 -06:00
Don McCurdy 94e2932c09 add comment on bit packing 2025-11-25 11:10:32 -06:00
Don McCurdy 2f3a731840 fix(BillboardCollection): Fix precision loss in billboard image texcoords 2025-11-25 11:01:20 -06:00
5 changed files with 103 additions and 78 deletions

View File

@ -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)) {
@ -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;
@ -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;
};

View File

@ -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.

View File

@ -57,12 +57,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
@ -78,11 +80,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,
@ -322,6 +324,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;
},
@ -938,8 +944,6 @@ function writePositionScaleAndRotation(
}
}
const scratchCartesian2 = new Cartesian2();
const UPPER_BOUND = 32768.0; // 2^15
const LEFT_SHIFT16 = 65536.0; // 2^16
@ -1003,22 +1007,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(
@ -1055,23 +1055,17 @@ 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(
@ -1079,28 +1073,28 @@ function writeCompressedAttrib0(
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,
);
}
}
@ -1254,23 +1248,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);

View File

@ -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

View File

@ -1,3 +1,4 @@
uniform vec2 u_atlasSize;
uniform float u_threePointDepthTestDistance;
#ifdef INSTANCED
in vec2 direction;
@ -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;
@ -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;
@ -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));
@ -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);