a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 系統 > Android > 正文

通過OpenGL ES混合模式縮放視頻緩沖區來適應顯示尺寸

2020-04-11 12:37:36
字體:
來源:轉載
供稿:網友
當開發基于軟件模式的游戲時,通過縮放視頻緩沖區來適應顯示尺寸是最棘手的問題之一。當面對眾多不同的分辨率時(比如開放環境下的Android),該問題會變得更加麻煩,作為開發人員,我們必須嘗試在性能與顯示質量之間找到最佳平衡點。正如我們在第2章中看到的,縮放視頻緩沖區從最慢到最快共有3種類型。

軟件模擬:3中類型中最慢,但最容易實現,是沒有GPU的老款設備上的最佳選擇。但是現在大部分智能手機都支持硬件加速。
混合模式:這種方式混合使用軟件模擬(創建圖像緩沖區)和硬件渲染(向顯示屏繪制)兩種模式。這種方法速度很快,而且可以在分辨率大于256×256的任意屏幕上渲染圖像。
硬件加速模式:3種類型中最快,但最難實現。這取決于游戲的復雜程度,需要更加強勁的GPU。如果有好的硬件,這種方法就可以創造出令人震撼的質量和效果。但在終端設備比較分裂的平臺上,比如Android,這將是十分艱難的選擇。

這里,我們選擇第二種方式,也是在終端設備分裂的平臺上的最佳選擇。你擁有軟件渲染器,并希望將游戲適配到任意分辨率的顯示屏上。此方法非常適合模擬器游戲、街機游戲、簡單的射擊游戲等。它在各種低端、中端、高端設備上都表現很好。

下面我們開始介紹混合模式并探討為什么這種方法更加可行。然后,將深入研究這種方法的實現,包括如何初始化surface并通過實際縮放來繪制到紋理。
1. 為什么使用混合縮放
這種縮放技術背后的原理很簡單:
你的游戲根據給定的尺寸創建圖像緩沖區(通常采用像素格式RGB565,即移動設備最常用的格式)。例如320×240,這是典型的模擬器尺寸。
當一張分辨率為320×240的圖像需要被縮放至平板電腦的尺寸(1024×768)或其他任意相同屏幕的設備時,我們可以使用軟件模擬的方式來完成縮放,但會慢的令人無法忍受。而采用混合模式進行縮放,需要創建OpenGL ES紋理并將圖片(320×240)渲染到GL四邊形上。
紋理會通過硬件被縮放到適合顯示屏的尺寸(1024×768),從而你的游戲性能將得到顯著提升。
從實現的角度看,這個過程可描述如下:
初始化OpenGL ES紋理:在游戲視頻被初始化的階段,必須創建硬件surface。其中包含簡單的紋理,要顯示的視頻圖像會被渲染至到該紋理(詳見代碼清單1與代碼清單2)。
將圖像緩沖區繪制到紋理:在游戲循環的末端,渲染要顯示的視頻圖像到紋理,該紋理會自動縮放至適合顯示屏的尺寸(詳見代碼清單3)。
代碼清單1 創建RGB656格式的空紋理
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">// 紋理ID
static unsigned int mTextureID;
// 被用來計算圖片繪制在紋理上的X、Y偏移量
static int xoffset;
static int yoffset;
/**
* 創建RGB565格式的空紋理
* 參數: (w,h) 紋理的寬, 高
* (x_offsety_offset): 圖片繪制在紋理上的X、Y偏移量
*/
static void CreateEmptyTextureRGB565 (int w, int h, int x_offset, int y_offset)
{
int size = w * h * 2;
xoffset = x_offset;
yoffset = y_offset;
// 緩沖區
unsigned short * pixels = (unsigned short *)malloc(size);
memset(pixels, 0, size);
// 初始化GL狀態
glDisable(GL_DITHER);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glClearColor(.5f, .5f, .5f, 1);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
// 創建紋理
glGenTextures(1, &mTextureID);
glBindTexture(GL_TEXTURE_2D, mTextureID);
// 紋理參數
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// RGB565格式的紋理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_
SHORT_5_6_5 , pixels);
free (pixels);
} </SPAN>

