bevyengine/bevy · error · GenerateTangentsError

the '{0}' vertex attribute should have {1:?} format

Error message

the '{0}' vertex attribute should have {1:?} format

What it means

GenerateTangentsError::InvalidVertexAttributeFormat (crates/bevy_mesh/src/mikktspace.rs:71), returned by Mesh::compute_tangents when a required attribute exists but with the wrong VertexFormat. Mikktspace consumes positions and normals as Float32x3 and UVs as Float32x2; anything else (Float32x2 positions, Float32x3 UVs, packed formats) is rejected with the attribute name and the format it found.

Source

Thrown at crates/bevy_mesh/src/mikktspace.rs:71

        tangent_space: Option<bevy_mikktspace::TangentSpace>,
        face: usize,
        vert: usize,
    ) {
        let idx = self.index(face, vert);
        self.tangents[idx] = tangent_space.unwrap_or_default().tangent_encoded();
    }
}

#[derive(Error, Debug)]
/// Failed to generate tangents for the mesh.
pub enum GenerateTangentsError {
    #[error("cannot generate tangents for {0:?}")]
    UnsupportedTopology(PrimitiveTopology),
    #[error("missing indices")]
    MissingIndices,
    #[error("missing vertex attributes '{0}'")]
    MissingVertexAttribute(&'static str),
    #[error("the '{0}' vertex attribute should have {1:?} format")]
    InvalidVertexAttributeFormat(&'static str, VertexFormat),
    #[error("mesh not suitable for tangent generation")]
    MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError),
    #[error("Mesh access error: {0}")]
    MeshAccessError(#[from] MeshAccessError),
}

pub(crate) fn generate_tangents_for_mesh(
    mesh: &Mesh,
) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {
    match mesh.primitive_topology() {
        PrimitiveTopology::TriangleList => {}
        other => return Err(GenerateTangentsError::UnsupportedTopology(other)),
    };

    let positions = mesh.try_attribute_option(Mesh::ATTRIBUTE_POSITION)?.ok_or(
        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
    )?;

View on GitHub (pinned to 396ca72708)

Solutions

  1. Read the error payload: '{0}' names the attribute, '{1:?}' the offending format
  2. Convert and re-insert the attribute with the required format (positions/normals -> Vec<[f32; 3]>, UVs -> Vec<[f32; 2]>)
  3. Keep compression/packing passes out of the pre-tangent stage; compress after tangents exist

Example fix

// before: UVs stored as float3
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[u, v, 0.0f32]; n]);
mesh.compute_tangents(); // Err(InvalidVertexAttributeFormat("Vertex_Uv", Float32x3))

// after: UVs as float2
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[u, v]; n]);
mesh.compute_tangents().unwrap();
Defensive patterns

Strategy: validation

Validate before calling

fn tangent_formats_ok(mesh: &Mesh) -> bool {
    mesh.attribute(Mesh::ATTRIBUTE_POSITION).is_some_and(|v| v.as_float3().is_some())
        && mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_some_and(|v| v.as_float3().is_some())
        && mesh.attribute(Mesh::ATTRIBUTE_UV_0).is_some_and(|v| v.as_float2().is_some())
}

if tangent_formats_ok(&mesh) {
    mesh.compute_tangents()?;
}

Try / catch

if let Err(GenerateTangentsError::InvalidVertexAttributeFormat(name, format)) =
    mesh.compute_tangents()
{
    bevy::log::error!("'{name}' has format {format:?}; need Float32x3/Float32x3/Float32x2");
}

Prevention

When it happens

Trigger: Calling compute_tangents() on a mesh where ATTRIBUTE_POSITION/ATTRIBUTE_NORMAL is not Float32x3 or ATTRIBUTE_UV_0 is not Float32x2 — e.g. UVs inserted as [f32; 3] from a texture pipeline, normals carried as Snorm8x4 after compression.

Common situations: Assets imported from engines that use vec3 UVs (w coordinate for projective texturing); vertex-compression passes that repack normals; custom attributes accidentally reusing a standard attribute id with an exotic format.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/336e77853ec9181e. Report an issue: GitHub.