光栅化阶段顶点会被丢弃吗?斯坦福龙像素化孔洞问题求解
解决斯坦福龙像素化时的顶点裁剪与孔洞问题
问题背景
我正在尝试制作斯坦福龙的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投影的中间步骤
- 可以添加一个
scaleuniform来微调模型在纹理中的显示大小,避免归一化后填充整个纹理的情况 - 对于复杂模型,AABB的计算可以提前离线完成,不需要每次运行时实时计算
内容的提问来源于stack exchange,提问作者Makogan
相关产品推荐
相关产品推荐

