awsm-renderer 0.4.0

awsm-renderer
Documentation
//***** INPUT/OUTPUT *****

struct ApplyVertexInput {
    vertex_index: u32,
    position: vec3<f32>,      // Model-space position
    normal: vec3<f32>,        // Model-space normal
    tangent: vec4<f32>,       // Model-space tangent (w = handedness)
    {% if instancing_transforms %}
        // instance transform matrix
        instance_transform_row_0: vec4<f32>,
        instance_transform_row_1: vec4<f32>,
        instance_transform_row_2: vec4<f32>,
        instance_transform_row_3: vec4<f32>,
    {% endif %}
}

struct ApplyVertexOutput {
    clip_position: vec4<f32>,
    world_normal: vec3<f32>,     // Transformed world-space normal
    world_tangent: vec4<f32>,    // Transformed world-space tangent (w = handedness)
    world_position: vec3<f32>,   // Transformed world-space position
}

fn apply_vertex(vertex_orig: ApplyVertexInput, camera: Camera {% if has_custom_vertex %}, custom_uv: array<vec2<f32>, 4>, custom_uv_count: u32, custom_instance_id: u32, custom_globals: FrameGlobals {% endif %}) -> ApplyVertexOutput {
    var out: ApplyVertexOutput;

    var vertex = vertex_orig;
    var normal = vertex_orig.normal;
    var tangent = vertex_orig.tangent;

    // Apply morphs to position, normal, and tangent
    if geometry_mesh_meta.morph_geometry_target_len != 0 {
        vertex = apply_position_morphs(vertex);

        // Apply morphed normals (correct behavior)
        normal = apply_normal_morphs(vertex_orig, normal);
        tangent = apply_tangent_morphs(vertex_orig, tangent);
    }

    {% if has_custom_vertex %}
    {
        let _disp = custom_displace_vertex(VertexDisplaceInput(
            vertex.position, normal, tangent, custom_uv, custom_uv_count,
            vertex.vertex_index, custom_instance_id,
            material_data_load(geometry_mesh_meta.material_mesh_meta_offset),
            custom_globals,
        ));
        vertex.position = _disp.position;
        normal = _disp.normal;
        tangent = _disp.tangent;
    }
    {% endif %}

    // Apply skinning to position, normal, and tangent
    if geometry_mesh_meta.skin_sets_len != 0 {
        vertex = apply_position_skin(vertex);
        normal = apply_normal_skin(vertex_orig, normal);
        tangent = vec4<f32>(apply_normal_skin(vertex_orig, tangent.xyz), tangent.w);
    }

    {% if instancing_transforms %}
        // Transform the vertex position by the instance transform
        let instance_transform = mat4x4<f32>(
            vertex.instance_transform_row_0,
            vertex.instance_transform_row_1,
            vertex.instance_transform_row_2,
            vertex.instance_transform_row_3,
        );

        var model_transform = get_model_transform(geometry_mesh_meta.transform_offset) * instance_transform;
    {% else %}
        var model_transform = get_model_transform(geometry_mesh_meta.transform_offset);
    {% endif %}

    // Skinned meshes are already in world space: `apply_position_skin` /
    // `apply_normal_skin` multiply by the joint matrices, each of which is
    // `jointWorld * inverseBind` and therefore folds in every ancestor of the
    // joint node — including the glTF Z-up→Y-up root conversion. Per the glTF
    // spec the skinned mesh node's own transform MUST NOT be applied on top, so
    // collapse the base model transform to identity (the per-instance transform,
    // if any, is preserved). Without this, models whose skinned mesh node sits
    // under a non-identity root (e.g. CesiumMan's `Z_UP`) get that rotation
    // applied twice and render lying flat.
    if (geometry_mesh_meta.skin_sets_len != 0u) {
        {% if instancing_transforms %}
            model_transform = instance_transform;
        {% else %}
            model_transform = mat4x4<f32>(
                vec4<f32>(1.0, 0.0, 0.0, 0.0),
                vec4<f32>(0.0, 1.0, 0.0, 0.0),
                vec4<f32>(0.0, 0.0, 1.0, 0.0),
                vec4<f32>(0.0, 0.0, 0.0, 1.0),
            );
        {% endif %}
    }

    // Camera-facing override. Replaces the rotation portion of the model
    // matrix while preserving translation + per-instance scale (encoded as
    // column lengths). Uniform scale is recovered as `length(col[i].xyz)` so
    // the per-instance `size` (Stage 3) stays baked-in through this rewrite.
    let billboard_mode = geometry_mesh_meta.billboard_mode;
    if (billboard_mode != 0u) {
        let translation = model_transform[3].xyz;
        let scale_x = length(model_transform[0].xyz);
        let scale_y = length(model_transform[1].xyz);
        let scale_z = length(model_transform[2].xyz);

        let to_cam = camera.position - translation;
        // BillboardMode::YAxis (1): rotate only around world +Y so the local
        // +Z axis points at the camera in the XZ plane. Preserves upright
        // orientation for sprites that should not pitch.
        // BillboardMode::Full (2): build a full look-at basis with world up
        // as the reference; local +Z points at the camera in 3D.
        var forward: vec3<f32>;
        if (billboard_mode == 1u) {
            // YAxis: project onto XZ plane, fall back to +Z when degenerate.
            var xz = vec3<f32>(to_cam.x, 0.0, to_cam.z);
            let len_sq = dot(xz, xz);
            if (len_sq < 1e-8) {
                xz = vec3<f32>(0.0, 0.0, 1.0);
            } else {
                xz = xz * inverseSqrt(len_sq);
            }
            forward = xz;
        } else {
            // Full: world-space look-at; degenerate when the camera is exactly
            // above / below the sprite — fall back to +Z.
            let len_sq = dot(to_cam, to_cam);
            if (len_sq < 1e-8) {
                forward = vec3<f32>(0.0, 0.0, 1.0);
            } else {
                forward = to_cam * inverseSqrt(len_sq);
            }
        }

        let world_up = vec3<f32>(0.0, 1.0, 0.0);
        var right_unnorm = cross(world_up, forward);
        let right_len_sq = dot(right_unnorm, right_unnorm);
        var right: vec3<f32>;
        var up: vec3<f32>;
        if (right_len_sq < 1e-8) {
            // forward is collinear with world up; use world-X as a fallback
            // right vector so the basis stays orthonormal.
            right = vec3<f32>(1.0, 0.0, 0.0);
            up = cross(forward, right);
        } else {
            right = right_unnorm * inverseSqrt(right_len_sq);
            up = cross(forward, right);
        }

        model_transform = mat4x4<f32>(
            vec4<f32>(right * scale_x, 0.0),
            vec4<f32>(up * scale_y, 0.0),
            vec4<f32>(forward * scale_z, 0.0),
            vec4<f32>(translation, 1.0),
        );
    }

    let world_pos = model_transform * vec4<f32>(vertex.position, 1.0);
    out.clip_position = camera.view_proj * world_pos;


    // Transform normal/tangent to world space (ignore translation)
    let model_matrix3 = mat3x3<f32>(
        model_transform[0].xyz,
        model_transform[1].xyz,
        model_transform[2].xyz
    );
    // Correct normal transform for non-uniform scaling using an explicit
    // inverse-transpose (cofactor) path, avoiding WGSL inverse() support issues.
    let c0 = model_matrix3[0];
    let c1 = model_matrix3[1];
    let c2 = model_matrix3[2];
    let r0 = vec3<f32>(c0.x, c1.x, c2.x);
    let r1 = vec3<f32>(c0.y, c1.y, c2.y);
    let r2 = vec3<f32>(c0.z, c1.z, c2.z);

    let cof0 = cross(r1, r2);
    let cof1 = cross(r2, r0);
    let cof2 = cross(r0, r1);
    let det_model = dot(r0, cof0);

    let world_normal_unnormalized = select(
        model_matrix3 * normal,
        vec3<f32>(
            dot(cof0, normal),
            dot(cof1, normal),
            dot(cof2, normal),
        ) / det_model,
        abs(det_model) > 1e-8
    );
    let world_normal = normalize(world_normal_unnormalized);

    // Tangents transform with the model matrix, then must be re-orthonormalized against N.
    let tangent_raw = model_matrix3 * tangent.xyz;
    var tangent_ortho = tangent_raw - world_normal * dot(tangent_raw, world_normal);
    let tangent_len_sq = dot(tangent_ortho, tangent_ortho);
    if (tangent_len_sq > 1e-8) {
        tangent_ortho *= inverseSqrt(tangent_len_sq);
    } else {
        // Deterministic fallback tangent orthogonal to N.
        let fallback_axis = select(
            vec3<f32>(0.0, 0.0, 1.0),
            vec3<f32>(0.0, 1.0, 0.0),
            abs(world_normal.z) > 0.999
        );
        tangent_ortho = normalize(cross(fallback_axis, world_normal));
    }

    out.world_normal = world_normal;
    out.world_tangent = vec4<f32>(tangent_ortho, tangent.w);

    out.world_position = world_pos.xyz;

    return out;
}