Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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:

- Corrected glTF import process to flip textures and UV coordinates from glTF's U-right, V-down convention to comply with Unity's U-right, V-up coordinate system.

## 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
51 changes: 46 additions & 5 deletions native~/Runtime/src/TextureLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@ 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 (right-handed) 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.
**/
template <typename TSrcByte, typename TDstByte>
void copyAndFlipY(
TSrcByte* dst,
const TDstByte* src,
const size_t dataLength,
const size_t height) {
assert(
(dataLength % height) == 0 &&
"Image data size is not an even multiple of image height.");

const size_t stride = dataLength / height;

for (size_t j = 0; j < height; ++j) {
memcpy(dst, src + (height - j) * stride, stride);
dst += stride;
}
}

} // namespace

UnityEngine::Texture
Expand Down Expand Up @@ -100,21 +125,37 @@ 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());
copyAndFlipY(
pixels,
image.pixelData.data(),
image.pixelData.size(),
image.height);
result.Apply(false, true);
} else {
// Copy the mipmaps explicitly.
std::uint8_t* pWritePosition = pixels;
const std::byte* pReadBuffer = image.pixelData.data();

// track square image dimension for each mip level
int32_t mipHeight = image.height;

for (const ImageAssetMipPosition& mip : image.mipPositions) {
size_t start = mip.byteOffset;
size_t end = mip.byteOffset + mip.byteSize;
if (start >= textureLength || end > textureLength)
assert(mipHeight > 0 && "Invalid image size.");
const size_t start = mip.byteOffset;
const size_t end = mip.byteOffset + mip.byteSize;
if (start >= textureLength || end > textureLength) {
mipHeight /= 2;
continue; // invalid mip spec, ignore it
}

std::memcpy(pWritePosition, pReadBuffer + start, mip.byteSize);
copyAndFlipY(
pWritePosition,
pReadBuffer + start,
mip.byteSize,
mipHeight);
pWritePosition += mip.byteSize;
// adjust height for next mip level.
mipHeight /= 2;
}

result.Apply(false, true);
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 left-handed UV coordinates
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 left-handed UV coordinates
texCoord.y = 1 - texCoord.y;
*reinterpret_cast<Vector2*>(pWritePos) = texCoord;
pWritePos += sizeof(Vector2);
}
}
Expand Down
Loading