Stage3D学习笔记(五):通过矩阵操作纹理
2014-11-05 12:58 阿诚de窝 阅读(649) 评论(0) 编辑 收藏 举报虽然我们上一节已经实现了正交矩阵的显示,但是可以明显的感觉到要调整显示纹理的坐标和尺寸是相当复杂的,需要对每个顶点进行操作,如果还要加上注册点和旋转的话,用上一节的方法来做是会让人发疯的!
所以我们距离实用还有很长的路要走,加上之前面试时由于对Starling内部实现的不了解导致的失利,所以接下来的一段学习笔记会专注在通过正交矩阵来实现2D框架的实现上,并参考Starling的架构做一个精简的山寨版!
首要任务,就是要能方便的对纹理设置位置、尺寸和旋转!下面介绍Starling中使用的方法,借助Flash原生的Matrix3D类来实现对纹理的操作。
我们基于Stage3D学习笔记(三)的代码来修改;
首先我们除了需要一个正交矩阵用来进行正交运算外,还需要一个矩阵用来记录模型的信息(我们可以把显示的纹理看做由两个三角面拼成的一个四方形模型)。
1 //正交矩阵 2 private var _projectionMatrix:Matrix3D; 3 //模型矩阵, 通过操作该矩阵来变换纹理的显示 4 private var _modelViewMatrix:Matrix3D;
正交矩阵的初始化相对于上一节有所变动,为了清晰没有使用新版Starling的简化过的矩阵,而是使用第一版的矩阵。
1 private function initOrthographicProjection(width:Number, height:Number, near:Number = -1.0, far:Number = 1.0):void 2 { 3 //创建正交矩阵的实例 4 _projectionMatrix = new Matrix3D(); 5 //设置正交矩阵数据, 这个公式记死即可 6 var coords:Vector.<Number> = new <Number> 7 [ 8 2.0 / width, 0.0, 0.0, 0.0, 9 0.0, -2.0 / height, 0.0, 0.0, 10 0.0, 0.0, -2.0 / (far - near), 0.0, 11 -1.0, 1.0, -(far + near) / (far - near), 1.0 12 ]; 13 _projectionMatrix.copyRawDataFrom(coords); 14 }
还有一点需要更改的是,由于我们的模型的尺寸选择等信息都交由_modelViewMatrix对象来存储,所以顶点数据可以不需要修改,因为我们的需求只有一个,就是显示一个四边形的图片,所以顶点数据可以写死,可以去掉z轴信息,uv信息和xy的信息一致,也可以去掉,不过我们这里进行了保留。
1 private function initBuffer():void 2 { 3 //顶点数据, 因为只需要显示一张图片所以这里的顶点数据是可以写死的, 同时可以去掉 z 轴 4 //的数据, 因为不需要使用到 z 轴, 我们按照下面的规则来排列顶点: 5 //0 - 1 6 //| / | 7 //2 - 3 8 //有趣的是 uv 的数据和顶点数据其实是一致的, 所以 uv 的数据也可以去除, 不过我们这里 9 //先留着, Starling 框架中 uv 数据是已经去掉的 10 var vertexData:Vector.<Number> = Vector.<Number>( 11 [ 12 // x, y, u, v 13 0, 0, 0, 0, 14 1, 0, 1, 0, 15 0, 1, 0, 1, 16 1, 1, 1, 1 17 ]); 18 //省略 19 }
由于去掉了z轴的数据,所以数据上传的格式也要进行相应的更改,具体可以看最终的代码。
模型矩阵是直接操作当前的纹理的显示的,对他的修改也比较简单:
1 private function transformMatrix():void 2 { 3 //设置纹理的转换矩阵 4 _modelViewMatrix = new Matrix3D(); 5 //设置矩阵的位置 6 _modelViewMatrix.prependTranslation(0, 0, 0); 7 //设置矩阵的旋转 8 _modelViewMatrix.prependRotation(0, Vector3D.Z_AXIS); 9 //设置矩阵的尺寸 10 _modelViewMatrix.prependScale(128, 128, 1); 11 //设置纹理的中心点 12 _modelViewMatrix.prependTranslation(0, 0, 0); 13 14 //将纹理的转换矩阵和正交矩阵结合就得到了最终需要的上传到 GPU 进行运算的矩阵数据 15 var mvpMatrix:Matrix3D = new Matrix3D(); 16 mvpMatrix.append(_modelViewMatrix); 17 mvpMatrix.append(_projectionMatrix); 18 //将我们的最终矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器 19 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mvpMatrix, true); 20 }
需要注意的是,模型矩阵和正交矩阵是需要进行合并的。
我们看一下现在的效果:
不错哦,是我们想要的效果,下面测试一下旋转和位移:
1 private function transformMatrix():void 2 { 3 //设置纹理的转换矩阵 4 _modelViewMatrix = new Matrix3D(); 5 //设置矩阵的位置 6 _modelViewMatrix.prependTranslation(100, 100, 0); 7 //设置矩阵的旋转 8 _modelViewMatrix.prependRotation(45, Vector3D.Z_AXIS); 9 //设置矩阵的尺寸 10 _modelViewMatrix.prependScale(128, 128, 1); 11 //设置纹理的中心点 12 _modelViewMatrix.prependTranslation(0, 0, 0); 13 //省略 14 }
直接给出代码了:
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 import flash.geom.Vector3D; 23 24 [SWF(width=550, height=400, frameRate=60)] 25 public class Matrix3DTest extends Sprite 26 { 27 [Embed(source="img.png")] 28 private var IMG_CLASS:Class; 29 30 //3D 场景对象 31 private var _stage3D:Stage3D; 32 //3D 上下文渲染对象 33 private var _context3D:Context3D; 34 35 //顶点缓冲数据 36 private var _vertexBuffer:VertexBuffer3D; 37 //索引缓冲数据 38 private var _indexBuffer:IndexBuffer3D; 39 //纹理数据对象 40 private var _texture:Texture; 41 42 //着色器对象 43 private var _program3D:Program3D; 44 45 //正交矩阵 46 private var _projectionMatrix:Matrix3D; 47 //模型矩阵, 通过操作该矩阵来变换纹理的显示 48 private var _modelViewMatrix:Matrix3D; 49 50 public function Matrix3DTest() 51 { 52 addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 53 } 54 55 private function addedToStageHandler(event:Event):void 56 { 57 removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 58 59 //3D 场景存在, 一般存在 4 个 3D 场景对象 60 if(stage.stage3Ds.length > 0) 61 { 62 //使用最下层的 3D 场景 63 _stage3D = stage.stage3Ds[0]; 64 //请求 3D 上下文渲染对象 65 _stage3D.addEventListener(ErrorEvent.ERROR, stage3DErrorHandler); 66 _stage3D.addEventListener(Event.CONTEXT3D_CREATE, context3DCreateHandler); 67 _stage3D.requestContext3D(Context3DRenderMode.AUTO, Context3DProfile.BASELINE); 68 } 69 } 70 71 private function stage3DErrorHandler(event:ErrorEvent):void 72 { 73 trace("Context3D对象请求失败:", event.text); 74 } 75 76 private function context3DCreateHandler(event:Event):void 77 { 78 initContext3D(); 79 initOrthographicProjection(stage.stageWidth, stage.stageHeight); 80 initBuffer(); 81 initTexture(); 82 transformMatrix(); 83 initProgram(); 84 85 //每帧进行渲染 86 addEventListener(Event.ENTER_FRAME, render); 87 } 88 89 private function initContext3D():void 90 { 91 //获取 3D 渲染对象 92 _context3D = _stage3D.context3D; 93 //设置后台缓冲区 94 _context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2); 95 } 96 97 private function initOrthographicProjection(width:Number, height:Number, near:Number = -1.0, far:Number = 1.0):void 98 { 99 //创建正交矩阵的实例 100 _projectionMatrix = new Matrix3D(); 101 //设置正交矩阵数据, 这个公式记死即可 102 var coords:Vector.<Number> = new <Number> 103 [ 104 2.0 / width, 0.0, 0.0, 0.0, 105 0.0, -2.0 / height, 0.0, 0.0, 106 0.0, 0.0, -2.0 / (far - near), 0.0, 107 -1.0, 1.0, -(far + near) / (far - near), 1.0 108 ]; 109 _projectionMatrix.copyRawDataFrom(coords); 110 } 111 112 private function initBuffer():void 113 { 114 //顶点数据, 因为只需要显示一张图片所以这里的顶点数据是可以写死的, 同时可以去掉 z 轴 115 //的数据, 因为不需要使用到 z 轴, 我们按照下面的规则来排列顶点: 116 //0 - 1 117 //| / | 118 //2 - 3 119 //有趣的是 uv 的数据和顶点数据其实是一致的, 所以 uv 的数据也可以去除, 不过我们这里 120 //先留着, Starling 框架中 uv 数据是已经去掉的 121 var vertexData:Vector.<Number> = Vector.<Number>( 122 [ 123 // x, y, u, v 124 0, 0, 0, 0, 125 1, 0, 1, 0, 126 0, 1, 0, 1, 127 1, 1, 1, 1 128 ]); 129 130 //创建顶点缓冲对象, 参数设定存在几组数据和每组数据的个数 131 _vertexBuffer = _context3D.createVertexBuffer(vertexData.length / 4, 4); 132 //上传顶点数据到GPU, 参数设定从第几组数据开始上传和上传多少组数据 133 _vertexBuffer.uploadFromVector(vertexData, 0, vertexData.length / 4); 134 135 //索引数据 136 var indexData:Vector.<uint> = Vector.<uint>( 137 [ 138 0, 1, 2, 139 1, 2, 3 140 ]); 141 142 //创建索引缓冲对象, 每个索引对应顶点数据中的相对应的一组数据, 143 //每3个索引组成一个会被绘制出来的三角形, 参数指定索引的长度 144 _indexBuffer = _context3D.createIndexBuffer(indexData.length); 145 //上传索引数据到GPU, 参数设定从第几个数据开始上传和上传多少个数据 146 _indexBuffer.uploadFromVector(indexData, 0, indexData.length); 147 } 148 149 private function initTexture():void 150 { 151 //创建位图 152 var bitmap:Bitmap = new IMG_CLASS() as Bitmap; 153 //创建纹理, 注意尺寸必须是 2 的幂数 154 _texture = _context3D.createTexture(128, 128, Context3DTextureFormat.BGRA, true); 155 //上传纹理到 GPU, 第二个参数表示该纹理的 mipmap 级别, 级别零是高级全分辨率图像 156 _texture.uploadFromBitmapData(bitmap.bitmapData, 0); 157 } 158 159 private function transformMatrix():void 160 { 161 //设置纹理的转换矩阵 162 _modelViewMatrix = new Matrix3D(); 163 //设置矩阵的位置 164 _modelViewMatrix.prependTranslation(100, 100, 0); 165 //设置矩阵的旋转 166 _modelViewMatrix.prependRotation(45, Vector3D.Z_AXIS); 167 //设置矩阵的尺寸 168 _modelViewMatrix.prependScale(128, 128, 1); 169 //设置纹理的中心点 170 _modelViewMatrix.prependTranslation(0, 0, 0); 171 172 //将纹理的转换矩阵和正交矩阵结合就得到了最终需要的上传到 GPU 进行运算的矩阵数据 173 var mvpMatrix:Matrix3D = new Matrix3D(); 174 mvpMatrix.append(_modelViewMatrix); 175 mvpMatrix.append(_projectionMatrix); 176 //将我们的最终矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器 177 _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mvpMatrix, true); 178 } 179 180 private function initProgram():void 181 { 182 //顶点着色器代码, 每个上传的顶点前都会执行一次该代码 183 var vertexArr:Array = 184 [ 185 //op 代表位置输出寄存器, 无论对顶点进行多少次的运算最终都要将结果 186 //赋值给他, 这里和我们的正交矩阵进行相乘的运算 187 "m44 op, va0, vc0", 188 //片段着色器需要用的数据要在这里通过 v0 中转一下, 因为片段着色器不 189 //能直接读取 va0 和 va1 的数据 190 "mov v0, va1" 191 ]; 192 193 //片段着色器代码, 每个可以显示的像素都会执行一次该代码 194 var fragmentArr:Array = 195 [ 196 //对纹理 fs0 进行取样, 通过 v0 代表的 uv 坐标来获取对应的像素点颜 197 //色, 将该颜色值存储到 ft0 中 198 "tex ft0, v0, fs0 <2d,repeat,linear,nomip>", 199 //oc 代表颜色输出寄存器, 每个顶点的颜色数据都要赋值给他 200 "mov oc, ft0" 201 ]; 202 203 //使用 Adobe 自己提供的编译器编译代码为程序可使用的二进制数据 204 var assembler:AGALMiniAssembler = new AGALMiniAssembler(); 205 _program3D = assembler.assemble2(_context3D, 1, vertexArr.join("\n"), fragmentArr.join("\n")); 206 207 //----- 这段代码是从 render 里搬过来的, 因为不会进行改动就不放在帧循环中了 ----- 208 209 //指定着色器代码的 va0 代表的数据段, 表示顶点的 x, y 坐标 210 _context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2); 211 //指定着色器代码的 va1 代表的数据段, 表示顶点的 u, v 数据 212 _context3D.setVertexBufferAt(1, _vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_2); 213 //指定上传的纹理由 fs0 表示 214 _context3D.setTextureAt(0, _texture); 215 //指定当前使用的着色器对象 216 _context3D.setProgram(_program3D); 217 } 218 219 private function render(event:Event):void 220 { 221 //清除已绘制过的 3D 图像 222 _context3D.clear(); 223 //通过顶点索引数据绘制所有的三角形 224 _context3D.drawTriangles(_indexBuffer); 225 //将后台缓冲的图像显示到屏幕 226 _context3D.present(); 227 } 228 } 229 }
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步