代碼清單2展示了CreateEmptyTextureRGB565的實現過程,創建RGB656格式的空紋理用于繪制,參數如下:
w和h:要顯示的視頻圖片的尺寸。
x_offset和y_offset:坐標系中X軸、Y軸的偏移量,視頻圖片將會按照這個坐標被渲染到紋理。但是為什么我們需要這些參數?請繼續閱讀。
在OpenGL中創建紋理,我們只需要調用:
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">glGenTextures(1, &mTextureID);
glBindTexture(GL_TEXTURE_2D, mTextureID);</SPAN>

這里的mTextureID是整型變量,用于存儲紋理的ID。然后需要設置下面這些紋理參數:
GL_TEXTURE_MIN_FILTER:指定紋理縮小的方式,當像素被紋理化后并映射到某個大于單個紋理元素的區域時使用的縮小方式為GL_NEAREST,返回距離像素被紋理化后的中心最近(曼哈頓距離)的紋理元素的值。
GL_TEXTURE_MAG_FILTER:指定紋理放大的方式,當像素被紋理化后并映射到某個小于或等于單個紋理元素的區域時使用的放大方式為GL_LINEAR,返回4個距離像素被紋理化后的中心最近的紋理元素的加權平均值。
GL_TEXTURE_WRAP_S:用于設置紋理坐標系中S軸方向上的紋理映射方式為GL_CLAMP,將紋理坐標限制在(0,1)范圍內,當映射單張圖像到對象時,可以有效防止畫面重疊。
GL_TEXTURE_WRAP_T:用于設置紋理坐標系中T軸方向上的紋理映射的方式為GL_CLAMP。
最后,我們通過glTexImage2D函數及以下參數來指定二維紋理:
GL_TEXTURE_2D:指定目標紋理的類型為二維紋理。
Level:指定圖像紋理的詳細程度。0是最基本的圖像紋理層。
Internal format:指定紋理的顏色成分,在這個例子中是RGB格式。
Width and height:紋理的尺寸,必須是2的冪。
Format:指定像素數據的格式,同時也必須與內部格式相同。
Type:指定像素數據的數據類型,在本例中使用RGB565(16位)格式。
Pixels:指向內存中圖像數據的指針,必須使用RGR656編碼。
注意:紋理的尺寸必須是2的冪,如256、512、1024等。但是,要顯示的視頻圖像的尺寸可以是任意尺寸。這就意味著,紋理的尺寸必須是大于或等于要顯示的視頻圖像尺寸的2的冪。稍后我們將進行詳細介紹。
現在,讓我們來看一看混合視頻縮放的實際實現,接下來的兩個小節將介紹如何初始化用來縮放的surface以及如何實現實際的繪制。
2. 初始化surface
要進行縮放,就必須保證紋理的尺寸大于或等于要顯示的視頻圖像的尺寸。否則,當圖像渲染的時候,會看到白色或黑色的屏幕。在代碼清單2中,JNI_RGB565_SurfaceInit函數將確保產生有效的紋理尺寸。使用圖像的寬度和高度為參數,然后調用getBestTexSize函數來獲取最接近要求的紋理尺寸,最后通過調用CreateEmptyTextureRGB565函數來創建空的紋理。注意,如果圖像尺寸小于紋理尺寸,就通過計算X、Y坐標的偏移量來將其置于屏幕的中心。
代碼清單2 初始化surface
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">// 獲取下一個POT紋理尺寸,該尺寸大于或等于圖像尺寸(WH)
static void getBestTexSize(int w, int h, int *tw, int *th)
{
int width = 256, height = 256;
#define MAX_WIDTH 1024
#define MAX_HEIGHT 1024
while ( width < w && width < MAX_WIDTH) { width *= 2; }
while ( height < h && height < MAX_HEIGHT) { height *= 2; }
*tw = width;
*th = height;
}
/**
* 初始化RGB565 surface
* 參數: (w,h) 圖像的寬高
*/
void JNI_RGB565_SurfaceInit(int w, int h)
{
//最小紋理的寬高
int texw = 256;
int texh = 256;
// 得到紋理尺寸 (必須是POT) >= WxH
getBestTexSize(w, h, &texw, &texh);
// 圖片在屏幕中心?
int offx = texw > w ? (texw - w)/2 : 0;
int offy = texh > h ? (texh - h)/2 : 0;
if ( w > texw || h > texh)
printf ("Error: Invalid surface size %sx%d", w, h);
// 創建OpenGL紋理,用于渲染
CreateEmptyTextureRGB565 (texw, texh, offx, offy);
}
</SPAN>

