转 第十七章 背面剔除与flash 3D 灯光 (as3.0)

第十六章介绍了所有建立 3D 立体模型的基础包括:如何创建点,线,用多边形组成
各种形状,以及如何为每个多边形设置颜色。但是,我们只能让颜色的透明度为 50%,才
能看到正确的效果。虽然制作出的 3D 立体模型也不差,但是这样做在真实度上还是有所
欠缺。
    本章,通过介绍背面剔除(不绘制背面的多边形) ,深度排序(第十五章作了一点介绍,
但这次要从多边形的角度重新审视它) ,以及 3D 灯光,来修正这个问题。
    我想应用了这三种技术所得到的 3D 模型一定会让大家感到惊讶。学习过前两项技术
后,我们将能够创建出看上去非常真实的 3D 立体模型。使用 3D 灯光可以让它们变得活
灵活现。
    在开始之前,我想先声明一下技术的来源。本章几乎所有的代码都来自 Todd Yard 的
Macromedia Flash MX Studio 一书第十章的技术。(Todd 也是本书的技术评论员,因此这就
不像未经他人允许剽窃过来的! )Macromedia Flash MX Studio 也许是我所能找到最适合本
章的专用材料,每当我要用到这些高级 3D 技术时都会去查阅它。因为我们这本书已经有
四年多的时间了(按照技术书籍来算,是本古老的书了)跨越了 Flash 的三个版本,我非
常愿意能够让本书的内容继续发展下去并且能够与时俱进。
    本章建立在前一章的旋转的、挤出的、3D 字母 A 的基础上。这会使代码非常复杂,
只要有一点点错误都会非常明显,当一切都正确时获得的效果会是超级棒的哟!
背面剔除
    背面剔除在前一章提到很多次了。下面,我们将了解它的一切以及弄清它是如何工作的。
    别忘了,我们早前的模型都是半透明填充的。原因是每次都在绘制多边形,但是没有控
制绘制的顺序。因此一个处于模型后面的多边形绘制在了模型的前面,  造成了一些奇怪的结
果。在集中讨论建模基础技术时,为了将这一部分的讨论向后推迟,我们将所有多边形的透
明度都设置为 50%。现在到了该处理它的时候了。
    总体而言,背面剔除是非常简单的。只绘制面向我们的多边形,不绘制背面的。这个技
术正如其名。大家应该还记得每个多边形的点都是顺时针安排的。  虽然在前面的例子中是完
全没有必要的,但是我们即将看到它为什么如此重要,  以及为什么从一开始就要养成这样的
习惯。
    很明显,如果面向我们的多边形的点是顺时钟安排的,  那么当它们转而背对我们时它们
都将是逆时针排列的。
    如图 17-1 所示一个面向我们的三角形。(因为我们使用的所有多边形都是三角形,所
以我常常混淆这两个词。多数情况下,我会使用“多边形”作为一个总称,而“三角形”只
是一个特殊的有三个角的多边形)。
转 <wbr>第十七章 <wbr>背面剔除与flash <wbr>3D <wbr>灯光 <wbr>(as3.0)

图 17-1 一个面向我们的三角形的点以顺时针方向排列

 

在图 17-2 中,我将这个三角形进行了旋转,看到了它相反的方向。

转 <wbr>第十七章 <wbr>背面剔除与flash <wbr>3D <wbr>灯光 <wbr>(as3.0)
图 17-2 一个背对我们的三角形以逆时针方向排列
      我们已经看到逆时针方向点的排列情况。
      这里有一点需要澄清。首先,我说的一个多边形“向面我们”是什么意思?意思是多边
形的外部面面向我们。  虽然我在展示单一的一个三角形时看起来并不明显,  但是别忘了我们
是在讨论 3D 立体模型。这样一来,每个多边形都有一个外部表面和一个内部面。
      另一点是,在确定顺时针或逆时针方向时,我们是在讨论点的屏幕上的位置。而不是 3D
x, y, z 坐标,而是经过透视后确定的 screenX, screenY 坐标。
      最后,还应该注意到我们可以将整个设置颠倒过来,让一个逆时针多边形系统面向我们,
让一个顺时针系统背对我们。每种方式都可以像平时一样工作。
      回到问题上,如何确定三个点是以顺时针安排还是以逆时针安排?请思考一下这个问
