原文链接 http://www.adobe.com/cn/devnet/flashplayer/articles/hello-triangle.html
在本文中,你将研究一个能够正常运行的基于Stage3D API的ActionScript应用程序。 首先,你需要学会如何正确地对一个准备就绪的Stage3D构建环境进行配置。 一旦范例项目建立,你将了解如何在ActionScript中对Stage3D进行初始化,以及如何使用Stage3D来创建和渲染一个由单一彩色三角形组成的超简单3D场景。
最后,你需要查看一下应用纹理映射(texture mapping)的过程,并且你需要回顾一下一个有纹理映射几何图形的Stage3D应用程序。
当你开始构建Stage3D应用程序时,第一个任务包括准备你的构建环境并确保其设置正确。
尽管肯定能够使用基础的Flex SDK从命令行中构建Stage3D应用程序,但是使用集成工具,例如Flash Builder 4.5,却常常更加便捷。 本文中提供的步骤将侧重于使用Flash Builder 4.5。
你可以通过下载和安装最新版本的Flex 4.5 SDK(版本4.5.0.20967或者更高版本)开始建立构建环境。 在本文一开始的要求(Requirements)部分已经提供下载Flex 4.5 SDK的链接。
接下来,将要求(Requirements)部分中链接的新的Flash Player 11版本的playerglobal.swc文件下载并安装至Flex 4.5 SDK中。 复制该SWC文件并将它粘帖到你刚下载的Flex SDK中的文件夹结构中:
<Flex SDK root folder>frameworkslibsplayer11.0
注意:你必须手动地创建“11.0”文件夹,然后将SWC文件复制到其中。 如果有必要的话,将该文件重新命名为playerglobal.swc。 SDK的未来版本将包括Flash Player 11版本的playerglobal.swc文件;一旦更新的SDK推出,那么这个额外的步骤将不再有存在的必要。
在此之后,将你刚下载的最新的Flex 4.5 SDK添加到你的Flash Builder 4.5环境中。 为了完成上述任务,选中Preferences > Flash Builder > Installed Flex SDKs。 使用相应的界面来添加新的Flex 4.5 SDK。
很显然,你还需要下载并安装Flash Player版本11,以便于支持Stage3D! 如果你还没有这样做的话,那么,现在就使用要求(Requirements)部分的链接下载并安装它。
当你设置好你刚下载的含有playerglobal.swc文件的新的Flex 4.5 SDK并将它添加到你的Flex Builder安装路径之后,你可以开始创建一个新的ActionScript 项目。
此外,你还需要将你项目的目标版本配置为SWF版本13。打开Project Properties并单击ActionScript Compiler标签。 将“Additional compiler arguments”设置为“-swf-version=13”(不带引号)。 此外,在同一面板中,仔细检查确认应用程序的目标版本是Flash Player版本11。
最后,为了使得Flash Player能够真正地使用3D硬件加速功能,你还需要将WMODE设置为"direct"。 在Flash Builder中,打开index.template.html文件并且找出将params传递到SWF文件的相应位置。 添加下面代码:
params.wmode = “direct”;
图1所示的截图说明了在何处添加该行代码。
该显示设置可以引用将Flash Player作为目标的ActionScript。 当你编译一个AIR应用程序时,你需要将应用程序描述符中的renderMode
元素设置为direct
。
现在编译你的应用程序,以便确保它能够正常运行。 它应该仅仅只显示一个空白的窗口。
当你建立好ActionScript应用程序之后,你需要做的第一件事情就是初始化Stage3D。
为了能够实现3D渲染功能,你需要一个Context3D类的实例,它基本上可以用作一个3D渲染表面。
因此,在构造函数中,添加下面的代码:
public function HelloTriangleColored() { stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill ); stage.stage3Ds[0].requestContext3D(); }
上述代码仅使用Stage3D API请求一个Context3D实例,并且注册一个事件侦听器。 当Context3D实例准备就绪时,该事件将回调initStage3D
函数。
当initStage3D
被调用以后,通过调用Context3D::configureBackBuffer
方法来正确配置你的Context3D将显得非常重要。
protected function initMolehill(e:Event):void { context3D = stage.stage3Ds[0].context3D; context3D.configureBackBuffer(800, 600, 2, true); ... }
上述代码指定了你使用的渲染视口为800 x 600像素,它含有最低级别的锯齿消除功能(anti-aliasing)(第三参数),并且为该渲染表面创建了相应的深度和模版缓冲区(stencil buffer)(第四参数)。
在本章节中,你将创建一些3D几何图形(需要渲染的3D对象)。 就本范例而言,你将创建一个可能是最简单的几何图形:一个彩色三角形。
为了实现这一任务,你需要一个Vertex Buffer,并且你应该为顶点位置(x,y,z)和顶点颜色(r,g,b)定义Vertex Attributes。 每一个顶点均有6个组件。 首先,你应该将相应的Vertex Buffer数据定义到一个矢量中,如下所示:
protected function initMolehill(e:Event):void { ... var vertices:Vector.<Number> = Vector.<Number>([ -0.3,-0.3,0, 1, 0, 0, // x, y, z, r, g, b -0.3, 0.3, 0, 0, 1, 0, 0.3, 0.3, 0, 0, 0, 1]); ... }
然后,创建一个你可以用来将Vertex Buffer数据上传给GPU的VertexBuffer3D实例。
protected var vertexbuffer:VertexBuffer3D; ... protected function initMolehill(e:Event):void { ... // Create VertexBuffer3D. 3 vertices, of 6 Numbers each vertexbuffer:VertexBuffer3D = context3D.createVertexBuffer(3, 6); // Upload VertexBuffer3D to GPU. Offset 0, 3 vertices vertexbuffer.uploadFromVector(vertices, 0, 3); ... }
此外,你还需要一个Index Buffer来定义你的三角形。 在本例中,这个单独的三角形将仅仅由顶点0、1和2组成。 与Vertex Buffer相似,Index Buffer也必须上传给GPU。 为了完成这一任务,你需要使用IndexBuffer3D类:
protected var indexbuffer:IndexBuffer3D; ... protected function initMolehill(e:Event):void { ... var indices:Vector.<uint> = Vector.<uint>([0, 1, 2]); // Create IndexBuffer3D. Total of 3 indices. 1 triangle of 3 vertices indexbuffer = context3D.createIndexBuffer(3); // Upload IndexBuffer3D to GPU. Offset 0, count 3 indexbuffer.uploadFromVector (indices, 0, 3); ... }
上面的代码定义了相应的几何图形。 现在,你需要一个Vertex和一个Fragment Shader。
为简单起见,你应该使用在之前一篇文章中讨论过的相同的Shader程序,该文章包含于标题为 什么是AGAL(What is AGAL)的系列教程。 Vertex Shader只不过按照一个从ActionScript传入的转换矩阵对顶点进行转换,然后将顶点颜色沿着渲染管线(rendering pipeline)传递给Fragment Shader。
m44 op, va0, vc0 mov v0, va1
Fragment Shader从自己的输入获取内插颜色(interpolated color),并且将它作为输出颜色进行传递。
mov oc, v0
你需要使用AGAL Mini汇编程序(AGAL Mini Assembler)将Shader代码汇编至对象代码中,然后使用Program3D API类将Shader上传至GPU中。
protected function initMolehill(e:Event):void { ... var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler(); vertexShaderAssembler.assemble( Context3DProgramType.VERTEX, "m44 op, va0, vc0 " + // pos to clipspace "mov v0, va1" // copy color ); var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler(); fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT, "mov oc, v0 " ); program = context3D.createProgram(); program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode); ... }
至此,可以对相应的场景进行渲染。 在本章节中,你将建立一个渲染循环。 你只需要创建一个onRender
函数即可,利用ENTER_FRAME事件能够在每一帧中调用该函数。
protected function onRender(e:Event):void { if ( !context3D ) return; ... }
在每一个帧渲染器开始处,你都将调用Context3D::clear.
。 这一操作可以利用我们传入的背景色清除渲染颜色缓冲区(内容被渲染的表面)(因为与Context3D相关联的深度和模版缓冲区将被清除)。 使用下面给出的代码传入一个白色的背景:
protected function onRender(e:Event):void { ... context3D.clear ( 1, 1, 1, 1 ); ... }
在每一帧中,你必须使用已上传的Shader启用Program3D以及VertexBuffer3D功能,以便将Vertex Attributes与适当的 Shader Attribute Register建立关联,正如在之前的文章什么是AGAL(What is AGAL)中讨论的一样。
此外,你还需要传入相应的转换矩阵以便Vertex Shader使用。 让我们使用一个在每一帧中都发生变化的旋转矩阵,使得我们的三角形能够一点点地旋转…
protected function onRender(e:Event):void { ... // vertex position to attribute register 0 context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // color to attribute register 1 context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_3); // assign shader program context3D.setProgram(program); var m:Matrix3D = new Matrix3D(); m.appendRotation(getTimer()/40, Vector3D.Z_AXIS); context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true); ... }
在完成这个设置之后,就到了执行实际渲染任务的时候。 你需要调用Context3D::drawTriangles
方法,以便在Index Buffer中传递;该操作能够将三角形渲染至渲染表面(颜色缓冲区)。
最后,当你完成该帧上场景的所有3D对象的渲染任务后(在本范例中,只有一个对象),你将需要调用Context3D::present
。 该方法将告诉Stage3D,应用程序已经完成帧的渲染并且该帧能够在屏幕上显示。
protected function onRender(e:Event):void { ... context3D.drawTriangles(indexbuffer); context3D.present(); }
运行Hello Triangle Colored应用程序以便观察最终的结果,并且忙里偷闲欣赏一下你的杰作(参见图2)。
下面是用来创建Hello Triangle Colored应用程序的完整代码范例:
package { import com.adobe.utils.AGALMiniAssembler; import flash.display.Sprite; import flash.display3D.Context3D; import flash.display3D.Context3DProgramType; import flash.display3D.Context3DVertexBufferFormat; import flash.display3D.IndexBuffer3D; import flash.display3D.Program3D; import flash.display3D.VertexBuffer3D; import flash.events.Event; import flash.geom.Matrix3D; import flash.geom.Rectangle; import flash.geom.Vector3D; import flash.utils.getTimer; [SWF(width="800", height="600", frameRate="60", backgroundColor="#FFFFFF")] public class HelloTriangleColored extends Sprite { protected var context3D:Context3D; protected var program:Program3D; protected var vertexbuffer:VertexBuffer3D; protected var indexbuffer:IndexBuffer3D; public function HelloTriangleColored() { stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill ); stage.stage3Ds[0].requestContext3D(); addEventListener(Event.ENTER_FRAME, onRender); } protected function initMolehill(e:Event):void { context3D = stage.stage3Ds[0].context3D; context3D.configureBackBuffer(800, 600, 1, true); var vertices:Vector.<Number> = Vector.<Number>([ -0.3,-0.3,0, 1, 0, 0, // x, y, z, r, g, b -0.3, 0.3, 0, 0, 1, 0, 0.3, 0.3, 0, 0, 0, 1]); // Create VertexBuffer3D. 3 vertices, of 6 Numbers each vertexbuffer = context3D.createVertexBuffer(3, 6); // Upload VertexBuffer3D to GPU. Offset 0, 3 vertices vertexbuffer.uploadFromVector(vertices, 0, 3); var indices:Vector.<uint> = Vector.<uint>([0, 1, 2]); // Create IndexBuffer3D. Total of 3 indices. 1 triangle of 3 vertices indexbuffer = context3D.createIndexBuffer(3); // Upload IndexBuffer3D to GPU. Offset 0, count 3 indexbuffer.uploadFromVector (indices, 0, 3); var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler(); vertexShaderAssembler.assemble( Context3DProgramType.VERTEX, "m44 op, va0, vc0 " + // pos to clipspace "mov v0, va1" // copy color ); var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler(); fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT, "mov oc, v0" ); program = context3D.createProgram(); program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode); } protected function onRender(e:Event):void { if ( !context3D ) return; context3D.clear ( 1, 1, 1, 1 ); // vertex position to attribute register 0 context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // color to attribute register 1 context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_3); // assign shader program context3D.setProgram(program); var m:Matrix3D = new Matrix3D(); m.appendRotation(getTimer()/40, Vector3D.Z_AXIS); context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true); context3D.drawTriangles(indexbuffer); context3D.present(); } } }
你在本文前一部分建立的Hello Triangle Colored应用程序渲染了一个彩色的三角形。 该三角形的颜色被定义为Vertex Attributes,它们是Vertex Buffer的组成部分。 该三角形是彩色几何图形的一个范例,并且相应的颜色是基于顶点(per-vertex)定义的。
在本章节中,你将充分了解一种不同的渲染几何图形的方式,它使用一种被称为纹理映射(texture mapping)的通用技术。 纹理映射(Texture mapping)是指将图像(纹理)应用于几何图形的过程。 你可以将这种纹理图像(texture image)想象成一张图文并茂的纸,有点像墙纸。 定义一个三角形(或者,范围更广一些,一个3D对象),并将这种图文并茂的纸覆盖在该对象的表面。
通过使用这样的策略,你可以渲染一个3D对象,就好像它真地包含该图文并茂纹理的所有微小细节。 实际上,该纹理细节不是几何图形的一部分。 这一视觉复杂性仅仅是已应用的纹理图像(texture image)的图文并茂细节。
当你应用纹理映射(texture mapping)时,你需要指定纹理元素需要放置在几何图形顶端的确切位置。 基本上,当你使用纹理图像(texture image)对几何图形进行覆盖时,你需要创建一个精确的映射,用来定义每一个纹理图像的像素应该落在3D几何图形上的准确位置。
使用UV坐标
将一种纹理在3D几何图形上进行合理排列的方式涉及到基于顶点(per-vertex)指定相应的映射:对于每一个顶点,你需要指定一对2D坐标,用(U,V)表示,该坐标将定义与该特定顶点相对应的纹理图像(texture image)的点。 因此,这些UV坐标在Vertex Buffer中将被指定为Vertex Attributes,并且Vertex Shader将以输入流的方式接收它们。
然后,由于Vertex Shader通常都是伴随Vertex Attributes出现的,Vertex Shader将UV坐标作为输出沿着渲染管线(rendering pipeline)传递出来,并且Rasterizer对它们进行内插操作(如需了解更多细节,请参见本系列前面的一篇文章,其标题为Vertex和Fragment Shaders(Vertex and Fragment Shaders)。 在这种方式下,Fragment Shader能够为每一个三角形Fragment接收合适的UV坐标值(对于每一个将被渲染的像素)。 因此,每一个已渲染的三角形的每一像素都被映射到一个特定的纹理像素(也称为纹理元素( texture element),或者纹(texel))。
换句话说,通过指定UV坐标,你已经创建一个3D几何图形和纹理图像之间的映射。 这就是术语纹理映射(texture mapping)背后的概念。
Stage3D API中的Texture类包括应用纹理的支持功能。
纹理图像(texture image)首先必须上传至GPU内存以便在渲染过程中使用。 你可以利用下面代码,使用Texture类将纹理图像(texture image)上传至GPU:
protected var texture:Texture; ... protected function initMolehill(e:Event):void { ... var bitmap:Bitmap = new TextureBitmap(); texture = context3D.createTexture(bitmap.bitmapData.width, bitmap.bitmapData.height, Context3DTextureFormat.BGRA, false); texture.uploadFromBitmapData(bitmap.bitmapData); ... }
正如上面讨论的那样,Vertex Shader将UV纹理坐标作为一个Vertex Attribute接收下来,并且沿着渲染管线(rendering pipeline)将它们作为输出进行传递,这样,能够正确地对它们进行内插操作并且将它们传递至Fragment Shader。 除了Attribute Register 1包含UV坐标,而不是颜色值之外,Vertex Shader与上面描述的彩色三角形范例项目非常相似。
m44 op, va0, vc0 mov v0, va1
Fragment Shader接收内插的UV坐标并使用它们以及通过一个Texture Sampler对纹理进行取样。
让我们假定纹理与ActionScript以及ActionScript 0都是相关联的。 在这种情况下,Fragment Shader将是:
tex ft1, v0, fs0 <2d> mov oc, ft1
Fragment Shader的第一行使用Texture Sampler 0和存储于可变寄存器0(varying register 0)中的UV坐标对纹理进行取样,并且将相应的结果复制到Temporary Register 1中。 第二行仅仅将Temporary Register 1(已取样的纹理)中的内容复制到输出。
修改Hello Triangle Colored应用程序以便应用纹理映射(texture map)
在本章节中,你需要修改之前的Hello Triangle应用程序,以便它能够使用纹理映射(texture mapping)。
在应用程序中需要更新的第一件事情是为纹理添加一个图像。 使用下面的代码来到导入一个外部的纹理图像(texture image):
[Embed( source = "RockSmooth.jpg" )] protected const TextureBitmap:Class; ...
你还需要更改一下Vertex Buffer的定义。 你需要提供UV坐标,而不是传递颜色Vertex Attribute:
protected function initMolehill(e:Event):void { ... var vertices:Vector.<Number> = Vector.<Number>([ -0.3,-0.3,0, 1, 0, // x, y, z, u, v -0.3, 0.3, 0, 0, 1, 0.3, 0.3, 0, 1, 1]); // Create VertexBuffer3D. 3 vertices, of 5 Numbers each vertexbuffer = context3D.createVertexBuffer(3, 5); // Upload VertexBuffer3D to GPU. Offset 0, 3 vertices vertexbuffer.uploadFromVector(vertices, 0, 3); ... }
注意,UV坐标是在0和1之间定义的,因此,(U,V) = (0,0) 表示纹理图像(texture image)的左下角,而(U,V) = (1,1)表示右上角。
然后,渲染循环(rendering loop)启用Texture对象,并将它与 Texture Sampler 0关联,而Fragment Shader能够使用Texture Sampler 0:
protected function onRender(e:Event):void { ... // assign texture to texture sampler 0 context3D.setTextureAt(0, texture); ... }
当完成这些变更之后,再一次运行该应用程序以便看一看在你创建的Stage3D应用程序中显示的带有纹理的三角形(参见图3)。
下面是用于创建Hello Textured Triangle应用程序的完整代码范例:
package { import com.adobe.utils.AGALMiniAssembler; import flash.display.Bitmap; import flash.display.Sprite; import flash.display3D.Context3D; import flash.display3D.Context3DProgramType; import flash.display3D.Context3DTextureFormat; import flash.display3D.Context3DVertexBufferFormat; import flash.display3D.IndexBuffer3D; import flash.display3D.Program3D; import flash.display3D.VertexBuffer3D; import flash.display3D.textures.Texture; import flash.events.Event; import flash.geom.Matrix3D; import flash.geom.Rectangle; import flash.geom.Vector3D; import flash.utils.getTimer; [SWF(width="800", height="600", frameRate="60", backgroundColor="#FFFFFF")] public class HelloTriangleTextured extends Sprite { [Embed( source = "RockSmooth.jpg" )] protected const TextureBitmap:Class; protected var texture:Texture; protected var context3D:Context3D; protected var program:Program3D; protected var vertexbuffer:VertexBuffer3D; protected var indexbuffer:IndexBuffer3D; public function HelloTriangleTextured() { stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill ); stage.stage3Ds[0].requestContext3D(); addEventListener(Event.ENTER_FRAME, onRender); } protected function initMolehill(e:Event):void { context3D = stage.stage3Ds[0].context3D; context3D.configureBackBuffer(800, 600, 1, true); var vertices:Vector.<Number> = Vector.<Number>([ -0.3,-0.3,0, 1, 0, // x, y, z, u, v -0.3, 0.3, 0, 0, 1, 0.3, 0.3, 0, 1, 1]); // Create VertexBuffer3D. 3 vertices, of 5 Numbers each vertexbuffer = context3D.createVertexBuffer(3, 5); // Upload VertexBuffer3D to GPU. Offset 0, 3 vertices vertexbuffer.uploadFromVector(vertices, 0, 3); var indices:Vector.<uint> = Vector.<uint>([0, 1, 2]); // Create IndexBuffer3D. Total of 3 indices. 1 triangle of 3 vertices indexbuffer = context3D.createIndexBuffer(3); // Upload IndexBuffer3D to GPU. Offset 0, count 3 indexbuffer.uploadFromVector (indices, 0, 3); var bitmap:Bitmap = new TextureBitmap(); texture = context3D.createTexture(bitmap.bitmapData.width, bitmap.bitmapData.height, Context3DTextureFormat.BGRA, false); texture.uploadFromBitmapData(bitmap.bitmapData); var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler(); vertexShaderAssembler.assemble( Context3DProgramType.VERTEX, "m44 op, va0, vc0 " + // pos to clipspace "mov v0, va1" // copy UV ); var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler(); fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT, "tex ft1, v0, fs0 <2d> " + "mov oc, ft1" ); program = context3D.createProgram(); program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode); } protected function onRender(e:Event):void { if ( !context3D ) return; context3D.clear ( 1, 1, 1, 1 ); // vertex position to attribute register 0 context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // UV to attribute register 1 context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_2); // assign texture to texture sampler 0 context3D.setTextureAt(0, texture); // assign shader program context3D.setProgram(program); var m:Matrix3D = new Matrix3D(); m.appendRotation(getTimer()/40, Vector3D.Z_AXIS); context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true); context3D.drawTriangles(indexbuffer); context3D.present(); } } }
在本文中,你使用了在Stage3D系列的前面文章中学到的概念,最终掌握了相应的核心过程并且创建了两个能够完全正常运行的基于Stage3D的ActionScript应用程序。 即使相应的范例应用程序只创建一个由单一三角形组成的简单场景,但是所有的使用Stage3D的概念都已包含在内。 从今以后,当你构建Stage3D应用程序时,你只会感觉到一切变得更深入和更有趣。