3. 繪制到紋理
最后,為了將圖像顯示到屏幕上(也稱作surface翻轉),我們調用JNI_RGB565_Flip函數,其參數是像素數組(使用RGR656編碼)和要顯示的圖像尺寸。JNI_RGB565_Flip函數通過調用DrawIntoTextureRGB565將圖像繪制到紋理并交換緩沖區。注意交換緩沖區的函數是用Java編碼的,而不是用C語言編碼的,因此我們需要一個方法來調用Java的交換函數。我們可以通過使用JNI方法調用某個Java方法來完成緩沖區的交換工作(見代碼清單3)。
代碼清單3 用四邊形將圖像緩沖區繪制到紋理
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">// 四邊形頂點的X、Y和Z坐標
static const float vertices[] = {
-1.0f, -1.0f, 0,
1.0f, -1.0f, 0,
1.0f, 1.0f, 0,
-1.0f, 1.0f, 0
};
// 四邊形坐標(0-1)
static const float coords[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};
// 四邊形頂點索引
static const unsigned short indices[] = { 0, 1, 2, 3};
/**
* 使用四邊形像素(RGB565的unsigned short)將像素數組繪制到全部屏幕
*
*/
static void DrawIntoTextureRGB565 (unsigned short * pixels, int w, int h)
{
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 啟用頂點和紋理坐標
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTextureID);
glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, w, h, GL_RGB,
GL_UNSIGNED_SHORT_5_6_5 , pixels);
// 繪制四邊形
glFrontFace(GL_CCW);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glEnable(GL_TEXTURE_2D);
glTexCoordPointer(2, GL_FLOAT, 0, coords);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, indices);
}
// 翻轉surface (繪制到紋理中)
void JNI_RGB565_Flip(unsigned short *pixels , int width, int height)
{
if ( ! pixels) {
return;
}
DrawIntoTextureRGB565 (pixels, width, height);
// 在這里必須交換GLES緩沖區
jni_swap_buffers ();
}
</SPAN>

使用OpenGL渲染到紋理
(1) 使用glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT)清除顏色與深度緩沖區。
(2) 啟用客戶端狀態:當glDrawElements函數被調用時,寫入頂點數組與紋理坐標數組。
(3) 通過glActiveTexture函數選擇要激活的紋理單元,初始值是GL_TEXTURE0。
(4) 將已經生成的紋理綁定到等待被紋理化的目標。GL_TEXTURE_2D (一個二維紋理)是默認的紋理綁定目標,mTextureID是紋理的ID。
(5) 通過glTexSubImage2D函數來指定二維紋理子圖,參數如下:
GL_TEXTURE_2D:指定目標紋理類型。
level:指定圖像的詳細程度(即層數)。0是基本的圖像紋理層。
Xoffset:指定紋理像素在X軸方向上、紋理數組內的偏移量。
Yoffset:指定紋理像素在Y軸方向上、紋理數組內的偏移量。
width:指定紋理子圖的寬度。
height:指定紋理子圖的高度
format:指定像素數據的格式。
Type:指定像素數據的數據類型。
data:指定指向內存中圖像數據的指針。
(6) 通過調用以下函數繪制四邊形頂點、坐標與索引:
glFrontFace:啟用四邊形的正面。
glVertexPointer:定義四邊形的頂點數據數組,頂點數據大小為3,數據類型是GL_FLOAT,數組中每個頂點間的間隔(步長)為0。
glTexCoordPointer:定義四邊形的紋理數組,紋理坐標大小為2,數據類型是GL_FLOAT,間隔為0。
glDrawElements:通過數據數組以三角形扇(GL_TRIANGLE_FAN)的方式渲染多邊形,有4個頂點,類型為短整型(GL_UNSIGNED_SHORT),外加指向索引的指針。
注意,從代碼清單3中我們可以看到四邊形的兩個軸坐標都在[−1,1]區間內。這是因為OpenGL的坐標系統在(−1,1)之間,原點(0,0)在窗口中心(如圖3-10所示)。
 
在理想的世界里,我們不應該過多地擔心視頻緩沖區的尺寸(尤其是使用軟件模擬僅有的定標器/渲染器)。當在Android中使用OpenGL縮放視頻時,這卻是事實。在這個示例中,緩沖區的尺寸至關重要。接下來你將學習如何處理任意尺寸的視頻,這一點在OpenGL中工作得不是很好。
4. 當圖像的尺寸不是2的冪時會發生什么
如前所述,當圖像的尺寸是2的冪時混合縮放會非常完美。但是,也有可能圖像緩沖區不是2的冪。例如,在處理Demo引擎的章節中有一段320×240尺寸的視頻。在這種情況下,圖像仍然被縮放,但是會縮放到紋理尺寸的百分比大小。在圖2和3中可以看到這個效果。
 
在圖2中,有以下尺寸:
設備顯示器:859×480
紋理:512×256
圖像:320×240
正如我們看到的一樣,圖像被縮放到紋理寬度的62%(320/512*100)和高度的93%
(240/256*100)。因此,在任何分辨率大于256的設備上,圖像都會被縮放到設備提供分辨率的62%×93%。現在我們來看看圖3。
 
圖3 縮放尺寸為2的冪的圖像
在圖3中,有以下尺寸:
設備顯示器:859×480
紋理:512×256
圖像:512×256
縮放和繪制
在圖3中,我們看見圖像被縮放到設備提供分辨率的100%,這正是我們想要的。但是如果圖像不是2的冪,那么我們要如何做呢?為了解決這個問題,我們應該:
(1) 用軟件縮放器將320×240尺寸的圖像縮放到接近2的冪(這里是512×256)。
(2) 將已縮放的surface轉換成RGB656格式的圖像,以兼容前面介紹的DrawInto-TextureRGB565。
(3) 繪制到紋理,從而使用硬件將其縮放到顯示屏的分辨率。
這種解決辦法可能比前面介紹的方法慢,但仍然比純軟件縮放快,尤其是運行在高分辨率設備時更明顯(如平板電腦)。
代碼清單4展示了如何使用流行的SDL_gfx庫來縮放SDL surface。
代碼清單4 用SDL_gfx庫縮放圖像
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">void JNI_Flip(SDL_Surface *surface )
{
if ( zoom ) {
// 如果surface是8位縮放,就是8位,否則surface就是32的RGBA!
SDL_Surface * sized = zoomSurface( surface, zoomx, zoomy, SMOOTHING_OFF);
JNI_FlipByBPP (sized);
// 必須清理掉!
SDL_FreeSurface(sized);
}
else {
JNI_FlipByBPP (surface);
}
}</SPAN>

縮放和繪制實現
要放大/縮小SDL surface,需要簡單地調用SDL_gfx庫的zoomSurface:
(1) 一個SDL surface。
(2) 水平縮放因子:(0-1)
(3) 垂直縮放因子:(0-1)
(4) SMOOTHING_OFF:為了能快速繪制,禁用反鋸齒處理。
接下來,讓我們基于分辨率(每個像素的位數)來翻轉SDL surface。代碼清單5展示了如何完成8位RBG格式的surface。
代碼清單5 根據分辨率翻轉SDL surface
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">/**
* 通過每個像素的位數翻轉SDL surface
*/
static void JNI_FlipByBPP (SDL_Surface *surface)
{
int bpp = surface->format->BitsPerPixel;
switch ( bpp ) {
case 8:
JNI_Flip8Bit (surface);
break;
case 16:
// 替換16位RGB (surface);
break;
case 32:
// 替換32為RGB (surface);
break;
default:
printf("Invalid depth %d for surface of size %dx%d", bpp, surface->w,
surface->h);
}
}
/**
* 替換8位SDL surface
*/
static void JNI_Flip8Bit(SDL_Surface *surface )
{
int i;
int size = surface->w * surface->h;
int bpp = surface->format->BitsPerPixel;
unsigned short pixels [size]; // RGB565
SDL_Color * colors = surface->format->palette->colors;
for ( i = 0 ; i < size ; i++ ) {
unsigned char pixel = ((unsigned char *)surface->pixels)[i];
pixels[i] = ( (colors[pixel].r >> 3) << 11)
| ( (colors[pixel].g >> 2) << 5)
| (colors[pixel].b >> 3); // RGB565
}
DrawIntoTextureRGB565 (pixels, surface->w, surface->h);
jni_swap_buffers ();
}
</SPAN>

指定SDL surface,然后檢查每個像素的格式:surface->format->BitsPerPixel,并根據該值創建能夠被DrawIntoTextureRGB565使用的RGB565像素數組:
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">for ( i = 0 ; i < size ; i++ ) {
unsigned char pixel = ((unsigned char *)surface->pixels)[i];
// RGB565
pixels[i] = ( (colors[pixel].r >> 3) << 11)
| ( (colors[pixel].g >> 2) << 5)
| (colors[pixel].b >> 3);
}</SPAN>

從surface調色板上提取每個像素包含的紅、綠和藍值:
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">SDL_Color * colors = surface->format->palette->colors;
RED: colors[pixel].r
GREEN: colors[pixel].g
BLUE: colors[pixel].b</SPAN>

為了構建RGB565像素,需要從每個顏色組件中拋棄最低有效位:
復制代碼 代碼如下:

<SPAN style="FONT-SIZE: 14px">colors[pixel].r >> 3 (8 -3 = 5)
colors[pixel].g >> 2 (8 主站蜘蛛池模板: 国产欧美一区二区三区在线看 | 羞羞视频免费观看 | 操操操日日日 | 91麻豆蜜桃一区二区三区 | 国产精品永久 | 国产精品视频一区二区三区四蜜臂 | 涩涩鲁亚洲精品一区二区 | 一本色道久久综合狠狠躁的推荐 | 四虎永久网址 | 越南性xxxx精品hd | 成人国产免费视频 | 久久免费视频网站 | 日韩欧美三级 | 精品18| 国产亚洲精品一区二区 | 欧美一区二区三区视频 | 国产中文在线播放 | 最新国产精品 | 免费一区| 精品视频免费 | 黄色毛片网站在线观看 | 蜜桃视频网站在线观看 | 91视频免费看网站 | 色综合一区 | 精品欧美激情在线观看 | 欧美在线一二三 | 亚洲嫩草 | 国产精品久久久久国产a级 久久国产精品精品 | 男女羞羞视频在线免费观看 | 久久亚洲天堂 | 久久国产精品视频 | 日本一级二级三级久久久 | 亚洲免费黄色 | 欧美激情一区 | 成人黄色免费网 | 91精品动漫在线观看 | 91精品国产99久久久久久红楼 | 欧美视频二区 | 超碰中文字幕 | 日韩福利在线观看 | 黄色大片网站在线观看 |