我们上一章节显示图片的时候,会发现我们制定的顶点在Stage3D中其实是存在一个区间的:

x轴(从左到右):[-1.0-1.0]

y轴(从下到上):[-1.0-1.0]

z轴(从近到远):[0-1.0]

超过这个区间的部分我们的图片都会看不见,大家可以重新修改上一节的代码中的顶点位置查看;

并且该区间不会跟随Stage3D的尺寸改变而改变,即无论Stage3D的显示尺寸如何变动,绘制的图像是会进行对应的拉伸操作的;

那么这就导致了一个问题的出现,如果我需要显示图片的原有尺寸(或者指定的尺寸)且该尺寸不会跟随Stage3D的显示尺寸变动时该怎么办?

为了解决这个问题,我们需要引入一个叫做矩阵转换的概念,即通过一定的转换运算把我们设定的尺寸转换为适用于Stage3D中的坐标尺寸。

一般存在两种矩阵转换:正交矩阵和透视矩阵。

透视矩阵:遵循现实世界中的近大远小法则,一般的3D游戏都使用该矩阵。

正交矩阵:没有近大远小的规则,所有物体看上去都是一样的大小,一般通过3D来实现的2D游戏都使用该矩阵。

我们基于上一章的代码,先修改一下要显示的纹理的顶点:

 private function initBuffer():void
{
//顶点数据
var vertexData:Vector.<Number> = Vector.<Number>(
[
// x, y, z, u, v
-1, -1, 0, 0, 1,
1, -1, 0, 1, 1,
1, 1, 0, 1, 0,
-1, 1, 0, 0, 0
]);
//省略
}

看一下效果:

我们看见,整个纹理都铺满了3D场景。同时纹理的原有的高宽比没有了。

使用正交矩阵:

首先需要创建一个矩阵对象保存正交矩阵:

 //存放正交矩阵的对象
private var _projectionMatrix:Matrix3D;

创建正交矩阵:

 /**
* 创建正交矩阵.
* @param width 场景宽度.
* @param height 场景高度.
* @param near 近截面.
* @param far 远截面.
*/
private function initOrthographicProjection(width:Number, height:Number, near:Number = -1.0, far:Number = 1.0):void
{
//创建正交矩阵的实例
_projectionMatrix = new Matrix3D();
//获取场景宽度和高度的比例
var ratio:Number = width / height;
//获取正交矩阵数据
var coords:Vector.<Number> = getOrthographicMatrix(-ratio, ratio, -1, 1, near, far);
//拷贝数据到矩阵中
_projectionMatrix.copyRawDataFrom(coords);
//将我们的正交矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器
_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _projectionMatrix, true);
} /**
* 获取正交矩阵的数据.
* @param left 左边界.
* @param right 右边界.
* @param bottom 底边界.
* @param top 顶边界.
* @param near 近截面.
* @param far 远截面.
* @return 正交矩阵的数据.
*/
private function getOrthographicMatrix(left:Number, right:Number, bottom:Number, top:Number, near:Number, far:Number):Vector.<Number>
{
var m:Vector.<Number> = new Vector.<Number>(16, true); m[0] = 2.0 * 1.0 / (right - left);
m[5] = 2.0 * 1.0 / (top - bottom);
m[10] = 1.0 / (far - near); m[12] = (right + left) / (right - left);
m[13] = (bottom + top) / (bottom - top);
m[14] = near / (near - far); m[1] = m[2] = m[3] = m[4] =m[6] = m[7] = m[8] = m[9] = m[11] = 0;
m[15] = 1.0; return m;
}

正交矩阵的信息需要作为常量传递到GPU中:

 //将我们的正交矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器
_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _projectionMatrix, true);

最后,修改一下顶点着色器,使我们的顶点数据和正交矩阵相乘:

 private function initProgram():void
{
//顶点着色器代码, 每个上传的顶点前都会执行一次该代码
var vertexArr:Array =
[
//op 代表位置输出寄存器, 无论对顶点进行多少次的运算最终都要将结果
//赋值给他, 这里和我们的正交矩阵进行相乘的运算
"m44 op, va0, vc0",
//片段着色器需要用的数据要在这里通过 v0 中转一下, 因为片段着色器不
//能直接读取 va0 和 va1 的数据
"mov v0, va1"
];
//省略
}

