iphone es教學 - 如何提高自定義OpenGL ES 2.0深度紋理生成的性能?





ios (5)


在桌面上,許多早期可編程設備就是這樣,雖然它們可以同時處理8或16個或任何片段,但它們實際上只有一個程序計數器用於它們(因為這也意味著只有一個提取/解碼單元和其他一切,只要它們以8或16像素為單位工作)。 因此,對條件的初始禁止以及在此之後的一段時間內,如果對將要一起處理的像素的條件評估返回不同的值,則這些像素將以某種佈置在較小的組中處理。

儘管PowerVR並不明確,但是他們的應用程序開發建議中有一節關於流量控制,並且只有在結果可以合理預測的情況下才能提出很多關於動態分支的建議通常是一個好主意,這讓我覺得它們是相同的那類的東西。 因此,我建議速度差異可能是因為你已經包含了條件。

作為第一個測試,如果您嘗試以下操作會發生什麼?

void main()
{
    float distanceFromCenter = length(impostorSpaceCoordinate);

    // the step function doesn't count as a conditional
    float inCircleMultiplier = step(distanceFromCenter, 1.0);

    float calculatedDepth = sqrt(1.0 - distanceFromCenter * distanceFromCenter * inCircleMultiplier);
    mediump float currentDepthValue = normalizedDepth - adjustedSphereRadius * calculatedDepth;

    // Inlined color encoding for the depth values
    float ceiledValue = ceil(currentDepthValue * 765.0) * inCircleMultiplier;

    vec3 intDepthValue = (vec3(ceiledValue) * scaleDownFactor) - (stepValues * inCircleMultiplier);

     // use the result of the step to combine results
    gl_FragColor = vec4(1.0 - inCircleMultiplier) + vec4(intDepthValue, inCircleMultiplier);

}

我有一個開源iOS應用程序,它使用自定義OpenGL ES 2.0著色器來顯示分子結構的三維表示。 它通過使用在矩形上繪製的程序生成的球體和圓柱體冒充者來實現這一點,而不是使用大量頂點構建的這些相同形狀。 這種方法的缺點是這些冒名頂替對象的每個片段的深度值需要在片段著色器中計算,以便在對象重疊時使用。

不幸的是,OpenGL ES 2.0 不允許你寫入gl_FragDepth ,所以我需要將這些值輸出到自定義深度紋理。 我使用幀緩衝對象(FBO)對場景進行傳遞,僅渲染出與深度值對應的顏色,並將結果存儲到紋理中。 然後將此紋理加載到渲染過程的後半部分,在該過程中生成實際的屏幕圖像。 如果該階段的片段處於存儲在屏幕上該點的深度紋理中的深度級別,則顯示該片段。 如果沒有,它就會被拋出。 有關該過程的更多信息,包括圖表,可以在我的帖子中here

這種深度紋理的生成是我渲染過程中的一個瓶頸,我正在尋找一種方法來加快它的速度。 它似乎比它應該慢,但我無法弄清楚為什麼。 為了正確生成此深度紋理,禁用GL_DEPTH_TEST ,使用glBlendFunc(GL_ONE, GL_ONE)啟用glBlendEquation() ,並將glBlendEquation()設置為GL_MIN_EXT 。 我知道以這種方式輸出的場景在基於圖塊的延遲渲染器上並不是最快的,比如iOS設備中的PowerVR系列,但我想不出更好的方法。

我對球體的深度片段著色器(最常見的顯示元素)看起來是這個瓶頸的核心(儀器中的渲染器利用率固定在99%,表明我受片段處理的限制)。 目前看起來如下:

precision mediump float;

varying mediump vec2 impostorSpaceCoordinate;
varying mediump float normalizedDepth;
varying mediump float adjustedSphereRadius;

const vec3 stepValues = vec3(2.0, 1.0, 0.0);
const float scaleDownFactor = 1.0 / 255.0;

void main()
{
    float distanceFromCenter = length(impostorSpaceCoordinate);
    if (distanceFromCenter > 1.0)
    {
        gl_FragColor = vec4(1.0);
    }
    else
    {
        float calculatedDepth = sqrt(1.0 - distanceFromCenter * distanceFromCenter);
        mediump float currentDepthValue = normalizedDepth - adjustedSphereRadius * calculatedDepth;

        // Inlined color encoding for the depth values
        float ceiledValue = ceil(currentDepthValue * 765.0);

        vec3 intDepthValue = (vec3(ceiledValue) * scaleDownFactor) - stepValues;

        gl_FragColor = vec4(intDepthValue, 1.0);
    }
}

在iPad 1上,這需要35 - 68 ms來渲染DNA空間填充模型的幀,使用直通著色器進行顯示(iPhone 4上為18至35毫秒)。 根據PowerVR PVRUniSCo編譯器( 其SDK的一部分),該著色器最多使用11個GPU週期,最差時使用16個週期。 我知道你被建議不要在著色器中使用分支,但在這種情況下,導致比其他方式更好的性能。

