回顧之前,我們已經實現了顏色填充的四邊形,以及具有紋理貼圖的四邊形。如果僅僅只是這些,那實在是太無聊了,通過這些我們能夠實現的東西無非就是一堆可以動的圖片,當然對於某些需求這已經足夠,但我們並不因此而止步。另一方面,GPU硬件的能力也遠不止如此,這些單調的貼圖四邊形遠遠沒有發揮為其提供的巨大資源。
接下來我們該研究怎樣讓GPU盡量地發揮它們應有的價值了。但是為了給GPU施加指令,就需要編寫shader program,於是你會發現有一個沖突,我們之前的很多顯示對象是共享shader program的,但是為了實現更豐富的表現效果,應該讓每個對象的shader program獨立開來。所以,很明顯,我們不能在原有的shader program上動手腳了。
但是如何才能實現shader program的分離呢?不要着急,慢慢來看:
如果你理解了圖形引擎的基本原理,你應該很快能看懂這幅圖。它描述的是多次繪制,一次呈現的渲染過程,這也是我目前已經實現的。現在的問題是,一旦我們將像素繪制到緩沖區中,我們就(幾乎)沒有辦法來改變它,換句話說我們可以對像素進行操作的階段只能在繪制執行之前。如果有一種方法,能夠讓我對繪制出來的像素,即在緩沖區中的像素進行更多的操作,那么結果將會更加的豐富。
為了實現這種功能,stage3D為我們提供了一個接口:
1 Context3D::setRenderToTexture(texture:flash.display3D.textures:TextureBase, enableDepthAndStencil:Boolean = false, antiAlias:int = 0, surfaceSelector:int = 0):void
通過該接口可以指定一個紋理對象,並將之后(通過調用Context3D::drawTriangles())繪制的內容繪制到相應的紋理之上。有了這個接口,我們就可以將像素繪制到紋理,紋理在手,天下我有,接下來我們只需要對紋理進行fragment program並繪制到緩沖區,當然你也可以不讓它馬上進入緩沖區,而是重復上一過程,繼續繪制到另一個紋理,然后再進行fragment program,直到你得到了想要的效果。事實上,如下圖:
我們可以不斷地重復上述的過程,或者通過不同的子過程進行組合,從而創造出豐富的效果的同時,不失程序設計的靈活性。
下面,我們將這一過程進行實現。首先我們建立一個叫做Pass的類:
1 package psw2d.pass 2 { 3 import com.adobe.utils.AGALMiniAssembler; 4 5 import flash.display3D.Context3D; 6 import flash.display3D.Context3DProgramType; 7 import flash.display3D.Context3DTextureFormat; 8 import flash.display3D.IndexBuffer3D; 9 import flash.display3D.Program3D; 10 import flash.display3D.textures.Texture; 11 import flash.utils.ByteArray; 12 13 public class Pass 14 { 15 protected static const agal:AGALMiniAssembler = new AGALMiniAssembler(); 16 protected var _shaderVertex:String; 17 protected var _shaderFragment:String; 18 protected var _program:Program3D; 19 protected var _context:Context3D; 20 protected var _isRenderToTexture:Boolean; 21 protected var _texture:Texture; 22 23 public function Pass(context:Context3D, isRenderToTexture:Boolean, width:Number=1, height:Number=1) 24 { 25 _context = context; 26 _isRenderToTexture = isRenderToTexture; 27 if(_isRenderToTexture) _texture = _context.createTexture(width, height, Context3DTextureFormat.BGRA, true); 28 } 29 30 public function assemble():void 31 { 32 var vertexShader:ByteArray = agal.assemble(Context3DProgramType.VERTEX, _shaderVertex); 33 var fragmentShader:ByteArray = agal.assemble(Context3DProgramType.FRAGMENT, _shaderFragment); 34 _program = _context.createProgram(); 35 _program.upload(vertexShader, fragmentShader); 36 } 37 38 public function render(iBuffer:IndexBuffer3D) : void { 39 if(!_isRenderToTexture) _context.setRenderToBackBuffer(); 40 else _context.setRenderToTexture(_texture, false, 1); 41 42 _context.clear(0, 0, 0, 1); 43 _context.setProgram(_program); 44 _context.drawTriangles(iBuffer); 45 } 46 47 public function getTexture() : Texture { return _texture; } 48 } 49 }
一個Pass對象對應着上圖中的一次渲染,因而他們有自己獨立的shader program,包括vertex shader 和 fragment shader,根據初始化的參isRenderToTexture來決定是繪制到紋理對象還是緩沖區。一般來說,只有最后一個Pass對象需要將數據繪制到緩沖區,而在它之前的則都需要繪制到紋理對象。這是一個抽象基類,我們需要在它的基礎之上構建具有實際意義的Pass類,為此我們需要編寫shader program,下面給出兩個具體的Pass類:
正弦波:
1 package psw2d.pass 2 { 3 import flash.display3D.Context3D; 4 5 public class PassSinWave extends Pass 6 { 7 public function PassSinWave(context:Context3D, isRenderToTexture:Boolean, width:Number=1, height:Number=1) 8 { 9 super(context, isRenderToTexture, width, height); 10 11 _shaderVertex = "m44 op,va0,vc0\n" + 12 "mov v0,va1"; 13 14 _shaderFragment="tex ft0,v0,fs1<2d,clamp,linear>\n" + 15 "sub ft0.x,v0.x,fc0.w\n" + 16 "mul ft0.x,ft0.x,ft0.x\n"+ 17 "sub ft0.y,v0.y,fc0.w\n"+ 18 "mul ft0.y,ft0.y,ft0.y\n"+ 19 "add ft0.z,ft0.x,ft0.y\n"+ 20 "sqt ft0.z,ft0.z\n"+ 21 "mul ft0.z,ft0.z,fc0.x\n"+ 22 "sub ft0.z,ft0.z,fc0.z\n"+ 23 "sin ft0.z,ft0.z\n"+ 24 "mul ft0.z,ft0.z,fc0.y\n"+ 25 "add ft0,v0,ft0.zzz\n"+ 26 "tex oc,ft0,fs0<2d,clamp,linear>\n"; 27 } 28 } 29 }
灰度:
1 package psw2d.pass 2 { 3 import flash.display3D.Context3D; 4 5 public class PassGrayscale extends Pass 6 { 7 public function PassGrayscale(context:Context3D, isRenderToTexture:Boolean, width:Number=1, height:Number=1) 8 { 9 super(context, isRenderToTexture, width, height); 10 11 _shaderVertex = "" + 12 "m44 op, va0,vc0\n" + 13 "mov v0, va1\n"; 14 15 _shaderFragment = "" + 16 "tex ft0, v0, fs0 <2d,linear,clamp>\n" + 17 "add ft1.x, ft0.x, ft0.y\n" + 18 "add ft1.x, ft1.x, ft0.z\n" + 19 "div ft1.x, ft1.x, fc1.w\n" + 20 "mov ft0.xyz, ft1.xxx\n" + 21 "mov oc ft0\n"; 22 } 23 } 24 }
以下是具體效果:
本文中所用到的案例取自:http://wonderfl.net/c/zQ6L#code_forked,有修改。