lunes, 4 de enero de 2016

Using WebGL2 for pixel perfect sprites and multi fragment outputs for color picking

For Ganymede Gate's commercial release i'm going strictly the HTML5 way, so when blitting sprites on a shader chain it can get cumbersome to produce pixel perfect results, even worse, if you want to allow picking on your game (mouse selections and such) manipulating multiple shaders on multiple render passes is not only sub-optimal, it is error prone (and by prone, i mean you will fall into bug-hell).

Enter WebGL 2.0: A subset of OpenGL ES 3.0 that allows several nifty approaches to 2D sprite management, and that unleashes powers that were only available to native applications until now.

Setup your WebGL 2.0 debug environment

WebGL 2.0 is a VERY experimental tecnology, and as such you won't find it everywhere out there, but chances are that if you're reading this, you're very much into pre-alpha or alpha territory, and the final implementantions will see the light of day when you're done with your app/game.

Currently the only two options are Firefox and Chrome (surprise surprise!). However, as i'm targetting a specific deployment platform (more on that later) i will use Chrome Canary, which doesn't have support for WebGL 2.0 out of the gate. You must enable the functionality by appending:
To Chrome's command line. After that, you must go to your canvas code and make this change:
After that you can check if you've obtained a WebGL 2.0 context by logging the version to the console or on some place visible in your HTML with:

After that, you should be ready to prepare your app for a shiny new WebGL 2.0 treatment.

Create color attachments and UintXArrays

Now that we have our new capabilities on our HTML5, we must take full advantage of our newfound GPU prowess. You should go check the OpenGL ES 3.0 cheat sheet and familiarize yourself with it.

Did you do it? Nice, isn't it shiny and new, with all those new kind of shader types and directives? Welp, we will dive into that in the next section, in this one i want to show how to deal with the new kind of color attachments.

You see, in WebGL 1.0 you can render to just 1 color attachment and 1 depth/stencil attachment at the same time. This is good for simple graphics, but it tends to produce two things that aren't really cool, namely: slow shaders and duplication of code.

In my case, i wanted to have blitting of tiles regardless of resolution or atlas size and also have picking. This sounds simple, until you have a shader chain where you introduce scanlines, curvature, distortions and such. Even worse, i intend letting users modify the shader chain to their hearts' content, and asking them to do one shader for color modification and another for picking modification isn't either good style nor good programming.

With this in mind, i decided that the only way to go is to have multiple color attachments, and one of them must be an integer attachment, so i can index directly on a tile grid where the player is moving the mouse and clicking.

You need to create a second color attachment for your framebuffer like this:
As you can see, there are new types of storage and internalFormat specifiers that seem rather funky for the uninitiated, but have a lot of sense when you need them. You can see all the new storage and internal formats on table 2 of the online reference for TexImage2D.

After that, you can also use the new integer array buffers to pass pixel perfect data and booleans (a bool being a 1 or 0 in a 8 bit integer, suboptimal, but better than a 32 bit float). I use a function to simplify array buffer creation:

WebGL 2.0 automatically detects the array type and fails if you happen to bind to a non-conforming attribute later on.

Migrate your WebGL 1.0 shaders to GLES 3.0 dialect

Now we must tackle migration of the shaders to the new architecture. First of, to enable your shaders for ES 3.00 you must add the following to your shader in the VERY FIRST LINE (if you're coding inside your HTML in <script> tags, you must put this immediatly after the ">" symbol:
#version 300 es
Then, if you try to test it, you'll probably see errors on your console. That's because WebGL 1.0 and ES300 dialects have a lot of differences. Also, you can't compile a shader with a ES300 shader for fragment and a WebGL 1.0 shader for vertex, or viceversa, as this throws an error.

Fragment Shader

  • In the fragment shader you will see that there's no gl_FragColor builtin, so you must declare your output manually and change your shader's code accordingly.
  • You cannot name variables with a gl_ prefix as this throws an error on most compilers.
  • If your output is typed as vec3 your alpha will be 0.0.
  • If you have multiple fragments, you must specify layout location with the new layout(location=x) directive.
  • Texture lookups lost the "2D" and "3D" parts, they're now just texture. Also, if you're handling an integer texture, you must use the corresponding isampler2D or usampler2D and the receiving variable must be of the corresponding ivec4 or uvec4 type.
Here's a real simple fragment shader that just copies a texture to the active framebuffer:
Notice that there are two out parameters, this correspond to the fragment outputs configured as draw buffers. One of the outputs is an integer output, which is the picking data we're getting from the first shader. This must be configured on program change before drawing with the glDrawBuffers function like this:

Vertex Shader

  • Attributes don't exist anymore, they are just inputs to the shader.
  • Varying also went the way of the dodo, now the vertex shader's output is the fragment shader's input. Using the layout() directive you can even have differently named inputs and outputs.
  • When passing integer out to the fragment shader, you must use the new flat directive, to indicate that you don't want the shading process to interpolate values.
Here's a real simple shader which enables flipping along the X axis when the coordinates of the vertex are below 0:

Package your app for distribution

After this, having your shiny new WebGL 2.0 app/game will do nothing for you if you can't let other people use it.

You could tell them to stop being pussies and install Chrome Canary and put that scary "unsafe" flag on the command line.

Fortunately, there is a great software called NW.js that let's you put your nice app/game in a tidy package. I won't go into the details, you can learn from the vast documentation online. All you have to do is download the latest Beta from the official page (version 0.13.0-beta2 at moment of publishing this entry) and modify your package.json file to include the unsafe flag as parameter for the chromium runtime. You should end up with a package.json a lot like this:
That's all there is to it, if you want to ask questions or make corrections, drop me a line on the comments section or tweet me.

EDIT: Forgot to add the progress of what's being discussed here, on the backend side of the stuff picking is implemented and working even with radial distortion.

No hay comentarios:

Publicar un comentario en la entrada