jax 0.0.0.8 → 0.0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/CHANGELOG +22 -0
  2. data/Rakefile +2 -1
  3. data/builtin/shaders/functions/noise.ejs +0 -3
  4. data/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js +31 -3
  5. data/guides/jax_guides/common.rb +1 -1
  6. data/guides/source/index.html.erb +4 -4
  7. data/guides/source/shaders.textile +496 -2
  8. data/lib/jax/generators/app/templates/public/javascripts/jax.js +130 -94
  9. data/lib/jax/generators/app/templates/spec/javascripts/support/spec_helpers/jax_spec_environment_helper.js +33 -0
  10. data/lib/jax/generators/app/templates/spec/javascripts/support/spec_layout.html.erb +1 -13
  11. data/lib/jax/generators/shader/templates/fragment.ejs.tt +2 -3
  12. data/lib/jax/generators/shader/templates/spec.js.tt +4 -11
  13. data/lib/jax/routes.rb +0 -3
  14. data/lib/jax/tasks/rake.rb +4 -0
  15. data/lib/jax/version.rb +1 -1
  16. data/spec/example_app/app/shaders/blob/vertex.ejs +2 -0
  17. data/spec/example_app/spec/javascripts/shaders/blob_spec.js +5 -8
  18. data/spec/example_app/spec/javascripts/support/spec_helpers/jax_spec_environment_helper.js +33 -0
  19. data/spec/example_app/spec/javascripts/support/spec_layout.html.erb +1 -13
  20. data/spec/generators/app_generator_spec.rb +1 -0
  21. data/spec/javascripts/helpers/jax_spec_environment_helper.js +33 -0
  22. data/spec/javascripts/helpers/{SpecHelper.js → jax_spec_helper.js} +0 -0
  23. data/spec/javascripts/jax/core/utils_spec.js +21 -2
  24. data/spec/lib/jax/tasks/jax_rake_spec.rb +13 -0
  25. data/src/jax/context.js +1 -1
  26. data/src/jax/core/util.js +12 -5
  27. data/src/jax/mvc/model.js +1 -0
  28. data/src/jax/webgl/scene/light_manager.js +58 -45
  29. data/src/jax/webgl/scene/light_source.js +49 -4
  30. data/src/jax/webgl/world.js +12 -46
  31. metadata +11 -6
data/CHANGELOG CHANGED
@@ -1,3 +1,25 @@
1
+ * 0.0.0.9 *
2
+
3
+ * Minor bugfixes for Jax.Util.colorize() and Jax.Util.Vectorize(). Specifically,
4
+ fixes involved correctly processing 3D colors and 2D vectors.
5
+
6
+ * Switch shader test generator to use SPEC_CONTEXT, which is now defined in the
7
+ spec_layout and helper files for new applications. This isn't *immediately*
8
+ app-breaking but you should regenerate these files anyways or the next time you
9
+ generate a shader will cause a bad day.
10
+
11
+ * List enum name next to error number for clarity when render errors occur.
12
+
13
+ * Abstract test suite setup into a separate helper file
14
+
15
+ * Add a global SPEC_CONTEXT so users don't have to manually create a new context for
16
+ each spec
17
+
18
+ * Specs run on a completely separate context so that they don't cause issues with
19
+ the visual tests
20
+
21
+
22
+
1
23
  * 0.0.0.8 *
2
24
 
3
25
  * Added +Jax.Shaders.max_vertex_textures+ which tracks the value of
data/Rakefile CHANGED
@@ -130,9 +130,10 @@ end
130
130
 
131
131
  namespace :guides do
132
132
  # gen doc:js first because we're going to include a direct link to the JS API dox
133
- task :generate => 'doc:js' do
133
+ task :generate do
134
134
  rm_rf "guides/output"
135
135
  if !ENV["SKIP_API"]
136
+ Rake::Task['doc:js'].invoke
136
137
  mkdir_p "guides/output/api"
137
138
  cp_r "doc", "guides/output/api/js"
138
139
  end
@@ -12,9 +12,6 @@
12
12
  * implementation.
13
13
  **/
14
14
 
