第十六章介绍了所有建立 3D 立体模型的基础包括:如何创建点,线,用多边形组成
各种形状,以及如何为每个多边形设置颜色。但是,我们只能让颜色的透明度为 50%,才
能看到正确的效果。虽然制作出的 3D 立体模型也不差,但是这样做在真实度上还是有所
欠缺。
但这次要从多边形的角度重新审视它) ,以及 3D 灯光,来修正这个问题。
后,我们将能够创建出看上去非常真实的 3D 立体模型。使用 3D 灯光可以让它们变得活
灵活现。
Macromedia Flash MX Studio 一书第十章的技术。(Todd 也是本书的技术评论员,因此这就
不像未经他人允许剽窃过来的! )Macromedia Flash MX Studio 也许是我所能找到最适合本
章的专用材料,每当我要用到这些高级 3D 技术时都会去查阅它。因为我们这本书已经有
四年多的时间了(按照技术书籍来算,是本古老的书了)跨越了 Flash 的三个版本,我非
常愿意能够让本书的内容继续发展下去并且能够与时俱进。
只要有一点点错误都会非常明显,当一切都正确时获得的效果会是超级棒的哟!
背面剔除
制绘制的顺序。因此一个处于模型后面的多边形绘制在了模型的前面,
果。在集中讨论建模基础技术时,为了将这一部分的讨论向后推迟,我们将所有多边形的透
明度都设置为 50%。现在到了该处理它的时候了。
术正如其名。大家应该还记得每个多边形的点都是顺时针安排的。
全没有必要的,但是我们即将看到它为什么如此重要,
习惯。
都将是逆时针排列的。
以我常常混淆这两个词。多数情况下,我会使用“多边形”作为一个总称,而“三角形”只
是一个特殊的有三个角的多边形)。
图 17-1 一个面向我们的三角形的点以顺时针方向排列
在图 17-2 中,我将这个三角形进行了旋转,看到了它相反的方向。
图 17-2 一个背对我们的三角形以逆时针方向排列
形的外部面面向我们。
是在讨论 3D 立体模型。这样一来,每个多边形都有一个外部表面和一个内部面。
x, y, z 坐标,而是经过透视后确定的 screenX, screenY 坐标。
让一个顺时针系统背对我们。每种方式都可以像平时一样工作。
题。这个问题对于眼睛来说非常好回答,但是要它们变为代码,立即就变成了一个非常抽象
的概念。
一定是尽可能最好的方法,
运行正常的程序放到一起,结果我的方法比现有的解决方法要长上两倍,并且非常复杂,其
中还包括了坐标旋转和其它三角函数。
的解决方法。
入 Triangle 类,名为 isBackFace。它会求出三角形三个点的值,如果是逆时针的方向则返
回 true 如果顺时针则返回 false。以下是加入了该函数的类。同样注意在调用 beginFill 时
不再使用 .5 的透明度了。现在开始我们就要绘制不透明的形状了。
package {
}
Macromedia Flash MX Studio 中推荐了这个网站,所以我将它发给大家。除此之外,这个网
站还有一些很棒的参考资料以及类似主题的指南。
诉我们多边形是向哪个方向运动的。如果您对它的实现很感兴趣,就去看看刚才说到的那个
网站,或者搜索一下“背面剔除”["Backface culling"]。我保证您会找到大量的阅读资料。
现在,只能说它是个简单、快速、高效并且 100% 可以使用的函数,将它放在这里即可!
味着只有在 Triangle 类中才能调用它。它可以作为 draw 方法的第一句被调用。如果返回
值为 true,则三角形是背面,不应绘制,因此 draw 方法停止并返回。如果 isBackFace 返
回值为 false,三角形是正面,则像平常一样被绘制出来。
一点点变化。 当旋转这个模型时,我们看到一旦某个面转向了反方向,那么它将不再被绘制。
目前为止效果还不够好,因为这里仍然有些离得远的部分绘制在了距离较近的部分的前面,
不过这是我们曾经学过的。如果“z 排序”或“深度排序”一词出现在您的脑海里,那就对
了。这就是下一节的内容。
图 17-3 运行中的背面剔除
深度排序
子中,为一个 Sprite 影片数组通过 zpos 属性进行排序(确切地说是一个拥有 3D 属性的
Sprite 的子类)。
行绘制。因此,无论何时绘制多边形,它都将绘制在前一个图形的上面。与其去改变它们的
深度,不如决定何时绘制这个多边形。具体来讲,我们要先绘制距离最远的;然后再依次绘
制其余的,这样最近的多边形会在最后被绘制,盖住所有可能放到前面的图形。
triangles,从第 0 个到最后一个依次绘制每个三角形。我们要做的就是将这个数组进行排序
以便让最远的三角形为数组的第 0 个元素,距离观察者最近的成为最后一个元素。
的集合。它们没有一个专门描述整个三角形深度的属性。但是要创建这个属性也相当容易。
计算这个值最好的方法就是找到三角形的三个点里面最小的 z 值。换句话讲,如果一个三
角形的三个顶点深度分别为 200, 250, 300,就可以说这个三角形的 z 坐标为 200。我们可
以使用 Math.min 方法来确定三个点最小的 z 值。但需要使用两次,因为一次只能传入两
个数值。我们将在 Triangle 类的 depth 方法中进行实现。以下是更新的类:
package {
private function isBackFace():Boolean {
}
public function get depth():Number {
}
数组降序排列,让深度最高的(最远的)在第一个。这个操作要在文档类中完成,并且在绘
制三角形之前进行。这里我使用 ExtrudedA 类,因此这个类的 onEnterFrame 方法像是这
样:
private function onEnterFrame(event:Event):void {
}
这里只需改变一行代码。面向对象编程的奇迹让我们的生活更简单了!
在我们已经真正达到了某种境界。下面将带大家步入最 Cool 的巅峰!
图 17-4 深度排序修正了错误
3D 灯光
些单调。OK,OK,大家看到标题就已经知道了,下面就让我们加入 3D 的灯光效果吧。
的空间讨论每个漂亮的细节,但是通过快速的网络搜索大家可以获得非常更多的相关资料,
也许这些资料多得我们一生也看不完。 在这里,我给大家的都是一些基础的需要用到的函数。
更加复杂的 3D 系统中,它也能够指向某个方向,并且还带有颜色,衰减率(falloff rate),
圆锥区域等等。但是这些都超出了本例的范围。
亮度。
package {
}
现在可以在主类的 init 方法中创建一个新的默认灯光:
或者可以创建一个指定位置和区域的灯光:
这里有两个重要的地方需要注意。一个是位置,仅用于计算灯光的角度。灯光的亮度不会因
为距离而衰减。因此改变 x, y, z 到 –1,000,000 或 -1 对于照射在物体上的灯光的亮度是
没有区别的。
。不会很难,现在已经介绍得差不多了,与物体间的距离来计算灯光的亮度值(brightness)
因此把这个函数留给大家去做。
就是这个原因,我创建了一个私有属性 _brightness,并允许通过公共的 getter 和 setter 访
问 brightness。这样做,允许我们传入的数值得到有效性的验证,确保这个数在有效范围内。
getter 和 setter 函数才能访问。这里我抄了近路,为的是让代码简洁并突出动画编程的原则。
但是在本例中,额外添加的这一步是有必要的。
此如果一个多边形直接面对灯光,它就会显示出全部的颜色值。当离开灯光时,就会变得越
来越暗。最终,当它完全离开光源时,它将完全变为阴影或黑色。
形只需访问这个 light 就可以实现自己 draw 函数。因此,让我们给所有三角形一个 light
属性。我还要超个近路设置它们为公有属性:
然后在主类中,创建这些三角形后,只需要循环它们把灯光的引用赋值给每个三角形:
var light:Light = new Light();
for(i = 0; i < triangles.length; i++) {
}
或者,我们也可以让 light 作为 Triangle 构造函数中的一个附加的参数,让每个三角形都
有一个光源。我将这个方法留给大家去选择。
色值。以下是这个函数:
function getAdjustedColor():uint {
}
后调用另一个方法 getLightFactor,稍后会看到这个函数。现在,只需要知道它返回的是 0.0
到 1.0 之间的一个数, 表示该颜色需要改变的大小,1.0 表示全部亮度, 0.0 表示为全黑色。
的颜色值,并且将它作为调整后的颜色返回。它将成为灯光照射下三角形的颜色。
private function getLightFactor():Number {
}
但是我也会试将基础的地方解释一下。
垂线,如图 17-5 所示。想象一下,我们拿着一块木制的三角板,然后从背后钉入一根钉子,
它会从正面穿出。这根钉子就代表三角形平面的法线。如果您学过 3D 渲染和灯光的话,
一定看过各种关于法线的资料。
图 17-5 法线是到达三角形表面的一条垂线
两个向量的积是一条垂直于这两条向量的新向量。我们将使用的这两条向量是点 A 和 B,
点 B 和 C 之间的连线。每个向量都用有带有 x, y, z 的 Object 持有。
var ab:Object = new Object();
ab.x = pointA.x - pointB.x;
ab.y = pointA.y - pointB.y;
ab.z = pointA.z - pointB.z;
var bc:Object = new Object();
bc.x = pointB.x - pointC.x;
bc.y = pointB.y - pointC.y;
bc.z = pointB.z - pointC.z;
然后计算法线,即另一个向量。求该对象的模(norm)。下面的代码用于计算向量 ab 和 bc
的外积:
var norm:Object = new Object();
norm.x = (ab.y * bc.z) - (ab.z * bc.y);
norm.y = -((ab.x * bc.z) - (ab.z * bc.x));
norm.z = (ab.x * bc.y) - (ab.y * bc.x);
我没有太多的篇幅来介绍这种计算方法的细节,这是计算向量外积的标准公式。如果您对它
的推导感兴趣,可以随便找一本线性代数的正规参考书查一查。
它与外积不同。我们有了法线的向量和灯光的向量。下面计算点积:product)
我们看到,内积要比外积简单一些!都差不多了!接下来,计算法线的量值,以及灯光的量值,
大家应该还认识这个 3D
版的勾股定理吧:
var normMag:Number = Math.sqrt(norm.x * norm.x + norm.y * norm.y + norm.z * norm.z);
var lightMag:Number = Math.sqrt(light.x * light.x + light.y * light.y + light.z * light.z);
请注意,当一个三角形被渲染时,变量 lightMag 每次都要进行计算,这样就允许灯光是移
动的。如果知道光源是固定的,我们可以在代码的一开始就加入这个变量,只需在创建灯光
或为三角形赋值时进行一次计算。或者可以为 Light 类添加 lightMag 属性,让它可以在每
次 x, y, z 属性发生变化时被计算。看,我已经给大家留出了各种发挥的空间!
最后,将前面计算出的这些数放入一个具有魔力公式中:
其中 dotProd 是一个分量, normMag * lightMag 是另一个分量。而两者相除得出一个比率。
回忆一下第三章,一个角度的余弦给了我们一个比率,而一个比率的反余弦给了我们一个角
度。这就是灯光照射在多边形表面上的角度。它的范围在 0 到 Math.PI 个弧度之间(0 到
180 度),也就是说灯光完全照射在物体前面上或完全照射在物体背面。
终用于改变底色的滤光系数。
了。我们在 draw 方法中使用它。应该像这样直接使用这个调整后的颜色:
为了把上述内容综合起来,以下是全部最终的 Triangle.as 和 ExtrudedA.as 代码,列出了我
们本章所有发生变化的部分:
首先是 Triangle:
package {
}
private function getLightFactor():Number {
}
}
然后是 ExtrudedA:
package {
public function ExtrudedA() {
}
private function init():void {
}
triangles = new Array();
triangles[0] =new Triangle(points[0], points[1],
points[8], 0xcccccc);
triangles[1] =new Triangle(points[1], points[9],
points[8], 0xcccccc);
triangles[2] =new Triangle(points[1], points[2],
points[9], 0xcccccc);
triangles[3] =new Triangle(points[2], points[4],
points[9], 0xcccccc);
triangles[4] =new Triangle(points[2], points[3],
points[4], 0xcccccc);
triangles[5] =new Triangle(points[4], points[5],
points[9], 0xcccccc);
triangles[6] =new Triangle(points[9], points[5],
points[10], 0xcccccc);
triangles[7] =new Triangle(points[5], points[6],
points[7], 0xcccccc);
triangles[8] =new Triangle(points[5], points[7],
points[10], 0xcccccc);
triangles[9] =new Triangle(points[0], points[10],
points[7], 0xcccccc);
triangles[10] =new Triangle(points[0], points[8],
points[10], 0xcccccc);
triangles[11] =new Triangle(points[11], points[19],
points[12], 0xcccccc);
triangles[12] =new Triangle(points[12], points[19],
points[20], 0xcccccc);
triangles[13] =new Triangle(points[12], points[20],
points[13], 0xcccccc);
triangles[14] =new Triangle(points[13], points[20],
points[15], 0xcccccc);
triangles[15] =new Triangle(points[13], points[15],
points[14], 0xcccccc);
triangles[16] =new Triangle(points[15], points[20],
points[16], 0xcccccc);
triangles[17] =new Triangle(points[20], points[21],
points[16], 0xcccccc);
triangles[18] =new Triangle(points[16], points[18],
points[17], 0xcccccc);
triangles[19] =new Triangle(points[16], points[21],
points[18], 0xcccccc);
triangles[20] =new Triangle(points[11], points[18],
points[21], 0xcccccc);
triangles[21] =new Triangle(points[11], points[21],
points[19], 0xcccccc);
triangles[22] =new Triangle(points[0], points[11],
points[1], 0xcccccc);
triangles[23] =new Triangle(points[11], points[12],
points[1], 0xcccccc);
triangles[24] =new Triangle(points[1], points[12],
points[2], 0xcccccc);
triangles[25] =new Triangle(points[12], points[13],
points[2], 0xcccccc);
triangles[26] =new Triangle(points[3], points[2],
points[14], 0xcccccc);
triangles[27] =new Triangle(points[2], points[13],
points[14], 0xcccccc);
triangles[28] =new Triangle(points[4], points[3],
points[15], 0xcccccc);
triangles[29] =new Triangle(points[3], points[14],
points[15], 0xcccccc);
triangles[30] =new Triangle(points[5], points[4],
points[16], 0xcccccc);
triangles[31] =new Triangle(points[4], points[15],
points[16], 0xcccccc);
triangles[32] =new Triangle(points[6], points[5],
points[17], 0xcccccc);
triangles[33] =new Triangle(points[5], points[16],
points[17], 0xcccccc);
triangles[34] =new Triangle(points[7], points[6],
points[18], 0xcccccc);
triangles[35] =new Triangle(points[6], points[17],
points[18], 0xcccccc);
triangles[36] =new Triangle(points[0], points[7],
points[11], 0xcccccc);
triangles[37] =new Triangle(points[7], points[18],
points[11], 0xcccccc);
triangles[38] =new Triangle(points[8], points[9],
points[19], 0xcccccc);
triangles[39] =new Triangle(points[9], points[20],
points[19], 0xcccccc);
triangles[40] =new Triangle(points[9], points[10],
points[20], 0xcccccc);
triangles[41] =new Triangle(points[10], points[21],
points[20], 0xcccccc);
triangles[42] =new Triangle(points[10], points[8],
points[21], 0xcccccc);
triangles[43] =new Triangle(points[8], points[19],
points[21], 0xcccccc);
var light:Light = new Light();
for (i = 0; i < triangles.length; i++) {
}
}
我还让所有的三角形使用相同的颜色,我认为这样做可以更好地观察灯光效果(见图 17-6)
图 17-6 带有背面剔除,深度排序及 3D 灯光的三维立体模型
(如果要转载请注明出处http://blog.sina.com.cn/jooi,谢谢)