Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add packer option to retain texture sizes (avoid scaling) in atlas #18

Closed
ghost opened this issue Jun 21, 2019 · 9 comments
Closed

Add packer option to retain texture sizes (avoid scaling) in atlas #18

ghost opened this issue Jun 21, 2019 · 9 comments

Comments

@ghost
Copy link

ghost commented Jun 21, 2019

I'm working on a project which allows you to generate texture atlases for 3D models (.obj files) in order to reduce the number of images used by the model by copying the pixels from textures to atlas. xatlas is nice, but it will scale the UVs in order to fit multiple charts within one atlas.

How about adding an option to not resize the UVs at all and if the charts don't fit within the resolution of the atlas, then create multiple atlases for the remaining charts if they don't fit in one?

I've tested xatlas with two .obj models (textures included):
models.zip

  1. Cube - A basic cube with 3 textures.
    In this case, UVs are scaled up.

  2. Dwarf - model from DirectX samples. I converted it to .obj.
    For this model, UVs are scaled down.

If this gets resolved, xatlas will be perfect 💯

@jpcy
Copy link
Owner

jpcy commented Jun 22, 2019

AddMesh only uses input UVs to find texture seams and are otherwise discarded as a new unique parameterization is generated. You should use AddUvMesh and PackCharts to pack existing UVs.

Input UVs should be in texture space - scale the them by their corresponding texture dimensions and set PackOptions::texelsPerUnit to 1 to disable scaling.

To copy texture data to the atlas you will have to rasterize chart triangles using the new UVs as position (destination) and use barycentric coordinates to interpolate the old UVs (source). Rasterization must be conservative and you'll need to handle bilinear filtering.

@ghost
Copy link
Author

ghost commented Jun 22, 2019

AddMesh only uses input UVs to find texture seams and are otherwise discarded as a new unique parameterization is generated. You should use AddUvMesh and PackCharts to pack existing UVs.

Input UVs should be in texture space - scale the them by their corresponding texture dimensions and set PackOptions::texelsPerUnit to 1 to disable scaling.

To copy texture data to the atlas you will have to rasterize chart triangles using the new UVs as position (destination) and use barycentric coordinates to interpolate the old UVs (source). Rasterization must be conservative and you'll need to handle bilinear filtering.

Thanks a lot. I knew I was doing something wrong but couldn't pinpoint it. I'll try and let you know.

@ghost
Copy link
Author

ghost commented Jun 23, 2019

If I use example.exe without making any changes to it, I get this (UVs are scaled up for cube.obj):

atlas charts with default example project(click me) W3Schools.com

You gave me some tips.

  • use AddUvMesh and PackCharts to pack existing UVs.
  • Input UVs should be in texture space - scale the them by their corresponding texture dimensions
  • set PackOptions::texelsPerUnit to 1 to disable scaling.

I took the code from your example project and made a couple of changes that you advised me to do, and I'm still not getting the expected result (cube.obj model).

atlas charts with modified example project(click me) W3Schools.com

The model I used was cube.obj. The UVs are within range from 0-1. There are only three textures.

laughcryemoji.png (256x256), oak_golden.png (256x256), steel.png (512x512)

Here's the code that I modified in example project of xatlas to avoid scaling of UV charts:

code for normalizing UVs to texture space(click me)

Loop through all .obj shapes (meshes) and within the loop, convert the UVs

uint32_t materialIDIndex = 0;

for (uint32_t f = 0; f < (objMesh.indices.size() / 3); f += 3, materialIDIndex++){

    int materialID = objMesh.material_ids[materialIDIndex];

    STextureDimension& textureDimensions = texturesArray[materialID];

    for (uint32_t j = 0; j < 3; j++)
    {
        unsigned int vertexIndex = objMesh.indices[f + j];

        auto it = setOfVertexIndices.find(vertexIndex);

        // Check if we have updated the UVs

        if (it == setOfVertexIndices.end())
        {
            setOfVertexIndices.insert(vertexIndex);

            const float* texcoord = &objMesh.texcoords[vertexIndex * 2];
            float* normalizedTexCoord = &vecNormalizedTexCoords[vertexIndex * 2];

            normalizedTexCoord[0] = texcoord[0];
            normalizedTexCoord[1] = texcoord[1];

            //normalizedTexCoord[0] -= std::floor(normalizedTexCoord[0]);
            //normalizedTexCoord[1] -= std::floor(normalizedTexCoord[1]);

            normalizedTexCoord[0] *= textureDimensions.width;
            normalizedTexCoord[1] *= textureDimensions.height;

            printf("vertex: %u | original U, V: %f, %f | normalized U, V: %f, %f\n",
                vertexIndex, texcoord[0], texcoord[1], normalizedTexCoord[0], normalizedTexCoord[1]);
        }
    }
}
code for Adding mesh using xatlas::AddUvMesh(click me)

