深入浅出了解Molehill的底层API-顶点着色器与片段着色器

来源:互联网 发布:科层制的优缺点知乎 编辑:程序博客网 时间:2024/06/05 09:34

引言:
Molehill是一套可以调用显卡资源来渲染显示图形的ActionScript 3.0 API,这套API将在Flash Player 11开始支持,目前在Adobe实验室中已经放出了Flash Player 11的预览版Incubator。在这个预览版公布的当天,很多第三方3D引擎开发比如Away3D, Flare3D也立刻公布了基于Molehill的Alpha版开发框架。对于包括我在内大部分的ActionScript程序员来说,以前应该有过使用第三方框架来开发2.5D的经验,所以对使用新的3D开发框架应该不会有什么大问题,但同时也对Molehill的底层API很感兴趣,很想知道Molehill是如何调用GPU,而且也想知道很多新的名词比如顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)到底是什么意思。本文是我在研究之后的一些经验,希望能帮助那些对此依然困惑的朋友们。也欢迎3D方面的专业人士来指正。

如果你是第一次听说Molehill,或者你不知道如何在Flash Builder里配置开发Incubator的环境,请依次参考我翻译以及总结的其他文章:
Molehill 3D APIs - GPU硬件加速的Flash 3D API
Adobe实验室新品:支持3D硬件加速以及立方贝赛尔曲线绘制的预览版运行时——Incubator
如何通过Flex SDK或者Flash Professional来开发测试Incubator
使用Flare3D在FB中搭建第一个Molehill项目

本文将按照下面的步骤介绍如何使用Molehill底层API在舞台上绘制一个三角形:

在舞台上初始化一个3D环境
创建顶点和片段
使用着色器处理顶点与片段
调用显卡并显示结果

了解flash.display3D.Context3D
Molehill底层API中最重要的一个就是Context3D,它是一个三维空间的处理环境,负责创建并处理三维对象的各个要素比如顶点、片段、透视等等,并将处理的结果使用AGAL(Adobe Graphics Assembly Language, Adobe图形汇编语言)上传给显卡进行运算,运算结果最终被回传给CPU添加到Flash Player的显示层,并呈现在舞台上。

 

Context3D不能被构造函数实例化,而是可以通过stage中的一个新的成员Stage3D来访问,下面这段代码就是如何在2D舞台中初始化一个3D的处理环境:

var stage3D:Stage3D = stage.stage3Ds[0];
stage3D.requestContext3D(Context3DRenderMode.AUTO);
stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextReady);
stage3D.viewPort = new Rectangle(0,0,stage.stageWidth,stage.stageHeight);

在Context3D创建完成之后会调用侦听器onContextReady,接下来便可以访问Context3D并建立它的背景缓冲区:

