在OpenGL中如何更好地控制曲面细分?

3
我今天在制作一个OpenGL应用程序,它将对网格进行镶嵌并应用镜头畸变。目标是能够为各种不同的镜头展现广角拍摄。到目前为止,我的着色器已经正确地应用了畸变,但我一直在处理我想要的方式来控制镶嵌的问题。目前,我的镶嵌控制着色器只是将单个三角形分成一组更小的三角形,然后在镶嵌评估着色器中应用镜头畸变。
我使用这种方法的问题在于,当场景中有非常大的三角形时,它们往往需要更多的扭曲。这意味着它们需要被更多地镶嵌以确保好的效果。不幸的是,我不能在顶点着色器或镶嵌控制着色器中计算三角形的大小(在屏幕空间中),但我需要在镶嵌控制着色器中定义镶嵌量。
那么,我的问题是,在OpenGL可编程管道中有没有办法获得整个基元,并计算一些指标,然后使用该信息来控制镶嵌?
以下是一些示例图像以便更好地理解此问题...
图1(上图):每个红色或绿色正方形最初都是2个三角形,此示例看起来很好,因为三角形很小。
图2(上图):每个红色或绿色区域最初都是2个三角形,此示例看起来很糟糕,因为三角形很大。
图3(上图):另一个具有小三角形但网格要大得多的示例。请注意边缘上的曲线程度。仍然在镶嵌级别为4时看起来很好。
图4(上图):另一个具有大三角形的示例,仅显示中心4列,因为如果存在更多列,则图像就不可理解了。这说明非常大的三角形无法很好地进行镶嵌。如果我将镶嵌设置得非常高,则会出现不错的效果。但是这样会在更小的三角形上执行大量的镶嵌。

你是否考虑过在剪裁空间中简单地使用 z 坐标?通常情况下,距离更远的补丁需要更少的细分,而更接近的补丁需要更多的细分。这通常是为了提高性能(避免在远程补丁上出现亚像素三角形),但它也有益于图像质量。 - Andon M. Coleman
@Andon 感谢您的建议,但我不确定它如何解决长三角形的问题,因为扭曲只是剪辑空间中 x 和 y 的函数。 - jodag
哦,我想我误解了我看到的东西。是的,在这种情况下,那样做不会特别有帮助。但是,如果您为每个补丁添加一些包围信息(例如,中心和包围它的球体的半径),则可以在NDC空间中计算补丁的覆盖面积。尽管如此,我认为您的思路完全错误了。因为在细分之前,您永远无法知道三角形的大小,因为它们还不存在;您只有一个控制点的抽象集合。您可以在TCS中获得整个基元,但它将是一个补丁基元。 - Andon M. Coleman
@Andon 当然可以!!!这是我第一次使用镶嵌着色器,我没有意识到您在TCS中有整个补丁可用。明确一下,我目前正在使用单个三角形作为补丁,我没有意识到我可以从TCS内部访问所有三个坐标。有了这个,我至少能够解决长三角形的细分问题。如果您将其作为答案,我将标记它为已回答。 - jodag
太棒了,谢谢 Andon :) - jodag
1个回答

1
在Tessellation Control Shader(TCS)中,您可以读取输入补丁原语中的每个顶点。虽然这在理论上听起来很好,但如果您试图计算补丁的最大边长,那么这实际上意味着在每个TCS调用中迭代补丁中的每个顶点,并且这不是特别高效的方式。
相反,更实际的方法可能是预先计算对象空间中补丁的中心,并确定紧密包围补丁的球体的半径。将此绑定信息作为每个顶点的额外 vec4 属性存储,如下所示打包。
以下是一个计算NDC空间中补丁最长长度的TCS的伪代码。
#version 420

uniform mat4 model_view_proj;

in vec4 bounding_sphere []; // xyz = center (object-space), w = radius

void main (void)
{
  vec4  center = vec4 (bounding_sphere [0].xyz, 1.0f);
  float radius =       bounding_sphere [0].w;

  // Transform object-space X extremes into clip-space
  vec4 min_0 = model_view_proj * (center - vec4 (radius, 0.0f, 0.0f, 0.0f));
  vec4 max_0 = model_view_proj * (center + vec4 (radius, 0.0f, 0.0f, 0.0f));

  // Transform object-space Y extremes into clip-space
  vec4 min_1 = model_view_proj * (center - vec4 (0.0f, radius, 0.0f, 0.0f));
  vec4 max_1 = model_view_proj * (center + vec4 (0.0f, radius, 0.0f, 0.0f));

  // Transform object-space Z extremes into clip-space
  vec4 min_2 = model_view_proj * (center - vec4 (0.0f, 0.0f, radius, 0.0f));
  vec4 max_2 = model_view_proj * (center + vec4 (0.0f, 0.0f, radius, 0.0f));

  // Transform from clip-space to NDC
  min_0 /= min_0.w; max_0 /= max_0.w;
  min_1 /= min_1.w; max_1 /= max_1.w;
  min_2 /= min_2.w; max_2 /= max_2.w;

  // Calculate the distance (ignore depth) covered by all three pairs of extremes
  float dist_0 = distance (min_0.xy, max_0.xy);
  float dist_1 = distance (min_1.xy, max_1.xy);
  float dist_2 = distance (min_2.xy, max_2.xy);

  // A max_dist >= 2.0 indicates the patch spans the entire screen in one direction
  float max_dist = max (dist_0, max (dist_1, dist_2));

  // ...
}

如果你通过这个TCS运行第四个图表,应该会得到一个非常接近2.0max_dist值,这意味着你需要尽可能多的细分。同时,在第三个图表中,球体周边的许多补丁将接近于0.0;它们不需要太多的细分。 这并没有正确处理部分补丁超出屏幕的情况。你需要将NDC的极限锁定在[-1.0,1.0]以正确处理这些情况。但这似乎比值得麻烦。

我非常喜欢这种方法,它很好而且干净。我进行了一些测试,并且能够得到一些漂亮的结果。我需要花更多时间来处理镜头畸变模型的数学问题,因为曲面细分的数量并不是线性缩放的,但是作为一个经验法则,我使用边界圆的半径和剪辑空间中心的加权乘积来确定曲面细分的数量。结果看起来好多了。感谢您的所有帮助! - jodag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接