OLD | NEW |
| (Empty) |
1 <!DOCTYPE html> | |
2 <html><head> | |
3 <meta charset="utf-8"> | |
4 | |
5 <title>OpenGL for the web</title> | |
6 | |
7 <script type="application/x-javascript" src="../util.js"></script> | |
8 | |
9 <script type="application/x-javascript"> | |
10 | |
11 function log(msg) { | |
12 document.getElementById('note').textContent += "\n"+msg; | |
13 } | |
14 | |
15 | |
16 | |
17 | |
18 function init(ev) { | |
19 var canvas = document.getElementById('canvas'); | |
20 var gl = canvas.getContext(GL_CONTEXT_ID); | |
21 | |
22 var shader = new Shader(gl, "ppix-vert", "ppix-frag"); | |
23 shader.compile(); | |
24 var fbo = new FBO(gl, canvas.width, canvas.height); | |
25 var fbo2 = new FBO(gl, canvas.width, canvas.height); | |
26 var fbo3 = new FBO(gl, canvas.width, canvas.height); | |
27 var depth = new Shader(gl, "depth-vert", "depth-frag"); | |
28 var identity = new Filter(gl, "identity-vert", "identity-frag"); | |
29 var unpremult = new Filter(gl, "identity-vert", "unpremult-frag"); | |
30 var hblur = new Filter(gl, "identity-vert", "hblur-frag"); | |
31 var vblur = new Filter(gl, "identity-vert", "vblur-frag"); | |
32 var hdof = new Filter(gl, "identity-vert", "hdof-frag"); | |
33 var vdof = new Filter(gl, "identity-vert", "vdof-frag"); | |
34 | |
35 redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult, hblu
r, vblur, hdof, vdof); | |
36 | |
37 setInterval(function(){ | |
38 redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult,
hblur, vblur, hdof, vdof); | |
39 }, 33); | |
40 } | |
41 | |
42 function drawCube (gl, shader, angle, axis, x,y,z, s, va, na, ta) { | |
43 Matrix.copyMatrix(look, vmat); | |
44 Matrix.translate3InPlace(x,y,z,vmat); | |
45 Matrix.scale1InPlace(s,vmat); | |
46 Matrix.rotateInPlace(angle, axis, vmat); | |
47 | |
48 // Note: we could just use mat3(MVMatrix) as the normal matrix | |
49 // as MVMatrix has only rotations, translations and uniform scaling | |
50 // <=> MVMatrix is a scaled orthonormal matrix | |
51 // hence normalize(mat3(MVMatrix)*v) == normalize(mat3(transpose(inverse(MVM
atrix))*v) | |
52 // | |
53 // But let's do it the hard way to see if Matrix.inverse3x3 works... | |
54 Matrix.inverseTo3x3InPlace(vmat, nmat); | |
55 Matrix.transpose3x3InPlace(nmat); | |
56 | |
57 shader.uniformMatrix4fv("MVMatrix", vmat); | |
58 shader.uniformMatrix3fv("NMatrix", nmat); | |
59 | |
60 var cube = Cube.getCachedVBO(gl); | |
61 cube.draw(va, na, ta); | |
62 } | |
63 | |
64 var carr = []; | |
65 for (var i=0; i<25; i++) { | |
66 carr.push([Math.random(), Math.random(), Math.random()]); | |
67 } | |
68 | |
69 function drawScene (gl, shader, va, na, ta) { | |
70 var ot = new Date().getTime(); | |
71 var t = ot; | |
72 | |
73 shader.uniformMatrix4fv("PMatrix", pmat); | |
74 for (var i=0; i<carr.length; i++){ | |
75 var c = carr[i]; | |
76 var f = c[1] < 0.5 ? 1 : -1; | |
77 var t = ot; | |
78 drawCube(gl, shader, | |
79 (t/(f*400*(c[0]+0.5))) % (2*Math.PI), c, | |
80 | |
81 0.45+0.8*c[2], | |
82 -0.4+Math.cos((i/carr.length*Math.PI*2)+t/1000), | |
83 0.8+Math.sin((i/carr.length*Math.PI*2)+t/1000)*3.2, | |
84 | |
85 0.05 + Math.pow((c[0]+c[1]+c[2])*0.33, 2)*0.3, | |
86 va, na, ta); | |
87 } | |
88 } | |
89 | |
90 var nmat = Matrix.newIdentity3x3(); | |
91 var vmat = Matrix.newIdentity(); | |
92 var vmat2 = Matrix.newIdentity(); | |
93 var pmat = null; | |
94 var look = Matrix.lookAt([4,-1,8], [-0.2,0,0], [0,1,0]); | |
95 var useDoF = false; | |
96 | |
97 var firstFrame = true; | |
98 | |
99 function redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult,
hblur, vblur, hdof, vdof) { | |
100 | |
101 var doDoF = useDoF; | |
102 gl.viewport(0, 0, canvas.width, canvas.height); | |
103 gl.clearColor(0.0, 0.0, 0.0, 0.0); | |
104 gl.enable(gl.DEPTH_TEST); | |
105 | |
106 gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
107 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
108 | |
109 fbo.use(); | |
110 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
111 | |
112 shader.use(); | |
113 | |
114 var va = shader.attrib("Vertex"); | |
115 var na = shader.attrib("Normal"); | |
116 var ta = shader.attrib("Tex"); | |
117 | |
118 if (pmat == null) | |
119 pmat = Matrix.perspective(30, canvas.width/canvas.height, 1, 100); | |
120 | |
121 shader.uniform4f("MaterialSpecular", 0.95, 0.9, 0.6, 1); | |
122 shader.uniform4f("MaterialDiffuse", 0.50, 0.35, 0.35, 1); | |
123 shader.uniform4f("MaterialAmbient", 0.0, 0.1, 0.2, 1); | |
124 shader.uniform1f("MaterialShininess", 1.5); | |
125 | |
126 shader.uniform4f("GlobalAmbient", 0.1, 0.1, 0.1, 1); | |
127 | |
128 shader.uniform4f("LightPos", 1, 5, 3, 1.0); | |
129 | |
130 shader.uniform4f("LightSpecular", 0.9, 0.9, 0.9, 1); | |
131 shader.uniform4f("LightDiffuse", 0.8, 0.8, 0.8, 1); | |
132 shader.uniform4f("LightAmbient", 0.0, 0.06, 0.2, 1); | |
133 shader.uniform1f("LightConstantAtt", 0.0); | |
134 shader.uniform1f("LightLinearAtt", 0.1); | |
135 shader.uniform1f("LightQuadraticAtt", 0.0); | |
136 | |
137 drawScene(gl, shader, va, na); | |
138 | |
139 if (doDoF || firstFrame) { | |
140 | |
141 fbo3.use(); | |
142 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
143 depth.use(); | |
144 var dva = depth.attrib("Vertex"); | |
145 | |
146 drawScene(gl, depth, dva); | |
147 | |
148 gl.disable(gl.DEPTH_TEST); | |
149 gl.activeTexture(gl.TEXTURE1); | |
150 gl.bindTexture(gl.TEXTURE_2D, fbo3.texture); | |
151 gl.activeTexture(gl.TEXTURE0); | |
152 | |
153 | |
154 for (var i=0; i<3; i++) { | |
155 fbo2.use(); | |
156 gl.bindTexture(gl.TEXTURE_2D, fbo.texture); | |
157 | |
158 hdof.apply(function(f){ | |
159 f.uniform1i("Texture", 0); | |
160 f.uniform1i("Depth", 1); | |
161 f.uniform1f("iter", i); | |
162 f.uniform1f("step", 1.0/canvas.width); | |
163 }); | |
164 | |
165 fbo.use(); | |
166 gl.bindTexture(gl.TEXTURE_2D, fbo2.texture); | |
167 | |
168 vdof.apply(function(f){ | |
169 f.uniform1i("Texture", 0); | |
170 f.uniform1i("Depth", 1); | |
171 f.uniform1f("iter", i); | |
172 f.uniform1f("step", 1.0/canvas.width); | |
173 }); | |
174 } | |
175 | |
176 } | |
177 firstFrame = false; | |
178 | |
179 gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
180 | |
181 gl.activeTexture(gl.TEXTURE1); | |
182 gl.bindTexture(gl.TEXTURE_2D, null); | |
183 gl.activeTexture(gl.TEXTURE0); | |
184 gl.bindTexture(gl.TEXTURE_2D, fbo.texture); | |
185 | |
186 // The DoF blur blurs the color from the transparent black background with | |
187 // the cubes. To get rid of the border, we can treat it as premultiplied alp
ha. | |
188 // To see the problem, try replacing unpremult with identity. | |
189 unpremult.apply(function(f){ | |
190 f.uniform1i("Texture", 0); | |
191 }); | |
192 | |
193 } | |
194 | |
195 window.addEventListener("load", init, false); | |
196 </script> | |
197 | |
198 <script id="ppix-vert" type="x-shader/x-vertex"> | |
199 attribute vec3 Vertex; | |
200 attribute vec3 Normal; | |
201 attribute vec2 Tex; | |
202 | |
203 uniform mat4 PMatrix; | |
204 uniform mat4 MVMatrix; | |
205 uniform mat3 NMatrix; | |
206 | |
207 uniform vec4 MaterialAmbient; | |
208 uniform vec4 MaterialDiffuse; | |
209 | |
210 uniform vec4 LightAmbient; | |
211 uniform vec4 LightDiffuse; | |
212 | |
213 uniform vec4 GlobalAmbient; | |
214 | |
215 uniform vec4 LightPos; | |
216 | |
217 varying vec4 diffuse, ambientGlobal, ambient; | |
218 varying vec3 normal, lightDir, halfVector; | |
219 varying float dist; | |
220 | |
221 void main() | |
222 { | |
223 vec4 worldPos; | |
224 vec3 lightVector; | |
225 vec4 v = vec4(Vertex, 1.0); | |
226 | |
227 /* transform vertex normal into world space and normalize */ | |
228 normal = normalize(NMatrix * Normal); | |
229 | |
230 /* transform vertex into world space and compute the vector | |
231 from it to the light */ | |
232 worldPos = MVMatrix * v; | |
233 lightVector = vec3(LightPos - worldPos); | |
234 lightDir = normalize(lightVector); | |
235 dist = length(lightVector); | |
236 | |
237 /* Half-vector used in Blinn-Phong shading due to computational efficien
cy */ | |
238 halfVector = normalize(lightVector - vec3(worldPos)); | |
239 | |
240 diffuse = MaterialDiffuse * LightDiffuse; | |
241 | |
242 /* The ambient terms have been separated since one of them */ | |
243 /* suffers attenuation */ | |
244 ambient = MaterialAmbient * LightAmbient; | |
245 ambientGlobal = GlobalAmbient * MaterialAmbient; | |
246 | |
247 gl_Position = PMatrix * worldPos; | |
248 } | |
249 </script> | |
250 | |
251 <script id="ppix-frag" type="x-shader/x-fragment"> | |
252 precision mediump float; | |
253 | |
254 uniform vec4 LightSpecular; | |
255 uniform vec4 MaterialSpecular; | |
256 uniform float MaterialShininess; | |
257 | |
258 uniform float LightConstantAtt; | |
259 uniform float LightLinearAtt; | |
260 uniform float LightQuadraticAtt; | |
261 | |
262 varying vec4 diffuse,ambientGlobal, ambient; | |
263 varying vec3 normal, lightDir, halfVector; | |
264 varying float dist; | |
265 | |
266 void main() | |
267 { | |
268 vec3 n, halfV, viewV, ldir; | |
269 float NdotL, NdotHV; | |
270 vec4 color = ambientGlobal; | |
271 float att; | |
272 | |
273 n = normalize(normal); | |
274 | |
275 NdotL = max(dot(n, normalize(lightDir)), 0.0); | |
276 | |
277 if (NdotL > 0.0) { | |
278 | |
279 att = 1.0 / (LightConstantAtt + LightLinearAtt * dist + LightQuadratic
Att * dist * dist); | |
280 | |
281 color += att * (diffuse * NdotL + ambient); | |
282 | |
283 halfV = normalize(halfVector); | |
284 NdotHV = max( dot(normal, halfV), 0.0 ); | |
285 | |
286 color += att * MaterialSpecular * LightSpecular * pow(NdotHV, Material
Shininess); | |
287 } | |
288 | |
289 gl_FragColor = color; | |
290 } | |
291 </script> | |
292 <script id="depth-vert" type="x-shader/x-vertex"> | |
293 attribute vec3 Vertex; | |
294 uniform mat4 PMatrix; | |
295 uniform mat4 MVMatrix; | |
296 varying float depth; | |
297 void main() | |
298 { | |
299 gl_Position = PMatrix * (MVMatrix * vec4(Vertex, 1.0)); | |
300 depth = 1.0-(gl_Position.z / gl_Position.w); | |
301 } | |
302 </script> | |
303 <script id="depth-frag" type="x-shader/x-fragment"> | |
304 precision mediump float; | |
305 | |
306 varying float depth; | |
307 void main() | |
308 { | |
309 vec4 c = vec4(depth, 0.0, 0.0, 1.0); | |
310 gl_FragColor = c; | |
311 } | |
312 </script> | |
313 | |
314 <script id="identity-vert" type="x-shader/x-vertex"> | |
315 attribute vec3 Vertex; | |
316 attribute vec2 Tex; | |
317 | |
318 varying vec4 texCoord0; | |
319 void main() | |
320 { | |
321 texCoord0 = vec4(Tex,0.0,0.0); | |
322 gl_Position = vec4(Vertex, 1.0); | |
323 } | |
324 </script> | |
325 <script id="identity-frag" type="x-shader/x-fragment"> | |
326 precision mediump float; | |
327 | |
328 uniform sampler2D Texture; | |
329 | |
330 varying vec4 texCoord0; | |
331 void main() | |
332 { | |
333 vec4 c = texture2D(Texture, texCoord0.st); | |
334 gl_FragColor = c; | |
335 } | |
336 </script> | |
337 <script id="premult-frag" type="x-shader/x-fragment"> | |
338 precision mediump float; | |
339 | |
340 uniform sampler2D Texture; | |
341 | |
342 varying vec4 texCoord0; | |
343 void main() | |
344 { | |
345 vec4 c = texture2D(Texture, texCoord0.st); | |
346 float a = c.a; | |
347 c *= a; | |
348 c.a = a; | |
349 gl_FragColor = c; | |
350 } | |
351 </script> | |
352 <script id="unpremult-frag" type="x-shader/x-fragment"> | |
353 precision mediump float; | |
354 | |
355 uniform sampler2D Texture; | |
356 | |
357 varying vec4 texCoord0; | |
358 void main() | |
359 { | |
360 vec4 c = texture2D(Texture, texCoord0.st); | |
361 float a = c.a; | |
362 c /= a; | |
363 c.a = a; | |
364 gl_FragColor = c; | |
365 } | |
366 </script> | |
367 | |
368 <script id="hblur-frag" type="x-shader/x-fragment"> | |
369 precision mediump float; | |
370 | |
371 uniform sampler2D Texture; | |
372 uniform float step; | |
373 float kernel[7] = float[](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046)
; | |
374 | |
375 varying vec4 texCoord0; | |
376 void main() | |
377 { | |
378 int i=0; | |
379 if (texture2D(Texture, texCoord0.st).a > 0.0) { | |
380 vec4 sum = vec4(0.0); | |
381 for (i=0; i<7; i++) { | |
382 vec4 tmp = texture2D(Texture, texCoord0.st + vec2(i*step,0)); | |
383 sum += tmp * kernel[i]; | |
384 } | |
385 gl_FragColor = sum; | |
386 } else { | |
387 gl_FragColor = texture2D(Texture, texCoord0.st); | |
388 } | |
389 } | |
390 </script> | |
391 <script id="vblur-frag" type="x-shader/x-fragment"> | |
392 precision mediump float; | |
393 | |
394 uniform sampler2D Texture; | |
395 uniform float step; | |
396 float kernel[7] = float[](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046)
; | |
397 | |
398 varying vec4 texCoord0; | |
399 void main() | |
400 { | |
401 int i=0; | |
402 if (texture2D(Texture, texCoord0.st).a > 0.0) { | |
403 vec4 sum = vec4(0.0); | |
404 for (i=0; i<7; i++) { | |
405 vec4 tmp = texture2D(Texture, texCoord0.st + vec2(0,i*step)); | |
406 sum += tmp * kernel[i]; | |
407 } | |
408 gl_FragColor = sum; | |
409 } else { | |
410 gl_FragColor = texture2D(Texture, texCoord0.st); | |
411 } | |
412 } | |
413 </script> | |
414 <script id="hdof-frag" type="x-shader/x-fragment"> | |
415 precision mediump float; | |
416 | |
417 uniform sampler2D Texture; | |
418 uniform sampler2D Depth; | |
419 uniform float step; | |
420 uniform float iter; | |
421 float kernel[7] = { 0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046 }; | |
422 | |
423 varying vec4 texCoord0; | |
424 void main() | |
425 { | |
426 vec4 tmp; | |
427 vec4 sum = vec4(0.0); | |
428 bool b = (iter < -26.0+36.0*(1.0-texture2D(Depth, texCoord0.st).r) && te
xture2D(Texture, texCoord0.st).a > 0.0); | |
429 tmp = texture2D(Texture, texCoord0.st + vec2(float(0-3)*step,0)); | |
430 sum += tmp * kernel[0]; | |
431 tmp = texture2D(Texture, texCoord0.st + vec2(float(1-3)*step,0)); | |
432 sum += tmp * kernel[1]; | |
433 tmp = texture2D(Texture, texCoord0.st + vec2(float(2-3)*step,0)); | |
434 sum += tmp * kernel[2]; | |
435 tmp = texture2D(Texture, texCoord0.st + vec2(float(3-3)*step,0)); | |
436 sum += tmp * kernel[3]; | |
437 tmp = texture2D(Texture, texCoord0.st + vec2(float(4-3)*step,0)); | |
438 sum += tmp * kernel[4]; | |
439 tmp = texture2D(Texture, texCoord0.st + vec2(float(5-3)*step,0)); | |
440 sum += tmp * kernel[5]; | |
441 tmp = texture2D(Texture, texCoord0.st + vec2(float(6-3)*step,0)); | |
442 sum += tmp * kernel[6]; | |
443 gl_FragColor = mix(texture2D(Texture, texCoord0.st), sum, b ? 1.0 : 0.0)
; | |
444 } | |
445 </script> | |
446 <script id="vdof-frag" type="x-shader/x-fragment"> | |
447 precision mediump float; | |
448 | |
449 uniform sampler2D Texture; | |
450 uniform sampler2D Depth; | |
451 uniform float step; | |
452 uniform float iter; | |
453 float kernel[7] = float[7](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046
); | |
454 | |
455 varying vec4 texCoord0; | |
456 void main() | |
457 { | |
458 vec4 tmp; | |
459 vec4 sum = vec4(0.0); | |
460 bool b = (iter < -26.0+36.0*(1.0-texture2D(Depth, texCoord0.st).r) && te
xture2D(Texture, texCoord0.st).a > 0.0); | |
461 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(0-3)*step)); | |
462 sum += tmp * kernel[0]; | |
463 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(1-3)*step)); | |
464 sum += tmp * kernel[1]; | |
465 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(2-3)*step)); | |
466 sum += tmp * kernel[2]; | |
467 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(3-3)*step)); | |
468 sum += tmp * kernel[3]; | |
469 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(4-3)*step)); | |
470 sum += tmp * kernel[4]; | |
471 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(5-3)*step)); | |
472 sum += tmp * kernel[5]; | |
473 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(6-3)*step)); | |
474 sum += tmp * kernel[6]; | |
475 gl_FragColor = mix(texture2D(Texture, texCoord0.st), sum, b ? 1.0 : 0.0)
; | |
476 } | |
477 </script> | |
478 | |
479 <style> | |
480 * { margin: 0px; padding: 0px; } | |
481 html { | |
482 background-color: #707888; | |
483 color: #222222; | |
484 } | |
485 #canvas { | |
486 position: absolute; | |
487 cursor: pointer; | |
488 top: 115px; left: 550px; | |
489 } | |
490 #note { | |
491 position: absolute; | |
492 top: 4px; | |
493 left: 4px; | |
494 } | |
495 #content { | |
496 margin-left: 99px; | |
497 padding-left: 8px; | |
498 padding-right: 8px; | |
499 padding-bottom: 16px; | |
500 width: 600px; | |
501 background-color: rgba(255,255,255,1.0); | |
502 text-align: center; | |
503 border-left: 1px solid rgba(0,0,0,0.5); | |
504 border-right: 2px solid rgba(0,0,0,0.75); | |
505 } | |
506 h1 { | |
507 padding-top: 24px; | |
508 padding-bottom: 16px; | |
509 margin-bottom: 24px; | |
510 border-bottom: 1px solid black; | |
511 font-family: Times New Roman, Serif; | |
512 font-weight: bold; | |
513 font-size: 40px; | |
514 } | |
515 #content p { | |
516 text-indent: 24px; | |
517 margin-left: 24px; | |
518 margin-right: 32px; | |
519 text-align: justify; | |
520 font-family: Serif; | |
521 padding-bottom: 16px; | |
522 } | |
523 #above { | |
524 position: absolute; | |
525 top: 300px; | |
526 left: 716px; | |
527 padding: 10px 20px; | |
528 background-color: rgba(0,225,0,0.5); | |
529 border-left: 2px solid rgba(0,64,0,0.75); | |
530 color: white; | |
531 font-size: small; | |
532 font-family: sans-serif; | |
533 } | |
534 #above p { | |
535 text-align: center; | |
536 } | |
537 </style> | |
538 | |
539 </head><body> | |
540 <canvas id="canvas" width="400" height="400" title="Click to toggle DOF shad
er" onclick="useDoF = !useDoF"></canvas> | |
541 <pre id="note"></pre> | |
542 | |
543 <div id="content"> | |
544 <h1>OpenGL for the web</h1> | |
545 <p> | |
546 The WebGL specification gives web developers access to an | |
547 OpenGL ES 2.0 drawing context for the canvas tag. What that means is | |
548 that you can finally harness the power of the GPU for awesome visuals | |
549 and heavy-duty number crunching in your web apps. </p><p> OpenGL ES 2.0 is a sub
set of OpenGL 2.0 aimed at embedded | |
550 devices and game consoles. As such, it's a very minimalistic low-level | |
551 API, even more so than desktop OpenGL. In fact, if you took desktop | |
552 OpenGL and stripped out everything but shaders, vertex arrays and | |
553 textures, you'd get something quite like OpenGL ES 2.0. </p> | |
554 <p> | |
555 As there is no fixed-function pipeline, you need to write GLSL shaders to
draw <i>anything</i>. | |
556 And you need to do your own transformation math, including keeping | |
557 track of the transformation matrix stack. So the raw API is really not | |
558 for the faint of heart; you do need to know your 3D math and shading | |
559 equations. </p> | |
560 <p> For example, to draw the spinning cubes on the | |
561 right - around 200 lines of application code, 250 lines of shaders and | |
562 800 lines of library code - I had to scrounge the internet for <a href="http://w
ww.lighthouse3d.com/opengl/glsl/index.php?pointlight">GLSL shaders</a> | |
563 to do the transformation and lighting, write a small matrix math | |
564 library in JavaScript and a DOF blur shader. While highly educational, | |
565 it was also a rather steep hill to climb. </p> | |
566 <p> So, the intended audience of the raw context | |
567 interface are not really end-users, but library developers who can | |
568 write easy-to-use interfaces to the functionality, and 3D developers | |
569 who require a high level of control over the rendering pipeline. </p> | |
570 <p> The really cool thing about the OpenGL Canvas is | |
571 that it doesn't make policy decisions. There's no single set-in-stone | |
572 use case for it: In addition to 3D graphics, you can also use it for | |
573 filtering images, visualizing fluid dynamics, doing real-time video | |
574 effects, or just crunching a whole lot of FP math. If you can do it on | |
575 the GPU, you're in luck! </p> | |
576 </div> | |
577 <div id="above"> | |
578 <p>You can also place content above the canvas</p> | |
579 </div> | |
580 </body></html> | |
OLD | NEW |