题。这个问题对于眼睛来说非常好回答,但是要它们变为代码,立即就变成了一个非常抽象
的概念。
      刚刚说过我给大家的解决办法来自 Macromedia Flash MX Studio。请相信我提供给你的
一定是尽可能最好的方法,  我也试着用自己写的函数来区分顺时针和逆时针方法。  我将所有
运行正常的程序放到一起,结果我的方法比现有的解决方法要长上两倍,并且非常复杂,其
中还包括了坐标旋转和其它三角函数。  由于我通常不想作恶人,  所以我决定给大家那个简单
的解决方法。  该方法与我的那个极度复杂的方法同样精确!  我们所要做的就是将这个方法加
入 Triangle 类,名为 isBackFace。它会求出三角形三个点的值,如果是逆时针的方向则返
回 true 如果顺时针则返回 false。以下是加入了该函数的类。同样注意在调用 beginFill 时
不再使用 .5 的透明度了。现在开始我们就要绘制不透明的形状了。
package {
 import flash.display.Graphics;
 public class Triangle {
  private var pointA:Point3D;
  private var pointB:Point3D;
  private var pointC:Point3D;
  private var color:uint;
  public function Triangle(a:Point3D, b:Point3D, c:Point3D, color:uint) {
    pointA = a;
    pointB = b;
    pointC = c;
    this.color = color;
  }
  public function draw(g:Graphics):void {
    if (isBackFace()) {
      return;
    }
    g.beginFill(color);
      g.moveTo(pointA.screenX, pointA.screenY);
      g.lineTo(pointB.screenX, pointB.screenY);
      g.lineTo(pointC.screenX, pointC.screenY);
      g.lineTo(pointA.screenX, pointA.screenY);
      g.endFill();
    }
    private function isBackFace():Boolean {
      var cax:Number = pointC.screenX - pointA.screenX;
      var cay:Number = pointC.screenY - pointA.screenY;
      var bcx:Number = pointB.screenX - pointC.screenX;
      var bcy:Number = pointB.screenY - pointC.screenY;
      return cax * bcy > cay * bcx;
    }
  }
}
    我 们 看 到 这 个网站 的 链 接 , http://www.jurjans.lv/flash/shape.html 。 Todd Yard 在
Macromedia Flash MX Studio 中推荐了这个网站,所以我将它发给大家。除此之外,这个网
站还有一些很棒的参考资料以及类似主题的指南。
    快速地解释一下,这个函数计算了三角形两条边的长度,并使用乘法和比较的方式,告
诉我们多边形是向哪个方向运动的。如果您对它的实现很感兴趣,就去看看刚才说到的那个
网站,或者搜索一下“背面剔除”["Backface culling"]。我保证您会找到大量的阅读资料。
现在,只能说它是个简单、快速、高效并且 100% 可以使用的函数,将它放在这里即可!
    那么如何使用这个方法呢?我们并不需要真正去考虑它的实现。这是一个私有方法, 意
味着只有在 Triangle 类中才能调用它。它可以作为 draw 方法的第一句被调用。如果返回
值为 true,则三角形是背面,不应绘制,因此 draw 方法停止并返回。如果 isBackFace 返
回值为 false,三角形是正面,则像平常一样被绘制出来。
    现在可以运行一下 ExtrudedA.as 或其它已经创建的 3D 模型,我们会发现情况发生了
一点点变化。 当旋转这个模型时,我们看到一旦某个面转向了反方向,那么它将不再被绘制。
目前为止效果还不够好,因为这里仍然有些离得远的部分绘制在了距离较近的部分的前面,
不过这是我们曾经学过的。如果“z 排序”或“深度排序”一词出现在您的脑海里,那就对
了。这就是下一节的内容。
    现在我们应该可以看到如图 17-3 所示的效果。

转 <wbr>第十七章 <wbr>背面剔除与flash <wbr>3D <wbr>灯光 <wbr>(as3.0)

图 17-3 运行中的背面剔除

 


深度排序
    深度排序,或叫做 z 排序,我们已经在第十五章为影片应用透视时讨论过。在那个例
子中,为一个 Sprite 影片数组通过 zpos 属性进行排序(确切地说是一个拥有 3D 属性的
Sprite 的子类)。
     而现在我们不是在处理多个影片。所有的多边形都使用主类中相同的 graphics 对象进