xatlas::UvMeshDecl meshDecl;
meshDecl.vertexCount = (int)vecNormalizedTexCoords.size() / 2; //objMesh.texcoords.size() / 2;
if (!vecNormalizedTexCoords.empty())
{
    meshDecl.vertexUvData = vecNormalizedTexCoords.data(); // objMesh.texcoords.data();
    meshDecl.vertexStride = sizeof(float) * 2;
}
meshDecl.indexCount = (int)objMesh.indices.size();
meshDecl.indexData = objMesh.indices.data();
meshDecl.indexFormat = xatlas::IndexFormat::UInt32;
xatlas::AddMeshError::Enum error = xatlas::AddUvMesh(atlas, meshDecl);

And finally:

xatlas::PackOptions packOptions;
packOptions.texelsPerUnit = 1.0f;
xatlas::PackCharts(atlas, packOptions, ProgressCallback, &stopwatch);

Full code: https://pastebin.com/c2iJpeB9

jpcy added a commit that referenced this issue Jun 24, 2019
@jpcy
Copy link
Owner

jpcy commented Jun 24, 2019

I added an example with a rough implementation of this. Here's the dwarf model atlas:
example_repack_output

That's with random chart placement - brute force packs a little better. I haven't dumped a new model file and verified it's correct, but it looks about right. It doesn't handle bilinear filtering so there will be black fringes around charts.

Also, bear in mind this only packs charts. If multiple charts use the same parts of a texture they will be duplicated in the atlas. The same with any tiling UVs. So the atlas may end up using more texture space than the source textures.

@ghost
Copy link
Author

ghost commented Jun 24, 2019

I added an example with a rough implementation of this.

Thank you so much for your help. This looks amazing. I tried using uvatlastool, but it failed miserably in packing. I think xatlas is the only library which can create an atlas for a model without needing any human interaction whatsoever because adding seams in the proper location is the hardest part.

It doesn't handle bilinear filtering so there will be black fringes around charts.

I see, I'll fix that.

Also, bear in mind this only packs charts. If multiple charts use the same parts of a texture they will be duplicated in the atlas. The same with any tiling UVs. So the atlas may end up using more texture space than the source textures.

Yes, that's a small price to pay but worth it. Once again, thanks for the help.

@ghost
Copy link
Author

ghost commented Jun 24, 2019

@jpcy Do I need to modify the output UVs when writing them to .obj? I am normalizing the output UVs (diving x and y by atlas width and height).

I added support for generating .mtl and .obj files to example_repack. This is what I'm getting:

Cube output W3Schools.com
Dwarf output W3Schools.com

It doesn't handle bilinear filtering so there will be black fringes around charts.

Maybe that's why?

Code: https://pastebin.com/mZu1ubDq

@jpcy
Copy link
Owner

jpcy commented Jun 25, 2019

UV y coordinate needs to be flipped.

I updated the repack example to export an obj file. Looks like everything is working. You can see black fringes around charts where bilinear filtering samples outside the rasterized areas. Other than that, it looks the same as the source model.
Untitled

@ghost
Copy link
Author

ghost commented Jun 25, 2019

UV y coordinate needs to be flipped.

Correct.

I updated the repack example to export an obj file. Looks like everything is working. You can see black fringes around charts where bilinear filtering samples outside the rasterized areas. Other than that, it looks the same as the source model.

Perfect, thanks. :)

@ghost ghost closed this as completed Jun 25, 2019
jpcy added a commit that referenced this issue Jun 29, 2019
jpcy added a commit that referenced this issue Jun 30, 2019
… texture. If that fails, average surrounding texels color in source texture. If that fails, average using the atlas texture. #18 #19
@yongsiang-fb
Copy link

@jpcy The current version of xatlas still scales down the charts if any of the input triangles cannot be fit to the output resolution. Although it could be configured to output a warning message, it is a bit inconvenient for detecting such events. Would it be possible to add an option that makes xatlas fail when scaling is unavoidable?

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants