本文是此系列兩部分中的第
部分
介紹了 Mobile
D Graphics API (JSR
) 的有關內容
作者將帶領您進入 Java 移動設備的
D 編程世界
並展示了處理光線
攝像機和材質的方法
在移動設備上玩游戲是一項有趣的消遣
迄今為止
硬件性能已足以滿足經典游戲概念的需求
這些游戲確實令人著迷
但圖像非常簡單
今天
人們開發出大量二維平面動作游戲
其圖像更為豐富
彌補了俄羅斯方塊和吃豆游戲的單調感
下一步就是邁進
D 圖像的世界
Sony PlayStation Portable 將移動設備能夠實現的圖像能力展現在世人面前
雖然普通的移動電話在技術上遠不及這種特制的游戲機
但由此可以看出整個市場的發展方向
Mobile
D Graphics API(簡稱為 M
G)是在 JSR
(Java 規范請求
Java Specification Request)中定義的
JSR
是一項工業成就
用於為支持 Java 程序設計的移動設備提供標准
D API
M
G API 大致可分為兩部分
快速模式和保留模式
在快速模式下
您渲染的是單獨的
D 對象
而在保留模式下
您需要定義並顯示整個
D 對象世界
包括其外觀信息在內
可以將快速模式視為低級的
D 功能實現方式
保留模式顯示
D 圖像的方式更為抽象
令人感覺也更要舒服一些
本文將對快速模式 API 進行介紹
而本系列的第
部分將介紹保留模式的使用方法
MG 以外的技術 M
G 不是孤獨的
HI Corporation 開發的 Mascot Capsule API 在日本國內非常流行
日本三大運營商均以不同形式選用了這項技術
在其他國家也廣受歡迎
例如
Sony Ericsson 為手機增加了 M
G 和 HI Corporation 的特定 API
根據應用程序開發人員在 Sony Ericsson 網站上發布的報告
Mascot Capsule 是一種穩定且快速的
D環境
JSR
也就是 Java Bindings for OpenGL ES
它面向的設備與 M
G 相同
OpenGL ES 是人們熟知的 OpenGL
D 庫的子集
事實上已成為約束設備上本地
D 實現的標准
JSR
定義了一個幾乎與 OpenGL ES 的 C 接口相同的 Java API
使現有 OpenGL 內容的移植更為容易
到
年
月為止
JSR
還依然處於早期的藍圖設計狀態
關於它是否會給手機帶來深刻的影響
我只能靠推測
盡管 OpenGL ES 與其 API 不兼容
但卻對 M
G 的定義產生了一定影響
JSR
專家組確保了 MSG 在 OpenGL ES 之上的有效實現
如果您了解 OpenGL
那麼就會在 M
G 中看到許多似曾相識的屬性
盡管還有其他可選技術
但 M
G 獲得了所有主要電話制造商和運營商的支持
之前我提到過
游戲是最大的吸引力所在
但 M
G 是一種通用 API
您可以將其用於創建各種
D 內容
未來的幾年中
手機將廣泛采用
D API
您的第一個 D 對象 在第一個示例中
我們將創建一個如圖
所示的立方體
圖 示例立方體 a) 有頂點索引的正面圖b) 切割面的側面視圖(正面側面)
這個立方體存在於 M
G 定義的右手坐標系中
舉起右手
伸出拇指
食指和中指
保持其中任一手指與其他兩指均成直角
那麼拇指就表示 x 軸
食指表示 y 軸
中指表示 z 軸
試著將拇指和食指擺成圖
a 中的樣子
那麼您的中指必然指向自己
我在這裡使用了
個頂點(立方體的頂點)並使立方體的中心與坐標系的原點相重合
從圖
中可以看到
拍攝
D 場景的攝像機朝向 z 軸的負軸方向
正對立方體
攝像機的位置和屬性定義了隨後將在屏幕上顯示的東西
圖
b 展示了同一場景的側面視圖
這樣您就可以更容易地看清攝像機究竟能看到
D 世界中的哪些地方
限制因素之一就是觀察角度
這與使用照相機的情況類似
長焦鏡頭的視野比廣角鏡頭的觀察角度要窄得多
因此觀察角度決定了您的視野
與真實世界中的情況不同
D 計算給我們增加了兩個視圖邊界
近切割面和遠切割面
觀察角度和切割面共同定義了視域
視域中的一切都是可見的
而超出視域范圍的一切均不可見
在清單
中
您可以看到 VerticesSample 類
實現了上面提到的所有內容
清單
顯示立方體的示例
第
部分
類成員
package m
gsamples
;
import javax
microedition
lcdui
*;
import javax
microedition
m
g
*;
/**
* Sample displaying a cube defined by eight vertices
which are connected
* by triangles
*
* @author Claus Hoefele
*/
public class VerticesSample extends Canvas implements Sample
{
/** The cube
s vertex positions (x
y
z)
*/
private static final byte[] VERTEX_POSITIONS = {
};
/** Indices that define how to connect the vertices to build
* triangles
*/
private static int[] TRIANGLE_INDICES = {
};
/** The cube
s vertex data
*/
private VertexBuffer _cubeVertexData;
/** The cube
s triangles defined as triangle strips
*/
private TriangleStripArray _cubeTriangles;
/** Graphics singleton used for rendering
*/
private Graphics
D _graphics
d;
VerticesSample 繼承自 Canvas
應該能夠直接繪制到屏幕
並且還實現了 Sample
定義它的目的是協助組織本文中的其他源代碼示例
VERTEX_POSITIONS 以同樣的順序定義了與圖
a 相同的
個頂點
例如
頂點
定義為坐標(
)
由於我將立方體的中心點放在坐標系原點位置處
因此立方體的各邊長應為
個單位
隨後
攝像機的位置和視角可定義一個單位在屏幕上所占的像素數
僅有頂點位置還不夠
您還必須描述出想要建立的幾何圖形
只能像逐點描圖法那樣
將頂點用直線連接起來
最終得到所需圖形
但 M
G 也帶來了一個約束
必須用三角形建立幾何圖形
任何多邊形都可定義為一組三角形的集合
因此三角形在
D 實現中應用十分廣泛
三角形是基本的繪圖操作
在此基礎上可建立更為抽象的操作
不幸的是
如果只能使用三角形描述立方體
就需要
條邊 *
個三角形 *
個頂點 =
個頂點
這麼多重復的頂點顯然浪費了大量內存
為節約內存
首先應將頂點與其三角形定義分隔開來
TRIANGLE_INDICES 使用 VERTEX_POSITIONS 數組索引定義幾何圖形
使頂點可重用
然後用三角形帶取代三角形
從而減少索引數量
通過使用三角形帶
新的三角形可重用最後兩個索引
舉例來說
三角形帶(
)可轉換為兩個三角形(
)及(
)
圖
a 的各角均已標注相應索引數
如果您在圖
a 的 TRIANGLE_INDICES 中遵循這一規則處理
就會發現兩個面之間意外地多出了一些三角形
這只是一種用於避免定義某些三角形帶的模式
我曾用一個有
個立方體索引的三角形帶處理過
個頂點的情況
使用其余的類成員即可繪制出立方體
清單
展示了其初始化方法
清單
顯示立方體的示例
第
部分
初始化
/**
* Called when this sample is displayed
*/
public void showNotify()
{
init();
}
/**
* Initializes the sample
*/
protected void init()
{
// Get the singleton for
D rendering
_graphics
d = Graphics
D
getInstance();
// Create vertex data
_cubeVertexData = new VertexBuffer();
VertexArray vertexPositions =
new VertexArray(VERTEX_POSITIONS
length/
);
vertexPositions
set(
VERTEX_POSITIONS
length/
VERTEX_POSITIONS);
_cubeVertexData
setPositions(vertexPositions
f
null);
// Create the triangles that define the cube; the indices point to
// vertices in VERTEX_POSITIONS
_cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES
new int[] {TRIANGLE_INDICES
length});
// Create a camera with perspective projection
Camera camera = new Camera();
float aspect = (float) getWidth() / (float) getHeight();
camera
setPerspective(
f
aspect
f
f);
Transform cameraTransform = new Transform();
cameraTransform
postTranslate(
f
f
f);
_graphics
d
setCamera(camera
cameraTransform);
}
init() 中的第一個步驟就是使用戶獲取圖形上下文(GC)
以便繪制
D 圖形
Graphics
D 是一個單元素
_graphics
d 中保存了一個引用
以備將來使用
接下來
創建一個 VertexBuffer 以保存頂點數據
在後文中可以看到
可以為一個頂點指派多種類型的信息
所有頂點都包含於 VertexBuffer 之中
在設置使用 _cubeVertexData
setPositions() 的 VertexArray 中
您惟一需要獲取的信息就是頂點位置
VertexArray 構造函數中保存了頂點數量(
個)
各頂點的組件數(x
y
z)以及各組件的大小(
字節)
由於這個立方體非常小
個字節足以容納一個坐標
如果需要創建大型的對象
那麼還可以創建使用 Short 值(
個字節)的 VertexArray
但不能使用實數
只能使用整數
接下來
使用 TRIANGLE_INDICES 中的索引對 TriangleStripArray 進行初始化操作
初始化代碼的最後一部分就是攝像機設置
在 setPersective() 中
可設置觀察角度
縱橫比和剪貼板
注意縱橫比和剪貼板的值應為浮點值
M
G 需要 Java 虛擬機(Java Virtual Machine
JVM)提供浮點值支持
這是在 CLDC
以後的版本中增加的功能
經過觀察後
將攝像機從立方體處移開
以查看對象的全視圖
可通過平移實現這一操作
轉換 部分將就這一主題進行詳細討論
現在
您只要相信
帶有第三個正數參數的 postTranslate() 可使攝像機沿 z 軸移動
初始化後
您就可以將場景渲染到屏幕上
清單
實現了此功能
清單
顯示立方體的示例
第
部分
繪圖
/**
* Renders the sample on the screen
*
* @param graphics the graphics object to draw on
*/
protected void paint(Graphics graphics)
{
_graphics
d
bindTarget(graphics);
_graphics
d
clear(null);
_graphics
d
render(_cubeVertexData
_cubeTriangles
new Appearance()
null);
_graphics
d
releaseTarget();
}
關於示例代碼 如果想嘗試建立並運行本文中的示例
可以在
下載
部分中下載完整的源代碼
我使用了 Sun 的 Java Wireless Toolkit
並將我的項目配置為使用 MIDP
CLDC
—— 當然
還有 M
G
我將各部分的示例均作為單獨類加以實現
另外還實現了一個簡單的界面
您可以在這裡選擇及執行各個示例
wi
m
gsamples
zip 壓縮包中包含 readme
txt 文件
其中的信息更為詳細
在 paint() 中
bindTarget() 將 Canvas 的圖形上下文指派給 Graphics
D
從而開始渲染
D 對象
到調用 releaseTarget() 時終止渲染
調用 clear() 清除背景後
即可通過 init() 中創建的頂點數據和三角形繪制對象
許多 Graphics
D 的方法都會拋出不可控異常
但絕大多數錯誤都是不可恢復的
所以我決定不在代碼中使用 try/catch 塊
可在 VerticesSample
java 處找到本文的全部源代碼
我編寫了一個簡單的 MIDlet
用於顯示示例
可從 下載 中獲得 MIDlet 及本文全部源代碼
示例運行結果如圖
所示
圖 示例立方體
很難看出這個屏幕上的矩形就是立方體
這是因為我將攝像機放置在其正對面
這就像站在一堵白牆前面一樣
為什麼是白色呢?我還沒有指派任何顏色
而默認顏色就是白色
下一節將在顏色方面對程序進行完善
頂點顏色 創建 VertexBuffer 時
我曾提到可以為一個頂點指派多種類型的信息 —— 顏色也是其中之一
圖形硬件是以流水線形式處理頂點的
就像工廠以流水線組裝汽車一樣
它逐個地對各頂點進行一系列的處理
直至各頂點都顯示在屏幕上
在這一架構中
來自所有頂點的所有數據都必須同時可用
可以設想
如果組裝工人必須每次從不同的地方取螺絲
效率該有多麼低
圖
以平面布局展示了立方體的前五個頂點
還包括(R
G
B)格式的顏色信息
角上的數字同樣是在三角形帶中使用的索引
圖 帶有索引頂點顏色和方位的三角形帶
為頂點指派顏色對三角形內的像素會有什麼影響呢?可能性之一就是為整個三角形使用同樣的頂點顏色
另外還有可能在兩個頂點之間插入顏色
實現顏色梯度效果
M
G 允許用戶在這兩個選項之間任選其一
在平面著色渲染模式下
可用三角形的第三個頂點顏色為整個三角形著色
如果您將圖
所示第
個三角形定義為(
)
則其顏色為紅色(
)
在光影渲染模式下
三角形中的各像素都通過插值而獲得了自己的顏色
索引
和
之間的像素初始顏色為綠色(
)
漸變為紅色
有些三角形共享索引
和索引
處的頂點
由於一個頂點只能有一種顏色
所以這也就意味著這些三角形也使用了一種相同的顏色
圖
還指出了索引的定義順序
例如
(
)按逆時針方向定義第
個三角形的頂點
而第二個三角形為(
)是按照順時針方向定義的
這就叫做多邊形環繞
可以利用它來確定哪個面在前
哪個面在後
從正前方查看立方體時
您總是會認為自己看的僅僅是外部
但如果盒子能打開呢?您一定也想到裡邊去看看
立方體的每一面都有正反兩面
默認地
逆時針方向表示正面
但這裡還有一個小小的問題
如圖
所示
三角形帶中的環繞在每個後續三角形處都會發生變化
按慣例
由三角形帶中的第一個三角形定義其環繞
當我將一個三角形帶環繞在清單
實現的整個立方體上時
首先從一個逆時針方向環繞的三角形(
)開始
通過這樣的方式
也就隱式地將立方體的外部定義為正面
而將內部作為背面
根據具體的需求
您可以要求 M
G 僅渲染正面
僅渲染背面或同時渲染兩面
如果立方體有一個半掩的蓋子
您同時可看到其正面和背面
此時同時渲染兩面的操作非常有用
如果可能
您應該禁用那些看不到的面
這樣可以提高渲染速度
將三角形排除在渲染操作之外的方法稱為背景揀出
清單
示范了使用頂點顏色的方法
清單
各頂點都有顏色的立方體
第
部分
初始化頂點顏色
/** The cube
s vertex colors (R
G
B)
*/
private static final byte[] VERTEX_COLORS = {
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
(byte)
};
/**
* Initializes the sample
*/
protected void init()
{
// Get the singleton for
D rendering
_graphics
d = Graphics
D
getInstance();
// Create vertex data
_cubeVertexData = new VertexBuffer();
VertexArray vertexPositions =
new VertexArray(VERTEX_POSITIONS
length/
);
vertexPositions
set(
VERTEX_POSITIONS
length/
VERTEX_POSITIONS);
_cubeVertexData
setPositions(vertexPositions
f
null);
VertexArray vertexColors =
new VertexArray(VERTEX_COLORS
length/
);
vertexColors
set(
VERTEX_COLORS
length/
VERTEX_COLORS);
_cubeVertexData
setColors(vertexColors);
// Create the triangles that define the cube; the indices point to
// vertices in VERTEX_POSITIONS
_cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES
new int[] {TRIANGLE_INDICES
length});
// Define an appearance object and set the polygon mode
The
// default values are: SHADE_SMOOTH
CULL_BACK
and WINDING_CCW
_cubeAppearance = new Appearance();
_polygonMode = new PolygonMode();
_cubeAppearance
setPolygonMode(_polygonMode);
// Create a camera with perspective projection
Camera camera = new Camera();
float aspect = (float) getWidth() / (float) getHeight();
camera
setPerspective(
f
aspect
f
f);
Transform cameraTransform = new Transform();
cameraTransform
postTranslate(
f
f
f);
_graphics
d
setCamera(camera
cameraTransform);
}
/**
* Renders the sample on the screen
*
* @param graphics the graphics object to draw on
*/
protected void paint(Graphics graphics)
{
_graphics
d
bindTarget(graphics);
_graphics
d
clear(null);
_graphics
d
render(_cubeVertexData
_cubeTriangles
_cubeAppearance
null);
_graphics
d
releaseTarget();
drawMenu(graphics);
}
在類成員部分的 VERTEX_COLORS 中定義了各頂點顏色
將顏色放在 init()中全新的 VertexArray 內
並通過調用 setColors() 將其指派給 VertexBuffer
在這段代碼中還初始化了一個名為 _cubeAppearance 的 Appearance 對象
_graphics
d
render() 使用該對象來更改立方體外觀
PolygonMode 是 _cubeAppearance 的一部分
其中包含更改多邊形級屬性(包括顯示哪些面)的方法
為交互地更改這些屬性
我還在代碼中增加了一個 keyPressed() 方法
如清單
所示
清單
各頂點都有顏色的立方體
第
部分
處理按鍵事件
/**
* Handles key presses
*
* @param keyCode key code
*/
protected void keyPressed(int keyCode)
{
switch (getGameAction(keyCode))
{
case FIRE:
init();
break;
case GAME_A:
if (_polygonMode
getShading() == PolygonMode
SHADE_FLAT)
{
_polygonMode
setShading(PolygonMode
SHADE_SMOOTH);
}
else
{
_polygonMode
setShading(PolygonMode
SHADE_FLAT);
}
break;
case GAME_B:
if (_polygonMode
getCulling() == PolygonMode
CULL_BACK)
{
_polygonMode
setCulling(PolygonMode
CULL_FRONT);
}
else
{
_polygonMode
setCulling(PolygonMode
CULL_BACK);
}
break;
case GAME_C:
if (_polygonMode
getWinding() == PolygonMode
WINDING_CCW)
{
_polygonMode
setWinding(PolygonMode
WINDING_CW);
}
else
{
_polygonMode
setWinding(PolygonMode
WINDING_CCW);
}
break;
// no default
}
repaint();
}
鍵位映射 示例中使用了 MIDP 的動作游戲作為處理按鍵事件的范例
其控制游戲動作的物理鍵映射到運行示例的設備上
Sun 的 Java Wireless Toolkit 將 LEFT
RIGHT
UP
DOWN 和 FIRE 映射為游戲操縱桿
GAME_A 映射為
鍵
GAME_B 映射為
鍵
GAME_C 映射為
鍵
GAME_D 映射為
鍵
按下相應的鍵更改以下三個屬性之一
渲染模式(平面著色渲染模式或光影渲染模式)
背景揀出(看見的是立方體的外面還是裡面)
環繞(逆時針三角形表示的是正面還是背面)
圖
展示了這些選項
VertexColorsSample
java 中包含該示例的完整源代碼
圖 經著色的立方體a) 光影渲染模式b) 平面著色渲染模式背面被揀出c) 正面被揀出逆時針環繞
轉換 在本文開始處
我曾經使用了一個 Transform 對象將攝像機向後移動
以便查看整個立方體
通過同樣的方式可以轉換任意
D 對象
您可以通過數學方式將轉換表示為矩陣操作
一個向量 —— 例如
攝像機位置 —— 乘以恰當的平移矩陣從而得到相應移動的向量
Transform 對象就表示了這樣的一個矩陣
對於絕大多數普通轉換來說
M
G 提供了
種便於使用的接口
隱藏了底層的數學計算
Transform
postScale(float sx
float sy
float sz)
在 x
y
z 方向伸縮
D 對象
大於
的值將按照給定因數擴大對象
和
之間的值將縮小對象
負值則同時執行伸縮和鏡像操作
Transform
postTranslate(float tx
float ty
float tz)
通過為 x
y 和 z 坐標增加指定值移動
D 對象
負值則表示向負軸方向移動對象
Transform
postRotate(float angle
float ax
float ay
float az)
按給定角度繞穿過(
)和(ax
ay
az)的軸旋轉對象
角度為正值
則表示若您順著正旋轉軸方向觀察
對象是按順時針旋轉的
例如
postRotate(
) 將繞 x 軸將對象旋轉
度
所有操作名都是以
post
開頭的
表示當前 Transform 對象是從右邊與給定轉換矩陣相乘的 —— 矩陣操作的順序是非常重要的
如果您向右旋轉
度
然後走兩步
這時您所處的位置顯然與先走兩步再轉身不同
您可以在各步行指令之後調用兩個 post 方法 postRotate() 和 postTranslate()
從而獲得上面的步行指令
調用順序決定了所獲得的步行指令
由於使用的是後乘
所以您最後使用的轉換會首先應用
M
G 有一個 Transform 類和一個 Transformable 接口
所有快速模式的 API 均可接受 Transform 對象作為參數
用於修改其關聯的
D 對象
另外
在保留模式下使用 Transformable 接口來轉換作為
D 世界一部分的節點
在本系列的第
部分中將就此詳細討論
清單
的示例展示了轉換
清單
轉換
/**
* Renders the sample on the screen
*
* @param graphics the graphics object to draw on
*/
protected void paint(Graphics graphics)
{
_graphics
d
bindTarget(graphics);
_graphics
d
clear(null);
_graphics
d
render(_cubeVertexData
_cubeTriangles
new Appearance()
_cubeTransform);
_graphics
d
releaseTarget();
drawMenu(graphics);
}
/**
* Handles key presses
*
* @param keyCode key code
*/
protected void keyPressed(int keyCode)
{
switch (getGameAction(keyCode))
{
case UP:
transform(_transformation
TRANSFORMATION_X_AXIS
false);
break;
case DOWN:
transform(_transformation
TRANSFORMATION_X_AXIS
true);
break;
case LEFT:
transform(_transformation
TRANSFORMATION_Y_AXIS
false);
break;
case RIGHT:
transform(_transformation
TRANSFORMATION_Y_AXIS
true);
break;
case GAME_A:
transform(_transformation
TRANSFORMATION_Z_AXIS
false);
break;
case GAME_B:
transform(_transformation
TRANSFORMATION_Z_AXIS
true);
break;
case FIRE:
init();
break;
case GAME_C:
_transformation++;
_transformation %=
;
break;
// no default
}
repaint();
}
/**
* Transforms the cube with the given parameters
*
* @param transformation transformation (rotate
translate
scale)
* @param axis axis of translation (x
y
z)
* @param positiveDirection true for increase
false for decreasing
* value
*/
protected void transform(int transformation
int axis
boolean positiveDirection)
{
if (transformation == TRANSFORMATION_ROTATE)
{
float amount =
f * (positiveDirection ?
:
);
switch (axis)
{
case TRANSFORMATION_X_AXIS:
_cubeTransform
postRotate(amount
f
f
f);
break;
case TRANSFORMATION_Y_AXIS:
_cubeTransform
postRotate(amount
f
f
f);
break;
case TRANSFORMATION_Z_AXIS:
_cubeTransform
postRotate(amount
f
f
f);
break;
// no default
}
}
else if (transformation == TRANSFORMATION_SCALE)
{
float amount = positiveDirection ?
f :
f;
switch (axis)
{
case TRANSFORMATION_X_AXIS:
_cubeTransform
postScale(amount
f
f);
break;
case TRANSFORMATION_Y_AXIS:
_cubeTransform
postScale(
f
amount
f);
break;
case TRANSFORMATION_Z_AXIS:
_cubeTransform
postScale(
f
f
amount);
break;
// no default
}
}
else if (transformation == TRANSFORMATION_TRANSLATE)
{
float amount =
f * (positiveDirection ?
:
);
switch (axis)
{
case TRANSFORMATION_X_AXIS:
_cubeTransform
postTranslate(amount
f
f);
break;
case TRANSFORMATION_Y_AXIS:
_cubeTransform
postTranslate(
f
amount
f);
break;
case TRANSFORMATION_Z_AXIS:
_cubeTransform
postTranslate(
f
f
amount);
break;
// no default
}
}
}
paint() 方法現有一個 Transform 對象 _cubeTransform
該對象是 _graphics
d
render() 調用的第
個參數
改進的 keyPressed() 方法中包含使用 transform() 交互地更改轉換的代碼
GAME_C 鍵在旋轉
平移和縮放立方體之間切換
UP/DOWN 鍵更改當前轉換的 x 軸
LEFT/RIGHT 更改 y 軸
GAME_A/GAME_B 更改 z 軸
按 FIRE 可將立方體重新設置為初始位置
您可以在 TransformationsSample
java 中找到完整的源代碼
圖 示例立方體a) 旋轉b) 平移c) 縮放
深度緩沖和投影 這裡我想介紹兩個在使用轉換時已用到但未說明過的概念
投影
定義了將
D 對象映射到
D 屏幕的方法
深度緩沖
是根據對象與攝像機之間的距離正確渲染對象的一種方法
要從攝像機的觀察點觀察渲染後的圖像
您必須考慮攝像機的位置和方位
將
D 世界轉換為攝像機空間
在前面的示例代碼中
我用 Camera 和 Transform 對象調用了 Graphics
D
setCamera()
可將後者視為攝像機轉換或告訴 MSG 如何從世界坐標轉換為攝像機坐標的指令 —— 兩種定義都是正確的
最後
三維對象被顯示在二維屏幕上
到這裡
Camera
setPerspective() 告訴了 M
G 在將
D 轉換為
D 空間時實現透視投影
透視投影與真實世界中的情況比較類似
當您俯視一條又長又直的道路時
道路兩邊看上去似乎在地平線處交匯了
距離攝像機越遠
路旁的對象看起來也就越小
您也可以忽略透視
以相同大小繪制所有對象
不管它們離得多遠
這對於某些應用程序
如 CAD 程序來說是很有意義的
因為沒有透視可更容易地將精力集中在繪圖上
要禁用透視投影
可用 Camera
setParallel() 替換 Camera
setPerspective()
在攝像機空間中
對象的 z 坐標表示其與攝像機之間的距離
如果渲染一些具有不同 z 坐標的
D 對象
那麼您當然希望距離攝像機較近的對象比遠處的對象清晰
通過使用深度緩沖
對象可得到正確的渲染
深度緩沖與屏幕有著相同的寬和高
但用 z 坐標取代顏色值
它存儲著繪制在屏幕上的所有像素與攝像機之間的距離
然而
M
G 僅在一個像素比現有同一位置上的像素距離攝像機近時
才將其繪制出來
通過將進入的像素的 z 坐標與深度緩沖中的值相比較
就可以驗證這一點
因此
啟用深度緩沖可根據對象的
D 位置渲染對象
而不受 Graphics
D
render() 命令順序的影響
反之
如果您禁用了深度緩沖
那麼必須在繪制
D 對象的順序上付出一定精力
在將目標圖像綁定到 Graphics
D 時
可啟用深度緩沖
也可不啟用
在使用接受一個參數的 bindTarget() 重載版本時
默認為啟用深度緩沖
在使用帶有三個參數的 bindTarget() 時
您可以通過作為第二個參數的布爾值顯式切換深度緩沖的開關狀態
您可以更改兩個屬性
深度緩沖與投影
如清單
所示
清單
深度緩沖與投影
/**
* Initializes the sample
*/
protected void init()
{
// Get the singleton for
D rendering
_graphics
d = Graphics
D
getInstance();
// Create vertex data
_cubeVertexData = new VertexBuffer();
VertexArray vertexPositions =
new VertexArray(VERTEX_POSITIONS
length/
);
vertexPositions
set(
VERTEX_POSITIONS
length/
VERTEX_POSITIONS);
_cubeVertexData
setPositions(vertexPositions
f
null);
// Create the triangles that define the cube; the indices point to
// vertices in VERTEX_POSITIONS
_cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES
new int[] {TRIANGLE_INDICES
length});
// Create parallel and perspective cameras
_cameraPerspective = new Camera();
float aspect = (float) getWidth() / (float) getHeight();
_cameraPerspective
setPerspective(
f
aspect
f
f);
_cameraTransform = new Transform();
_cameraTransform
postTranslate(
f
f
f);
_cameraParallel = new Camera();
_cameraParallel
setParallel(
f
aspect
f
f);
_graphics
d
setCamera(_cameraPerspective
_cameraTransform);
_isPerspective = true;
// Enable depth buffer
_isDepthBufferEnabled = true;
}
/**
* Renders the sample on the screen
*
* @param graphics the graphics object to draw on
*/
protected void paint(Graphics graphics)
{
// Create transformation objects for the cubes
Transform origin = new Transform();
Transform behindOrigin = new Transform(origin);
behindOrigin
postTranslate(
f
f
f);
Transform inFrontOfOrigin = new Transform(origin);
inFrontOfOrigin
postTranslate(
f
f
f);
// Disable or enable depth buffering when target is bound
_graphics
d
bindTarget(graphics
_isDepthBufferEnabled
);
_graphics
d
clear(null);
// Draw cubes front to back
If the depth buffer is enabled
// they will be drawn according to their z coordinate
Otherwise
// according to the order of rendering
_cubeVertexData
setDefaultColor(
x
FF
);
_graphics
d
render(_cubeVertexData
_cubeTriangles
new Appearance()
inFrontOfOrigin);
_cubeVertexData
setDefaultColor(
x
FF
);
_graphics
d
render(_cubeVertexData
_cubeTriangles
new Appearance()
origin);
_cubeVertexData
setDefaultColor(
x
FF);
_graphics
d
render(_cubeVertexData
_cubeTriangles
new Appearance()
behindOrigin);
_graphics
d
releaseTarget();
drawMenu(graphics);
}
/**
* Handles key presses
*
* @param keyCode key code
*/
protected void keyPressed(int keyCode)
{
switch (getGameAction(keyCode))
{
case GAME_A:
_isPerspective = !_isPerspective;
if (_isPerspective)
{
_graphics
d
setCamera(_cameraPerspective
_cameraTransform);
}
else
{
_graphics
d
setCamera(_cameraParallel
_cameraTransform);
}
break;
case GAME_B:
_isDepthBufferEnabled = !_isDepthBufferEnabled;
break;
case FIRE:
init();
break;
// no default
}
repaint();
}
使用 GAME_A 鍵可在透視投影與平行投影之間切換
GAME_B 可啟用或禁用深度緩沖
完整的源代碼包含在 DepthBufferProjectionSample
java 中
圖
展示了不同設置下的效果
圖 立方體a) 啟用深度緩沖根據與攝像機之間的距離進行渲染b) 禁用深度緩沖根據繪圖操作的順序進行渲染c) 使用平行投影而非透視投影進行渲染
照明 在一個沒有光線的房間中
所有的東西看上去都是黑的
那麼前面的示例中沒有光線
怎麼還能看到東西呢?頂點顏色和後面即將介紹的材質是不需要光線的
它們永遠顯示為定義好的顏色
但光線會使它們發生一些變化
可增加景深
光線的方向會根據對象的位置發生反射
如果您用手電筒垂直地照射您面前的鏡子
那麼光線會反射到您身上
如果鏡子是傾斜的
則光線的入射角和反射角是完全相同的
總的來說
您需要一個與照射平面相垂直的方向向量
這一向量就稱為法線向量 或簡稱為法線
M
G 會根據法線
光源位置和攝像機位置計算著色情況
此外
法線是各頂點都具備的屬性
各頂點之間的像素著色既可采用插值法(PolygonMode
SHADE_SMOOTH)也可從三角形的第三個頂點處選取(PolygonMode
SHADE_FLAT)
由於立方體有
個頂點
支持法線的方法之一就是指定從立方體中心指向各角的向量
如圖
a 所示
但這樣做可能會導致立方體著色不當
有三個面的顏色可能會相同
其中有些邊成為不可見狀態
使立方體看上去缺乏稜角
這顯然更適合球體
不太適合立方體
圖
b 展示了如何為每邊使用
條法線 —— 共
條
從而創建稜角分明的邊線
由於一個頂點只能有一條法線
所以還要復制頂點
圖 帶有法線向量的立方體a) 條法線b) 條法線(每邊 條)
可使用法線計算光線後
還需要告訴 M
G 您需要什麼類型的光線
光線來源於不同形式
燈泡
太陽
手電筒等等
在 M
G 中的對應術語分別為全向光
定向光和聚光
全向光是從一個點發出的
並平均地照射各個方向
沒有燈罩的燈泡發出的就是這樣的光
定向光向一個方向發出平行的光線
太陽離我們的距離非常遠
所以可以將其光線視為平行的
定向光沒有位置
只有方向
手電筒或劇場中使用的聚光燈發射出的光線就是聚光
其光線呈錐形
與圓錐相交的平面上的對象會被照亮
在真實世界中
光線還會從對象上反射回來而將周圍照亮
如果您打開臥室燈
就會發現即便沒有能直接照射到床底下的光線
但床下仍會被照亮
Raytracer 通過追蹤從攝像機到光源的路徑而清晰真實地展示了圖像
但需要很長時間
要獲得交互式幀頻
必須滿足一個簡單的模型
環境光
環境光以不變的頻率從各方向照亮對象
您可以用環境光模擬前面的臥室場景
將所有對象都照亮到一定程度
從而提供了另外一個全向光源
清單
描述了設置不同光線的方法
清單
設置光線模式
// Create light
_light = new Light();
_lightMode = LIGHT_OMNI;
setLightMode(_light
_lightMode);
Transform lightTransform = new Transform();
lightTransform
postTranslate(
f
f
f);
_graphics
d
resetLights();
_graphics
d
addLight(_light
lightTransform);
/**
* Sets the light mode
*
* @param light light to be modified
* @param mode light mode
*/
protected void setLightMode(Light light
int mode)
{
switch (mode)
{
case LIGHT_AMBIENT:
light
setMode(Light
AMBIENT);
light
setIntensity(
f);
break;
case LIGHT_DIRECTIONAL:
light
setMode(Light
DIRECTIONAL);
light
setIntensity(
f);
break;
case LIGHT_OMNI:
light
setMode(Light
OMNI);
light
setIntensity(
f);
break;
case LIGHT_SPOT:
light
setMode(Light
SPOT);
light
setSpotAngle(
f);
light
setIntensity(
f);
break;
// no default
}
}
在圖
中
您可以看到各種光線模式的不同效果
分別以
種類型的光照射示例立方體
這裡的光線均為白色
就在攝像機前面
朝向立方體的三個面
圖 使用不同的光線照射立方體 a) 全向光b) 聚光c) 環境光d) 定向光
全向光在面對光源的頂點處最亮
然後逐漸暗淡下來
另外
聚光在聚光圓錐的邊緣處制造了強烈的明暗對比
如果定義了一個足夠大的光錐
那麼所得到的結果可能與全向光相同
環境光從各個方向照亮立方體
立方體看上去是平的
這是因為缺乏陰影
最後
定向光使每面都具有不同的顏色
每面內的顏色都相同
這是因為光線是平行的
照明並不精確
否則
聚光照亮的圓錐體范圍應該是圓形
這是因為光線計算比較復雜
手機的部件將簡化這一計算
可以為立方體的各邊添加更多的三角形
從而提高其顯示質量
盡管三角形並不能定義一個可見的幾何圖形
但可使 M
G 擁有更多的控制點(要計算的數量也更多)
材質 通過光線可實現不同的效果
一個閃閃發光的銀色球反射光線的方式與一張紙顯然不同
M
G 使用以下屬性為這些材質的特征建立模型
環境反射
由環境光源反射的光線
漫反射
反射光均勻地分散到各個方向
放射光
一個像熾熱的物體那樣發射光線的對象
鏡面反射
光線從有光亮平面的對象反射回來
您可為各材質屬性設置顏色
閃閃發光的銀色球的漫反射光線應該是銀色的
其鏡面反射部分為白色
材質的顏色與光線的顏色相融合
從而得到最終的對象顏色
如果您用藍光照射銀色球
那麼球看上去應該略帶藍色
清單
展示了使用材質的方式
清單
設置材質
// Create appearance and the material
_cubeAppearance = new Appearance();
_colorTarget = COLOR_DEFAULT;
setMaterial(_cubeAppearance
_colorTarget);
/**
* Sets the material according to the given target
*
* @param appearance appearance to be modified
* @param colorTarget target color
*/
protected void setMaterial(Appearance appearance
int colorTarget)
{
Material material = new Material();
switch (colorTarget)
{
case COLOR_DEFAULT:
break;
case COLOR_AMBIENT:
material
setColor(Material
AMBIENT
x
FF
);
break;
case COLOR_DIFFUSE:
material
setColor(Material
DIFFUSE
x
FF
);
break;
case COLOR_EMISSIVE:
material
setColor(Material
EMISSIVE
x
FF
);
break;
case COLOR_SPECULAR:
material
setColor(Material
SPECULAR
x
FF
);
material
setShininess(
);
break;
// no default
}
appearance
setMaterial(material);
}
setMaterial() 創建了一個新的 Material 對象
通過使用各顏色組件標識符的 setColor() 設置顏色
Material 對象隨後被指派給 Appearance 對象
該對象用於調用 Graphics
D
render()
盡管這裡沒有展示
但您還可以使用 Material
setVertexColorTrackingEnable() 為環境反射和漫反射使用頂點顏色
不必使用 Material
setColor()
LightingMaterialsSample
java 這一示例中實現了光線和材質
按其中的鍵可以將不同的顏色與材質綜合
感受不同的效果
在圖
中
用全向光展示了不同的材質特征
各截圖都將顏色組件設置為紅色
以突出表現其效果
圖 不同的顏色組件a) 環境反射b) 漫反射c) 放射光d) 鏡面反射
環境反射僅對環境光起作用
因此
使用全向光是無效的
漫反射材質組件會造成一種不光滑的表面
而放射光組件則制造出一種發光效果
鏡面反射顏色組件強調了發亮的效果
此外
您還可以通過使用更多的三角形改進明暗對比的著色質量
紋理 至此
我已經介紹了更改立方體外觀的兩種方式
頂點顏色和材質
但經過這兩種方式處理後的立方體看起來依然很不真實
在現實世界中
應該還有更多的細節
這就是紋理的效果
紋理是像包在禮物外面的包裝紙那樣環繞在
D 對象外的圖像
您必須為各種情況選擇恰當的包裝紙
並且決定如何排列
在
D 編程中也必須作出相同的決策
現在
您或許已經猜測到我將引入另外一種每個頂點都具備的屬性
對於每個頂點而言
紋理坐標定義了使用紋理的位置
然後 M
G 會映射紋理以適應您的對象
可以這樣設想
將一塊有彈性的包裝紙釘在禮物的各頂點上
這些坐標所引用的紋理像素就叫做 texel
圖
展示了將
x
texel 的正方形紋理映射到立方體正面的效果
圖 將多邊形坐標(xy)映射為紋理坐標(st)
將紋理坐標命名為(s
t)是為了與用於表示頂點位置的(x
y)區分開來(從文字角度來講
(u
v)更為常用)
坐標(s
t)定義為(
)的地方就是紋理的左上角
而(
)位於右下角
相應地
如果您需要將立方體正面的左下角映射到紋理的左下角
必須將紋理坐標(
)指派給頂點
由於您定義了與紋理的角相關的紋理坐標
所以任意大小的圖像都有相同的坐標
M
G 為最接近的 texel 插入
到
之間的值
例如
表示紋理的中點
如果紋理坐標超過
~
的范圍
M
G 會提示您確認
坐標既可環繞(例如
與
的效果相同)
也可采用加強方式
所謂加強
也就意味著任何小於
的值都按
使用
任何大於
的值都按
使用
紋理的寬和高可有所不同
但必須是
的冪
如圖
中的
部件必須至少支持
的紋理大小
這是 M
G 的一個可選屬性
Graphics
D
getProperties() 返回一個 Hashtable
其中填充了特定於部件的屬性
如最大紋理維度或支持的最大光源數
getProperties() 的文檔包含一個屬性及其最低需求的清單
在使用超過這些值的屬性之前
應該檢查設備的部件是否能提供支持
清單
展示了紋理的使用
清單
使用紋理
第
部分
初始化
/** The cube
s vertex positions (x
y
z)
*/
private static final byte[] VERTEX_POSITIONS = {
// front
// back
// right
// left
// top
// bottom
};
/** Indices that define how to connect the vertices to build
* triangles
*/
private static final int[] TRIANGLE_INDICES = {
// front
// back
// right
// left
// top
// bottom
};
/** Lengths of triangle strips in TRIANGLE_INDICES
*/
private static int[] TRIANGLE_LENGTHS = {
};
/** File name of the texture
*/
private static final String TEXTURE_FILE =
/texture
png
;
/** The texture coordinates (s
t) that define how to map the
* texture to the cube
*/
private static final byte[] VERTEX_TEXTURE_COORDINATES = {
// front
// back
// right
// left
// top
// bottom
};
/** First color for blending
*/
private static final int COLOR_
=
x
FF;
/** Second color for blending
*/
private static final int COLOR_
=
x
FF
;
/**
* Initializes the sample
*/
protected void init()
{
// Get the singleton for
D rendering
_graphics
d = Graphics
D
getInstance();
// Create vertex data
_cubeVertexData = new VertexBuffer();
VertexArray vertexPositions =
new VertexArray(VERTEX_POSITIONS
length/
);
vertexPositions
set(
VERTEX_POSITIONS
length/
VERTEX_POSITIONS);
_cubeVertexData
setPositions(vertexPositions
f
null);
VertexArray vertexTextureCoordinates =
new VertexArray(VERTEX_TEXTURE_COORDINATES
length/
);
vertexTextureCoordinates
set(
VERTEX_TEXTURE_COORDINATES
length/
VERTEX_TEXTURE_COORDINATES);
_cubeVertexData
setTexCoords(
vertexTextureCoordinates
f
null);
// Set default color for cube
_cubeVertexData
setDefaultColor(COLOR_
);
// Create the triangles that define the cube; the indices point to
// vertices in VERTEX_POSITIONS
_cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES
TRIANGLE_LENGTHS);
// Create a camera with perspective projection
Camera camera = new Camera();
float aspect = (float) getWidth() / (float) getHeight();
camera
setPerspective(
f
aspect
f
f);
Transform cameraTransform = new Transform();
cameraTransform
postTranslate(
f
f
f);
_graphics
d
setCamera(camera
cameraTransform);
// Rotate the cube so we can see three sides
_cubeTransform = new Transform();
_cubeTransform
postRotate(
f
f
f
f);
_cubeTransform
postRotate(
f
f
f
f);
// Define an appearance object and set the polygon mode
_cubeAppearance = new Appearance();
_polygonMode = new PolygonMode();
_isPerspectiveCorrectionEnabled = false;
_cubeAppearance
setPolygonMode(_polygonMode);
try
{
// Load image for texture and assign it to the appearance
The
// default values are: WRAP_REPEAT
FILTER_BASE_LEVEL/
// FILTER_NEAREST
and FUNC_MODULATE
Image
D image
D = (Image
D) Loader
load(TEXTURE_FILE)[
];
_cubeTexture = new Texture
D(image
D);
_cubeTexture
setBlending(Texture
D
FUNC_DECAL);
// Index
is used because we have only one texture
_cubeAppearance
setTexture(
_cubeTexture);
}
catch (Exception e)
{
System
out
println(
Error loading image
+ TEXTURE_FILE);
e
printStackTrace();
}
}
在 init() 中
向 VertexBuffer 增加了定義為類的靜態成員的紋理坐標
與在照明示例中的情況類似
我為立方體的每個面都使用了
個向量
以將各頂點映射到紋理的一個角
注意
我使用了比例
作為 _cubeVertexData
setTexCoords() 的第三個參數
這也就告訴 M
G 將所有紋理坐標都乘以此值
實際上
紋理僅使用了立方體面的四分之一
這樣做的目的是展示 M
G 的加強和環繞特性
如果是加強
那麼僅在左上角繪制
如果是環繞
那麼紋理圖案將填充滿整個面
紋理是用 Loader
load() 載入的
並指派給 Texture
D 對象
您還應使用 MIDP 的 Image
createImage()
但如果您想從 Java Archive(JAR)文件中讀取紋理
那麼 Loader 類是最快的方式
所得到的 Texture
D 對象隨後被設置為立方體外觀的紋理
在進行紋理化處理時
您可能依然希望使用通過照明獲得或直接指派給頂點的顏色
出於此方面的考慮
M
G 提供了各種混色功能
可通過調用 _cubeTexture
setBlending() 來設置
在 init() 中
我使用了 Texture
D
FUNC_DECAL
它將根據 α 值將紋理與基本頂點顏色相混合
圖
中紋理圖像的灰色位透明度為
%
這裡沒有設置頂點顏色或使用照明
而是使用 _cubeVertexData
setDefaultColor() 為立方體設置了一個默認顏色
這也就意味著立方體中的所有三角形都將使用同樣的顏色
通過混色
您也可以在各紋理上使用多重紋理
從而獲得更豐富的效果
我還內置了一個可選的 M
G 特性
如照明部分中所示
渲染的質量取決於您所使用的三角形數量 —— 頂點之間的距離越小
插值效果就越好
這對於紋理來說也是成立的
高質量對於紋理而言就意味著紋理在不失真的情況下映射
如圖
所示的紋理是有缺陷的
因為其中包含直線
顯然會發生失真的情況
MSG 提供了一種在處理能力方面代價低廉的方法來解決這一問題
可用 PolygonMode
setPerspectiveCorrectionEnable() 設置可選的透視修正標志
如清單
所示
清單
使用紋理
第
部分
交互式更改透視修正
環繞模式及混色
/**
* Checks whether perspective correction is supported
*
* @return true if perspective correction is supported
false otherwise
*/
protected boolean isPerspectiveCorrectionSupported()
{
Hashtable properties = Graphics
D
getProperties();
Boolean supportPerspectiveCorrection =
(Boolean) properties
get(
supportPerspectiveCorrection
);
return supportPerspectiveCorrection
booleanValue();
}
/**
* Handles key presses
*
* @param keyCode key code
*/
protected void keyPressed(int keyCode)
{
switch (getGameAction(keyCode))
{
case LEFT:
_cubeTransform
postRotate(
f
f
f
f);
break;
case RIGHT:
_cubeTransform
postRotate(
f
f
f
f);
break;
case FIRE:
init();
break;
case GAME_A:
if (isPerspectiveCorrectionSupported())
{
_isPerspectiveCorrectionEnabled = !_isPerspectiveCorrectionEnabled;
_polygonMode
setPerspectiveCorrectionEnable(
_isPerspectiveCorrectionEnabled);
}
break;
case GAME_B:
if (_cubeTexture
getWrappingS() == Texture
D
WRAP_CLAMP)
{
_cubeTexture
setWrapping(Texture
D
WRAP_REPEAT
Texture
D
WRAP_REPEAT);
}
else
{
_cubeTexture
setWrapping(Texture
D
WRAP_CLAMP
Texture
D
WRAP_CLAMP);
}
break;
case GAME_C:
if (_cubeVertexData
getDefaultColor() == COLOR_
)
{
_cubeVertexData
setDefaultColor(COLOR_
);
}
else
{
_cubeVertexData
setDefaultColor(COLOR_
);
}
break;
// no default
}
repaint();
}
在示例中
isPerspectiveCorrectionSupported() 用於檢查部件是否支持透視修正
如果支持
您可在 keyPressed() 中交互地切換標志的開關狀態
這裡還增加了一個更改紋理映射到立方體的方式(加強或重復)的選項以及一個更改混色的選項
對混色的更改示范了可以容易地將顏色與紋理相混合以獲得更豐富的效果
在 TexturesSample
java 中可以看到完整的示例
圖
展示了使用不同選項的紋理映射效果
圖 紋理a) 無透視修正b) 有透視修正c) 加強而非平鋪d) 用綠色代替蘭色進行混色
結束語 本文中介紹了大量基礎知識
包括使用頂點數據創建立方體
使用攝像機為立方體照相
在立方體上應用光線和材質
利用紋理創建具有真實感的立方體的方法等詳細信息
給出了許多立方體作為示例
在論證概念時
立方體是一種極好的示例
但它並不是復雜的
D 設計的裡程碑
在介紹過程中
我從游戲的角度強調了
D 圖像
如果像示例那樣通過手工組合頂點數據
那麼設計一個復雜的游戲世界將成為一項令人望而卻步的工作
您需要一種方法
通過建模工具來設計
D 場景
並將數據導入程序
導入模型後
必須再尋求一種組織數據的方法
如果使用 VertexBuffer 方法
您必須記住所有的轉換以及對象之間的關系
比如說
上臂與下臂相連
而下臂應該與手相連
您必須對應地安置手臂與手
M
G 提供的一種場景圖形 API —— 保留模式 —— 簡化了此類任務
通過保留模式您可以為全部對象及其屬性建模
在本系列的第
部分中將就此進行詳細論述
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26106.html