周五晚上回家在家改了一晚上的bug.之前改了一下shader的结构,结构悲剧。。。有童鞋在问前一篇里面提到的模型的深度值是怎么计算的。其实呢,这个我没有提,这里面也是很大一个坑。这个深度值是通过projection算出来的。 projection我只是提了一下,然后给了一个实现链接。 这个深度值以后也会用到,比如说阴影啊之类的。
今天补上昨天的,初窥AGAL。之前给了一个demo.里面有两段AGAL。但是这个AGAL是什么呢?AGAL说白了,就是对显卡进行编程的指令。我们在写的时候,大家会看到类似汇编的mov之类的指令。 最终我们写的这些汇编级的代码,被通过adobe提供的agalassemble转换成二进制。注意这个二进制只能使用小头格式。应该是上传到gpu里面的二进制都只能使用小头格式。这里也是一个坑。因为as3默认的是大头。
1、stage3d中gpu可用的寄存器。 (一个寄存器由四个浮点数组成,也就是说一个寄存器的体积是四个浮点数。如果要取第一个浮点数,可以通过xyzw来取出。例如va.x, va.y, va.z)
va 寄存器。 va寄存器用于顶点着色器程序里面,属于输入流。一共8个,也就是va0,va1,va2,va3.....va7。在stage3d中。模型的顶点,uv,法线等数据全都是通过vertexbuffer上传的。va寄存器对应这vertexbuff的一段数据。有多少段数据,顶点程序就会执行多少次。顶点程序当前执行的数据来源,就是这一段数据。例如:
[
//X, Y, Z, U, V, nX, nY, nZ
-1, -1, 1, 0, 0, 0, 0, 1,
1, -1, 1, 1, 0, 0, 0, 1,
1, 1, 1, 1, 1, 0, 0, 1,
-1, 1, 1, 0, 1, 0, 0, 1
]
这块数据就是之前demo里面用到的数据。一共有四段。(注意看demo,我们在创建vertexbuffer的时候,就会指定一段数据的长度,数一下,一段8个)。那么顶点着色器程序在执行的流程:
某一段数据存到va寄存器,执行agal顶点程序的指令,然后执行片段着色器程序,执行很多次。大家在看demo的时候就会法线,有一段代码context3D.setVertexBufferAt(0, vertexBuffer, 0,Context3DVertexBufferFormat.FLOAT_3);那么这个过程呢,就是告诉顶点程序, 我使用了vertexBuffer这个顶点数据输入流。将vertexbuffer一段中,从第0个位置开始,取float_3也就是三个数据,当做va9寄存器的输入值。也就是将某一段的前面三个数据当存到了va0寄存器里面。前面这三个数据呢,就是我们指定的顶点坐标。我们也可以指定从第三个位置开始,取float_2,存到va1寄存器,那么这个值就是uv的数据了。context3D.setVertexBufferAt(1, vertexBuffer, 1,Context3DVertexBufferFormat.FLOAT_2);
vc 寄存器,vc寄存器同样也是顶点程序里面可用的寄存器。vc寄存器是常量寄存器,当设置好了vc寄存器的值以后,vc寄存器的值就不会在变化了,同样你也不能修改它的值。之前提到的va寄存器的值,是根据vertexbuffer的数据,一直在变的。类似一个for循环,for each(var va : * in vertexbuffer) {// 这里的va就会一段一段的变化}。但是vc寄存器在整个过程中,都是不会变的。就好比在for循环外面写了一个const vc = xxx。然后这个vc在for循环里面一直都是那个值,也不会变化,也不能被改变。大家看demo的时候就会发现以下一段代码:
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0, modelViewProjection, true );这段代码就是在设置顶点程序的vc寄存器的值。注意看第一个参数。指定了vertex,第二个参数指定从vc里面哪一个寄存器开始。modelViewProjection则是一个矩阵。这是在设置矩阵常量,同样也可以设置vector.<number>常量。因为是设置的矩阵常量,那么之前就说到矩阵是4x4,那么就是16个浮点数,会占用4个寄存器。也就是说,这个0其实是指定的从哪一个vc寄存器开始。在这里vc0 vc1 vc2 vc3都被占用。如果是上传vector.<num>那么寄存器的使用同样也是根据传入的值的多少来决定。vc寄存器可以在agal里面通过[]符合来取。例如vc[0] vc[1] vc[2] vc[3]。但是也只有vc才行。这个功能是由adobe的那个agalassemble工具提供的。vc寄存器一共只有128个。vt 寄存器。vt寄存器是顶点程序里面的临时寄存器,可以对他进行读写。就相当于在for循环里面定义了一个变量。for each(va in vertexbuffer){var vt ......}。临时寄存器只有8个。所以呢,在顶点程序里面,寄存器的数量是有限的,资源很紧缺,这也导致了我们在开发的时候受到了约束,不能随意,大家得极尽优化省着使用。
v 寄存器。因为顶点程序和片段程序是分开的。大家看demo的时候就会发现(如下),在写的时候,我们都是分开写的。 但是如果我们想吧顶点程序的数据传入到片段着色器程序呢?这个时候v寄存器就派上用场了。注意这个是单向的,因为顶点程序执行完了之后,才会执行片段着色程序。所以也就不可能出现片段着色器的数据被传到顶点程序里面去了。v寄存器只有8个可以用。使用场合:例如在片段程序中我们可能需要uv,normal数据,但是这个数据是存在va寄存器里面(因为每一个顶点的uv,normal数据是不同的,所以也只可能存到va)。所以我们就需要通过v寄存器来传输了。
var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
vertexShaderAssembler.assemble
(
Context3DProgramType.VERTEX,
// op是输出目标,va0是我们指定的顶点buffer中的数据,vc0是我们传入的modelViewProj矩阵。m44就是表示,用顶点乘以modelViewProj矩阵,
"m44 op, va0, vc0\n" +
// 将顶点传入中间变量寄存器,因为顶点程序和片段着色器程序是分开的,他们的数据不能通用,因此需要一个中间变量哎传递,并且这个是单向的,只能从顶点程序到片段程序
"mov v0, va0\n" +
// 将指定的buffer中的数据1,也就是uv数据(后面再谈)
"mov v1, va1\n"
);
// 片段着色器程序,专门用来渲染颜色
var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
fragmentShaderAssembler.assemble
(
Context3DProgramType.FRAGMENT,
// grab the texture color from texture fs0
// tex是采样命令,意思就是根据uv数据从传入的贴图fs0安照<2d,repeat,miplinear>方式进行获取颜色信息,存放在ft0中
"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
// oc是颜色输出目标,将ft0传入oc进行输出了。
"mov oc, ft0\n"
);
// 通过context3d创建着色器程序
shaderProgram = context3D.createProgram();<br> // 向显卡上传指令
shaderProgram.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
fs 寄存器,fs寄存器属于片段程序。fs寄存器对应这一个纹理图(texture)。如果没有一个纹理图和它对应,那么就会报错。fs寄存器只有8个。所以呢,我们在一个模型上面应用的特性有限。类似3dsmax里面那种复合材质。一个模型上面有很多个贴图,在stage3d里面就使用不了了。最多只能使用8个。之前demo里面context3D.setTextureAt(0, myTexture);这一段代码,就是在设置fs寄存器和哪一个纹理图进行绑定。0表示下标,myTexture就是绑定的纹理。 即fs0是myTexture。
fc 寄存器,fc寄存器同vc寄存器一样。只有28个。
ft 寄存器,同vt一样。只有8个。
上面谈到的,全都是gpu里面可用的寄存器类型以及数量。下面来谈一下基本的指令用法,虽然指令很少。但是我也记不住,我一般都是查:http://help.adobe.com/en_US/as3/dev/WSd6a006f2eb1dc31e-310b95831324724ec56-8000.html。大家以后再编程的时候,查就行了。但是最基本的要记住。
mov 指令:mov 目的 源。也就是将源寄存器的值复制到目的寄存器里面去。注意之前vc是常量,va是输入流,其实也就是说,只有v和vt寄存器可用(vertex程序,fragment里面也就只有ft)。
add 指令:add 目的 源1 源2。将源1寄存器和源2寄存器2相加,结果存放到目的寄存器。例如:add vt0 va1 vc0。将vc0+vca1的值存放到vt0里面。
mul,sub,div同理add。分别是乘,减,除。
tex 指令:tex指令是用于片段程序里面的,它的功能只有一样,就是采样。 "tex ft0, v1, fs0 <2d,repeat,miplinear>\n" 。它的意思就是将fs0寄存器里面的那个纹理图,通过使用repeat,miplinear算法,根据v1(注意顶点程序的代码)的值(坐标值)进行采样,得到颜色信息存放到ft0寄存器。
关于顶点里面的op和片段里面的oc。这两个分别是输出目标。最终结果向这个两个寄存器输出即可。