private function onContextReady(pEvent:Event):void{
          var context3D:Context3D = stage3D.context3D;
          context3D.configureBackBuffer(stage.stageWidth,stage.stageHeight,2,true);
          .....

顶点与片段
三维空间中任何复杂的模型都是由若干个三角形组成的,顶点(Vertex)就是指这些三角形的顶点,而片段(fragment)就是指三角形的填充区域。如果要在Molehill中处理一系列的顶点和片段,首先需要创建一个顶点缓冲(VertexBuffer3D):

var vertexBuffer:VertexBuffer3D = context3D.createVertexBuffer(3,6);
vertexBuffer.uploadFromVector(Vector.< Number >([
      -1,-1,1,1,0,0,
      0,2,1,0,1,0,
      1,-1,1,0,0,1]),0,3);

在这个例子里,通过context3D的方法createVertexBuffer来创建一个顶点缓冲,两个参数3和6说明要创建3个点,每个点需要6个数据,这相当于指定了一个3×6矩阵的结构。然后用vertexBuffer.uploadFromVector从一个Vector数组中定义这一共18个数据的值。以第一行为例,前三个值(-1,-1,1)是该顶点的三维坐标(x,y,z),后三个数据(1,0,0)是该顶点的颜色通道RGB的值,值域为0到1。这样,我们就定义了三个点a(-1,-1,1),b(0,2,1),c(1,-1,1),以及它们的颜色值0xFF0000(红色),0×00FF00(绿色),0×0000FF(蓝色)。

uploadFromVector的后两个参数分别表示从顶点0开始,一共有3个点。

之所以叫做顶点“缓冲”,是因为它是提供给着色器做进一步处理的基础数据,所以接下来是给顶点缓冲定义两个属性:

context3D.setVertexBufferAt(0,vertexBuffer,0,Context3DVertexBufferFormat.FLOAT_3);
context3D.setVertexBufferAt(1,vertexBuffer,3,Context3DVertexBufferFormat.FLOAT_3);

我们可以这样来理解上面的两行代码,假设我们需要给显卡提供一个运算对象v,需要运算的是顶点的位置与颜色,那么这两种数据需要存在两个属性中。setVertexBufferAt的第一个参数就是指定该属性的ID,0代表位置,1代表颜色;第二个参数vertextBuffer不用说了,它提供了运算对象v本身;第三个参数表示该属性要从v的什么位置开始取值;第四个参数可以理解为取几个值。刚才我们讲过,每一行的6个数据里前三个表示位置,后三个表示颜色,所以从每一行的第一个数据开始(索引为0)取3个值(Context3DVertexBufferFormat.FLOAT_3)构成一个3×3的矩阵赋给属性“0”表示位置矩阵,记为”va0″;同理从每一行的第四个数据(索引为3)开始再取3个值构成一个3×3的矩阵赋给属性“1”表示颜色矩阵,记为”va1″。这样一个顶点缓冲就定义完成了。


图1,顶点缓冲的属性分配

顶点着色器和片段着色器
一般在3D应用程序里,最大的运算量在于实时计算物体从三维到二维的投影。Molehill的优势就是利用了显卡资源来处理这些计算。在了解Molehill是如何调用显卡之前,我们需要先了解一下AGAL。

AGAL(Adobe Graphics Assembly Language)是Adobe开发的图形汇编语言,汇编语言是仅高于计算机二进制机器码的低级语言,可以精确地操控机器硬件比如可编程显卡,PC的Dirext9、MAC的OpenGL以及移动设备中的OpenGL ES 2都是可编程显卡,并且都支持AGAL。通过Adobe官方提供的编译器AGALMiniAssembler(实际上是一个AS类库),我们可以通过字符串指令来获得一个AGAL二进制流,再通过context3D上传给显卡的编程管线。对于顶点以及片段的运算都是通过AGAL交由显卡来处理的,这就是传说中的GPU硬件加速。

这个就是AGALMiniAssembler
adobe.zip

介绍AGAL的原因很简单,因为顶点着色器和片段着色器都是AGALMiniAssembler的实例。

先看顶点着色器,它是用来计算顶点从三维到二维的坐标投影。通过下面的代码可以创建一个顶点着色器:

 

var vertexAssembler:AGALMiniAssembler = new AGALMiniAssembler();
vertexAssembler.assemble(Context3DProgramType.VERTEX,
    "m44 op,va0,vc0 /n"+
           "mov v0,va1");

其中的字符串部分是关键,它代表了两个指令,m44和mov。m44表示将va0和vc0相乘后赋给op,va0就是我们上面提到的顶点缓冲属性“0”(vertex attribute 0),它表示三个顶点的位置矩阵。vc0是一个矩阵常量(vertex constant),这个矩阵是一个经过了一系列矩阵运算后,带有三维空间的透视、平移、旋转以及缩放等信息的4×4矩阵。“vc0”里面的“0”表示从最上面一行开始乘,由于指令为m44,所以va0和vc0、vc1、vc2、vc3依次进行相乘运算。运算结果仍然是一个矩阵,存在op(out position)里,它就是所有三维顶点到二维的投影矩阵。第二个指令是mov,它表示将va1复制到v0中,va1是顶点缓冲的属性“1”(vertex attribute 1),指向三个顶点的颜色信息。

再看片段着色器,它是用来计算顶点间三角形的填充颜色或者贴图。下面的片段着色器里是用顶点颜色填充的指令。

var fragmentAssembler:AGALMiniAssembler = new AGALMiniAssembler();
fragmentAssembler.assemble(Context3DProgramType.FRAGMENT,
    "mov oc,v0");

其中v0是刚才通过顶点缓冲颜色矩阵复制过来的矩阵,通过指令“mov”再复制给oc (output color),输出顶点间的颜色。

如果你想了解所有类似m44,mov这样的指令,请参考这个列表。

从上面可以看出,在顶点着色器中用到的那个矩阵常量vc0十分关键,因为它表示了该三维空间的透视以及变形特征。下面我简单介绍一下矩阵运算实际意义。

一个4×4矩阵的结构如下:

 


图2,4×4的矩阵结构

它在Molehill中用Matrix3D来定义:

var modelView:Matrix3D = new Matrix3D();

数学不好的朋友不用担心,Matrix3D提供了一些API来实现例如平移和旋转这样的运算,下面是将一个矩阵旋转后再平移的代码:

//沿X轴旋转60度
modelView.appendRotation(60,Vector3D.X_AXIS);
//沿Z轴平移-1
modelView.appendTranslation(0,0,-1);

在刚才下载的类库中有一个叫做PerspectiveMatrix3D的类,它包括了一些创建透视的API,比如下面这个表示的是创建一个宽高为2×2,最小景深为.5,最大景深为2000的三维透视空间。

var perspective:PerspectiveMatrix3D = new PerspectiveMatrix3D();
//将一个矩阵变成单位矩阵
perspective.identity();
perspective.perspectiveLH(2,2,.5,2000);

如图2,虚线是一个透视空间,红线表示空间的宽高;蓝线表示空间与坐标原点的距离。由于红线范围不关多大多小都表示2D舞台的大小,所以空间的宽高(红线范围)与三角形的投影大小成反比,空间离原点的距离(蓝线)与物体的投影大小成正比。

 


图3,透视空间与三维物体的关系

将这个矩阵与刚才的矩阵常量相乘便可以得到一个具有透视特征的矩阵常量:

 

modelView.append(perspective);

最后通过context3D来定义这个常量:

//0表示从最上面一行开始,最后一个参数表示是否翻转矩阵。
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,modelView,true);

调用显卡并显示结果
最后就是将定义好的着色器上传到显卡,通过下面的代码来实现:

//给显卡管线创建一个程序
var program:Program3D = context3D.createProgram();
//给该程序上传顶点着色器和片段着色器
program.upload(vertexAssembler.agalcode,fragmentAssembler.agalcode);
context3D.setProgram(program);

如果要运行该program,context3D还需要一个顶点的排列索引缓冲(IndexBuffer3D),因为如果一个顶点缓冲里有很多个点,context3D必须知道它们是如何分组来构成三角形的。

//创建一个顶点索引缓冲,用来排列3个点
indexBuffer = context3D.createIndexBuffer(3);
//用一个有序数组来表示三个点的先后顺序
indexBuffer.uploadFromVector(Vector.<uint>([
 0,1,2
        ]),0,3);

最后就可以用下面的代码把这个三角形绘制出来了。

context3D.drawTriangles(indexBuffer);
context3D.present();

下载源文件
IncubatorExample.zip

 


原文链接:http://jamesli.cn/blog/?p=748

原创粉丝点击