【问题标题】:Pixel space depth offset in vertex shader顶点着色器中的像素空间深度偏移
【发布时间】:2022-11-29 03:50:36
【问题描述】:

我正在尝试在我的自定义图形引擎中绘制简单的缩放点。这些点在像素空间中缩放,点的半径以像素为单位,但提供给绘制函数的点的位置在世界坐标中。

到目前为止,一切都很好,除了深度裁剪问题。这些点的大小不变,无论它们相距多远,这是通过在投影/裁剪空间中偏移顶点来完成的。但是,当它们靠近表面时,它们会在深度缓冲区中与它们部分相交。

由于这些点代表世界坐标,我希望它们使用深度缓冲区,并隐藏在它们前面的对象后面。但是,当点靠近表面时,我想将它推向相机,这样它就不会部分相交。我认为总是做这个推动更容易,不管点靠近表面。对我来说最有意义的是只按它的半径推动它,这样它的所有顶点都恰好足够远以避免剪到附近的表面。

我发现最简单的方法是在转换到视图投影空间后,简单地从顶点着色器中的 Z 值中减去。但是,我在将像素半径转换为深度偏移时遇到了一些问题。不管我使用什么数学方法,在近处起作用的东西似乎永远不会在远处起作用。我在想这可能是由于 z 缓冲区是非线性的,但可能是错误的。

目前,我最接近解决这个问题的是:

proj_vertex_pos.z -= point_pixel_radius / proj_vertex_pos.w * 100.0

老实说,我不确定为什么 100.0 帮助完成了这项工作。我添加它只是因为将半径除以 w 的值太小了。谁能指出我正确的方向?如何将像素距离转换为深度距离?特别是如果深度距离根据您所处的深度而改变比例?或者我只是路途遥远?

【问题讨论】:

    标签: graphics vertex-shader depth-buffer


    【解决方案1】:

    解决方案是将我的像素空间半径转换为世界空间单位,因为 z 缓冲区仍在世界空间中,即使在通过视图投影变换进行变换之后也是如此。这可以通过将像素转换为一个因子 (factor = pixels / screen_size),然后将该因子转换为世界空间单位来完成,这有点复杂——我必须计算给定距离处屏幕的世界空间大小,然后将该系数乘以得到世界单位。如果有人需要,我可以发布相关代码。可能有一种更简单的方法来计算它,但我的大脑总是直接寻找因素。

    我在不同距离得到不同结果的原因主要是因为我只是用结果偏移了剪辑位置的 z 分量。还需要偏移 w 组件,以使深度偏移在任何距离(线性)下都有效。但是,为了抵消 w 组件,您首先必须将 xy 缩放 w,根据需要修改 w,然后将 xy 除以新的 w。这导致数学变得非常复杂,所以我改变了在裁剪空间之前偏移顶点的策略,这需要手动计算 Z 空间中到相机的距离,但老实说,无论哪种方式,它最终的数学量大致相同。

    这是目前最终的顶点着色器。希望全球价值观有意义。我没有修改它来发布它,所以请原谅我的 cmets 中的任何愚蠢行为:

    lerpPoint main(vinBake vin)
    {
        // prepare output
        lerpPoint pin;
        
        // extract radius/size from input
        pin.InRadius = vin.TexCoord.y;
        
        // extract alpha falloff from input
        pin.Feather = vin.TexCoord.z;
        
        // compute the Z distance of the camera from the vertex
        float cam_z_dist = dot( Scene.CamZ, vin.Position.xyz - Scene.CamPos );
        
        // compute the radius factor
        // + this describes what percentage of the screen is covered by our radius
        // + this removes it from pixel space into factor-space
        float radius_fac = Scene.InvScreenRes.x * pin.InRadius;
        
        // compute world-space radius by scaling with FieldFactor
        // + FieldFactor.x represents the world-space-width of the camera view at whatever distance we scale it by
        // + here, we scale FieldFactor.x by the camera z distance, which gives us the world radius, in world units
        // + we must multiply by 2 because FieldFactor.x only represents HALF of the screen
        float radius_world = radius_fac * Scene.FieldFactor.x * cam_z_dist * 2.0;
        
        // finally, push the vertex toward the camera by the world radius
        // + note: moving by radius will only work with surfaces facing the camera, since we are moving toward the camera, rather than away from the surface
        // + because of this, we also multiply by another 4, to compensate for nearby surface angles, but there is no scale that would work for every angle
        float3 offset = Scene.CamZ * (radius_world * -4.0);
        
        // generate projected position
        // + in this space, xy are sort of in pixel-space, but are undivided by depth
        // + the more depth this point has from the camera, the higher xy will become as it travels away from the screen center
        // + after this, dividing xy by w will give us clip space, where x=-1 is left, x=+1 is right, y=-1 is bottom, and y=+1 is top of screen
        // + note that after this transform, w represents "distance from camera", and z represents "distance from near plane", both in world space
        pin.ClipPos = mul( Scene.ViewProj, float4( vin.Position.xyz + offset, 1.0) );
    
        // record clip xy position as center (before we offset it)
        pin.Center = pin.ClipPos.xy;
        
        // calculate radius of point, in clip space
        // + we scale by inverted resolution (1/width) and 2 to convert our pixel radius into clip-radius
        float clip_radius = radius_fac * 2.0 * pin.ClipPos.w;
        
        // compute scaled clip-space offset and apply it to our clip-position
        // + vin.Prop.xy: -1,-1 = bottom-left, -1,1 = top left, 1,-1 = bottom right, 1,1 = top right (note: in clip-space, +1 = top, -1 = bottom)
        // + we scale by clipping depth to retain constant scale, but this will give us a VERY LARGE result
        // + we scale by inverter resolution to convert our input screen scale (eg, 1->1024) into a clip scale (eg, 0.001 to 1.0 )
        pin.ClipPos.x += vin.Prop.x * clip_radius;
        pin.ClipPos.y += vin.Prop.y * clip_radius * Scene.Aspect;
        
        // copy diffuse color
        pin.Diffuse = vin.Color;
    
        // return pin data
        return pin;
    }
    

    【讨论】:

      猜你喜欢
      • 2012-06-11
      • 1970-01-01
      • 2018-11-12
      • 1970-01-01
      • 1970-01-01
      • 2017-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多