當我簡化它

precision mediump float;

varying mediump vec2 impostorSpaceCoordinate;
varying mediump float normalizedDepth;
varying mediump float adjustedSphereRadius;

void main()
{
    gl_FragColor = vec4(adjustedSphereRadius * normalizedDepth * (impostorSpaceCoordinate + 1.0) / 2.0, normalizedDepth, 1.0);
}

在iPad 1上需要18-35毫秒,但在iPhone 4上只需1.7到2.4毫秒。此著色器的估計GPU週期數為8個週期。 基於週期計數的渲染時間的變化似乎不是線性的。

最後,如果我只輸出一個恆定的顏色:

precision mediump float;

void main()
{
    gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);
}

iPad 1上的渲染時間下降到1.1 - 2.3 ms(iPhone 4上為1.3 ms)。

渲染時間的非線性縮放以及第二個著色器的iPad和iPhone 4之間的突然變化讓我覺得我在這裡缺少一些東西。 如果您希望自己嘗試here ,可以從here下載包含這三個著色器變體的完整源項目(查看SphereDepth.fsh文件並註釋掉相應的部分)和測試模型。

如果您已經閱讀過這篇文章,我的問題是:基於此分析信息,如何在iOS設備上提高自定義深度著色器的渲染性能?




發布答案的其他人已經涵蓋了其中許多要點,但這裡的首要主題是你的渲染做了大量的工作,將被丟棄:

  1. 著色器本身可以完成一些潛在的冗餘工作。 矢量的長度很可能被計算為sqrt(dot(vector, vector)) 。 你不需要sqrt來拒絕圓圈之外的碎片,無論如何你都要平方來計算深度。 另外,你看過是否真的需要對深度值進行顯式量化,或者你是否可以使用硬件從幀浮點到浮點到整數的轉換(可能會有額外的偏差來確保你的準-depth測試後來出來)?

  2. 許多碎片都在圓圈之外。 只有你正在繪製的四邊形區域的π/ 4產生有用的深度值。 此時,我想你的應用程序嚴重偏向於片段處理,因此您可能需要考慮增加繪製的頂點數量,以換取減少必須遮蔽的區域。 由於您通過正交投影繪製球體,所以任何外接規則多邊形都可以,但根據縮放級別可能需要一些額外的大小,以確保光柵化足夠的像素。

  3. 許多片段被其他片段輕易地阻塞。 正如其他人所指出的那樣,你沒有使用硬件深度測試,因此沒有充分利用TBDR早期殺死陰影工作的能力。 如果你已經為2)實現了一些東西,你需要做的就是在你可以生成的最大深度處繪製一個內切的正多邊形(穿過球體中間的平面),並以最小深度繪製真實多邊形(球體的前部)。 Tommy和rotoglup的帖子都包含狀態向量細節。

請注意,2)和3)也適用於光線跟踪著色器。




根據Tommy,Pivot和rotoglup的建議,我實現了一些優化,這些優化導致應用程序中深度紋理生成和整個渲染管道的渲染速度加倍。

首先,我重新啟用了之前使用過的預先計算的球體深度和光照紋理效果很小,現在我只在使用該紋理處理顏色和其他值時使用了正確的lowp精度值。 這種組合,以及紋理的適當mipmapping,似乎可以提高~10%的性能。

更重要的是,我現在在渲染我的深度紋理和最終光線追踪冒名頂替者之前進行了一次傳遞,其中我放置了一些不透明的幾何體來阻擋永遠不會被渲染的像素。 為此,我啟用了深度測試,然後繪製了構成場景中對象的方塊,由sqrt(2)/ 2縮小,使用簡單的不透明著色器。 這將創建插入方塊覆蓋已知在所表示的球體中不透明的區域。

然後,我使用glDepthMask(GL_FALSE)禁用深度寫入,並將方形球體冒充者渲染到靠近用戶一個半徑的位置。 這允許iOS設備中基於圖塊的延遲渲染硬件有效地剝離在任何條件下永遠不會出現在屏幕上的片段,但仍然基於每像素深度值在可見球體冒名頂替者之間提供平滑交叉。 這在我的粗略插圖中描述:

在這個例子中,前兩個冒名頂替者的不透明阻塞方塊不會阻止來自那些可見對象的任何碎片被渲染,但是它們會阻擋來自最低冒名頂替者的一大塊碎片。 然後,最前面的冒名頂替者可以使用每像素測試來生成平滑的交叉點,而來自後冒名頂替者的許多像素不會通過渲染來浪費GPU週期。

我沒有想過禁用深度寫入,但在進行最後一個渲染階段時會留下深度測試。 這是防止冒名頂替者相互堆疊,但仍然使用PowerVR GPU內部的一些硬件優化的關鍵。