行绘制。因此,无论何时绘制多边形,它都将绘制在前一个图形的上面。与其去改变它们的
深度,不如决定何时绘制这个多边形。具体来讲,我们要先绘制距离最远的;然后再依次绘
制其余的,这样最近的多边形会在最后被绘制,盖住所有可能放到前面的图形。
     如何实现呢?在这个名为 triangles 的数组中存着所有多边形。当绘制图形时,循环
triangles,从第 0 个到最后一个依次绘制每个三角形。我们要做的就是将这个数组进行排序
以便让最远的三角形为数组的第 0 个元素,距离观察者最近的成为最后一个元素。
     这与 Sprite 影片数组的排序非常相似。但是本例子中,三角形只是三个 Point3D 对象
的集合。它们没有一个专门描述整个三角形深度的属性。但是要创建这个属性也相当容易。
计算这个值最好的方法就是找到三角形的三个点里面最小的 z 值。换句话讲,如果一个三
角形的三个顶点深度分别为 200, 250, 300,就可以说这个三角形的 z 坐标为 200。我们可
以使用 Math.min 方法来确定三个点最小的 z 值。但需要使用两次,因为一次只能传入两
个数值。我们将在 Triangle 类的 depth 方法中进行实现。以下是更新的类:
package {
 import flash.display.Graphics;
 public class Triangle {
  private var pointA:Point3D;
  private var pointB:Point3D;
  private var pointC:Point3D;
  private var color:uint;
  public function Triangle(a:Point3D, b:Point3D, c:Point3D, color:uint) {
    pointA = a;
    pointB = b;
    pointC = c;
    this.color = color;
  }
  public function draw(g:Graphics):void {
    if (isBackFace()) {
      return;
    }
    g.beginFill(color);
    g.moveTo(pointA.screenX, pointA.screenY);
    g.lineTo(pointB.screenX, pointB.screenY);
    g.lineTo(pointC.screenX, pointC.screenY);
    g.lineTo(pointA.screenX, pointA.screenY);
    g.endFill();
  }
private function isBackFace():Boolean {
  // 见 http://www.jurjans.lv/flash/shape.html
  var cax:Number = PointC.screenX - PointA.screenX;
  var cay:Number = PointC.screenY - PointA.screenY;
  var bcx:Number = PointB.screenX - PointC.screenX;
  var bcy:Number = PointB.screenY - PointC.screenY;
  return cax * bcy > cay * bcx;
}
public function get depth():Number {
  var zpos:Number = Math.min(PointA.z, PointB.z);
      zpos = Math.min(zpos, PointC.z);
      return zpos;
    }
  }
}
       现在我们就能够知道哪个在前哪个在后了,可以为 triangles 数组排序了。同样,要让
数组降序排列,让深度最高的(最远的)在第一个。这个操作要在文档类中完成,并且在绘
制三角形之前进行。这里我使用 ExtrudedA 类,因此这个类的 onEnterFrame 方法像是这
样:
private function onEnterFrame(event:Event):void {
  var angleX:Number = (mouseY - vpY) * .001;
  var angleY:Number = (mouseX - vpX) * .001;
  for (var i:uint = 0; i < points.length; i++) {
    var point:Point3D = points[i];
    point.rotateX(angleX);
    point.rotateY(angleY);
  }
  triangles.sortOn("depth", Array.DESCENDING | Array.NUMERIC);
  graphics.clear();
  for (i = 0; i < triangles.length; i++) {
    triangles[i].draw(graphics);
  }
}
这里只需改变一行代码。面向对象编程的奇迹让我们的生活更简单了!
    运行这个程序后,我们将看到了一个被完美渲染出来的立体模型,如图 17-4 所示。现
在我们已经真正达到了某种境界。下面将带大家步入最 Cool 的巅峰!


转 <wbr>第十七章 <wbr>背面剔除与flash <wbr>3D <wbr>灯光 <wbr>(as3.0)

图 17-4 深度排序修正了错误
3D 灯光
     刚刚这个例子近乎可以让我们的渲染达到完美的效果,  但是它似乎还缺少点儿什么。有
