作者:omar-a-rodrigue
下載
藉助 OpenGL* ES 2.0 實現動態解析度渲染 [PDF 677KB]
代碼樣本: dynamic-resolution.zip [ZIP 4MB]
像素處理成本昂貴
當在遊戲和顯卡工作負載上執行效能分析時,似乎處理片段或(像素)著色器是主要的效能瓶頸。 當然這也在情理之中,因為照明計算、紋理採樣和後期處理效果等計算均在片段著色器中執行。 計算螢幕上每個像素的最終顏色需要大量處理能力和時間,且可能會非常昂貴。 此外,每次新發布的移動平台均採用更高的解析度,從而提高了總體成本。 支援高解析度需要增加片段著色器的調用次數。 但是,高解析度並非移動開發人員的唯一問題。 如何確定解析度不同的裝置也是一個問題。 在寫這篇文章時,我對市場上的一些裝置做的快速調查顯示解析度的差異非常大 — 即使是運行相同作業系統的裝置。
- Apple iPhone* 5: 1136 x 640,PowerVR* SGX543MP3
- Apple iPhone 4S: 960 x 640,PowerVR SGX543MP2
- Nexus* 4,1280 x 768, Adreno* 320
- Galaxy Nexus, 1280 x 720, PowerVR SGX540
- Motorola RAZR* i, 960 x 540, PowerVR SGX540
- Samsung Galaxy SIII, 1280 x 720, Adreno 225
不僅要清楚地找到不斷提高的解析度還要找到不同的解析度是遊戲開發人員正在面臨或即將面臨的問題。 另一個問題是,即使顯卡硬體不斷改進,它也會不可避免地因為處理更多像素而被消耗掉。
渲染遊戲畫面的幾種方法
有許多合理且業經驗證的方式來處理遊戲中的各種解析度。 最簡單的方式是將情境繪製為原生解析度。 在某些類型的遊戲中,您可能會一直需要用到這種方法。 或者片段著色器可能能力不夠強大而成為效能的瓶頸。 如果您遇到這種情況,多半會受到局限,但是仍然需要確保遊戲開發資源(art asset)能夠在各種重要解析度中使用。
第二種方法是確定固定解析度而非原生解析度。 這種方法支援您將遊戲開發資源(art asset)和著色器調整為固定解析度。 但是,它可能無法為使用者提供最佳的遊戲體驗。
另一種常見的方法是讓使用者在開始時設定所需的解析度。 這種方法可以使用玩家選擇的解析度產生後台緩衝,並能有效解決解析度各異的問題。 它支援玩家選擇其裝置上最適合的解析度。 此外,您還需要確認遊戲開發資源(art asset)是否能夠在使用者可選的解析度中使用。
本文中介紹的第三種方法名為動態解析度渲染。 這是單機遊戲和進階電腦遊戲上的一種常用技術。 本文中介紹的實施情況是從 [Binks 2011] 中的 DirectX* 版本中擷取,並用於配合 OpenGL* ES 2.0 使用。 藉助動態解析度渲染,後台緩衝是原生解析度的尺寸,但是情境使用了固定解析度繪製為螢幕外紋理。 1 所示,畫面被繪製為螢幕外紋理的一部分,然後被採樣填充後台緩衝。 UI 元素在原生解析度下繪製。
圖 1. 動態解析度渲染
繪製為螢幕外紋理
第一步是建立螢幕外紋理。 在 OpenGL ES 2.0 中,使用所需的紋理尺寸建立 GL_FRAMEBUFFER。 下列是完成此操作的代碼:
01 |
glGenFramebuffers(1, &(_render_target->frame_buffer)); |
02 |
glGenTextures(1, &(_render_target->texture)); |
03 |
glGenRenderbuffers(1, &(_render_target->depth_buffer)); |
05 |
_render_target->width = width; |
06 |
_render_target->height = height; |
08 |
glBindTexture(GL_TEXTURE_2D, _render_target->texture); |
09 |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
10 |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
11 |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
12 |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
13 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _render_target->width, _render_target->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); |
15 |
glBindRenderbuffer(GL_RENDERBUFFER, _render_target->depth_buffer); |
16 |
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _render_target->width, _render_target->height); |
18 |
glBindFramebuffer(GL_FRAMEBUFFER, _render_target->frame_buffer); |
19 |
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _render_target->texture, 0); |
20 |
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _render_target->depth_buffer); |
調用 glTexImage2D 可建立我們將要渲染的紋理,調用 glFramebufferTexture2D 可將著色的紋理綁定至幀緩衝。 然後,在幀緩衝綁定後按照下列方式渲染情境:
01 |
// 1. SAVE OUT THE DEFAULT FRAME BUFFER |
02 |
static GLint default_frame_buffer = 0; |
03 |
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &default_frame_buffer); |
05 |
// 2. RENDER TO OFFSCREEN RENDER TARGET |
06 |
glBindFramebuffer(GL_FRAMEBUFFER, render_target->frame_buffer); |
07 |
glViewport(0, 0, render_target->width * resolution_factor, render_target->height * resolution_factor); |
08 |
glClearColor(0.25f, 0.25f, 0.25f, 1.0f); |
09 |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
10 |
/// DRAW THE SCENE /// |
11 |
// 3. RESTORE DEFAULT FRAME BUFFER |
12 |
glBindFramebuffer(GL_FRAMEBUFFER, default_frame_buffer); |
13 |
glBindTexture(GL_TEXTURE_2D, 0); |
15 |
// 4. RENDER FULLSCREEN QUAD |
16 |
glViewport(0, 0, screen_width, screen_height); |
17 |
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); |
18 |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
渲染完情境後,預設的幀緩衝(後台緩衝)將會被再次綁定。 此時,情境綁定至螢幕外著色紋理。 下一步是渲染從螢幕外紋理採集的全屏四邊形(full-screen quad)。 下列代碼介紹了如何完成該操作:
01 |
glUseProgram(fs_quad_shader_program); |
02 |
glEnableVertexAttribArray( fs_quad_position_attrib ); |
03 |
glEnableVertexAttribArray( fs_quad_texture_attrib ); |
05 |
glBindBuffer(GL_ARRAY_BUFFER, fs_quad_model->positions_buffer); |
06 |
glVertexAttribPointer(fs_quad_position_attrib, 3, GL_FLOAT, GL_FALSE, 0, 0); |
08 |
glBindBuffer(GL_ARRAY_BUFFER, fs_quad_model->texcoords_buffer); |
10 |
const float2 texcoords_array[] = |
12 |
{ resolution_factor, resolution_factor }, |
13 |
{ 0.0f, resolution_factor }, |
15 |
{ resolution_factor, 0.0f }, |
18 |
glBufferData(GL_ARRAY_BUFFER, sizeof(float3) * fs_quad_model->num_vertices, texcoords_array, GL_STATIC_DRAW); |
19 |
glVertexAttribPointer(fs_quad_texture_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0); |
20 |
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, fs_quad_model->index_buffer ); |
21 |
glActiveTexture(GL_TEXTURE0); |
22 |
glBindTexture(GL_TEXTURE_2D, render_target->texture); |
23 |
glUniform1i(fs_quad_texture_location, 0); |
24 |
glDrawElements(GL_TRIANGLES, fs_quad_model->num_indices, GL_UNSIGNED_INT, 0); |
解析度因素
上述展示的大部分程式碼片段是 OpenGL 設定代碼。 比較重要的是使用變數 resolution_factor 的內容。 resolution_factor 的值決定了用於繪製和採樣的螢幕外紋理的寬、高比例。 設定用於繪製的螢幕外紋理非常簡單,可以通過調用 glViewpor 完成。
01 |
// 1. SAVE OUT THE DEFAULT FRAME BUFFER |
03 |
// 2. RENDER TO OFFSCREEN RENDER TARGET |
04 |
glBindFramebuffer(GL_FRAMEBUFFER, render_target->frame_buffer); |
05 |
glViewport(0, 0, render_target->width * resolution_factor, render_target->height * resolution_factor); |
07 |
/// DRAW THE SCENE /// |
09 |
// 3. RESTORE DEFAULT FRAME BUFFER |
11 |
// 4. RENDER FULLSCREEN QUAD |
12 |
glViewport(0, 0, screen_width, screen_height); |
綁定幀緩衝後,調用 glViewport 可根據寬和高設定要繪製的地區。 然後,這將重設為原生解析度來繪製全屏四邊形(full-screen quad)和使用者介面。 如只採集更新的螢幕外紋理,請為全屏四邊形(full-screen quad)的頂點設定紋理座標。 下列代碼可完成該操作:
01 |
glBindBuffer(GL_ARRAY_BUFFER, fs_quad_model->texcoords_buffer); |
03 |
const float2 texcoords_array[] = |
05 |
{ resolution_factor, resolution_factor }, |
06 |
{ 0.0f, resolution_factor }, |
08 |
{ resolution_factor, 0.0f }, |
11 |
glBufferData(GL_ARRAY_BUFFER, sizeof(float3) * fs_quad_model->num_vertices, texcoords_array, GL_STATIC_DRAW); |
使用動態解析度的優勢
設定完成後,情境將會儲存為螢幕外紋理並渲染至全屏四邊形(full-screen quad)的螢幕。 渲染情境的實際解析度將不再綁定在原生解析度。 針對該情境處理的像素數量可以進行動態更改。 根據遊戲的類型和風格,可在不退化映像的前提下大量降低解析度。 下列是幾種不同解析度的樣本樣本:
在這種情況下,可先將解析度降低至 75% 和 50% 之間,這一解析度不會導致映像品質明顯惡化。 將要顯示的主要圖形邊緣混疊。 對於這種情況,使用 75% 的原生解析度也可行,但是具體取決於您的遊戲,您也可以為了達到有趣的藝術風格而採用 25% 的解析度。
很顯然,動態解析度渲染提供了一種明確的方式來降低要處理的像素數量。 而且,它還支援在每個像素上處理更多任務。 因為您不再以全解析度進行渲染,片段著色器調用已降低,以便能夠在每次執行時處理更多任務。 如要確保樣本代碼清晰、可讀,片段著色器是合適的選擇,因為它非常簡單。 作為開發人員,實現效能和映像品質的平衡是最具挑戰性的任務之一。
我們的實施
圖 2. 實施詳圖
可下載的實施僅包括 Android* 項目,且僅以圖 2 中呈現的格式布局以便能夠輕鬆擴充至其他移動作業系統。 項目核心以 C 語言編寫,以 OpenGL ES 2.0 為目標且需要 Android NDK。 值得注意的是, C 非常適合跨平台開發。 系統抽象是指檔案 I/O 和其他不受 OS 影響的功能。
結論
動態解析度渲染非常適合解決多種與行動裝置螢幕解析度有關的問題。 藉助它,開發人員和使用者能夠更好地控制效能和映像品質之間的比率。 調整該比率還應考慮到實施動態解析度渲染的費用。 建立渲染目標和更改每幀的渲染目標將會增加每幀的處理時間。 瞭解並計算該費用可以協助您確定這一技術是否適合您的遊戲。
參考
[Binks 2011] Binks, Doug. “動態解析度渲染文章”。 http://software.intel.com/en-us/articles/dynamic-resolution-rendering-article
英特爾公司 2013 年著作權。 所有權利受到保護。
*其他的名稱和品牌可能是其他所有者的資產。
OpenGL 是註冊商標,OpenGL ES 標識是 Khronos 授權 Silicon Graphics Inc. 使用的商標。