You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vulkan实例化三角形渲染时出现随机伪影问题求助

Vulkan实例化三角形渲染器随机伪影排查

问题描述

开发的Vulkan渲染器采用实例化三角形渲染场景,当前屏幕出现大量随机伪影;相机移动等渲染状态变化时,伪影会在全屏范围内改变。

着色器代码

顶点着色器

#version 460

#extension GL_EXT_scalar_block_layout : enable

precision highp float;

struct Transform {
    vec2 translation;
    vec2 origin;
    vec2 scale;
    float rotation;
    int _align;
};

struct Triangle {
    Transform transform;
    Transform canvasTransform;
    vec4 colors[3];
    vec2 points[3];
    vec2 texCoords[3];
    float z;
    float texId;
    float blending;
    int _align;
};

layout(location = 0) out vec2 outTexCoord;
layout(location = 1) out vec4 outColor;
layout(location = 2) out float outTexId;
layout(location = 3) out float outBlending;

layout(set = 0, binding = 0) uniform Matrices {
    mat4 view;
    mat4 proj;
    vec2 screenSize;
} mat;

layout(set = 1, binding = 0, scalar) readonly buffer ObjectUbo {
    Triangle triangles[];
} triangles;

void main() {
    Triangle tri = triangles.triangles[gl_InstanceIndex];
    vec2 pos = tri.points[gl_VertexIndex];
    Transform transform = tri.transform;
    Transform canvasTransform = tri.canvasTransform;

    mat2 rot;
    rot[0] = vec2(cos(transform.rotation), 0 - sin(transform.rotation));
    rot[1] = vec2(sin(transform.rotation), cos(transform.rotation));
    pos -= transform.origin;
    pos = rot * (pos * transform.scale);
    pos += transform.origin;

    mat2 canvasRot;
    canvasRot[0] = vec2(cos(canvasTransform.rotation), 0 - sin(canvasTransform.rotation));
    canvasRot[1] = vec2(sin(canvasTransform.rotation), cos(canvasTransform.rotation));
    pos -= canvasTransform.origin;
    pos = canvasRot * (pos * canvasTransform.scale);
    pos += canvasTransform.origin;

    pos = pos + canvasTransform.translation + transform.translation;

    gl_Position = mat.proj * mat.view * vec4(pos, tri.z, 1.0);

    outTexCoord = tri.texCoords[gl_VertexIndex];
    outColor = tri.colors[gl_VertexIndex];
    outTexId = tri.texId;
    outBlending = tri.blending;
}

片段着色器

#version 420

#extension GL_EXT_nonuniform_qualifier : enable

layout(location = 0) out vec4 outColor;

layout(location = 0) in vec2 inTexCoords;
layout(location = 1) in vec4 inColor;
layout(location = 2) in float texId;
layout(location = 3) in float blending;

layout(set = 2, binding = 0) uniform sampler2D textures[];
layout(set = 2, binding = 1) uniform sampler2D fonts[];

float screenPxRange(sampler2D texture) {
    const float pxRange = 2.0f;

    vec2 unitRange = vec2(pxRange) / vec2(textureSize(texture, 0));
    vec2 screenTexSize = vec2(1.0) / fwidth(inTexCoords);
    return max(0.5 * dot(unitRange, screenTexSize), 1.0);
}

float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
}

void main() {
    int iTexId = int(texId);
    if (blending < 0.0f) {
        // font
        vec3 msd = texture(fonts[iTexId], inTexCoords).rgb;
        float sd = median(msd.r, msd.g, msd.b);
        float screenPxDistance = screenPxRange(fonts[iTexId]) * (sd - 0.5);
        float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);

        if (opacity <= 0) discard;

        outColor = vec4(vec3(1.0f), opacity);
    }
    else {
        // no font
        outColor = iTexId == -1 ? inColor : mix(texture(textures[iTexId], inTexCoords), inColor, blending);
    }
}

排查方向与解决方案

1. 内存对齐问题

  • 检查CPU端Triangle和Transform结构体的内存布局是否与着色器完全一致:
    • 着色器启用了GL_EXT_scalar_block_layout,采用标量对齐(C语言自然对齐),CPU端结构体必须严格匹配填充字节的位置与大小,否则会导致实例化数据读取错位,产生随机伪影。
    • 可通过SPIR-V交叉编译工具查看着色器中结构体的字节大小,与CPU端计算结果对比验证。

2. 实例化缓冲区绑定与更新

  • 确认实例化缓冲区的内存屏障设置正确:
    • CPU更新实例化数据后,必须插入VK_ACCESS_HOST_WRITE_BIT到VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT的内存屏障,确保GPU能读取到最新数据。
    • 检查缓冲区的使用标志是否同时支持主机写入和设备读取。
  • 验证渲染实例数量与缓冲区中triangles数组的实际长度匹配,避免gl_InstanceIndex越界读取垃圾数据。

3. 着色器版本与扩展兼容性

  • 统一顶点与片段着色器的版本(如都使用460),版本不匹配可能导致管线状态异常。
  • 确认设备支持GL_EXT_nonuniform_qualifier扩展,且片段着色器中正确启用。

4. 纹理采样合法性

  • 限制texId的取值范围,确保iTexId不会超出textures或fonts数组的索引边界,可在片段着色器中添加边界检查(如iTexId = clamp(iTexId, 0, textureCount-1)),避免采样无效纹理产生随机颜色。

5. 深度与光栅化状态

  • 检查深度缓冲区初始化与深度测试配置是否正确,错误的深度状态可能导致三角形被错误覆盖。
  • 确认光栅化状态的cullMode、frontFace参数符合需求,错误的剔除模式会导致部分三角形渲染异常。

内容的提问来源于stack exchange,提问作者v22

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 20:55:54