些单调。OK,OK,大家看到标题就已经知道了,下面就让我们加入 3D 的灯光效果吧。
     同背面剔除一样,3D 灯光的细节也是相当复杂并且需要数学运算的。我实在没有太多
的空间讨论每个漂亮的细节,但是通过快速的网络搜索大家可以获得非常更多的相关资料,
也许这些资料多得我们一生也看不完。 在这里,我给大家的都是一些基础的需要用到的函数。
     首先,需要一个光源。一个最简单的光源只有两个属性:位置和亮度(brightness)。在
更加复杂的 3D 系统中,它也能够指向某个方向,并且还带有颜色,衰减率(falloff rate),
圆锥区域等等。但是这些都超出了本例的范围。
     让我们从制作一个 Light 灯光类开始。它会持有我们刚说的那两个属性 —— 位置和
亮度。
package {
  public class Light {
    public var x:Number;
    public var y:Number;
    public var z:Number;
    private var _brightness:Number;
    public function Light(x:Number = -100,
    y:Number = -100,
    z:Number = -100,
    brightness:Number = 1) {
      this.x = x;
      this.y = y;
      this.z = z;
      this.brightness = brightness;
    }
    public function set brightness(b:Number):void {
      _brightness = Math.max(b, 0);
      _brightness = Math.min(_brightness, 1);
    }
    public function get brightness():Number {
      return _brightness;
    }
  }
}
现在可以在主类的 init 方法中创建一个新的默认灯光:
      var light:Light = new Light();
或者可以创建一个指定位置和区域的灯光:
      var light:Light = new Light(100, 200, 300, .5);
这里有两个重要的地方需要注意。一个是位置,仅用于计算灯光的角度。灯光的亮度不会因
为距离而衰减。因此改变 x, y, z 到 –1,000,000 或 -1 对于照射在物体上的灯光的亮度是
没有区别的。
      只有 brightness 属性才会改变灯光的特性。我们当然可以加入一个函数用以判断灯光
。不会很难,现在已经介绍得差不多了,与物体间的距离来计算灯光的亮度值(brightness)
因此把这个函数留给大家去做。
      brightness 必需是 0.0 到 1.0 之间的数。如果出了这个范围,会带来一些奇怪的结果。
就是这个原因,我创建了一个私有属性 _brightness,并允许通过公共的 getter 和 setter 访
问 brightness。这样做,允许我们传入的数值得到有效性的验证,确保这个数在有效范围内。
      一个理想的类不应该出现公有的属性,即使这些属性不需要验证,也只有私有属性通过
getter 和 setter 函数才能访问。这里我抄了近路,为的是让代码简洁并突出动画编程的原则。
但是在本例中,额外添加的这一步是有必要的。
      下面,光源要做的就是根据灯光照射在多边形上的角度来改变三角形颜色的亮度值。因
此如果一个多边形直接面对灯光,它就会显示出全部的颜色值。当离开灯光时,就会变得越
来越暗。最终,当它完全离开光源时,它将完全变为阴影或黑色。
      由于 Triangle 类的成员知道自己的颜色是什么,并知道如何绘制自己,似乎每个三角
形只需访问这个 light 就可以实现自己 draw 函数。因此,让我们给所有三角形一个 light
属性。我还要超个近路设置它们为公有属性:
      public var light:Light;
然后在主类中,创建这些三角形后,只需要循环它们把灯光的引用赋值给每个三角形:
var light:Light = new Light();
for(i = 0; i < triangles.length; i++) {
  triangles[i].light = light;
}
或者,我们也可以让 light 作为 Triangle 构造函数中的一个附加的参数,让每个三角形都
有一个光源。我将这个方法留给大家去选择。
      现在,Triangle 需要一个关于其灯光颜色、角度、亮度的函数,并返回一个调整后的颜
色值。以下是这个函数:
function getAdjustedColor():uint {
  var red:Number = color >> 16;
  var green:Number = color >> 8 & 0xff;
  var blue:Number = color & 0xff;
  var lightFactor:Number = getLightFactor();
  red *= lightFactor;
  green *= lightFactor;
  blue *= lightFactor;
  return red << 16 | green << 8 | blue;
}
      这个函数首先将三角形的基本颜色分为了 red, green, blue 三个成分(见第四章)。然