在我的基準測試中,渲染我上面使用的測試模型每幀產生18-35毫秒的時間,相比之前我所獲得的35-68毫秒,渲染速度幾乎翻倍。 將相同的不透明幾何體預渲染應用於光線追踪過程會使整體渲染性能提高一倍。

奇怪的是,當我試圖通過使用插入和限制的八邊形來進一步改進這一點時,它應該在繪製時覆蓋約17%的像素,並且在阻擋碎片時效率更高,性能實際上比使用簡單方塊更差。 在最壞的情況下,Tiler利用率仍然低於60%,因此較大的幾何結構可能導致更多的緩存未命中。

編輯(2011年5月31日):

根據Pivot的建議,我創建了用於代替我的矩形的內切和外接八邊形,只有我遵循here的建議here優化光柵化的三角形。 在之前的測試中,儘管刪除了許多不必要的碎片並且讓您更有效地阻擋被覆蓋的碎片,但八邊形的性能比方塊更差。 通過調整三角圖如下:

通過從正方形切換到八邊形,我能夠在上述優化的基礎上將整體渲染時間平均減少14%。 深度紋理現在在19毫秒內生成,偶爾下降到2毫秒,峰值到35毫秒。

編輯2(5/31/2011):

我重新考慮了Tommy使用step函數的想法,現在由於八邊形,我有更少的片段要丟棄。 這與球體的深度查找紋理相結合,現在導致iPad 1平均渲染時間為2毫秒,用於測試模型的深度紋理生成。 在這個渲染案例中,我認為這是我希望的那麼好,以及我從哪裡開始的巨大改進。 對於後代,這是我現在使用的深度著色器:

precision mediump float;

varying mediump vec2 impostorSpaceCoordinate;
varying mediump float normalizedDepth;
varying mediump float adjustedSphereRadius;
varying mediump vec2 depthLookupCoordinate;

uniform lowp sampler2D sphereDepthMap;

const lowp vec3 stepValues = vec3(2.0, 1.0, 0.0);

void main()
{
    lowp vec2 precalculatedDepthAndAlpha = texture2D(sphereDepthMap, depthLookupCoordinate).ra;

    float inCircleMultiplier = step(0.5, precalculatedDepthAndAlpha.g);

    float currentDepthValue = normalizedDepth + adjustedSphereRadius - adjustedSphereRadius * precalculatedDepthAndAlpha.r;

    // Inlined color encoding for the depth values
    currentDepthValue = currentDepthValue * 3.0;

    lowp vec3 intDepthValue = vec3(currentDepthValue) - stepValues;

    gl_FragColor = vec4(1.0 - inCircleMultiplier) + vec4(intDepthValue, inCircleMultiplier);
}

我已經在here更新了測試樣本,如果你希望看到這個新的方法與我最初的做法相比。

我仍然願意接受其他建議,但這對於這個應用來說是一個巨大的進步。




我根本不是移動平台專家,但我認為咬你的是:

  • 你的深度著色器非常昂貴
  • 在禁用GL_DEPTH測試時,會在深度傳遞中體驗大量透支

在深度測試之前繪製的額外通行證是否有用?

此過程可以執行GL_DEPTH填充,例如通過繪製表示為四面相機(或多維數據集,可能更容易設置)的每個球體,並包含在關聯的球體中。 可以在沒有顏色掩碼或片段著色器的情況下繪製此過程,只需啟用GL_DEPTH_TESTglDepthMask 。 在桌面平台上,這些類型的傳遞比顏色+深度傳遞更快。

然後在深度計算過程中,您可以啟用GL_DEPTH_TEST並禁用glDepthMask ,這樣您的著色器就不會在由更近的幾何體隱藏的像素上執行。

該解決方案將涉及發出另一組繪製調用,因此這可能沒有益處。




好的我有部分修復。 我發現以下代碼繪製了一個圓圈段。 它基本上是一個UIView子類,每次進行更新時都會觸發drawRect。 我現在唯一的問題是如何更改它,以便不是繪製截面或切片而只繪製圓形筆劃?

-(void)drawRect:(CGRect)rect 
{
    // Drawing code

    CGRect allRect = self.bounds;
    CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f);

    CGContextRef context = UIGraphicsGetCurrentContext();

    // Draw background
    CGContextSetRGBStrokeColor(context, self.strokeValueRed, self.strokeValueGreen, self.strokeValueBlue, self.strokeValueAlpha); // white
    CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 0.1f); // translucent white
    CGContextSetLineWidth(context, self.lineWidth);
    CGContextFillEllipseInRect(context, circleRect);
    CGContextStrokeEllipseInRect(context, circleRect);

    // Draw progress
    CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
    CGFloat radius = (allRect.size.width - 4) / 2;
    CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
    CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
    CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white
    CGContextMoveToPoint(context, center.x, center.y);
    CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
    CGContextClosePath(context);
    CGContextFillPath(context);
}




iphone ios ipad opengl-es glsl