雪の猫
2级
 
UID 459
精华
0
积分 0
帖子 102
阅读权限 10
注册 2006-10-18
状态 离线
|
[翻译计划]DMF Tutorial
原文在(以及后两篇)
http://psyark.jp/?entry=20050924181651
中日对照:[attach]121[/attach]
http://psyark.jp
DisplacementMapFilter教程
DisplacementMapFilter是利用位图来操作位图的滤镜。前者的位图一般叫做映射位图(mapBitmap) 这个词因为长,我决定用一个简短的名字。(在日语中mapBitmap是マップビットマップ,确实很长,是但是中文却不是。为了方便,翻译的人在以后中使用英文mapBitmap。)。
输出图象的各象素的值,是根据mapBitmap相同位置的象素,采用源位图上的某一象素来确定的。
任意点(x,y)对应的输出图象的象素可以由下面的公式表示。
m[y][x],d[y][x],s[y][x]分别是mapBitmap,输出图象,输入图象上(x,y)象素的值。
var dx = (getComponent(m[y][x], componentX) - 0x80) * scaleX / 0x100;
var dy = (getComponent(m[y][x], componentY) - 0x80) * scaleY / 0x100;
d[y][x] = s[y + dy][x + dx];
function getComponent(color32:Number, component:Number):Number {
switch (component) {
case 1 : return 0xFF & color32 >> 16;
case 2 : return 0xFF & color32 >> 8;
case 4 : return 0xFF & color32;
case 8 : return 0xFF & color32 >> 24;
}
}
下面演示一些代表性的例子。componentX, componentY是8(alpha通道)以外的数字
·mapBitmap是50%灰度(0x808080)的情况,输出图象和输入图象相同。
·mapBitmap是黑色(0x000000)的情况,输出图象对输入图象有(scaleX / 2, scaleY / 2)的偏移。
·mapBitmap是白色(0xFFFFFF)的情况,输出图象对输入图象有(-scaleX / 2, -scaleY / 2)的偏移。(实际上是0x7F / 0x100,所以有一些误差)。
鼠标在输出图象上移动时,可以看到究竟采用的是输入图象的哪一点。
不支持上传swf --b err......我有办法 ^_^
Flash: http://www.flashseer.org/bbs/attachment.php?aid=114
--------------------------------------------------------
利用上一次讲到的DisplacementMapFilter的性质,这次考虑的是如何生成能够完成期望变形的那个mapBitmap。
[attach]115[/attach]
这里,我们用把圆柱投影的世界地图的半面映射为地球仪的变形作为例子。输出图象上的地球的每一个象素,都对应输入图象上的一个象素,所以应该先把函数关系式表示出来。
[attach]116[/attach]
这一张是用经纬线表示的地图。从图中看到,几乎所有的经线都在变形后弯曲,而所有的纬线在变形之后还是直线。就是说可以判断,输出图象中Y相同的点,在输入图象中也有相同的Y。所以,变形前后的Y与X无关。
那让我们先从纬度(Y轴)方向的对应开始考虑。
下图表示的是输出图象的左视图。图中的蓝色曲线代表输入图象的映射。
[attach]117[/attach]
那么,试着把输入图象上任意的Y对应的输出图象的Y坐标(Y')的公式表示出来。
从图上Y的1/3处(输入图象的上1/3)入手。这可以用北纬PI/6表示。这时到赤道的距离是sin(PI/6),所以输出图象上的Y坐标(Y')是(1 - sin(PI / 6)) / 2
一般化得到 Y' = (1 - sin(PI * (0.5 - Y))) / 2
我们是根据“输出图象的某一坐标,是输入图象的哪一个坐标”来确定mapBitmap,所以把式子中Y作为函数解得:
Y = 0.5 - asin(1 - Y' * 2) / PI
而mapBitmap要使用的值是Y到 Y' 的距离,移项得:
yd = 0.5 - asin(1 - Y' * 2) / PI - Y'
然后就能写出Y轴方向变换的代码了。来试用一下滤镜吧。
import flash.display.BitmapData;
import flash.filters.DisplacementMapFilter;
import flash.geom.Rectangle;
Stage.scaleMode = "noScale";
//为了以后使用方便,宽高用变量表示
var W:Number = 160;
var H:Number = 160;
var earth:BitmapData = BitmapData.loadBitmap("earth");
var map:BitmapData = new BitmapData(W, H, false);
var out:BitmapData = map.clone();
for (var y = 0; y < H; ++y) {
var yr = y / H; // 图象高度对应的Y的比例
var lat = Math.asin(1 - yr * 2); // 纬度
var yd = 0.5 - lat / Math.PI - yr; // 变形前后Y坐标的比例的差
var yc = Math.round(yd * 0x100) + 0x80;
map.fillRect(new Rectangle(0, y, W, 1), yc);
}
var disp:DisplacementMapFilter = new DisplacementMapFilter(map, null, 1, 4, 0, H);
out.applyFilter(earth, map.rectangle, null, disp);
attachBitmap(out, 0);
第13,14行使用了上面讲到的公式。
Flash: http://www.flashseer.org/bbs/attachment.php?aid=119
运行结果
Y轴方向的变换成功了。
因为文章较长,X周方向的变换下一次再讲。
※地球的图象是NASA的the Blue Marble经过同意使用
使用NASA网站的图象参照Guide line。
------------------------------------
接着上一次讲解DisplacementMapFilter的mapBitmap的制作方法。
上一次我们得到了Y轴方向的变换公式。
Y = 0.5 - asin(1 - Y' * 2) / PI
yd = 0.5 - asin(1 - Y' * 2) / PI - Y'
X轴方向的变换也按照这样来求取。
[attach]118[/attach]
图是输出图象的俯视图。蓝色的曲线表示赤道。
和上一次一样,输入图象的赤道上的点X和输出图象赤道上的点X',以及两点距离xd的关系式可以表示为
X' = (1 - cos(PI * X)) / 2
X = acos((0.5 - X') * 2) / PI
xd = acos((0.5 - X') * 2) / PI - X'
纬线的长度因为要随着纬度变高而变短,对于赤道,纬线的长度ew是:
X' = (1 - cos(PI * X) * ew) / 2
X = acos((0.5 - X') / ew * 2) / PI
xd = acos((0.5 - X') / ew * 2) / PI - X'
又,ew可以用式 cos(纬度) 得到,参考上一次的方法:
ew = cos(asin(1 - Y' * 2))
这样输出图象上任意一点的坐标(X', Y')所对应的输入图象的坐标(X,Y)就可以用公式表达出来了。
用下面的代码放到上一次的Actionscript代码中(替换掉11-18)。
for (var y = 0; y < H; ++y) {
var yr = y / H; // 图象高度对应Y坐标的比例
var lat = Math.asin(1 - yr * 2); // 纬度
var yd = 0.5 - lat / Math.PI - yr; // 变形前后的Y坐标比例的差
var yc = Math.round(yd * 0x100) + 0x80 << 8; // mapBitmap的Y分量
var ew = Math.cos(lat); //纬线长
for (var x = 0; x < W; ++x) {
var xr = x / W; //图象宽对应X坐标的比例
var xd = Math.acos((0.5 - xr) / ew * 2) / Math.PI - xr; //变形前后的X坐标比例的差
var xc = Math.round(xd * 0x100) + 0x80; //mapBitmap的X分量
map.setPixel(x, y, yc | xc);
}
}
var disp:DisplacementMapFilter = new DisplacementMapFilter(map, null, 4, 2, W, H);
不过代码没有考虑地球仪所在圆外边的部分,圆的外边还残留有图象。
那么,圆外(0.5 - xr) / ew * 2不在-1~1的区间,所以可以利用Math.acos返回NaN。
for (var x = 0; x < W; ++x) {
var xr = x / W; // 图象宽对应X坐标的比例
var lap = Math.acos((0.5 - xr) / ew * 2); // 经度
if (isNaN(lap)) { //如果经度得不到,说明点在圆外
var xc = xr > 0.5 ? 0xFF : 0; // 为mapBitmap的X分量指定一个最大数
} else {
var xd = lap / Math.PI - xr; //变形前后的X坐标比例的差
var xc = Math.round(xd * 0x100) + 0x80; // mapBitmap的X分量
}
map.setPixel(x, y, yc | xc);
}
然后,输入图象上得不到坐标点被涂黑了,所以要改一下滤镜的参数
var disp:DisplacementMapFilter = new DisplacementMapFilter(map, null, 4, 2, W, H, "color", 0x000000);
这样滤镜就完成了。大家辛苦了。
Flash: http://www.flashseer.org/bbs/attachment.php?aid=120
运行结果
跋
mapBitmap中各分量在0x00~0xFF的范围应尽量有效的使用,这样可以提高画质量。
以下的的步骤在这一次省略了,但是都很简单请尝试:
让mapBitmap的X分量以0x80为中心变成原来的N倍
让mapBitmap的scaleX被N除
N太大导致超出0x00~0xFF的范围的调整
附件的rar文件其实都是swf文件,如果要下载的话,得改名:p
[ 本帖最后由 雪の猫 于 2007-3-15 00:47 编辑 ]
附件: 您所在的用户组无法下载或查看附件
|
|