后调用另一个方法 getLightFactor,稍后会看到这个函数。现在,只需要知道它返回的是 0.0
到 1.0 之间的一个数, 表示该颜色需要改变的大小,1.0 表示全部亮度, 0.0 表示为全黑色。
      然后将每个颜色成分乘以这个滤光系数 (light factor) 最后再将它们组合为一个 24 位
的颜色值,并且将它作为调整后的颜色返回。它将成为灯光照射下三角形的颜色。
  现在,如何得到这个 lightFactor 呢?让我们看一下:
private function getLightFactor():Number {
  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;
  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);
  var dotProd:Number = norm.x * light.x +
  norm.y * light.y +
  norm.z * light.z;
  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);
  return Math.acos(dotProd / normMag * lightMag) / Math.PI * light.brightness;
}
      哇,好大一个函数不是吗?要想完全理解它,就一定要对高等向量学有较深的掌握,
但是我也会试将基础的地方解释一下。
      首先,我们需要找到三角形的法线(normal)。它是一个向量,是三角形平面上的一条
垂线,如图 17-5 所示。想象一下,我们拿着一块木制的三角板,然后从背后钉入一根钉子,
它会从正面穿出。这根钉子就代表三角形平面的法线。如果您学过 3D 渲染和灯光的话,
一定看过各种关于法线的资料。


转 <wbr>第十七章 <wbr>背面剔除与flash <wbr>3D <wbr>灯光 <wbr>(as3.0)
图 17-5 法线是到达三角形表面的一条垂线
     我们可以通过该平面的两个向量计算出它们的外积(cross product)从而求出这条法线。
两个向量的积是一条垂直于这两条向量的新向量。我们将使用的这两条向量是点 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);
我没有太多的篇幅来介绍这种计算方法的细节,这是计算向量外积的标准公式。如果您对它
的推导感兴趣,可以随便找一本线性代数的正规参考书查一查。
      现在我们需要知道这条法线与灯光的角度。向量数学的另一个好东西叫做内积(dot
它与外积不同。我们有了法线的向量和灯光的向量。下面计算点积:product)
      var dotProd:Number = norm.x * light.x + norm.y * light.y + norm.z * light.z;
我们看到,内积要比外积简单一些!都差不多了!接下来,计算法线的量值,以及灯光的量值,

大家应该还认识这个 3D OK,
版的勾股定理吧:
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 属性发生变化时被计算。看,我已经给大家留出了各种发挥的空间!
最后,将前面计算出的这些数放入一个具有魔力公式中:
    return (Math.acos(dotProd / (normMag * lightMag)) / Math.PI) * light.brightness;
其中 dotProd 是一个分量, normMag * lightMag 是另一个分量。而两者相除得出一个比率。
回忆一下第三章,一个角度的余弦给了我们一个比率,而一个比率的反余弦给了我们一个角
度。这就是灯光照射在多边形表面上的角度。它的范围在 0 到 Math.PI 个弧度之间(0 到
180 度),也就是说灯光完全照射在物体前面上或完全照射在物体背面。
    用这个数除以 Math.PI 得出一个百分数,再用它乘以 brightness 的百分比就得出了最
终用于改变底色的滤光系数。
    OK,所有这些仅仅给出了多边形表面颜色!此刻,在现有的代码中实现它就非常简单
了。我们在 draw 方法中使用它。应该像这样直接使用这个调整后的颜色:
    g.beginFill(getAdjustedColor());
