Skip to content

Pass:setQuad and UV2 attribute#929

Open
bjornbytes wants to merge 2 commits intodevfrom
pass-quad
Open

Pass:setQuad and UV2 attribute#929
bjornbytes wants to merge 2 commits intodevfrom
pass-quad

Conversation

@bjornbytes
Copy link
Owner

@bjornbytes bjornbytes commented Feb 6, 2026

This tries to solve 2 problems:

  • Make it easier to draw a sub-rectangle of a texture. A frequent pain point is that this currently requires either creating a new Material object for each new rectangle, or writing a custom shader.
  • Add support for a second set of UVs when importing models, initially attempted in Add support for second set of UVs; #860

Background

We'd like to switch to 16 bit unsigned normalized UVs to save space and keep model vertices at 32 bytes after we add the second UV set. However, this would mean that we could only support UVs that go from 0-1, which while very common, isn't an acceptable restriction.

To solve this, the model importer normalizes UV data to the 0-1 range and stores the original range of the UVs for each part. That way it's possible to take a compressed 16-bit 0-1 UV and reconstruct the original UV, even for floating point UVs. There is a minor precision loss.

However, we need to send this UV rectangle to shaders somehow. It would be possible to use Material for this, since it already has a UV rectangle, but it would get complicated if 2 meshes shared a material, and we would need to duplicate materials to fix it.

Solution

Another solution is to just add per-draw data for a UV rectangle, which 1) makes it possible for models to scale and bias their compressed UVs, and 2) provides a convenient way of adjusting texture UVs on a per-draw basis. This was added as Pass:setQuad(x, y, w, h). This is intended to replace Material's uvShift and uvScale properties, eventually.

Note that the quad only applies to the first UV set -- the second set of UVs are not transformed. UV2 is intended to be used for lightmapping, and it's rare for lightmap UVs to go outside of 0-1.

Drawbacks

  • This unconditionally adds 16 bytes of data to every draw, raising the total from 64 to 80 bytes, and adds a new uniform buffer. It may be possible to eliminate this overhead for passes that don't use quads.
  • While the precision issues are very minor, they could still cause problems.
  • If people are drawing ModelData manually, they now have to remember to plumb through the quad, if they want to support UVs outside 0-1 range.
  • The VertexUV data isn't the true UV anymore (but, like, normals/tangents are compressed too).

Other stuff:

  • :setQuad naming
  • Units (0-1 uvs vs. pixels, if you know the texture)
  • Could add quad as optional arguments after texture/material in :setMaterial

copyAttribute(vertices, tangents, SN10x3, 1, false, 28, sizeof(ModelVertex), vertexCount, 0);
copyAttribute(vertices, positions, F32, 3, false, 0, sizeof(ModelVertex), vertexCount, 0, false);
copyAttribute(vertices, normals, SN10x3, 1, false, 12, sizeof(ModelVertex), vertexCount, 0, false);
copyAttribute(vertices, uvs, U16, 2, true, 16, sizeof(ModelVertex), vertexCount, 0, true);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should only "supernormalize" if the UVs are actually outside of the 0-1 range. It gives worse precision, but means that the UV data can be used as-is in more cases (quad is optional more often)

uint32_t material;
ModelDrawMode mode;
float bounds[6];
float quad[4];
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to expose this in ModelData

.material = model->materials && part->material != ~0u ? model->materials[part->material] : NULL,
.transform = transform, // TODO fix skinned mesh transforms?
.bounds = part->bounds,
.quad = part->quad,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth passing NULL here (and in drawNode) if we know the part doesn't have a quad?

if (accessor->type == F32 && accessor->components == 2) {
for (uint32_t i = 0; i < accessor->count; i++, data += stride) {
float* f = (float*) data;
accessor->min[0] = MIN(accessor->min[0], f[0]);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should initialize the min/max to FLT_MAX/-FLT_MAX

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

Successfully merging this pull request may close these issues.

2 participants