我们上一章节显示图片的时候,会发现我们制定的顶点在Stage3D中其实是存在一个区间的:
x轴(从左到右):[-1.0-1.0]
y轴(从下到上):[-1.0-1.0]
z轴(从近到远):[0-1.0]
超过这个区间的部分我们的图片都会看不见,大家可以重新修改上一节的代码中的顶点位置查看;
并且该区间不会跟随Stage3D的尺寸改变而改变,即无论Stage3D的显示尺寸如何变动,绘制的图像是会进行对应的拉伸操作的;
那么这就导致了一个问题的出现,如果我需要显示图片的原有尺寸(或者指定的尺寸)且该尺寸不会跟随Stage3D的显示尺寸变动时该怎么办?
为了解决这个问题,我们需要引入一个叫做矩阵转换的概念,即通过一定的转换运算把我们设定的尺寸转换为适用于Stage3D中的坐标尺寸。
一般存在两种矩阵转换:正交矩阵和透视矩阵。
透视矩阵:遵循现实世界中的近大远小法则,一般的3D游戏都使用该矩阵。
正交矩阵:没有近大远小的规则,所有物体看上去都是一样的大小,一般通过3D来实现的2D游戏都使用该矩阵。
我们基于上一章的代码,先修改一下要显示的纹理的顶点:
1 private function initBuffer():void 2 { 3 //顶点数据 4 var vertexData:Vector.<Number> = Vector.<Number>( 5 [ 6 // x, y, z, u, v 7 -1, -1, 0, 0, 1, 8 1, -1, 0, 1, 1, 9 1, 1, 0, 1, 0, 10 -1, 1, 0, 0, 0 11 ]); 12 //省略 13 }
看一下效果:
我们看见,整个纹理都铺满了3D场景。同时纹理的原有的高宽比没有了。
使用正交矩阵:
首先需要创建一个矩阵对象保存正交矩阵:
1 //存放正交矩阵的对象 2 private var _projectionMatrix:Matrix3D;
创建正交矩阵:
1 /** 2 * 创建正交矩阵. 3 * @param width 场景宽度. 4 * @param height 场景高度. 5 * @param near 近截面. 6 * @param far 远截面. 7 */ 8 private function initOrthographicProjection(Number, height:Number, near:Number = -1.0, far:Number = 1.0):void 9 { 10 //创建正交矩阵的实例 11 _projectionMatrix = new Matrix3D(); 12 //获取场景宽度和高度的比例 13 var ratio:Number = width / height; 14 //获取正交矩阵数据 15 var coords:Vector.<Number> = getOrthographicMatrix(-ratio, ratio, -1, 1, near, far); 16 //拷贝数据到矩阵中 17 _projectionMatrix.copyRawDataFrom(coords); 18 //将我们的正交矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器 19 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _projectionMatrix, true); 20 } 21 22 /** 23 * 获取正交矩阵的数据. 24 * @param left 左边界. 25 * @param right 右边界. 26 * @param bottom 底边界. 27 * @param top 顶边界. 28 * @param near 近截面. 29 * @param far 远截面. 30 * @return 正交矩阵的数据. 31 */ 32 private function getOrthographicMatrix(left:Number, right:Number, bottom:Number, top:Number, near:Number, far:Number):Vector.<Number> 33 { 34 var m:Vector.<Number> = new Vector.<Number>(16, true); 35 36 m[0] = 2.0 * 1.0 / (right - left); 37 m[5] = 2.0 * 1.0 / (top - bottom); 38 m[10] = 1.0 / (far - near); 39 40 m[12] = (right + left) / (right - left); 41 m[13] = (bottom + top) / (bottom - top); 42 m[14] = near / (near - far); 43 44 m[1] = m[2] = m[3] = m[4] =m[6] = m[7] = m[8] = m[9] = m[11] = 0; 45 m[15] = 1.0; 46 47 return m; 48 }
正交矩阵的信息需要作为常量传递到GPU中:
1 //将我们的正交矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器 2 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _projectionMatrix, true);
最后,修改一下顶点着色器,使我们的顶点数据和正交矩阵相乘:
1 private function initProgram():void 2 { 3 //顶点着色器代码, 每个上传的顶点前都会执行一次该代码 4 var vertexArr:Array = 5 [ 6 //op 代表位置输出寄存器, 无论对顶点进行多少次的运算最终都要将结果 7 //赋值给他, 这里和我们的正交矩阵进行相乘的运算 8 "m44 op, va0, vc0", 9 //片段着色器需要用的数据要在这里通过 v0 中转一下, 因为片段着色器不 10 //能直接读取 va0 和 va1 的数据 11 "mov v0, va1" 12 ]; 13 //省略 14 }
改好了以后,看一下现在的效果:
我们发现纹理的高宽比被保留了下来,说明我们的正交矩阵开始起作用了!
保留我们的纹理的尺寸只要修改顶点的数据即可:
1 private function initBuffer():void 2 { 3 //顶点数据 4 var vertexData:Vector.<Number> = Vector.<Number>( 5 [ 6 // x, y, z, u, v 7 -1 * (128 / 500), -1 * (128 / 500), 0, 0, 1, 8 1 * (128 / 500), -1 * (128 / 500), 0, 1, 1, 9 1 * (128 / 500), 1 * (128 / 500), 0, 1, 0, 10 -1 * (128 / 500), 1 * (128 / 500), 0, 0, 0 11 ]); 12 //省略 13 }
顶点x和y都乘上宽度与高度分别除以场景高度的系数即可。
最终效果:
上代码:
1 package 2 { 3 import com.adobe.utils.AGALMiniAssembler; 4 5 import flash.display.Bitmap; 6 7 import flash.display.Sprite; 8 import flash.display.Stage3D; 9 import flash.display3D.Context3D; 10 import flash.display3D.Context3DProfile; 11 import flash.display3D.Context3DProgramType; 12 import flash.display3D.Context3DRenderMode; 13 import flash.display3D.Context3DTextureFormat; 14 import flash.display3D.Context3DVertexBufferFormat; 15 import flash.display3D.IndexBuffer3D; 16 import flash.display3D.Program3D; 17 import flash.display3D.VertexBuffer3D; 18 import flash.display3D.textures.Texture; 19 import flash.events.ErrorEvent; 20 import flash.events.Event; 21 import flash.geom.Matrix3D; 22 23 [SWF(width=800, height=600, frameRate=60)] 24 public class OrthographicProjection extends Sprite 25 { 26 [Embed(source="img.png")] 27 private var IMG_CLASS:Class; 28 29 //3D 场景对象 30 private var _stage3D:Stage3D; 31 //3D 上下文渲染对象 32 private var _context3D:Context3D; 33 34 //顶点缓冲数据 35 private var _vertexBuffer:VertexBuffer3D; 36 //索引缓冲数据 37 private var _indexBuffer:IndexBuffer3D; 38 //纹理数据对象 39 private var _texture:Texture; 40 41 //着色器对象 42 private var _program3D:Program3D; 43 44 //存放正交矩阵的对象 45 private var _projectionMatrix:Matrix3D; 46 47 public function OrthographicProjection() 48 { 49 addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 50 } 51 52 private function addedToStageHandler(event:Event):void 53 { 54 removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 55 56 //3D 场景存在, 一般存在 4 个 3D 场景对象 57 if(stage.stage3Ds.length > 0) 58 { 59 //使用最下层的 3D 场景 60 _stage3D = stage.stage3Ds[0]; 61 //请求 3D 上下文渲染对象 62 _stage3D.addEventListener(ErrorEvent.ERROR, stage3DErrorHandler); 63 _stage3D.addEventListener(Event.CONTEXT3D_CREATE, context3DCreateHandler); 64 _stage3D.requestContext3D(Context3DRenderMode.AUTO, Context3DProfile.BASELINE); 65 } 66 } 67 68 private function stage3DErrorHandler(event:ErrorEvent):void 69 { 70 trace("Context3D对象请求失败:", event.text); 71 } 72 73 private function context3DCreateHandler(event:Event):void 74 { 75 initContext3D(700, 500); 76 initOrthographicProjection(700, 500); 77 initBuffer(); 78 initTexture(); 79 initProgram(); 80 81 //每帧进行渲染 82 addEventListener(Event.ENTER_FRAME, render); 83 } 84 85 private function initContext3D(Number, height:Number):void 86 { 87 //获取 3D 渲染对象 88 _context3D = _stage3D.context3D; 89 //调整 3D 舞台位置 90 _stage3D.x = 50; 91 _stage3D.y = 50; 92 //设置后台缓冲区 93 _context3D.configureBackBuffer(width, height, 2); 94 } 95 96 /** 97 * 创建正交矩阵. 98 * @param width 场景宽度. 99 * @param height 场景高度. 100 * @param near 近截面. 101 * @param far 远截面. 102 */ 103 private function initOrthographicProjection(Number, height:Number, near:Number = -1.0, far:Number = 1.0):void 104 { 105 //创建正交矩阵的实例 106 _projectionMatrix = new Matrix3D(); 107 //获取场景宽度和高度的比例 108 var ratio:Number = width / height; 109 //获取正交矩阵数据 110 var coords:Vector.<Number> = getOrthographicMatrix(-ratio, ratio, -1, 1, near, far); 111 //拷贝数据到矩阵中 112 _projectionMatrix.copyRawDataFrom(coords); 113 //将我们的正交矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器 114 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _projectionMatrix, true); 115 } 116 117 /** 118 * 获取正交矩阵的数据. 119 * @param left 左边界. 120 * @param right 右边界. 121 * @param bottom 底边界. 122 * @param top 顶边界. 123 * @param near 近截面. 124 * @param far 远截面. 125 * @return 正交矩阵的数据. 126 */ 127 private function getOrthographicMatrix(left:Number, right:Number, bottom:Number, top:Number, near:Number, far:Number):Vector.<Number> 128 { 129 var m:Vector.<Number> = new Vector.<Number>(16, true); 130 131 m[0] = 2.0 * 1.0 / (right - left); 132 m[5] = 2.0 * 1.0 / (top - bottom); 133 m[10] = 1.0 / (far - near); 134 135 m[12] = (right + left) / (right - left); 136 m[13] = (bottom + top) / (bottom - top); 137 m[14] = near / (near - far); 138 139 m[1] = m[2] = m[3] = m[4] =m[6] = m[7] = m[8] = m[9] = m[11] = 0; 140 m[15] = 1.0; 141 142 return m; 143 } 144 145 private function initBuffer():void 146 { 147 //顶点数据 148 var vertexData:Vector.<Number> = Vector.<Number>( 149 [ 150 // x, y, z, u, v 151 -1 * (128 / 500), -1 * (128 / 500), 0, 0, 1, 152 1 * (128 / 500), -1 * (128 / 500), 0, 1, 1, 153 1 * (128 / 500), 1 * (128 / 500), 0, 1, 0, 154 -1 * (128 / 500), 1 * (128 / 500), 0, 0, 0 155 ]); 156 157 //创建顶点缓冲对象, 参数设定存在几组数据和每组数据的个数 158 _vertexBuffer = _context3D.createVertexBuffer(vertexData.length / 5, 5); 159 //上传顶点数据到GPU, 参数设定从第几组数据开始上传和上传多少组数据 160 _vertexBuffer.uploadFromVector(vertexData, 0, vertexData.length / 5); 161 162 //索引数据 163 var indexData:Vector.<uint> = Vector.<uint>( 164 [ 165 0, 3, 1, 166 1, 2, 3 167 ]); 168 169 //创建索引缓冲对象, 每个索引对应顶点数据中的相对应的一组数据, 170 //每3个索引组成一个会被绘制出来的三角形, 参数指定索引的长度 171 _indexBuffer = _context3D.createIndexBuffer(indexData.length); 172 //上传索引数据到GPU, 参数设定从第几个数据开始上传和上传多少个数据 173 _indexBuffer.uploadFromVector(indexData, 0, indexData.length); 174 } 175 176 private function initTexture():void 177 { 178 //创建位图 179 var bitmap:Bitmap = new IMG_CLASS() as Bitmap; 180 //创建纹理, 注意尺寸必须是 2 的幂数 181 _texture = _context3D.createTexture(128, 128, Context3DTextureFormat.BGRA, true); 182 //上传纹理到 GPU, 第二个参数表示该纹理的 mipmap 级别, 级别零是高级全分辨率图像 183 _texture.uploadFromBitmapData(bitmap.bitmapData, 0); 184 } 185 186 private function initProgram():void 187 { 188 //顶点着色器代码, 每个上传的顶点前都会执行一次该代码 189 var vertexArr:Array = 190 [ 191 //op 代表位置输出寄存器, 无论对顶点进行多少次的运算最终都要将结果 192 //赋值给他, 这里和我们的正交矩阵进行相乘的运算 193 "m44 op, va0, vc0", 194 //片段着色器需要用的数据要在这里通过 v0 中转一下, 因为片段着色器不 195 //能直接读取 va0 和 va1 的数据 196 "mov v0, va1" 197 ]; 198 199 //片段着色器代码, 每个可以显示的像素都会执行一次该代码 200 var fragmentArr:Array = 201 [ 202 //对纹理 fs0 进行取样, 通过 v0 代表的 uv 坐标来获取对应的像素点颜 203 //色, 将该颜色值存储到 ft0 中 204 "tex ft0, v0, fs0 <2d,repeat,linear,nomip>", 205 //oc 代表颜色输出寄存器, 每个顶点的颜色数据都要赋值给他 206 "mov oc, ft0" 207 ]; 208 209 //使用 Adobe 自己提供的编译器编译代码为程序可使用的二进制数据 210 var assembler:AGALMiniAssembler = new AGALMiniAssembler(); 211 _program3D = assembler.assemble2(_context3D, 1, vertexArr.join(" "), fragmentArr.join(" ")); 212 213 //----- 这段代码是从 render 里搬过来的, 因为不会进行改动就不放在帧循环中了 ----- 214 215 //指定着色器代码的 va0 代表的数据段, 表示顶点的 x, y, z 坐标 216 _context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); 217 //指定着色器代码的 va1 代表的数据段, 表示顶点的 u, v 数据 218 _context3D.setVertexBufferAt(1, _vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2); 219 //指定上传的纹理由 fs0 表示 220 _context3D.setTextureAt(0, _texture); 221 //指定当前使用的着色器对象 222 _context3D.setProgram(_program3D); 223 } 224 225 private function render(event:Event):void 226 { 227 //清除已绘制过的 3D 图像 228 _context3D.clear(); 229 //通过顶点索引数据绘制所有的三角形 230 _context3D.drawTriangles(_indexBuffer); 231 //将后台缓冲的图像显示到屏幕 232 _context3D.present(); 233 } 234 } 235 }