Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ CesiumForUnityNativeBindings/native/
*.user
.DS_STORE
.idea
obj.meta
bin.meta
Reinterop.dll
Reinterop.deps.json
Reinterop.deps.json.meta
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## ? - ?

##### Fixes :wrench:

- Textures and UV coordinates from glTFs are now flipped to properly comply with Unity's U-right, V-up convention.

## v1.18.1 - 2025-10-01

This release updates [cesium-native](https://github.com/CesiumGS/cesium-native) from v0.51.0 to v0.52.1. See the [changelog](https://github.com/CesiumGS/cesium-native/blob/main/CHANGES.md) for a complete list of changes in cesium-native.
Expand Down
47 changes: 40 additions & 7 deletions native~/Runtime/src/TextureLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,29 @@ getUncompressedPixelFormat(const CesiumGltf::ImageAsset& image) {
}
}

/**
* Copy image data while flipping the data vertically. According to the glTF 2.0
* spec, glTF stores textures in x-right, y-down coordinates.
* However, Unity uses x-right, y-up coordinates, so we need to flip the
* textures and UV coordinates. See loadPrimitive() in
* UnityPrepareRenderResources.cpp for the corresponding UV flip.
**/
void copyAndFlipTexture(
std::uint8_t* pDst,
const std::byte* pSrc,
const size_t dataLength,
const size_t stride) {
assert(
(dataLength % stride == 0) &&
"Image data size is not an even multiple of the given row pitch.");

const int32_t height = static_cast<int32_t>(dataLength / stride);
for (int32_t i = height - 1; i >= 0; --i) {
memcpy(pDst, pSrc + i * stride, stride);
pDst += stride;
}
}

} // namespace

UnityEngine::Texture
Expand Down Expand Up @@ -100,20 +123,30 @@ TextureLoader::loadTexture(const CesiumGltf::ImageAsset& image, bool sRGB) {
if (image.mipPositions.empty()) {
// No mipmaps, copy the whole thing and then let Unity generate mipmaps on a
// worker thread.
std::memcpy(pixels, image.pixelData.data(), image.pixelData.size());
const size_t stride = image.pixelData.size() / image.height;
copyAndFlipTexture(
pixels,
image.pixelData.data(),
image.pixelData.size(),
stride);
result.Apply(false, true);
} else {
// Copy the mipmaps explicitly.
std::uint8_t* pWritePosition = pixels;
const std::byte* pReadBuffer = image.pixelData.data();

for (const ImageAssetMipPosition& mip : image.mipPositions) {
size_t start = mip.byteOffset;
size_t end = mip.byteOffset + mip.byteSize;
if (start >= textureLength || end > textureLength)
continue; // invalid mip spec, ignore it

std::memcpy(pWritePosition, pReadBuffer + start, mip.byteSize);
const size_t start = mip.byteOffset;
const size_t end = mip.byteOffset + mip.byteSize;
if (start >= textureLength || end > textureLength) {
// Invalid mip, skip this level.
continue;
}
copyAndFlipTexture(
pWritePosition,
pReadBuffer + start,
mip.byteSize,
mip.rowPitch);
pWritePosition += mip.byteSize;
}

Expand Down
15 changes: 8 additions & 7 deletions native~/Runtime/src/UnityPrepareRendererResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,31 +605,32 @@ void loadPrimitive(
}
for (uint32_t texCoordIndex = 0; texCoordIndex < numTexCoords;
++texCoordIndex) {
*reinterpret_cast<Vector2*>(pWritePos) =
texCoordViews[texCoordIndex][vertexIndex];
Vector2 texCoord = texCoordViews[texCoordIndex][vertexIndex];
// Flip Y to comply with Unity's V-up coordinate convention
texCoord.y = 1 - texCoord.y;
*reinterpret_cast<Vector2*>(pWritePos) = texCoord;
pWritePos += sizeof(Vector2);
}
}
} else {
for (int64_t i = 0; i < vertexCount; ++i) {
*reinterpret_cast<Vector3*>(pWritePos) = positionView[i];
pWritePos += sizeof(Vector3);

if (hasNormals) {
*reinterpret_cast<Vector3*>(pWritePos) = normalView[i];
pWritePos += sizeof(Vector3);
}

// Skip the slot allocated for vertex colors, we will fill them in
// bulk later.
if (hasVertexColors) {
pWritePos += sizeof(uint32_t);
}

for (uint32_t texCoordIndex = 0; texCoordIndex < numTexCoords;
++texCoordIndex) {
*reinterpret_cast<Vector2*>(pWritePos) =
texCoordViews[texCoordIndex][i];
Vector2 texCoord = texCoordViews[texCoordIndex][i];
// Flip Y to comply with Unity's V-up coordinate convention
texCoord.y = 1 - texCoord.y;
*reinterpret_cast<Vector2*>(pWritePos) = texCoord;
pWritePos += sizeof(Vector2);
}
}
Expand Down
Loading