自定义图形引擎中顶点着色器的像素空间深度偏移问题
解决点图元深度偏移与非线性深度缓冲区的适配问题
核心问题出在你直接在投影空间调整Z值——深度缓冲区的非线性特性(与1/视图空间深度正相关)导致这种硬调整无法在全深度范围生效。正确的思路是在视图空间(相机空间)完成偏移,再转换到投影空间,这样就能保证远近处的偏移量都符合像素半径对应的真实空间距离。
具体步骤
将点转换到视图空间
先把世界坐标的点通过视图矩阵转换到相机空间(视图空间),注意坐标系差异:多数引擎中视图空间Z轴指向相机外(值越大离相机越远),部分引擎(如OpenGL)Z轴指向相机内(值为负),后续计算要对应符号。计算像素半径对应的视图空间偏移量
利用相似三角形原理,屏幕上的像素半径对应视图空间的线性距离,和深度成正比:- 将垂直视场角转为弧度:
fov_y_rad = radians(fov_y)(fov_y是引擎设置的垂直视场角,比如60°) - 计算单个像素对应的垂直角度:
pixel_angle = fov_y_rad / screen_height(screen_height是屏幕像素高度) - 获取点到相机的视图空间距离:
view_distance = abs(view_pos.z) - 计算视图空间偏移量:
view_offset = tan(pixel_angle / 2.0) * point_pixel_radius * view_distance * 2.0
(乘2是因为tan(theta/2)是半角对应的对边,乘以2得到完整的像素半径对应的空间长度)
- 将垂直视场角转为弧度:
向相机方向偏移视图空间点
根据视图空间Z轴方向调整:- 若Z轴指向相机外(值越大越远):
view_pos.z += view_offset(减小深度值,靠近相机) - 若Z轴指向相机内(值为负):
view_pos.z -= view_offset(让Z值更靠近相机,即更负)
- 若Z轴指向相机外(值越大越远):
转换到投影空间
用投影矩阵将偏移后的视图空间点转换为投影空间坐标,此时的深度值会正确映射到深度缓冲区,远近偏移效果一致。
伪代码示例(GLSL风格)
// 输入参数 uniform mat4 view_matrix; uniform mat4 proj_matrix; uniform float fov_y; // 垂直视场角,单位:度 uniform float screen_height; // 屏幕像素高度 uniform float point_pixel_radius; in vec3 world_pos; void main() { // 转换到视图空间 vec3 view_pos = (view_matrix * vec4(world_pos, 1.0)).xyz; // 计算视图空间偏移量 float fov_y_rad = radians(fov_y); float pixel_angle = fov_y_rad / screen_height; float view_distance = abs(view_pos.z); float view_offset = tan(pixel_angle * 0.5) * point_pixel_radius * view_distance * 2.0; // 向相机方向偏移(假设视图空间Z轴指向相机外) view_pos.z += view_offset; // 转换到投影空间 vec4 proj_pos = proj_matrix * vec4(view_pos, 1.0); gl_Position = proj_pos; // 保持点的恒定像素大小(你已实现的部分) gl_PointSize = point_pixel_radius; }
关键说明
- 你之前用
proj_vertex_pos.z -= point_pixel_radius / proj_vertex_pos.w * 100.0能在近处生效,是因为近处proj_vertex_pos.w(即视图空间Z值的绝对值)较小,除以w后的值相对大;但远处w值大,除以w后的值趋近于0,乘100也无法弥补非线性映射的问题,属于凑数的临时方案。 - 如果是正交投影,无需计算角度,直接将像素半径转换为正交投影的单位长度即可(正交投影是线性深度,偏移量恒定)。
- 偏移后要注意避免点超出近裁剪面,可添加
view_pos.z = max(view_pos.z, near_plane + view_offset)这类限制。
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