改好了以后,看一下现在的效果:

我们发现纹理的高宽比被保留了下来,说明我们的正交矩阵开始起作用了!

保留我们的纹理的尺寸只要修改顶点的数据即可:

 private function initBuffer():void
{
//顶点数据
var vertexData:Vector.<Number> = Vector.<Number>(
[
// x, y, z, u, v
-1 * (128 / 500), -1 * (128 / 500), 0, 0, 1,
1 * (128 / 500), -1 * (128 / 500), 0, 1, 1,
1 * (128 / 500), 1 * (128 / 500), 0, 1, 0,
-1 * (128 / 500), 1 * (128 / 500), 0, 0, 0
]);
//省略
}

顶点x和y都乘上宽度与高度分别除以场景高度的系数即可。

最终效果:

上代码:

 package
{
import com.adobe.utils.AGALMiniAssembler; import flash.display.Bitmap; import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.display3D.Context3DProfile;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
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.ErrorEvent;
import flash.events.Event;
import flash.geom.Matrix3D; [SWF(width=800, height=600, frameRate=60)]
public class OrthographicProjection extends Sprite
{
[Embed(source="img.png")]
private var IMG_CLASS:Class; //3D 场景对象
private var _stage3D:Stage3D;
//3D 上下文渲染对象
private var _context3D:Context3D; //顶点缓冲数据
private var _vertexBuffer:VertexBuffer3D;
//索引缓冲数据
private var _indexBuffer:IndexBuffer3D;
//纹理数据对象
private var _texture:Texture; //着色器对象
private var _program3D:Program3D; //存放正交矩阵的对象
private var _projectionMatrix:Matrix3D; public function OrthographicProjection()
{
addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
} private function addedToStageHandler(event:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); //3D 场景存在, 一般存在 4 个 3D 场景对象
if(stage.stage3Ds.length > 0)
{
//使用最下层的 3D 场景
_stage3D = stage.stage3Ds[0];
//请求 3D 上下文渲染对象
_stage3D.addEventListener(ErrorEvent.ERROR, stage3DErrorHandler);
_stage3D.addEventListener(Event.CONTEXT3D_CREATE, context3DCreateHandler);
_stage3D.requestContext3D(Context3DRenderMode.AUTO, Context3DProfile.BASELINE);
}
} private function stage3DErrorHandler(event:ErrorEvent):void
{
trace("Context3D对象请求失败:", event.text);
} private function context3DCreateHandler(event:Event):void
{
initContext3D(700, 500);
initOrthographicProjection(700, 500);
initBuffer();
initTexture();
initProgram(); //每帧进行渲染
addEventListener(Event.ENTER_FRAME, render);
} private function initContext3D(width:Number, height:Number):void
{
//获取 3D 渲染对象
_context3D = _stage3D.context3D;
//调整 3D 舞台位置
_stage3D.x = 50;
_stage3D.y = 50;
//设置后台缓冲区
_context3D.configureBackBuffer(width, height, 2);
} /**
* 创建正交矩阵.
* @param width 场景宽度.
* @param height 场景高度.
* @param near 近截面.
* @param far 远截面.
*/
private function initOrthographicProjection(width:Number, height:Number, near:Number = -1.0, far:Number = 1.0):void
{
//创建正交矩阵的实例
_projectionMatrix = new Matrix3D();
//获取场景宽度和高度的比例
var ratio:Number = width / height;
//获取正交矩阵数据
var coords:Vector.<Number> = getOrthographicMatrix(-ratio, ratio, -1, 1, near, far);
//拷贝数据到矩阵中
_projectionMatrix.copyRawDataFrom(coords);
//将我们的正交矩阵作为常量传递到 GPU 中, 指定其是名称为 vc0 的那个寄存器
_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _projectionMatrix, true);
} /**
* 获取正交矩阵的数据.
* @param left 左边界.
* @param right 右边界.
* @param bottom 底边界.
* @param top 顶边界.
* @param near 近截面.
* @param far 远截面.
* @return 正交矩阵的数据.
*/
private function getOrthographicMatrix(left:Number, right:Number, bottom:Number, top:Number, near:Number, far:Number):Vector.<Number>
{
var m:Vector.<Number> = new Vector.<Number>(16, true); m[0] = 2.0 * 1.0 / (right - left);
m[5] = 2.0 * 1.0 / (top - bottom);
m[10] = 1.0 / (far - near); m[12] = (right + left) / (right - left);
m[13] = (bottom + top) / (bottom - top);
m[14] = near / (near - far); m[1] = m[2] = m[3] = m[4] =m[6] = m[7] = m[8] = m[9] = m[11] = 0;
m[15] = 1.0; return m;
} private function initBuffer():void
{
//顶点数据
var vertexData:Vector.<Number> = Vector.<Number>(
[
// x, y, z, u, v
-1 * (128 / 500), -1 * (128 / 500), 0, 0, 1,
1 * (128 / 500), -1 * (128 / 500), 0, 1, 1,
1 * (128 / 500), 1 * (128 / 500), 0, 1, 0,
-1 * (128 / 500), 1 * (128 / 500), 0, 0, 0
]); //创建顶点缓冲对象, 参数设定存在几组数据和每组数据的个数
_vertexBuffer = _context3D.createVertexBuffer(vertexData.length / 5, 5);
//上传顶点数据到GPU, 参数设定从第几组数据开始上传和上传多少组数据
_vertexBuffer.uploadFromVector(vertexData, 0, vertexData.length / 5); //索引数据
var indexData:Vector.<uint> = Vector.<uint>(
[
0, 3, 1,
1, 2, 3
]); //创建索引缓冲对象, 每个索引对应顶点数据中的相对应的一组数据,
//每3个索引组成一个会被绘制出来的三角形, 参数指定索引的长度
_indexBuffer = _context3D.createIndexBuffer(indexData.length);
//上传索引数据到GPU, 参数设定从第几个数据开始上传和上传多少个数据
_indexBuffer.uploadFromVector(indexData, 0, indexData.length);
} private function initTexture():void
{
//创建位图
var bitmap:Bitmap = new IMG_CLASS() as Bitmap;
//创建纹理, 注意尺寸必须是 2 的幂数
_texture = _context3D.createTexture(128, 128, Context3DTextureFormat.BGRA, true);
//上传纹理到 GPU, 第二个参数表示该纹理的 mipmap 级别, 级别零是高级全分辨率图像
_texture.uploadFromBitmapData(bitmap.bitmapData, 0);
} private function initProgram():void
{
//顶点着色器代码, 每个上传的顶点前都会执行一次该代码
var vertexArr:Array =
[
//op 代表位置输出寄存器, 无论对顶点进行多少次的运算最终都要将结果
//赋值给他, 这里和我们的正交矩阵进行相乘的运算
"m44 op, va0, vc0",
//片段着色器需要用的数据要在这里通过 v0 中转一下, 因为片段着色器不
//能直接读取 va0 和 va1 的数据
"mov v0, va1"
]; //片段着色器代码, 每个可以显示的像素都会执行一次该代码
var fragmentArr:Array =
[
//对纹理 fs0 进行取样, 通过 v0 代表的 uv 坐标来获取对应的像素点颜
//色, 将该颜色值存储到 ft0 中
"tex ft0, v0, fs0 <2d,repeat,linear,nomip>",
//oc 代表颜色输出寄存器, 每个顶点的颜色数据都要赋值给他
"mov oc, ft0"
]; //使用 Adobe 自己提供的编译器编译代码为程序可使用的二进制数据
var assembler:AGALMiniAssembler = new AGALMiniAssembler();
_program3D = assembler.assemble2(_context3D, 1, vertexArr.join("\n"), fragmentArr.join("\n")); //----- 这段代码是从 render 里搬过来的, 因为不会进行改动就不放在帧循环中了 ----- //指定着色器代码的 va0 代表的数据段, 表示顶点的 x, y, z 坐标
_context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
//指定着色器代码的 va1 代表的数据段, 表示顶点的 u, v 数据
_context3D.setVertexBufferAt(1, _vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
//指定上传的纹理由 fs0 表示
_context3D.setTextureAt(0, _texture);
//指定当前使用的着色器对象
_context3D.setProgram(_program3D);
} private function render(event:Event):void
{
//清除已绘制过的 3D 图像
_context3D.clear();
//通过顶点索引数据绘制所有的三角形
_context3D.drawTriangles(_indexBuffer);
//将后台缓冲的图像显示到屏幕
_context3D.present();
}
}
}

