/src/de/nulldesign/nd2d/materials/Sprite2DBlurMaterial.as
ActionScript | 352 lines | 229 code | 74 blank | 49 comment | 26 complexity | 167b67a2248f652d7252f79995383dac MD5 | raw file
1/*
2 * ND2D - A Flash Molehill GPU accelerated 2D engine
3 *
4 * Author: Lars Gerckens
5 * Copyright (c) nulldesign 2011
6 * Repository URL: http://github.com/nulldesign/nd2d
7 * Getting started: https://github.com/nulldesign/nd2d/wiki
8 *
9 *
10 * Licence Agreement
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a copy
13 * of this software and associated documentation files (the "Software"), to deal
14 * in the Software without restriction, including without limitation the rights
15 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 * copies of the Software, and to permit persons to whom the Software is
17 * furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
21 *
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28 * THE SOFTWARE.
29 */
30
31package de.nulldesign.nd2d.materials {
32
33 import com.adobe.utils.AGALMiniAssembler;
34
35 import de.nulldesign.nd2d.display.Camera2D;
36
37 import de.nulldesign.nd2d.geom.Face;
38 import de.nulldesign.nd2d.geom.UV;
39 import de.nulldesign.nd2d.geom.Vertex;
40 import de.nulldesign.nd2d.materials.shader.Shader2D;
41 import de.nulldesign.nd2d.materials.shader.ShaderCache;
42 import de.nulldesign.nd2d.materials.texture.ASpriteSheetBase;
43 import de.nulldesign.nd2d.materials.texture.SpriteSheet;
44
45 import flash.display.Shader;
46 import flash.display.Stage;
47
48 import flash.display3D.Context3D;
49 import flash.display3D.Context3DProgramType;
50 import flash.display3D.Context3DTextureFormat;
51 import flash.display3D.Program3D;
52 import flash.display3D.textures.Texture;
53 import flash.geom.Matrix;
54 import flash.geom.Matrix3D;
55 import flash.geom.Point;
56 import flash.geom.Rectangle;
57
58 /**
59 * http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/
60 */
61 public class Sprite2DBlurMaterial extends Sprite2DMaterial {
62
63 protected const HORIZONTAL_FRAGMENT_SHADER:String =
64 // -4
65 "mov ft0, v0 \n" +
66 "sub ft0.x, ft0.x, fc3.y \n" +
67 "tex ft1, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
68 "mul ft1, ft1, fc2.x \n" +
69
70 // -3
71 "add ft0.x, ft0.x, fc3.z \n" +
72 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
73 "mul ft2, ft2, fc2.y \n" +
74 "add ft1, ft1, ft2 \n" +
75
76 // -2
77 "add ft0.x, ft0.x, fc3.z \n" +
78 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
79 "mul ft2, ft2, fc2.z \n" +
80 "add ft1, ft1, ft2 \n" +
81
82 // -1
83 "add ft0.x, ft0.x, fc3.z \n" +
84 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
85 "mul ft2, ft2, fc2.w \n" +
86 "add ft1, ft1, ft2 \n" +
87
88 // 0
89 "add ft0.x, ft0.x, fc3.z \n" +
90 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
91 "mul ft2, ft2, fc3.x \n" +
92 "add ft1, ft1, ft2 \n" +
93
94 // 1
95 "add ft0.x, ft0.x, fc3.z \n" +
96 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
97 "mul ft2, ft2, fc2.w \n" +
98 "add ft1, ft1, ft2 \n" +
99
100 // 2
101 "add ft0.x, ft0.x, fc3.z \n" +
102 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
103 "mul ft2, ft2, fc2.z \n" +
104 "add ft1, ft1, ft2 \n" +
105
106 // 3
107 "add ft0.x, ft0.x, fc3.z \n" +
108 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
109 "mul ft2, ft2, fc2.y \n" +
110 "add ft1, ft1, ft2 \n" +
111
112 // 4
113 "add ft0.x, ft0.x, fc3.z \n" +
114 "tex ft2, ft0, fs0 <TEXTURE_SAMPLING_OPTIONS> \n" +
115 "mul ft2, ft2, fc2.x \n" +
116 "add ft1, ft1, ft2 \n" +
117
118 "mul ft1, ft1, fc0 \n" +
119 "add oc, ft1, fc1 \n";
120
121 protected var VERTICAL_FRAGMENT_SHADER:String;
122
123 protected var horizontalShader:Shader2D;
124 protected var verticalShader:Shader2D;
125
126 protected const MAX_BLUR:uint = 4;
127
128 protected var blurredTexture:Texture;
129 protected var blurredTexture2:Texture;
130 protected var blurredTextureCam:Camera2D = new Camera2D(1, 1);
131 protected var activeRenderToTexture:Texture;
132
133 protected var blurX:uint;
134 protected var blurY:uint;
135
136 protected const BLUR_DIRECTION_HORIZONTAL:uint = 0;
137 protected const BLUR_DIRECTION_VERTICAL:uint = 1;
138
139 protected var fragmentData:Vector.<Number>;
140
141 public function Sprite2DBlurMaterial(blurX:uint = 4, blurY:uint = 4) {
142
143 super();
144
145 VERTICAL_FRAGMENT_SHADER = HORIZONTAL_FRAGMENT_SHADER.replace("sub ft0.x, ft0.x, fc3.y", "sub ft0.y, ft0.y, fc3.y");
146 var reg:RegExp = /add ft0.x, ft0.x, fc3.z/g;
147 VERTICAL_FRAGMENT_SHADER = VERTICAL_FRAGMENT_SHADER.replace(reg, "add ft0.y, ft0.y, fc3.z");
148
149 fragmentData = new Vector.<Number>(8, true);
150 setBlur(blurX, blurY);
151 }
152
153 public function setBlur(blurX:uint = 4, blurY:uint = 4):void {
154 this.blurX = blurX;
155 this.blurY = blurY;
156
157 drawCalls = Math.max(1, Math.ceil(blurX / MAX_BLUR) + Math.ceil(blurY / MAX_BLUR));
158 }
159
160 protected function updateBlurKernel(radius:uint, direction:uint):void {
161
162 fragmentData[0] = 0.0; //0.05; // fc2.x
163 fragmentData[1] = 0.0; //0.09; // fc2.y
164 fragmentData[2] = 0.0; //0.12; // fc2.z
165 fragmentData[3] = 0.0; //0.15; // fc2.w
166 fragmentData[4] = 1.0; //0.16; // fc3.x
167 // movement: minus 4 and plus 1 several times...
168 fragmentData[5] = 4.0 * (1.0 / (direction == BLUR_DIRECTION_HORIZONTAL ? texture.textureWidth : texture.textureHeight)); // fc3.y
169 fragmentData[6] = 1.0 * (1.0 / (direction == BLUR_DIRECTION_HORIZONTAL ? texture.textureWidth : texture.textureHeight)); // fc3.z
170 fragmentData[7] = 0.0; // fc3.w
171
172 // http://stackoverflow.com/questions/1696113/how-do-i-gaussian-blur-an-image-without-using-any-in-built-gaussian-functions
173 if(radius == 0) return;
174
175 var kernelLen:uint = radius * 2 + 1;
176 var r:Number = -radius;
177 var kernel:Array = [];
178 var twoRadiusSquaredRecip:Number = 1.0 / (2.0 * radius * radius);
179 var sqrtTwoPiTimesRadiusRecip:Number = 1.0 / (Math.sqrt(2.0 * Math.PI) * radius);
180 var kernelSum:Number = 0.0;
181 var i:int = 0;
182
183 for(i = 0; i < kernelLen; i++) {
184 var x:Number = r * r;
185 kernel[i] = sqrtTwoPiTimesRadiusRecip * Math.exp(-x * twoRadiusSquaredRecip);
186 r++;
187 kernelSum += kernel[i];
188 }
189
190 for(i = 0; i < kernelLen; i++) {
191 kernel[i] /= kernelSum;
192 }
193
194 var idx:uint = 4;
195 for(i = kernelLen / 2; i >= 0; i--) {
196 fragmentData[idx--] = kernel[i];
197 }
198 }
199
200 override protected function prepareForRender(context:Context3D):void {
201
202 // there is no ipad1 fragment shader fix for this material yet. so assume, the node is always tinted and use the color transform shader
203 nodeTinted = true;
204
205 super.prepareForRender(context);
206
207 context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, fragmentData, 2);
208
209 if(!blurredTexture) {
210 blurredTexture = context.createTexture(texture.textureWidth, texture.textureHeight, Context3DTextureFormat.BGRA, true);
211 }
212
213 if(!blurredTexture2) {
214 blurredTexture2 = context.createTexture(texture.textureWidth, texture.textureHeight, Context3DTextureFormat.BGRA, true);
215 }
216 }
217
218 protected function renderBlur(context:Context3D, startTri:uint, numTris:uint):void {
219 activeRenderToTexture = (activeRenderToTexture == blurredTexture ? blurredTexture2 : blurredTexture);
220 context.setRenderToTexture(activeRenderToTexture, false, 2, 0);
221 context.clear(0.0, 0.0, 0.0, 0.0);
222 context.drawTriangles(indexBuffer, startTri * 3, numTris);
223 context.setTextureAt(0, activeRenderToTexture);
224 }
225
226 override public function render(context:Context3D, faceList:Vector.<Face>, startTri:uint, numTris:uint):void {
227 generateBufferData(context, faceList);
228
229 // set up camera for blurry texture
230 blurredTextureCam.resizeCameraStage(texture.textureWidth, texture.textureHeight);
231 blurredTextureCam.x = -texture.textureWidth * 0.5;
232 blurredTextureCam.y = -texture.textureHeight * 0.5;
233
234 // save camera matrix
235 var savedCamMatrix:Matrix3D = viewProjectionMatrix;
236 var savedSpriteSheet:ASpriteSheetBase = spriteSheet;
237 var savedModelMatrix:Matrix3D = modelMatrix;
238 viewProjectionMatrix = blurredTextureCam.getViewProjectionMatrix();
239 spriteSheet = null;
240 modelMatrix = new Matrix3D();
241
242 updateBlurKernel(MAX_BLUR, BLUR_DIRECTION_HORIZONTAL);
243 prepareForRender(context);
244
245 activeRenderToTexture = null;
246 var totalSteps:int;
247 var i:uint;
248
249 // BLUR X
250 totalSteps = Math.floor(blurX / MAX_BLUR);
251
252 for(i = 0; i < totalSteps; i++) {
253 renderBlur(context, startTri, numTris);
254 }
255
256 if(blurX % MAX_BLUR != 0) {
257 updateBlurKernel(blurX % MAX_BLUR, BLUR_DIRECTION_HORIZONTAL);
258 context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, fragmentData, 2);
259
260 renderBlur(context, startTri, numTris);
261 }
262
263 // BLUR Y
264 context.setProgram(verticalShader.shader);
265 updateBlurKernel(MAX_BLUR, BLUR_DIRECTION_VERTICAL);
266 context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, fragmentData, 2);
267
268 totalSteps = Math.floor(blurY / MAX_BLUR);
269
270 for(i = 0; i < totalSteps; i++) {
271 renderBlur(context, startTri, numTris);
272 }
273
274 if(blurY % MAX_BLUR != 0) {
275 updateBlurKernel(blurY % MAX_BLUR, BLUR_DIRECTION_VERTICAL);
276 context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, fragmentData, 2);
277
278 renderBlur(context, startTri, numTris);
279 }
280
281 context.setRenderToBackBuffer();
282
283 // FINAL PASS
284 viewProjectionMatrix = savedCamMatrix;
285 spriteSheet = savedSpriteSheet;
286 modelMatrix = savedModelMatrix;
287
288 updateBlurKernel(0, BLUR_DIRECTION_HORIZONTAL);
289 prepareForRender(context);
290
291 if(blurX == 0 && blurY == 0) {
292 activeRenderToTexture = texture.getTexture(context);
293 }
294
295 context.setTextureAt(0, activeRenderToTexture);
296
297 context.drawTriangles(indexBuffer, startTri * 3, numTris);
298
299 clearAfterRender(context);
300 }
301
302 override public function handleDeviceLoss():void {
303 super.handleDeviceLoss();
304 blurredTexture = null;
305 blurredTexture2 = null;
306 }
307
308 override protected function initProgram(context:Context3D):void {
309 if(!shaderData) {
310 horizontalShader = ShaderCache.getInstance().getShader(context, this, VERTEX_SHADER, HORIZONTAL_FRAGMENT_SHADER, 4, texture.textureOptions, 0);
311 verticalShader = ShaderCache.getInstance().getShader(context, this, VERTEX_SHADER, VERTICAL_FRAGMENT_SHADER, 4, texture.textureOptions, 1000);
312
313 shaderData = horizontalShader;
314 }
315 }
316
317 override public function dispose():void
318 {
319 if(horizontalShader)
320 {
321 horizontalShader.dispose();
322 horizontalShader = null;
323 }
324
325 if(verticalShader)
326 {
327 verticalShader.dispose();
328 verticalShader = null;
329 }
330
331 if(blurredTexture)
332 {
333 blurredTexture.dispose();
334 blurredTexture = null;
335 }
336
337 if(blurredTexture2)
338 {
339 blurredTexture2.dispose();
340 blurredTexture2 = null;
341 }
342
343 if(activeRenderToTexture)
344 {
345 activeRenderToTexture.dispose();
346 activeRenderToTexture = null;
347 }
348
349 super.dispose();
350 }
351 }
352}