本文是此系列两部分中的第 1 部分,介绍了 Mobile 3D Graphics API (JSR 184) 的有关内容。作者将带领您进入 Java 移动设备的 3D 编程世界,并展示了处理光线、摄像机和材质的方法。
/**
* 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 = 10.0f * (positiveDirection ? 1 : -1);
switch (axis)
{
case TRANSFORMATION_X_AXIS:
_cubeTransform.postRotate(amount, 1.0f, 0.0f, 0.0f);
break;
case TRANSFORMATION_Y_AXIS:
_cubeTransform.postRotate(amount, 0.0f, 1.0f, 0.0f);
break;
case TRANSFORMATION_Z_AXIS:
_cubeTransform.postRotate(amount, 0.0f, 0.0f, 1.0f);
break;
// no default
}
}
else if (transformation == TRANSFORMATION_SCALE)
{
float amount = positiveDirection ? 1.2f : 0.8f;
switch (axis)
{
case TRANSFORMATION_X_AXIS:
_cubeTransform.postScale(amount, 1.0f, 1.0f);
break;
case TRANSFORMATION_Y_AXIS:
_cubeTransform.postScale(1.0f, amount, 1.0f);
break;
case TRANSFORMATION_Z_AXIS:
_cubeTransform.postScale(1.0f, 1.0f, amount);
break;
// no default
}
}
else if (transformation == TRANSFORMATION_TRANSLATE)
{
float amount = 0.2f * (positiveDirection ? 1 : -1);
switch (axis)
{
case TRANSFORMATION_X_AXIS:
_cubeTransform.postTranslate(amount, 0.0f, 0.0f);
break;
case TRANSFORMATION_Y_AXIS:
_cubeTransform.postTranslate(0.0f, amount, 0.0f);
break;
case TRANSFORMATION_Z_AXIS:
_cubeTransform.postTranslate(0.0f, 0.0f, amount);
break;
// no default
}
}
}
paint() 方法现有一个 Transform 对象 _cubeTransform,该对象是 _graphics3d.render() 调用的第 4 个参数。改进的 keyPressed() 方法中包含使用 transform() 交互地更改转换的代码。GAME_C 键在旋转、平移和缩放立方体之间切换。UP/DOWN 键更改当前转换的 x 轴,LEFT/RIGHT 更改 y 轴,GAME_A/GAME_B 更改 z 轴。按 FIRE 可将立方体重新设置为初始位置。您可以在 TransformationsSample.java 中找到完整的源代码。
图 5. 示例立方体:a) 旋转;b) 平移;c) 缩放
深度缓冲和投影 这里我想介绍两个在使用转换时已用到但未说明过的概念:投影,定义了将 3D 对象映射到 2D 屏幕的方法;深度缓冲,是根据对象与摄像机之间的距离正确渲染对象的一种方法。
要从摄像机的观察点观察渲染后的图像,您必须考虑摄像机的位置和方位,将 3D 世界转换为摄像机空间。在前面的示例代码中,我用 Camera 和 Transform 对象调用了 Graphics3D.setCamera()。可将后者视为摄像机转换或告诉 MSG 如何从世界坐标转换为摄像机坐标的指令 —— 两种定义都是正确的。最后,三维对象被显示在二维屏幕上。到这里,Camera.setPerspective() 告诉了 M3G 在将 3D 转换为 2D 空间时实现透视投影。
透视投影与真实世界中的情况比较类似:当您俯视一条又长又直的道路时,道路两边看上去似乎在地平线处交汇了。距离摄像机越远,路旁的对象看起来也就越小。您也可以忽略透视,以相同大小绘制所有对象,不管它们离得多远。这对于某些应用程序,如 CAD 程序来说是很有意义的,因为没有透视可更容易地将精力集中在绘图上。要禁用透视投影,可用 Camera.setParallel() 替换 Camera.setPerspective()。
在摄像机空间中,对象的 z 坐标表示其与摄像机之间的距离。如果渲染一些具有不同 z 坐标的 3D 对象,那么您当然希望距离摄像机较近的对象比远处的对象清晰。通过使用深度缓冲,对象可得到正确的渲染。深度缓冲与屏幕有着相同的宽和高,但用 z 坐标取代颜色值。它存储着绘制在屏幕上的所有像素与摄像机之间的距离。然而,M3G 仅在一个像素比现有同一位置上的像素距离摄像机近时,才将其绘制出来。通过将进入的像素的 z 坐标与深度缓冲中的值相比较,就可以验证这一点。因此,启用深度缓冲可根据对象的 3D 位置渲染对象,而不受 Graphics3D.render() 命令顺序的影响。反之,如果您禁用了深度缓冲,那么必须在绘制 3D 对象的顺序上付出一定精力。在将目标图像绑定到 Graphics3D 时,可启用深度缓冲,也可不启用。在使用接受一个参数的 bindTarget() 重载版本时,默认为启用深度缓冲。在使用带有三个参数的 bindTarget() 时,您可以通过作为第二个参数的布尔值显式切换深度缓冲的开关状态。