Stage3D学习笔记(四):正交矩阵的更多相关文章

  1. 【Stage3D学习笔记续】山寨Starling(八):核心优化(批处理)的实现

    批处理是使GPU进行高效绘制的一种技术手段,也是整个渲染流程中最核心的技术,到目前为止我们并没有使用到这种技术手段,下面我们看看我们现在的渲染机制. 先想一想我们最开始是怎么向GPU绘制一幅图像的,可 ...

  2. Stage3D学习笔记(五):通过矩阵操作纹理

    虽然我们上一节已经实现了正交矩阵的显示,但是可以明显的感觉到要调整显示纹理的坐标和尺寸是相当复杂的,需要对每个顶点进行操作,如果还要加上注册点和旋转的话,用上一节的方法来做是会让人发疯的! 所以我们距 ...

  3. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  4. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

  5. java之jvm学习笔记四(安全管理器)

    java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...

  6. Learning ROS for Robotics Programming Second Edition学习笔记(四) indigo devices

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  7. Typescript 学习笔记四:回忆ES5 中的类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  8. ES6学习笔记<四> default、rest、Multi-line Strings

    default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...

  9. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  10. python3.4学习笔记(四) 3.x和2.x的区别,持续更新

    python3.4学习笔记(四) 3.x和2.x的区别 在2.x中:print html,3.x中必须改成:print(html) import urllib2ImportError: No modu ...