15
- uniform float time; // Used for texture animation
16
-
17
-
18
15
  <%if (shader_type != 'vertex' || Jax.Shader.max_vertex_textures > 0) {%>
19
16
 
20
17
 
@@ -46,7 +46,16 @@
46
46
  'jmp_buf mbstate_t _off_t _onexit_t _PNH ptrdiff_t _purecall_handler ' +
47
47
  'sig_atomic_t size_t _stat __stat64 _stati64 terminate_function ' +
48
48
  'time_t __time64_t _timeb __timeb64 tm uintptr_t _utimbuf ' +
49
- 'va_list wchar_t wctrans_t wctype_t wint_t signed';
49
+ 'va_list wchar_t wctrans_t wctype_t wint_t signed ' +
50
+
51
+ // glsl
52
+ 'void bool int float vec2 vec3 vec4 bvec2 bvec3 bvec4 ivec2 ivec3 ivec4 mat2 '+
53
+ 'mat3 mat4 sampler2D samplerCube ' +
54
+
55
+ // jax preprocessor
56
+ '' +
57
+
58
+ '';
50
59
 
51
60
  var keywords = 'break case catch class const __finally __exception __try ' +
52
61
  'const_cast continue private public protected __declspec ' +
@@ -56,7 +65,14 @@
56
65
  'register reinterpret_cast return selectany ' +
57
66
  'sizeof static static_cast struct switch template this ' +
58
67
  'thread throw true false try typedef typeid typename union ' +
59
- 'using uuid virtual void volatile whcar_t while';
68
+ 'using uuid virtual void volatile whcar_t while ' +
69
+
70
+ // glsl
71
+ 'uniform varying attribute ' +
72
+
73
+ // jax preprocessor
74
+ 'shared '
75
+ ;
60
76
 
61
77
  var functions = 'assert isalnum isalpha iscntrl isdigit isgraph islower isprint' +
62
78
  'ispunct isspace isupper isxdigit tolower toupper errno localeconv ' +
@@ -73,7 +89,19 @@
73
89
  'wcstombs wctomb memchr memcmp memcpy memmove memset strcat strchr ' +
74
90
  'strcmp strcoll strcpy strcspn strerror strlen strncat strncmp ' +
75
91
  'strncpy strpbrk strrchr strspn strstr strtok strxfrm asctime ' +
76
- 'clock ctime difftime gmtime localtime mktime strftime time';
92
+ 'clock ctime difftime gmtime localtime mktime strftime time ' +
93
+
94
+ // glsl
95
+ 'radians degrees sin cos tan asin acos atan pow exp log exp2 log2 sqrt ' +
96
+ 'inversesqrt abs sin floor ceil fract mod min max clamp mix step smoothstep ' +
97
+ 'length distance dot cross normalize faceforward reflect matrixCompMult ' +
98
+ 'lessThan lessThanEqual greaterThan greaterThanEqual equal notEqual '+
99
+ 'any all not texture2D texture2DProj texture2DProjLod textureCube '+
100
+ 'textureCubeLod'
101
+
102
+ // jax preprocessor
103
+ 'import export '
104
+ ;
77
105
 
78
106
  this.regexList = [
79
107
  { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments
@@ -1,5 +1,5 @@
1
1
  module JaxGuides
2
- CODE_ALIASES = %w(yaml shell ruby erb html sql plain js)
2
+ CODE_ALIASES = %w(yaml shell ruby erb html sql plain js c)
3
3
 
4
4
  def self.code_aliases
5
5
  CODE_ALIASES.join("|")
@@ -31,6 +31,10 @@
31
31
 
32
32
  <dl>
33
33
 
34
+ <%= guide("Custom Shaders", 'shaders.html', :work_in_progress => false) do %>
35
+ <p>This guide shows how to add and use your own shaders to add advanced custom visual effects.</p>
36
+ <% end %>
37
+
34
38
  <%= guide("Materials", 'materials.html', :work_in_progress => true) do %>
35
39
  <p>This guide demonstrates how to create, use and dynamically alter Materials in Jax.</p>
36
40
  <% end %>
@@ -39,10 +43,6 @@
39
43
  <p>This guide shows how to make the most of lighting support in Jax, and outlines potential caveats. It will also discuss how to prevent certain objects from being lit at all, and in what circumstances you'd want to do this.</p>
40
44
  <% end %>
41
45
 
42
- <%= guide("Custom Shaders", 'shaders.html', :work_in_progress => true) do %>
43
- <p>This guide shows how to add and use your own shaders to add advanced custom visual effects.</p>
44
- <% end %>
45
-
46
46
  <%= guide("Matrices", "matrices.html", :work_in_progress => true) do%>
47
47
  <p>This guide explains all of the various matrices and "spaces", and how they relate to one another. It will help you understand the difference between world space, camera space and object space; and how a 3D object goes from a set of vertices in object space to a translated, rotated and scaled image on your computer screen.</p>
48
48
  <% end %>
@@ -1,5 +1,499 @@
1
- h2. This Guide Not Yet Started
1
+ h2. Writing Shaders With Jax
2
2
 
3
- Please come back after I've had time to work on this guide a bit.
3
+ This guide will help you understand the principles of shader design in Jax. It will cover the following topics:
4
+
5
+ * Generating a Jax shader and understanding the resultant files
6
+ * How your shader interacts with the Jax preprocessor
7
+ * How the Jax preprocessor combines multiple shaders
8
+ * How Material files use your shaders
9
+ * How the Jax preprocessor handles hardware constraints
4
10
 
5
11
  endprologue.
12
+
13
+
14
+ h3. Assumptions
15
+
16
+ This guide is for people who have already read the "Getting Started Guide":getting_started.html and have perhaps run through a few demos of their own, and reached the limits of what the built-in Jax shaders can do for them. It assumes you are familiar with the fundamentals of JavaScript as well as the "OpenGL Shading Language (GLSL)":http://en.wikipedia.org/wiki/GLSL.
17
+
18
+ The commands shown in this guide are, unless otherwise noted, executed in the root directory of a Jax application.
19
+
20
+
21
+ h3. Generating a Shader
22
+
23
+ Generating a shader is very similar to any other generator in Jax. The first step is, of course, deciding on a name for it; that's easier to do if we have an idea of what the shader should produce. In this guide, we'll design a shader called _brick_ that draws a procedural brick-like texture onto whichever objects make use of it.
24
+
25
+ <shell>
26
+ $ jax generate shader brick
27
+
28
+ create app/shaders/brick/common.ejs
29
+ create app/shaders/brick/fragment.ejs
30
+ create app/shaders/brick/manifest.yml
31
+ create app/shaders/brick/material.js
32
+ create app/shaders/brick/vertex.ejs
33
+ create spec/javascripts/shaders/brick_spec.js
34
+ </shell>
35
+
36
+ As you can see, six files were generated for us. Two were standard JavaScript files; one was a YAML file; and the remaining three were <em>Embedded JavaScript</em>, or EJS, files. Let's examine them one at a time:
37
+
38
+ * +spec/javascripts/shaders/brick_spec.js+ -- this is a test file, or <em>spec file</em>, generated by Jax to unit test our shader. If you open it, you'll see that it's quite complete. By default, it will test the shader under two conditions: as a stand-alone shader, and as part of a group of shaders merged together by the Jax preprocessor. More on that later.
39
+ * +app/shaders/brick/manifest.yml+ -- the manifest file contains meta data about your shader. It's not used in the JavaScript that your shader will eventually produce; instead, it is read by the Ruby component of Jax and used primarily for the command-line +material+ generator.
40
+ * +app/shaders/brick/common.ejs+ -- the contents of this file are prepended to both the _vertex_ and _fragment_ shaders. It can contain function definitions, uniform and varying variables, constants and global variables. Attribute variables don't belong here, because attributes cannot appear within fragment shaders.
41
+ * +app/shaders/brick/vertex.ejs+ -- houses the body of your vertex shader.
42
+ * +app/shaders/brick/fragment.ejs+ -- houses the body of your fragment shader.
43
+ * +app/shaders/brick/material.js+ -- the interface between JavaScript and your shader, this file is primarily responsible for setting the values of +uniform+ and +attribute+ variables.
44
+
45
+
46
+ h3. The Jax Preprocessor
47
+
48
+ There are technically two preprocessors at your disposal. The compiler preprocessor is the lower-level one, and is built into WebGL. All WebGL applications support the compiler preprocessor, which is invoked using the '#' character. Directives such as +#ifdef+ and +#version+ make use of the compiler preprocessor.
49
+
50
+ The Jax preprocessor operates at a much higher level than the compiler processor, and is essentially a glorified string manipulator.
51
+
52
+ INFO: This is why we must make a distinction between the compiler preprocessor and the Jax preprocessor. While the former operates entirely within the OpenGL drivers, the latter is executed in JavaScript and is primarily responsible for building your shader and controlling when and how your shader is merged with other shaders. We'll discuss all of this in detail.
53
+
54
+
55
+ h4. Combining Shaders
56
+
57
+ WebGL doesn't technically support the arbitrary mixing and matching of shaders. While it does support multiple shader _libraries_, these are analogous to C++ libraries. Lots of files can be plugged in, but only one can define a _main_ function.
58
+
59
+ This poses a problem in the realm of reusable code. If you have a shader that supports lighting effects, but you want to run a shader that supports lighting _and_ some other effect (for instance, a brick pattern), you have to write an entirely new shader with its own _main_ function. In the easiest case, you'll have to extract the lighting code into a single reusable function library, and then call those functions from the new shader. The list of shaders you must keep and maintain can easily grow into the hundreds or (for large apps) thousands, mostly due to variations of otherwise similar shaders.
60
+
61
+ Jax addresses this problem by directly manipulating the source code for all shaders. It seeks out the _main_ functions, renames them to something unique based on the particular instance of the particular shader, (a process known as <em>"name mangling":http://en.wikipedia.org/wiki/Name_mangling</em>, which is certainly not unique to Jax), and then constructs its own _main_ function just prior to compiling the code.
62
+
63
+
64
+ h4. Shared Variables
65
+
66
+ The process of combining shaders is entirely automatic and there's no need for the developer to get involved, but it's something the developer _does_ need to be aware of due to the relatively stringent limitations of graphics hardware.
67
+
68
+ It would be wasteful (and impossible!) to create exact copies of all variables, such as the modelview matrix, the projection matrix, vertex positions, normals, texture coordinates, colors... the list goes on. Instead, the Jax preprocessor introduces the +shared+ keyword. Whenever you use the +shared+ keyword as part of a variable declaration in the global scope, (for instance, in the +app/shaders/brick/common.ejs+ file), Jax will _not_ name mangle the variable in question. This means that as long as all of the shaders use the same name for the variable, it won't be redeclared when those shaders are combined.
69
+
70
+ Here are some examples of the +shared+ keyword, extracted from the +common.ejs+ file:
71
+
72
+ <c>
73
+ shared uniform mat3 nMatrix;
74
+ shared uniform mat4 mvMatrix, pMatrix;
75
+
76
+ shared varying vec2 vTexCoords;
77
+ shared varying vec3 vNormal;
78
+ shared varying vec4 vBaseColor;
79
+ </c>
80
+
81
+
82
+ h4. Exports and Imports
83
+
84
+ Similar to shared variables, _exports_ and _imports_ allow you to share values across shaders. The difference is that they don't need to be predeclared beforehand -- you can use them on-the-fly within your shaders. They also allow you to prepare them with default values in case they haven't been previously exported by another shader.
85
+
86
+ h5. Exporting a Value
87
+
88
+ When Jax encounters an export, it will be declared in the global scope as long as it hasn't been already.
89
+
90
+ There are two ways to export a variable: with an <em>implicit value</em> or with an <em>explicit value</em>. An _implicit_ value simply means that Jax will look for a local variable of the same name as the export, and use it. An _explicit_ value takes the form of a third argument, and represents the value or variable whose value you are assigning. Here are some examples of both:
91
+
92
+ <c>
93
+ void main(void) {
94
+ vec4 ambient = vec4(1,1,1,1);
95
+
96
+ // implicit value
97
+ export(vec4, ambient);
98
+
99
+ // explicit value
100
+ export(vec4, GlobalAmbientColor, vec4(ambient.rgb * 0.5, 1));
101
+ }
102
+ </c>
103
+
104
+ h5. Importing a Value
105
+
106
+ Shaders can import values previously exported by other shaders. If the value has never been exported, the global variable won't exist and the import will be silently ignored. This makes it very easy to use default values for imports, as you'll see in the next code snippet.
107
+
108
+ Imports always take two arguments. The first is the actual name of the variable to be imported; the second is an expression representing what to do with the import if it can be found.
109
+
110
+ Here's an example:
111
+
112
+ <c>
113
+ void main(void) {
114
+ vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
115
+ import(ambient, color = ambient);
116
+ }
117
+ </c>
118
+
119
+ INFO: When the import exists in the above example, we are effectively assigning a value to the +color+ variable and then immediately overwriting that value. This would be inefficient, but the GLSL compiler will be smart enough to optimize the redundancy away for us so that it is not an issue. If the import does _not_ exist, then the +import()+ call is ignored entirely and the color defaults to white.
120
+
121
+
122
+ h4. Requiring Supporting Libraries
123
+
124
+ You can create your own libraries of functions and other reusable code. Just put it in a subdirectory of +app/shaders+ (I like to put my functions in +app/shaders/functions+) and be sure to give the file a +.ejs+ extension.
125
+
126
+ To make use of your function libraries, use the +require+ directive like so:
127
+
128
+ <js>
129
+ //= require "functions/name_of_library.ejs"
130
+ </js>
131
+
132
+ ...where _name_of_library_ represents the name of the function library you're trying to import.
133
+
134
+ INFO: Library requires are performed on the Ruby side of life, so this does not result in an AJAX or similar call. Instead, Ruby performs the lookup while building the JavaScript portion of your shader. This yields not only the runtime advantages of avoiding multiple requests, but is more configurable. You can alter the +Jax.application.config.shader_paths+ variable, which is an array of strings, in the Ruby API to tell Ruby where else to look for function libraries.
135
+
136
+ INFO: By default, the +Jax.application.config.shader_paths+ variable in Ruby is set to include the +app/shaders+ directory within your own application, as well as the Jax built-in shader path. This allows you to require the various function libraries that ship with Jax for your own use. See "the Wiki":http://github.com/sinisterchipmunk/jax/wiki for more information about what function libraries are available to your shaders, and how to use them.
137
+
138
+
139
+ h4. +main+ in the Fragment Shader
140
+
141
+ Your fragment shader's +main+ function may accept up to 3 _inout_ arguments: _ambient_, _diffuse_ and _specular_, in that order. Each of these is a +vec4+ representing the named color channel. They default to (1, 1, 1, 1) but may be altered by other shaders before being passed into yours. In this way, you can produce cumulative effects on the color of a given fragment by directly altering the color components they represent.
142
+
143
+ If you define your +main+ function to not accept any arguments (that is, its argument definition is +void+), then Jax will assume you intend to write to +gl_FragColor+ directly. In this case, <strong>all color components are ignored</strong>. This has an effect on the entire shader chain, and is generally not recommended unless you understand the tradeoffs.
144
+
145
+ WARNING: Most of the built-in shaders are designed to accept the three color components, and these shaders do not pay any attention to +gl_FragColor+.
146
+
147
+
148
+ h3. Embedded JavaScript
149
+
150
+ Because Jax shaders are composed using Embedded JavaScript (EJS), you can easily insert JavaScript code directly into your shader. The JavaScript code is evaluated before any other preprocessors are run, and can be used to manipulate exactly what will ultimately be compiled.
151
+
152
+ Embedding JavaScript into an EJS file is quite simple. Here's how the built-in Perlin noise functions make use of this feature:
153
+
154
+ <js>
155
+ <% if (shader_type == 'vertex' && Jax.Shader.max_vertex_textures == 0) { %>
156
+ // use slower, non-texture-based noise functions
157
+ <% } else { %>
158
+ // use faster, texture-based noise functions
159
+ <% } %>
160
+ </js>
161
+
162
+ INFO: In this example, Jax first checks the shader type. If it's a vertex shader, then there's a possibility that the vertex shader does not support texture lookups; if this is true, then texture-based noise in the vertex shader is going to fail to compile. Since Jax tries to support all graphics hardware seamlessly, it uses this information to fall back to a slower, but workable, noise solution that does not rely on texture lookups. If the shader type is 'fragment' or if the graphics hardware _does_ support vertex texture lookups, then the faster noise functions are used instead. This allows the Jax noise functions to be extremely flexible by not forcing the developer to manage many different libraries, or to manually query the capabilities of the graphics hardware, or both.
163
+
164
+ Another example is for the definition of globals. You can still hard-code global values using the compiler preprocessor like so...
165
+
166
+ <c>
167
+ #ifndef PI
168
+ #define PI 3.14159...
169
+ #endif
170
+
171
+ void main(void) {
172
+ float circle = 2 * PI;
173
+ // ...
174
+ }
175
+ </c>
176
+
177
+ Or, you can use Embedded JavaScript to use the values directly in-line while not sacrificing any readability. This allows you to reuse constants at the JavaScript level instead of coding them directly into your shader -- even if you don't necessarily know the exact values of those constants off the top of your head:
178
+
179
+ <c>
180
+ void main(void) {
181
+ float circle = 2 * <%=Math.PI%>;
182
+ // ...
183
+ }
184
+ </c>
185
+
186
+ h3. Putting it All Together
187
+
188
+ Let's use what we've learned so far to start building our _bricks_ shader.
189
+
190
+ h4. Manifest
191
+
192
+ The first thing we should look at is the Manifest. Its use will become apparent when we test our shader for the first time. Make it look like this:
193
+
194
+ <yaml>
195
+ description: |
196
+ Adds a simple brick texture to a mesh
197
+
198
+ options:
199
+ brick_color:
200
+ red: 1.0
201
+ green: 0.3
202
+ blue: 0.2
203
+
204
+ mortar_color:
205
+ red: 0.85
206
+ green: 0.86
207
+ blue: 0.84
208
+
209
+ brick_size:
210
+ x: 0.3
211
+ y: 0.15
212
+
213
+ brick_pct:
214
+ x: 0.9
215
+ y: 0.85
216
+ </yaml>
217
+
218
+ The description should be kept as concise as possible. Try to explain the shader in one sentence -- or less. The options are entirely made up by you, and will be passed into your shader as <tt>uniform</tt>s a little later on.
219
+
220
+ NOTE: See the pipe ("|") following the +description+? In case you're not familiar with YAML syntax, that's telling YAML that the description may span several lines. The description can continue until an empty line is encountered. If you remove the pipe, you'll have to fit the entire description into one line. That's not a bad thing, as long as you're aware of the need to do so.
221
+
222
+ h4. Common Source
223
+
224
+ First, open up the +app/shaders/bricks/common.ejs+ file and make it look like this:
225
+
226
+ <c>
227
+ varying vec2 MCposition;
228
+
229
+ shared uniform mat4 mvMatrix, pMatrix;
230
+ </c>
231
+
232
+ We are defining a 2D vector called +MCposition+ which will be used to generate the brick texture. Since both the vertex and fragment shader will make use of this, we can put it in the common code to reduce code repetition. This way, if we need to make changes to it later on, we only need to change it once.
233
+
234
+ We define +mvMatrix+ and +pMatrix+ for the same reason, but we use the +shared+ preprocessor keyword to tell Jax that these matrices are to be used for _all_ shaders in the chain, and not just this one.
235
+
236
+ h4. Vertex Shader Source
237
+
238
+ <c>
239
+ shared attribute vec4 VERTEX_POSITION;
240
+
241
+ void main(void) {
242
+ MCposition = VERTEX_POSITION.xy;
243
+ gl_Position = pMatrix * mvMatrix * VERTEX_POSITION;
244
+ }
245
+ </c>
246
+
247
+ This is a very simple vertex shader that just passes the X and Y coordinates of +VERTEX_POSITION+ into a varying for use in the fragment shader. Then it sets +gl_Position+, in case no shader before it has done so. We mark +VERTEX_POSITION+ as +shared+ because virtually every shader will make use of +VERTEX_POSITION+, and we don't want Jax to define a new attribute for each shader in the chain.
248
+
249
+ h4. Fragment Shader Source
250
+
251
+ Finally, open +app/shaders/bricks/fragment.ejs+ and replace its contents with:
252
+
253
+ <c>
254
+ uniform vec4 BrickColor, MortarColor;
255
+ uniform vec2 BrickSize, BrickPct;
256
+
257
+ void main(inout vec4 ambient, inout vec4 diffuse, inout vec4 specular) {
258
+ vec2 position = MCposition / BrickSize, useBrick;
259
+
260
+ if (fract(position.y * 0.5) > 0.5)
261
+ position.x += 0.5;
262
+
263
+ position = fract(position);
264
+ useBrick = step(position, BrickPct);
265
+
266
+ vec3 color = mix(MortarColor.rgb, BrickColor.rgb, useBrick.x * useBrick.y);
267
+ ambient.rgb *= color;
268
+ diffuse.rgb *= color;
269
+ }
270
+ </c>
271
+
272
+ Notice, in this case, that our uniforms are not marked as +shared+. This is because we want the brick color, mortar color, and so on to be unique to this shader. Moreover, we want the colors to be unique to this _instance_ of the shader; that is, if the user decides to use two separate Brick shaders with separate color and size parameters, we don't want the parameters to conflict with each other.
273
+
274
+ NOTE: Take notice that we didn't do any lighting calculations. In fact, though we could have simply added <tt>//= require "functions/lights"</tt> and called the Jax lighting functions directly, we decided not to do _any_ lighting whatsoever. Why? This is because Jax already has lighting functions; if you write your own, you're effectively reinventing the wheel. While this is OK to do if you're unhappy with the Jax implementation, you should do so in a completely separate shader so that it's easy to toggle lighting effects on and off. In essence, try to keep a shader as "bare-bones" as possible: it should ideally do what its description says, no more, no less. It can be combined with other shaders later on to maximize code reuse.
275
+
276
+ h4. Material File
277
+
278
+ The last step before we can test our material out is to tie the uniforms to their actual values. For that, we need to edit the material file in +app/shaders/bricks/material.js+. Make it look like this:
279
+
280
+ <js>
281
+ Jax.Material.Brick = Jax.Class.create(Jax.Material, {
282
+ initialize: function($super, options) {
283
+ options = Jax.Util.normalizeOptions(options, {
284
+ shader: "brick",
285
+ brick_color: [1, 0.3, 0.2],
286
+ mortar_color: [0.85, 0.86, 0.84],
287
+ brick_size: [0.3, 0.15],
288
+ brick_pct: [0.9, 0.85]
289
+ });
290
+
291
+ options.brick_color = Jax.Util.colorize(options.brick_color);
292
+ options.mortar_color = Jax.Util.colorize(options.mortar_color);
293
+ options.brick_size = Jax.Util.vectorize(options.brick_size);
294
+ options.brick_pct = Jax.Util.vectorize(options.brick_pct);
295
+
296
+ $super(options);
297
+ },
298
+
299
+ setUniforms: function($super, context, mesh, options, uniforms) {
300
+ uniforms.set('mvMatrix', context.getModelViewMatrix());
301
+ uniforms.set('nMatrix', context.getNormalMatrix());
302
+ uniforms.set('pMatrix', context.getProjectionMatrix());
303
+ uniforms.set('BrickColor', this.brick_color);
304
+ uniforms.set('MortarColor', this.mortar_color);
305
+ uniforms.set('BrickSize', this.brick_size);
306
+ uniforms.set('BrickPct', this.brick_pct);
307
+ },
308
+
309
+ setAttributes: function($super, context, mesh, options, attributes) {
310
+ attributes.set('VERTEX_POSITION', mesh.getVertexBuffer());
311
+ }
312
+ });
313
+ </js>
314
+
315
+ The +vectorize+ and +colorize+ utility methods called from the constructor are used to normalize the various different forms that a vector or color might take, respectively. This lets users use full words like "red", letters like "r", or just a comma-delimited list inside of their material files, and Jax will still interpret the value correctly.
316
+
317
+ The rest of the file should be pretty apparent. The +setUniforms+ method is in charge of setting uniform values, while the +setAttributes+ method does the same for attribute values. Both methods are passed a Jax context, mesh, and a set of options built during the render pass.
318
+
319
+ h3. Test the Shader
320
+
321
+ We're ready to test it out! Before we go generating any materials, try just loading up the Jax application in your browser. We should make sure those two specs that were generated for us way back at the beginning of this guide are still passing.
322
+
323
+ All green? Good! Let's generate a controller so we can see our new shader in action!
324
+
325
+ <shell>
326
+ $ jax generate controller bricks index
327
+ </shell>
328
+
329
+ IMPORTANT: This is all covered in the "Getting Started Guide":getting_started.html. Don't forget to set the root route in +config/routes.rb+ if you haven't done so already.
330
+
331
+ With that done, add the following code to the +index+ action of the newly-generated +app/controllers/bricks_controller.js+:
332
+
333
+ <js>
334
+ this.world.addObject(new Jax.Model({
335
+ position:[0,0,-5],
336
+ mesh:new Jax.Mesh.Quad(),
337
+ material:Material.find("bricks_demo")
338
+ }));
339
+ </js>
340
+
341
+ h4. Generate a Material
342
+
343
+ If you remember the "Getting Started Guide":getting_started.html, then you'll know that the above code will cause your application and its tests to fail. The next thing we need to do is generate the +bricks_demo+ material that we referenced in the controller.
344
+
345
+ Before you go typing in the whole command, type just a little bit:
346
+
347
+ <shell>
348
+ $ jax generate material
349
+ ...
350
+ - brick : Adds a simple brick texture to a mesh
351
+ </shell>
352
+
353
+ This is where your +app/shaders/bricks/manifest.yml+ file comes into play. Ruby reads this file in order to know how to help you when you're generating new materials. This becomes very useful when you're dealing with a lot of shaders all at once.
354
+
355
+ Now, let's complete the command:
356
+
357
+ <shell>
358
+ $ jax generate material bricks_demo brick
359
+ </shell>
360
+
361
+ Open up the generated +app/resources/materials/bricks_demo.yml+ file and, at the bottom, you'll see some more interesting tidbits:
362
+
363
+ <yaml>
364
+ - type: Brick
365
+ brick_color:
366
+ red: 1.0
367
+ green: 0.3
368
+ blue: 0.2
369
+ mortar_color:
370
+ red: 0.85
371
+ green: 0.86
372
+ blue: 0.84
373
+ brick_size:
374
+ x: 0.3
375
+ y: 0.15
376
+ brick_pct:
377
+ x: 0.9
378
+ y: 0.85
379
+ </yaml>
380
+
381
+ These are the options you specified in the manifest file! They are used as defaults for all generated Materials. Users can now edit these options as needed, without having to guess which options are available to them (or refer to documentation).
382
+
383
+ h3. Combining Shaders
384
+
385
+ You may have noticed in the +app/resources/material/bricks_demo.yml+ file that it listed +Lighting+ as one of the shader layers. This means Jax is implicitly combining both the Lighting shader and our Bricks shader into one program! You can enable lighting effects by simply adding a light source to the scene. This can be done by adding a light source via the +index+ action in the +app/controllers/bricks_controller.js+, which should now look like this:
386
+
387
+ <js>
388
+ //= require "application_controller"
389
+
390
+ var BricksController = (function() {
391
+ return Jax.Controller.create("bricks", ApplicationController, {
392
+ index: function() {
393
+
394
+ this.world.addObject(new Jax.Model({
395
+ position:[0,0,-5],
396
+ mesh:new Jax.Mesh.Quad({
397
+ material:"bricks_demo"
398
+ }),
399
+ }));
400
+
401
+ // add a light to show off the lighting effects that our
402
+ // shader has, "for free"
403
+ this.world.addLightSource(new Jax.Scene.LightSource({
404
+ position:[0.5, 0.5, -4],
405
+ attenuation: { linear: 1.25 },
406
+ type:Jax.POINT_LIGHT
407
+ }));
408
+ }
409
+ });
410
+ })();
411
+ </js>
412
+
413
+ h4. Falling Back
414
+
415
+ Obviously, not all clients are created equal. The sad reality is that you cannot expect everyone to view your application exactly as you do; some devices will perform better than others, and some will have a downright difficult time with apps that include a lot of visual effects.
416
+
417
+ Some of this must be chalked up to how you design your application. If it does a lot of number crunching on the CPU or has extremely complicated shaders tying up the GPU for extended periods of time, there's only so much Jax can do to compensate. In general, Jax leaves it up to you, the developer, to decide what type of hardware to target and how to improve performance in individual algorithms. As long as it's theoretically _possible_ to run an application as it was originally coded, Jax will not make any changes.
418
+
419
+ On the other hand, Jax is very good at "dumbing down" to clients that have physical hardware constraints that limit their ability to run your application. If, for instance, a given Material requests a set of shaders that, combined, would use too many +varying+ variables, Jax will begin to make changes to the list of shaders that can be used with the material in question.
420
+
421
+ h5. Example 1
422
+
423
+ Following is a step-by-step example, using our new "bricks with lighting" material (above) to demonstrate.
424
+
425
+ IMPORTANT: We're going to pull numbers from a hat in order to make a point; realistically speaking, all of these are much too low. Virtually any graphics hardware on the market today should run the entire example without issue.
426
+
427
+ Let's imagine a client visiting our site that has the following specific graphic limitations:
428
+
429
+ * Uniforms: 32
430
+ * Attributes: 8
431
+ * Varyings: 5
432
+
433
+ The basic, no-frills, color-only shader for Jax uses 11 uniforms, 5 attributes and 4 varyings. The lighting shader uses 13 uniforms, no additional attributes, and 1 varying. Our own brick shader uses 4 additional uniforms and 1 additional varying. (The rest of the variables are +share+'d with the other shaders, thankfully!)
434
+
435
+ This means our current list of shaders is asking for a total of 27 uniforms, 5 attributes and 6 varyings. Our shader list exceeds the number of available +varying+ slots by 1.
436
+
437
+ Jax must now look at the list of shaders, and make an educated guess as to which shaders to keep and which ones to drop. The +basic+ shader is part of the core material and therefore cannot be dropped (although this can be modified; we'll talk about this later). That leaves either +lighting+ or +bricks+, which both use exactly 1 varying.
438
+
439
+ With little other choice, Jax simply drops the last one to appear in the material: our bricks. But if Jax drops the bricks, then our material becomes a bland, white sheet of plastic! In this case, it's probably desirable to drop lighting in favor of keeping the bricks. We can cause Jax to drop +lighting+ instead of +bricks+ by simply making sure that +bricks+ appears _before_ +lighting+ in the +app/resources/materials/bricks_demo.yml+ file:
440
+
441
+ <yaml>
442
+ layers:
443
+ - type: Brick
444
+ brick_color:
445
+ # ...
446
+
447
+ - type: Lighting
448
+ </yaml>
449
+
450
+ Now, when Jax has to choose between two perfectly valid candidates, it will drop +lighting+ in favor of +bricks+.
451
+
452
+
453
+ h5. Example 2
454
+
455
+ Let's take another example, now. Assume that our client has the following hardware limitations:
456
+
457
+ * Uniforms: 16
458
+ * Attributes: 8
459
+ * Varyings: 8
460
+
461
+ Now we have an entirely different scenario. It is immediately obvious to Jax that, since it can't drop the +basic+ shader, it is patently impossible to run the +lighting+ shader on the specified material. It just can't be done, period. There aren't enough +uniform+ slots available.
462
+
463
+ In this situation, Jax will immediately drop the +lighting+ shader <em>regardless of its position in the material file!</em> By doing so, this opens up just enough +uniform+ slots to make the +brick+ shader run, so Jax stops here.
464
+
465
+ h5. Example 3: Worst Case
466
+
467
+ If the hardware were even more limited, Jax would now consider dropping both the +brick+ and +lighting+ shaders, and running on pure color support only. This is almost _never_ what you want Jax to do, but it is a last-ditch effort by Jax to try to run your application on hardware your application wasn't intended for.
468
+
469
+ Hypothetical numbers:
470
+
471
+ * Uniforms: 12
472
+ * Attributes: 6
473
+ * Varyings: 4
474
+
475
+ The +basic+ shader is guaranteed to run on all WebGL-compatible hardware, as long as the hardware follows the OpenGL ES standards for "minimum maximums". However, the +brick+ shader is well within the limits of our latest example <em>if the +basic+ shader is dropped</em>.
476
+
477
+ This is where the auto-generated spec for "standalone shaders" comes in handy, because it proves that our shader will, at the very least, _compile_, even if the +basic+ shader is omitted. So how can we tell Jax to omit the +basic+ shader and use the +brick+ shader as the default?
478
+
479
+ Easy! Simply move the +brick+ type (as well as all pertinent options) outside of the +layers+ attribute, making them attributes of the core material itself:
480
+
481
+ <yaml>
482
+ # ...
483
+
484
+ type: Brick
485
+ brick_color:
486
+ red: 1.0
487
+ green: 0.3
488
+ blue: 0.2
489
+ mortar_color:
490
+ # ...
491
+
492
+ layers:
493
+ # remove Lighting to conserve video memory if you don't need/want support for light sources
494
+ - type: Lighting
495
+ </yaml>
496
+
497
+ Done! Jax will now completely omit the +basic+ shader, and the +brick+ shader will be marked as "critical", or un-droppable.
498
+
499
+ WARNING: This last option is a good idea _only_ if you have developed a shader that you are sure will run on all hardware! If Jax encounters an un-droppable shader which cannot feasibly run on the client hardware, it will crash and burn in a rather spectacular way. Avoid this situation by developing critical shaders that are compatible with "least common denominator" hardware!