使用 Flash 3D API
这里的小教程向您解释如何在haxe里使用最新的Flash Playr 11 3D APIs
安装
请依次按以下操作进行:
- 下载Flash Player 11(或更高版本) ,点这里下载 Adobe
- 安装 Haxe 2.08 Release
- 下载并解压molehill.zip Haxe 例子(含基本的3d类库:顶点,uv等)
- 当Haxe安装后,运行下面命令行安装第三方库
format
:haxelib install format
例子 0 - 立方体
我们先做一个带有颜色的立方体例子。
你可以通过简单的命令行执行haxe test.hxml
命令构建一个输出文件 test.swf
,并在你的浏览器测试这个swf,(运行前请注意确定你已经安装了Flash Player 11。)
下面我们一起分析文件 Test.hx
:
在 new
函数 (Haxe 类构造函数), 我们装配一个新的3D场景(3D stage)用于3D渲染 . 代码如下 :
var s : flash.display.Stage3D; //... stage = flash.Lib.current.stage; s = stage.stage3Ds[0]; s.addEventListener( flash.events.Event.CONTEXT3D_CREATE, onReady ); s.requestContext3D();
当3D环境(3D context)准备妥当,我们要配置它,并分配几项内容:一个新的着色器(Shader),一个新的相机( Camera),还有我们的一个3D立方体(3D Cube polygon).
var c : flash.display3D.Context3D; var shader : Shader; var pol : Polygon; var camera : Camera; // ... function onReady( _ ) { c = s.context3D; c.enableErrorChecking = true; c.configureBackBuffer( stage.stageWidth, stage.stageHeight, 0, true ); shader = new Shader(c); camera = new Camera(); pol = new Cube(); pol.alloc(c); }
在 update
函数内,我们将开始装配一些3D环境(context 3D)需要的内容。
c.clear(0, 0, 0, 1); c.setDepthTest( true, flash.display3D.Context3DCompareMode.LESS_EQUAL ); c.setCulling(flash.display3D.Context3DTriangleFace.BACK);
接着处理相机移动(camera movement):通过方向键和+/-键进行控制
if( keys[K.UP] ) camera.moveAxis(0,-0.1); if( keys[K.DOWN] ) camera.moveAxis(0,0.1); if( keys[K.LEFT] ) camera.moveAxis(-0.1,0); if( keys[K.RIGHT] ) camera.moveAxis(0.1, 0); if( keys[109] ) camera.zoom /= 1.05; if( keys[107] ) camera.zoom *= 1.05; camera.update();
使用相机投影矩阵,把3d映射到2d屏幕。使用立方体转换矩阵使立方体绕Z轴进行旋转:
var project = camera.m.toMatrix(); var mpos = new flash.geom.Matrix3D(); mpos.appendRotation(t * 10, flash.geom.Vector3D.Z_AXIS);
剩下的是简单的装配我们的着色器(shader),绘制我们的立方体,并在3d环境(3D context)内显示:
shader.init( { mpos : mpos, mproj : project }, {} ); shader.bind(pol.vbuf); c.drawTriangles(pol.ibuf); c.present();
我们将看到一个旋转的3D立方体。请注意颜色是基于立方体顶点来描绘,色彩从(0,0,0)=黑色到(1,1,1)=白色。
着色器(Shaders)
第一部分我们没涉及立方体如何改变旋转和色彩变化,这个工作时由着色器完成了。与AS3创建着色器不同的是,AS3使用晦涩难懂的汇编语言来创建,而haxe是简单直接使用hxsl - Haxe 着色器语言 这个语言来创建。如你所见,在"Test.hx"相关代码如下:
class Shader extends format.hxsl.Shader { static var SRC = { var input : { pos : Float3, }; var color : Float3; function vertex( mpos : M44, mproj : M44 ) { out = pos.xyzw * mpos * mproj; color = pos; } function fragment() { out = color.xyzw; } } }
在 @:shader
section contains Haxe Metadata which is compiled at the same time you're compiling your project by using Haxe 宏.
要了解完整的着色器,请看 hxsl - Haxe 着色器语言 文档.
例子 1 - 灯光 Lighting
在这个例子里,我们将使用灯光照亮我们的3D立方体,它使用一个简单算法(Gouraud algorithm).为此我们给3D立方体添加 normals,在创建立方体完成后,添加如下代码:
pol.addNormals();
This will calculate a per-vertex normal. Since the cube points are shared by triangles faces, you'll get very smooth normals.
这样就可以计算每个顶点法线。由于立方体是由三角形面点共享,你会得到非常光滑的法线。
现在我们已经有了法线,让我们创建一个旋转的灯光,做为参数把它传递给我们的着色器:
var light = new flash.geom.Vector3D( Math.cos(t * 10) * 1, Math.sin(t * 5) * 2, Math.sin(t) * Math.cos(t) * 2 ); light.normalize(); shader.init( { mpos : mpos, mproj : project, light : light }, {} );
接着,我们只要修改我们的着色器代码。为了计算出光功率,我们在转化法线和光方向之间做积运算。我们要确保光强度不能为负,为此每个顶点的颜色范围设定为0-1:
var tnorm = (norm * mpos).normalize(); var lpow = light.dot(tnorm).max(0); color = pos * lpow;
如果你想有更多的正交法线,你可以简单地确保每个顶点有唯一法线,在下面代码计算法线之前添加:
pol.unindex();
例子 2 - 材质贴图 Texturing
在这个例子里,我们将添加一个材质贴图。为此,我们先给3D立方体添加纹理坐标:
pol.addTCoords();
请注意,纹理坐标将要求立方体点是已建立索引的。
然后,我们将从创建一个嵌入材质贴图文件并分配给我们的3D环境
@:bitmap("hxlogo.png") class HaxeLogo extends flash.display.BitmapData { } // ... var logo = new HaxeLogo(); texture = c.createTexture(logo.width, logo.height, flash.display3D.Context3DTextureFormat.BGRA, false); texture.uploadFromBitmapData(logo);
我们把材质贴图作为参数传递给我们的着色器:
shader.init( { mpos : mpos, mproj : project, light : light }, { tex : texture } );
现在剩下的就是简单地修改我们的着色器,着色器通过纹理坐标读取保存在立方体内的贴图颜色值:
var input : { pos : Float3, norm : Float3, uv : Float2, }; var tuv : Float2; var lpow : Float; function vertex( mpos : M44, mproj : M44, light : Float3 ) { out = pos.xyzw * mpos * mproj; var tnorm = (norm * mpos).normalize(); lpow = light.dot(tnorm).max(0); tuv = uv; } function fragment( tex : Texture ) { out = tex.get(tuv) * (lpow * 0.8 + 0.2); }
请注意,我们使用了一些0-1光功率因数操作。在这种情况下,我们还增加了20%的环境光,以确保背对灯光时候贴图颜色亮度不会完全变黑。