写在前面的话:
JAVA
应用结构简单,易于编写,能够轻易完成高强度的复杂交互,并且安全性高,稳定性强,免费资源丰富,网络功能强大,拥有近乎完美的多线程机制。有必要的前提下,
Java
程序员甚至可以使用
JNI
直接与本地环境沟通,从而绕过虚拟机的性能制约。
而
JAVA
应用的跨平台特性,更(理论上)让其可以运行于任何系统和平台之上,最大限度的增加了程序的通用可能。
从本质上讲,无论你以
Java
开发桌面应用也好,网页应用也罢,其实并没有明显的界线存在。究其根本,无非是使用
Applet/JApplet/JavaFX
当做容器,抑或
AWT/Swing/SWT
当作容器的区别罢了。
快捷、灵活、通用、稳定,以上这些优势,原本足以让
JAVA
将成为未来网页游戏乃至中小型桌面游戏开发的主流语言之一。
然而,
Java
的运行效率问题,似乎却成了这一些美好前景的绊脚石。更直接的说,有一些人武断的认为,
Java
“缓慢”的运行速度,让它根本不适合作为游戏客户端之用。
即便自
JDK1.6
起
Java
的图形渲染能力已经有了显著提升,即便国外像
RuneScape
之类的
Java3D
网页游戏已经上线盈利很多年(
PS
:顺便鄙视下
Jagex
最近对
RuneScape
作的人物属性调整……),即便连
NetBeans
的运行速度都已经变得能同普通桌面程序不遑多让。但是,某些自
2004
年后或许从未接触过新技术的家伙,
依旧乐此不疲的散布着有关
Java
性能的流言蜚语。
在某些落伍人士眼里,
Java
如同洪水猛兽,又好像是他们天生的对头。他们甚至宁愿选择某些行将就木的技术,他们甚至宁愿将某些只适合做低成本动画的东西视为命根,他们甚至宁愿花大力气去处理那些因为不支持实际多线程、
CPU
占用过高、硬件加速不到位、资源回收异常等等问题而引发的致命
BUG
,也不愿意去多了解一下
Java
。他们将一种原本可以带来巨大商业利益的语言视若等闲,他们宁愿让自己的雇主花费数倍的精力与财力去打造垃圾,也不愿意让雇主和公司拥有接触到更为优秀技术的机会。
不得不说,这即是
Java
的遗憾,更是某些落伍人士雇主及其公司,乃至整个游戏产业的遗憾。
当然,一味的指责他人,就成了抱怨,势必会犯“有嘴说别人,没嘴说自己”的民族通病。事实上,人们对于
Java
性能方面之所以会产生误解,除了旁人的傲慢与偏见外,自然也同
Java
自身的发展历程密不可分(具体原因我在其它的博文中已经阐述过很多次,此处不再赘述)。
但总体上讲,除了原
Sun
公司本身的不作为,以及
Java
偏向企业级开发,偏向服务器端开发的大环境影响外。
Java
进行游戏开发,或者说桌面开发的最大缺陷,就在于其图形开发方面,特别是有关于渲染优化方面,乃至整个
Java
游戏开发领域的书籍资料都严重匮乏。没错,相比浩如烟海的
Java
服务器端技术资料而言,
Java
游戏开发方面的资源凤毛麟角。在某个黑暗时期中,甚至连
Java
版的贪食蛇、俄罗斯方块、超级马里奥之类的资源都会被人视为经典。
不客气地说,如果凭那些东西就想让人树立对于
Java
游戏开发的信心,就算不等于痴人说梦,至少也是难于登天了。
而如果想要解决这个问题,那么更多的示例,以及更多的技术文章将必不可少。为此,笔者才会产生创作此系列博文的意愿,唯恨椽笔拙文,权作引玉之砖。
正文:
BufferedImage
与像素级渲染
常有人说
Java
图形渲染很慢?嗯,相对
C/C++
而言,
Java2D
固有的图像处理能力确实有待提高。
但是,这也仅仅局限于对比
C/C++
应用而言。
如果您是以其它什么东西与之比较,却得出
Java
渲染很慢的结论。那么,或者并不是出自
Java
本身的原因,而在于您并没能搞清楚该怎样正确的使用
Java
绘图。
况且,即便是相对于
C/C++
而谈,
Java
也并非相差到难以望其项背的地步。相对于某些行将就木的技术,至少我们除了异常积极的自行修改
JRE
,或者极端消极的等待
JRE
官方更新以外,还有使用
OpenGL
或者像素级优化这两条道路可走。
在本节当中,我们就先谈点基础的,来说说
Java
渲染的像素级优化吧。
像素与
RGB
:
像素是什么?简单的讲,像素就是色彩,像素是系统能够在计算机屏幕上显示的最小染色点。越高位的像素,其拥有的色板也就越丰富,越能表达颜色的真实感。
众所周知,图像是像素的复合,看似绚丽的形象,也无外是一个个肉眼难以分辨的细微颗粒集合罢了。
比如,在一些常见的
Java
图像处理中,我们经常会用到所谓的
RGB24
模式(
24
位三原色模式,在
Java2D
中以
TYPE_INT_RGB
表示),将
Red
,
Green
,
Blue
三种色彩加以混合,创造出唯一的色彩点并绘制到计算机之上。而这个色彩点,也就是所谓的像素。因为在
RGB24
中
Red
,
Green
,
Blue
三者都被分配有一个
0~255
的强度值,所以该
RGB
模式的极限机能就是
256*256*256
,即至多可以显示出
16777216
种颜色。
PS
:关于
16
位的
RGB565
(
Java2D
中表示为
TYPE_USHORT_565_RGB
)以及
RGB555
(
Java2D
中表示为
TYPE_USHORT_555_RGB
)会在以后章节中涉及,大家此刻只要知道,使用
24
位以下的图形处理模式,在显示速度上虽然会有提高,视觉效果上却必然会有损失就可以了。
也许有网友会感叹。哇!
16777216
种颜色,这么多?难道都能用上吗?!
没错,
16777216
种颜色确实很多;事实上,这已非常接近于人类肉眼所能观察到的颜色数目极限
,
所以我们又将它称之为真彩色。然而,人类的欲求却是无止境的,即便能够展现出
16777216
种颜色的
RGB
真彩模式,依旧有人嫌弃它的效果太差。
否则,在您计算机“颜色质量”一栏中,或许就不会再有
32
位这种“多余”的选择了。
正是因为人类天性的贪婪,当今
2D
、
3D
图形渲染中最为常见的
ARGB
模式,也就是
32
位真彩模式才会应运而生。
ARGB
模式:
您问什么是
ARGB
?其实,它就是个穿了
Alpha
通道马甲的
RGB
。
事实上,较之最初的
RGB
模式,
ARGB
仅仅增加了一个名为
Alpha
的色彩通道。这是一个
8
位的灰度通道,用
256
级灰度来记录图像中的透明度信息,定义透明、不透明和半透明区域。通俗的说,你的
ARGB
图像是否透明,与底层图像的遮挡关系如何,都将由
Alpha
这个参数所决定。
在
Java2D
中,
TYPE_INT_ARGB
象征着
32
位十六进制数的
ARGB
色彩模式。
将“
32
位十六进制数”的概念具象化后,也就是四对十六进制数字的序列。每个十六进制对定义四个颜色通道,即
Red
、
Green
、
Blue
和
Alpha
中每个颜色通道的强度,全以范围介于
0
到
255
之间的十进制数的十六进制表示法。(在
16
进制表示中,
FF
是指全强度
,最高的
255
。
00
是指通道中无颜色,最低为
0
)
正如大家都知道的那样
,
由于颜色值长度需要两位数字
,
因此您需要填充一个通道
,
例如用
01
代替
1
,这样才可确保十六进制数中始终具有八个数字。还应确保指定十六进制数前缀
0x
,这样才能被
Java
识别为
16
进制。
例如,白色
(
全强度
)
用十六进制记数法表示为
: 0xFFFFFFFF
。而黑色正好相反;它在红色、绿色和蓝色中的任何一个通道中都
无颜色,结果就成了
: 0xFF000000
。请注意
, Alpha
通道中的全强度意味着没有
Alpha
(FF)
,也就是不透明
,
而无强度
(00)
,则意味着全透明。
利用
ARGB
模式,我们可以轻易的创建出一些
RGB
所无法实现的艳丽图像,完成一些
RGB
所无法企及的缤纷效果。应该说,如果您只是想制作一个让人可以入目的画面,那么普通的
RGB
模式已然游刃有余,但如果您想百尺竿头更进一步,制作出一些让人心旷神怡的视觉盛宴,那就非
ARGB
不可。而一旦您开始使用
ARGB
,就与
Alpha
、
Red
、
Green
、
Blue
这四层色彩通道留下了不解之缘。
在
Java
中获得
ARGB
像素的方法如下:
public
static
int
getARGB(
int
r,
int
g,
int
b,
int
alpha) {
return
(alpha << 24) | (r <<
16) | (g << 8) | b;
}
关于
BufferedImage
:
当我们需要使用像素级操作,当我们需要设定针对不同图像的不同色彩模式时,最直接有效的方法,就是使用
BufferedImage
。
事实上,就像深入优化
Flash
渲染必须利用
BitmapData
一样,没有对
BufferedImage
的相关了解,提高
Java2D
性能根本无从谈起,甚至不能说你会用
Java2D
。
当您想要创建
BufferedImage
,并对其中像素进行直接操作时,大体上有三种方式可选:
1
、直接创建
BufferedImage
,导出
DataBufferInt
对象获取像素集合。
//
创建一个
640x480
的
BufferedImage
,设定渲染模式为
ARGB
BufferedImage
image =
new
BufferedImage
(640,
480,
BufferedImage
.
TYPE_INT_ARGB
);
//
获得当前
BufferedImage
的图像数据
存储器,并转为
DataBufferInt
DataBufferInt dataBuffer = ((DataBufferInt)
image.getRaster()
.getDataBuffer());
//
获得对应
BufferedImage
的像素数组
int
[] pixels = dataBuffer.getData();
2
、以
int[]
生成
WritableRaster
,以
WritableRaster
产生
BufferedImage
。
//
设定
BufferedImage
的宽与高
int
width = 640, height = 480;
int
size = width * height;
//
创建数组,用以保存对应
BufferedImage
的像素集合
int
[] pixels =
new
int
[size];
//
以指定数组创建出指定大小的
DataBuffer
DataBuffer dataBuffer =
new
DataBufferInt(pixels, size);
//
创建一个
WritableRaster
对象,用以
管理光栅
WritableRaster raster = Raster.createPackedRaster
(dataBuffer,
width, height,width,
new
int
[] { 0xFF0000, 0xFF00, 0xFF },
null
);
//
创建一个
24
位的
RGB
色彩模型,并填充相应的
R
、
G
、
B
掩码
DirectColorModel directColorModel =
new
DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF);
//
以下为
32
位
RGB
色彩模型
// DirectColorModel directColorModel = new
DirectColorModel(32, 0xFF000000, 0xFF0000, 0xFF00, 0xFF);
//
生成
BufferedImage,
预设
Alpha
,无配置
BufferedImage image =
new
BufferedImage(directColorModel, raster,
true
,
null
);
3
、与方法
2
基本相同,唯一差别在于使用了
SampleModel
int
width = 640, height = 480;
int
size = width * height;
int
[] pixels =
new
int
[size];
// 24
位色彩模型
DirectColorModel directColorModel =
new
DirectColorModel(24, 0xFF0000,
0xFF00, 0xFF);
//
以
SinglePixelPackedSampleModel
构建像素包
SampleModel sample =
new
SinglePixelPackedSampleModel(
DataBuffer
.
TYPE_INT
, width, height,
new
int
[] {
0xFF0000,
0xFF00, 0xFF });
//
生成
DataBuffer
DataBuffer
dataBuffer =
new
DataBufferInt(pixels, size);
//
以
SampleModel
及
DataBuffer
生成
WritableRaster
WritableRaster raster = Raster.createWritableRaster
(sample,
dataBuffer,
new
Point(0, 0));
//
生成
BufferedImage
BufferedImage image =
new
BufferedImage(directColorModel, raster,
true
,
null
);
实际上,虽然表面上有所不同,但无论您采用以上何种方式获得
BufferedImage
及其对应的像素集合(
PS:
此处并非一定要获得像素的
int[]
形式,如
short[]
、
byte[]
等各式亦可,请根据实际需求决定),
pixels
对您而言都将成为一块保存有图像数据的内存区域,针对此
pixels
进行的任何修改,都将被直接反馈于
BufferedImage
之上。
得到了像素集合,我们又该如何将其应用到
Java2D
中呢?下面,我将介绍两个像素级
Java
渲染组件给大家参考。下面我们所使用到的一切操作,也都将围绕
pixels
这个以
int[]
形式出现的数组展开。
一、
古董级的
Processing
项目地址:
http://processing.org/
这是一套完整的,开源的,兼顾
2D
与
3D
方面的
Java
渲染组件。事实上,
Processing
在针对
Java2D
性能优化上的意义并不太大,因为它本来就不是为了解决性能问题而出现的。
Processing
所做的,更多的是一种效果优化,一种对
Java
语言的延伸。它希望人们能利用它对
Java
的扩充,以简单高效的方式实现绚丽夺目的图形效果。应该说,
Processing
将
Java
的语法简化并将其运算结果
“
感官化
”
,让使用者能很快享有声光兼备的交互式多媒体作品。
由于
Processing
运行于
PApplet
之上,而
PApplet
继承自
Applet
。也就是说原本的
Processing
也是一种小程序,如果我们要将它应用在网页环境之外,要们就将
PApplet
插入到
Frame/JFrame
当中,要么就将其改写。
为了未来的演示更加方便,笔者选择了改写的道路,将其
PGraphics
渲染层直接封装。以下,是一个已经替换为
Processing
渲染的
LGame
示例:
二、
新生代的
PulpCore
项目地址:
http://www.interactivepulp.com/pulpcore/
事实上,
PulpCore
在国外的
Java
圈中也算颇有名气,甚至连某位
JavaFX
开发者都曾以它和自己的项目作过比较。如果有朋友泡过
http://www.javagaming.org/
,想必应该知道,如果你在该论坛中寻求
Java
游戏框架,那么
3D
方面的优先推荐必然是
JME
,
2D
方面的优先推荐绝对是
Slick2D
,至于网页游戏开发方面,则必属
PulpCore
无疑。
在以
OpenGL
为绝对主流的
javagaming
上,一款以标准
Java2D
开发的框架,居然会受到如此推崇,
PulpCore
的技术价值我们可想而知。
下图为
PulpCore
提供的应用示例:
PS
:虽然
PulpCore
所提供的示例多为小游戏,但该作者曾反复强调,
PulpCore
是一个开源的
2D
渲染和动画处理框架。
与
Processing
一样,启动
PulpCore
的
CoreApplet
继承自
Applet
,所以
PulpCore
依旧属于
Applet
实现,也就是默认情况下只能运行于网页之上。但相对于标准
Applet
应用,
PulpCore
却做了更多的优化,尤其注重用户体验与动画效果。应该说,
Pulpcore
是目前为止笔者所见过的,在不损失图像色彩的情况
下最高效的
Java2D
解决方案。
关于图像渲染部分,
PulpCore
中有对应于标准
Java2D
的
Graphics
类,名为
CoreGraphics
。其中对像素级操作进行了必要的封装,也基本参照标准
Java2D API
命名。(
PS
:具体留待下节讲解,目前请自行参考其源码)不过,或许是方便模块化管理的缘故,
CoreGraphics
默认情况下并不对外开放,而被统一封装在
PulpCore
所提供的各种精灵类里。
如果您想要获得
CoreGraphics
进行修改,要么请重载
Sprite
的
draw(
需要
super.draw
一下,否则会覆盖到基础操作
)
,要么请在
Scene2D
中重载
drawScene
(需要
super.drawScene
一下,否则会覆盖到基础操作),
PulpCore
并没有直接提供给您。对于仅想进行简单图形绘制的用户而言,这不得不说是一个小小的不足。
另外,虽然
PulpCore
也有对应于
Font
的
CoreFont
类,但相比于
Processing
的字体绘制方案,它明显寒酸了很多。
实际上,
PulpCore
中的
CoreFont
只是一个分图管理器,由用户导入一张由英文字母及各种符号组成的图像,而
CoreFont
负责分配不同的图像对应不同的字母绘制。这意味着,如果您不自行扩充其
CoreFont
部分,那么
PulpCore
将绝对无法支持中文输入及显示。(
PS
:目前来说,最偷懒的方法就是将
Processing
中的
PFont
和
text
部分直接“移植”到
CoreGraphics
中使用。毕竟两者都是操作像素绘制图像,很好
copy
……)
针对
PulpCore
的
CoreGraphics
,笔者也提供了一个
LGame
的替代封装,以方便后文讲解分析其渲染方式之用。
示例源码:
另外笔者还要补充一点,那就是
PulpCore
虽然提供了较为完善的“脏绘”机制,却必须和
Sprite
一起使用才能看到效果(被封装到了
Sprite
的
draw
函数里,所以单就渲染速度而言,在
PulpCore
中使用精灵绘图反而比不用更快)。
从下文开始,笔者将以
PulpCore
为基础,逐步讲解
Java
像素级渲染框架的设计与实现。
以下为以PulpCore与Processing进行渲染的LGame实验工程:
http://loon-simple.googlecode.com/files/Pixels-LGame.7z
——————————————————————
心理学上有一个名词叫做
The Halo Effect
,也就是俗称的晕轮效应或者说“刻板印象”。在这种心理现象影响下,很多人往往会将某种事物的“第一印象”当作终身的准则,而无视其实际究竟是怎样的。
其实对于
Java
桌面或网页应用而言,性能上的问题早就已经算不得什么问题。只要人们稍微留心一下,就会发现
Java
在游戏开发方面,至少在网页游戏方面完全可以比某些东西作的更好,更强,更复杂,更快捷,也更稳定。真正关键的,反倒是那些听信了流言蜚语的人们对于
Java
性能上的误解,以及食古不化的偏见,才是真正制约
Java
发展的拦路虎。
所谓积重难返,要想扭转这种顽固偏见,只凭小弟一人是绝对不足够的,还要靠各位
Java
同仁的努力。
都说“荒田无人耕,耕开有人争”,可都等着别人耕田,毕竟太慢,始终没有自己动手那么快捷。毕竟只有将
Java
做大做强,各位同仁才能有更多的出路,更好的待遇,以及更多的
Money
好赚……
分享到:
相关推荐
———————————————————————— 在学习完JavaSE基础后有幸观看了马士兵老师的坦克大战教学视频,并打算沿着马老师的思路写一款小游戏来巩固JavaSE知识。 本游戏的想法来源于小时候在4399玩的一款...
JavaSE教程——布小禅.docx,专门为零基础人设计的教程
JavaSE实战,java网络编程,文件,TCP等知识,适合java练手小项目
gui开发聊天页面。网络编程。UDP。图形化界面、发送i、记录、清屏、震动、聊天快捷键。(已优化)
这里总共有201道题,是自己亲手做完了的,希望有机会帮助更多的人,愿意跟大家分享也是觉得Java有开源的的特点,所以希望大家下载下来,能有所帮助!很不错的题,难度中偏上,希望大家还可以分享给别的小伙伴
下面小编就为大家带来一篇浅谈javaSE GUI (Action事件)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
下面小编就为大家带来一篇浅谈javaSE 面向对象(Object类toString)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
JavaSE JFC技术 (AWT + Swing + Graphics2D):完全不改变原生Swing代码,换肤。
本资源以思维导图的方式,罗列了javase基础及高级部分的知识点,剔除了一些java常识的知识点,只保留了易错、易混点,并提供了部分使用方法。适合初学者作为复习使用。
Java基础每日复习笔记-JavaSE高级阶段.edf
JavaSE基本知识 适合新手入门 JavaSE基本知识 适合新手入门 JavaSE基本知识 适合新手入门
javaSE
数据库课程设计1、JavaSE:Java入门.pdf1、JavaSE:Java入门.pdf1、JavaSE:Java入门.pdf1、JavaSE:Java入门.pdf1、JavaSE:Java入门.pdf1、JavaSE:Java入门.pdf1、JavaSE:Java入门.pdf1、JavaSE:Java入门.pdf1...
17.1.2 JavaSE 5.0中固定尺寸线程池的基本知识 374 17.1.3 自定义尺寸固定线程池的使用 375 17.1.4 单任务线程池的使用 377 17.1.5 可变尺寸线程池的使用 378 17.1.6 延迟线程池的使用 380 17.1.7 使用...
整理笔记的时候发现以前写的单利设计模式的文章,贴出来给大家分享下!有需要的小伙伴可以来参考下
Java基础每日复习笔记-JavaSE高级阶段.2020-10-13-211312.edf
JAVASE 进阶篇——web资料整理
JavaSE基础学习笔记 JavaSE基础学习笔记 JavaSE基础学习笔记
ocp甲骨文认证专业javase8程序员二级考试学习指南 ocp oracle certified professional javase8 programmer ii study guide exam 1z0_809
三年JavaEE开发积累的那些代码之一:JavaSE篇 有什么? 1)自己写的例子:或是为了项目中应用写的demo,或是为了学习某项技术写的demo。 2)网上下载的例子:或改过或没改过,或完善过或原封没动。 没什么? 1)公司...