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

光栅化阶段顶点会被丢弃吗?斯坦福龙像素化孔洞问题求解

解决斯坦福龙像素化时的顶点裁剪与孔洞问题

问题背景

我正在尝试制作斯坦福龙的2D像素化效果,后续计划扩展为3D体素化。目前用几何着色器实现投影到平面的逻辑,再通过片段着色器写入3D纹理,但遇到了两个棘手问题:

  • 初始生成的龙尺寸极小,在纹理上只占很小一块区域
  • 尝试放大缩放因子后,画面出现大量孔洞,推测是顶点超出NDC(标准化设备坐标)的(-1,1)范围,被光栅化阶段裁剪丢弃了

想请教有没有办法让着色器不丢弃这个范围外的顶点?或者有没有更合理的坐标映射方式?


当前使用的着色器代码

几何着色器

#version 440 
layout (triangles) in; 
layout (triangle_strip, max_vertices=136) out; 

in vec3 v_pos[]; 
in vec3 v_norm[]; 
in vec2 v_uv[]; 

out vec3 f_pos; 
out vec3 f_norm; 
out vec2 f_uv; 

void main() { 
    vec3 side1 = v_pos[0] - v_pos[1]; 
    vec3 side2 = v_pos[1] - v_pos[2]; 
    vec3 tNorm = abs(cross(side1,side2)); 

    for(uint i=0; i<3; i++) { 
        f_pos = v_pos[i]/10; 
        f_norm = v_norm[i]; 
        f_uv = v_uv[i]; 

        if(tNorm.x>tNorm.y && tNorm.x>tNorm.z) { 
            gl_Position = vec4(f_pos.y, f_pos.z, 0, 1); 
        } else if(tNorm.y>tNorm.x && tNorm.y>tNorm.z) { 
            gl_Position = vec4(f_pos.x, f_pos.z, 0, 1); 
        } else { 
            gl_Position = vec4(f_pos.x, f_pos.y, 0, 1); 
        } 
        EmitVertex(); 
    } 
    EndPrimitive(); 
} 

片段着色器

#version 440 
in vec3 f_pos; 
in vec3 f_norm; 
in vec2 f_uv; 

uniform layout(binding=0, rgba8) image3D image; 

out vec4 fragment_color; 

void main() { 
    imageStore(image, ivec3(f_pos.xy*50 + vec2(100),0), vec4(1,0,1,1)); 
    fragment_color = vec4(1,1,1,0); 
} 

问题分析

首先得明确:OpenGL的光栅化阶段一定会裁剪掉NDC范围(-1,1)之外的顶点,这是固定的管线行为,没办法直接禁用。你遇到的孔洞问题本质是缩放后的顶点超出了这个范围,导致三角形被部分裁剪甚至完全丢弃。

另外,初始尺寸极小的问题,是因为你直接将模型坐标除以固定值(比如10)作为NDC坐标,但模型本身的尺寸范围远大于这个缩放后的范围,导致大部分顶点挤在很小的NDC区域内。


解决方案

核心思路是先将模型坐标归一化到NDC的(-1,1)范围内,确保所有顶点都能被光栅化处理,再调整纹理映射逻辑匹配这个归一化后的坐标。

1. 计算模型的AABB(轴对齐包围盒)

先在CPU端遍历斯坦福龙的所有顶点,计算出模型的最小坐标model_min和最大坐标model_max,然后将这两个值作为uniform传入着色器:

// 在几何着色器顶部添加uniform定义
uniform vec3 model_min;
uniform vec3 model_max;

2. 修改几何着色器的坐标映射逻辑

把原始模型坐标归一化到NDC的(-1,1)范围,替换原来的f_pos = v_pos[i]/10;:

// 先将模型坐标从[model_min, model_max]转到[0,1]范围
vec3 normalized_pos = (v_pos[i] - model_min) / (model_max - model_min);
// 再转到NDC的(-1,1)范围
normalized_pos = normalized_pos * 2.0 - 1.0;
f_pos = normalized_pos; // 现在f_pos的范围完全在(-1,1)内

投影逻辑保持不变,但此时gl_Position的坐标会完全落在NDC范围内,不会被裁剪:

if(tNorm.x>tNorm.y && tNorm.x>tNorm.z) { 
    gl_Position = vec4(f_pos.y, f_pos.z, 0, 1); 
} else if(tNorm.y>tNorm.x && tNorm.y>tNorm.z) { 
    gl_Position = vec4(f_pos.x, f_pos.z, 0, 1); 
} else { 
    gl_Position = vec4(f_pos.x, f_pos.y, 0, 1); 
}

3. 调整片段着色器的纹理写入坐标

原来的f_pos.xy*50 + vec2(100)依赖于原始模型坐标,现在改成基于归一化后的坐标映射到纹理的像素空间:

// 可以把纹理分辨率定义为uniform,方便后续调整
uniform ivec2 texture_resolution;

void main() {
    // 将NDC的(-1,1)坐标转到纹理的[0, resolution)像素范围
    ivec2 tex_xy = ivec2((f_pos.xy + 1.0) / 2.0 * texture_resolution);
    // 写入3D纹理的第0层
    imageStore(image, ivec3(tex_xy, 0), vec4(1,0,1,1));
    fragment_color = vec4(1,1,1,0);
}

额外优化建议

  • 如果后续要做3D体素化,可以考虑直接在几何着色器中处理体素填充逻辑,跳过2D投影的中间步骤
  • 可以添加一个scale uniform来微调模型在纹理中的显示大小,避免归一化后填充整个纹理的情况
  • 对于复杂模型,AABB的计算可以提前离线完成,不需要每次运行时实时计算

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:07:39