随机推荐

  1. 创业公司Playcafe关门大吉 创始人总结10大失败教训

    导读:互联网电视游戏网站PlayCafe的创始人马克·高登森(Mark Goldenson)日前撰文,总结了自己创业失败的十个教训.以下为文章主要内容: 一年半前,我与公司联合创始人戴维·奈格(Dev ...

  2. 对Cost (%CPU) 粗略的理解

    今天研究执行计划,看到执行计划里面有Cost (%CPU),我这边研究了一把,不知道对与否,拿出来晒晒 在Oracle 10g中,Oracle 把CPU的cost也统计在执行计划中去了, 这和以前的8 ...

  3. Linux配置vpn

    配置如下

  4. Android2.3.7源码结构分析

    对Andorid系统进行分析或者系统功能定制的时候,我们经常需要在众多文件中花费大量时间定位所需关注的部分.为了减轻这部分枯燥而不可避免的工作,本文对2.3.7版本的源码结构进行了简单分析.希望对刚加 ...

  5. 第三部分 MediaPlayer的主要实现分析

    第三部分 MediaPlayer的主要实现分析 3.1 JAVA程序部分    在packages/apps/Music/src/com/android/music/目录的MediaPlaybackS ...

  6. Oracle 给表添加主键和使ID自增、触发器、创建结构一样的表

    1.关于主键:在建表时指定primary key字句即可:create table test( id number(6) primary key, name varchar2(30));如果是对于已经 ...

  7. ASP.NET MVC 基础

    ASP.NET MVC oo1 Mvc准备工作课程安排:ORM->AspNet MVC开发环境:VS2012/VS2013SqlServer2008/2005主讲Asp.Net Mvc4 Raz ...

  8. [转] 在Asp.net前台和后台弹出提示框

    一.在前台弹出提示框 1.点击"A"标记或者"控件按钮"弹出提示框 <asp:LinkButton ID="lbtnDel" runa ...

  9. java jvm学习笔记九(策略文件)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao/article/details/8271407 课程源码:http://download.csdn.net/detail ...

  10. 并发编程之--ConcurrentSkipListMap

    概要 本章对Java.util.concurrent包中的ConcurrentSkipListMap类进行详细的介绍.内容包括:ConcurrentSkipListMap介绍ConcurrentSki ...