为了把上述内容综合起来,以下是全部最终的 Triangle.as 和 ExtrudedA.as 代码,列出了我
们本章所有发生变化的部分:
首先是 Triangle:
package {
 import flash.display.Graphics;
 public class Triangle {
  private var pointA:Point3D;
  private var pointB:Point3D;
  private var pointC:Point3D;
  private var color:uint;
  public var light:Light;
  public function Triangle(a:Point3D, b:Point3D,
  c:Point3D, color:uint) {
    pointA = a;
    pointB = b;
    pointC = c;
    this.color = color;
  }
  public function draw(g:Graphics):void {
    if (isBackFace()) {
      return;
    }
    g.beginFill(getAdjustedColor());
    g.moveTo(pointA.screenX, pointA.screenY);
    g.lineTo(pointB.screenX, pointB.screenY);
    g.lineTo(pointC.screenX, pointC.screenY);
    g.lineTo(pointA.screenX, pointA.screenY);
    g.endFill();
  }
  private function getAdjustedColor():uint {
    var red:Number = color >> 16;
  var green:Number = color >> 8 & 0xff;
  var blue:Number =color & 0xff;
  var lightFactor:Number = getLightFactor();
  red *= lightFactor;
  green *= lightFactor;
  blue *= lightFactor;
  return red << 16 | green << 8 | blue;
}
private function getLightFactor():Number {
  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;
  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);
  var dotProd:Number = norm.x * light.x +
  norm.y * light.y +
  norm.z * light.z;
  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);
  return Math.acos(dotProd / normMag * lightMag) / Math.PI * light.brightness;
}
    private function isBackFace():Boolean {
      // 见 http://www.jurjans.lv/flash/shape.html
      var cax:Number = pointC.screenX - pointA.screenX;
      var cay:Number = pointC.screenY - pointA.screenY;
      var bcx:Number = pointB.screenX - pointC.screenX;
      var bcy:Number = pointB.screenY - pointC.screenY;
      return cax * bcy > cay * bcx;
    }
    public function get depth():Number {
      var zpos:Number = Math.min(pointA.z, pointB.z);
      zpos = Math.min(zpos, pointC.z);
      return zpos;
    }
  }
}
然后是 ExtrudedA:
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class ExtrudedA extends Sprite {
    private var points:Array;
    private var triangles:Array;
    private var fl:Number = 250;
    private var vpX:Number = stage.stageWidth / 2;
    private var vpY:Number = stage.stageHeight / 2;
public function ExtrudedA() {
  init();
}
private function init():void {
  points = new Array();
  points[0] = new Point3D( -50, -250, -50);
  points[1] = new Point3D( 50, -250, -50);
  points[2] = new Point3D( 200, 250, -50);
  points[3] = new Point3D( 100, 250, -50);
  points[4] = new Point3D( 50, 100, -50);
  points[5] = new Point3D( -50, 100, -50);
  points[6] = new Point3D(-100, 250, -50);
  points[7] = new Point3D(-200, 250, -50);
  points[8] = new Point3D( 0, -150, -50);
  points[9] = new Point3D( 50, 0, -50);
  points[10] = new Point3D( -50, 0, -50);
  points[11] = new Point3D( -50, -250, 50);
  points[12] = new Point3D( 50, -250, 50);
  points[13] = new Point3D( 200, 250, 50);
  points[14] = new Point3D( 100, 250, 50);
  points[15] = new Point3D( 50, 100, 50);
  points[16] = new Point3D( -50, 100, 50);
  points[17] = new Point3D(-100, 250, 50);
  points[18] = new Point3D(-200, 250, 50);
  points[19] = new Point3D( 0, -150, 50);
  points[20] = new Point3D( 50, 0, 50);
  points[21] = new Point3D( -50, 0, 50);
  for (var i:uint = 0; i < points.length; i++) {
  points[i].setVanishingPoint(vpX, vpY);
  points[i].setCenter(0, 0, 200);
}
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++) {
  triangles[i].light = light;
}
      addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
    private function onEnterFrame(event:Event):void {
      var angleX:Number = (mouseY - vpY) * .001;
      var angleY:Number = (mouseX - vpX) * .001;
      for (var i:uint = 0; i < points.length; i++) {
        var point:Point3D = points[i];
        point.rotateX(angleX);
        point.rotateY(angleY);
      }
      triangles.sortOn("depth", Array.DESCENDING | Array.NUMERIC);
      graphics.clear();
      for (i = 0; i < triangles.length; i++) {
        triangles[i].draw(graphics);
      }
    }
  }
}
        我们看到,在文档类中只有两个次要的变化。主要的工作都集中在 Triangle 中。同时,
我还让所有的三角形使用相同的颜色,我认为这样做可以更好地观察灯光效果(见图 17-6)
                                                                                            

转 <wbr>第十七章 <wbr>背面剔除与flash <wbr>3D <wbr>灯光 <wbr>(as3.0)

图 17-6 带有背面剔除,深度排序及 3D 灯光的三维立体模型

 

 

(如果要转载请注明出处http://blog.sina.com.cn/jooi,谢谢)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值