Source: common/Shader.js

  1. /**
  2. * @class Shader
  3. * @memberof SQR
  4. *
  5. * @description Represents a GLSL shader. The shader class takes the source GLSL code,
  6. * compiles it and extracts all the attributes and uniforms. It also exposes
  7. * methods to set the uniform values of this shader.
  8. *
  9. * Please read the {@tutorial basic-setup} tutorial to see how to use a shader
  10. * and the {@tutorial understanding-shaders} tutorial for an in depth discussion on shaders.
  11. *
  12. *
  13. * @param {string} source - the GLSL source code formatted
  14. * in a way to include both vertex and fragment shaders.
  15. *
  16. * @param {object} options - additional options, not required. Supported options in the code sample below.
  17. *
  18. * @example
  19. {
  20. // Do not compile
  21. // (yes, there is such option, but 99.99% of the time this is not necessary)
  22. doNotCompile: true,
  23. // Preprocesor directives.
  24. // This object will create
  25. // the following directives, attached to both
  26. // vertex and fragment shaders:
  27. // #define COLOR_ONLY
  28. // #define COLOR 1.0 0.0 0.0
  29. directives: [
  30. { name: 'COLOR_ONLY' },
  31. { name: 'COLOR', value: '1.0, 0.0, 0.0' }
  32. ]
  33. }
  34. */
  35. SQR.Shader = function(source, options) {
  36. var s = {}, program, gl;
  37. var attributes = {}, attrList = [];
  38. var uniforms = {}, uniformList = [], uniformTextures = [];
  39. var parseGLSL = function(s) {
  40. if(!s) throw "> SQR.Shader.parseGLSL - Shader source code missing";
  41. s = s.replace(/\r/g, "");
  42. var pp = "", pv = options ? options.directives : null;
  43. if(pv && pv instanceof Array) {
  44. for(var i = 0; i < pv.length; i++) {
  45. pp += "#define " + pv[i].name;
  46. if(pv[i].value) pp += " " + pv[i].value;
  47. pp += "\n";
  48. }
  49. }
  50. var vertex = pp, fragment = pp;
  51. var isVertex = true;
  52. var ls = s.split("\n");
  53. for(var i = 0; i < ls.length; i++) {
  54. var l = ls[i];
  55. if (l.indexOf("//#include") > -1) {
  56. var p = l.substring(11), inc;
  57. if(SQR.GLSL && SQR.GLSL[p]) {
  58. inc = SQR.GLSL[p];
  59. } else if(SQR.GLSLInclude && SQR.GLSLInclude[p]) {
  60. inc = SQR.GLSLInclude[p];
  61. } else if(options && options.includes) {
  62. inc = options.includes[p];
  63. }
  64. if(!inc) throw "> SQR.Shader.parseGLSL - Include not found: " + p;
  65. ls[i] = inc;
  66. }
  67. }
  68. var ls = ls.join('\n').split('\n');
  69. for(var i = 0; i < ls.length; i++) {
  70. var l = ls[i];
  71. if(l.indexOf("//#") > -1) {
  72. if (l.indexOf("//#fragment") > -1) {
  73. isVertex = false;
  74. } else if (l.indexOf("//#vertex") > -1) {
  75. isVertex = true;
  76. }
  77. } else {
  78. if(l.indexOf("//") > -1) l = l.substring(0, l.indexOf("//"));
  79. if(l.match(/^([\s\t]*)$/)) continue;
  80. if(isVertex) {
  81. vertex += l + "\n";
  82. } else {
  83. fragment += l + "\n";
  84. }
  85. }
  86. }
  87. return { vertex: vertex, fragment: fragment };
  88. };
  89. s.compile = function() {
  90. var sc = parseGLSL(source);
  91. var gl = SQR.gl;
  92. var vs = gl.createShader(gl.VERTEX_SHADER);
  93. gl.shaderSource(vs, sc.vertex);
  94. gl.compileShader(vs);
  95. var fs = gl.createShader(gl.FRAGMENT_SHADER);
  96. gl.shaderSource(fs, sc.fragment);
  97. gl.compileShader(fs);
  98. program = gl.createProgram();
  99. gl.attachShader(program, vs);
  100. gl.attachShader(program, fs);
  101. gl.linkProgram(program);
  102. if(!gl.getShaderParameter(vs, gl.COMPILE_STATUS))
  103. throw "> SQR.Shader. Vertex shader compile error: " + gl.getShaderInfoLog(vs);
  104. if(!gl.getShaderParameter(fs, gl.COMPILE_STATUS))
  105. throw "> SQR.Shader. Fragment shader compile error: " + gl.getShaderInfoLog(fs);
  106. if(!gl.getProgramParameter(program, gl.LINK_STATUS))
  107. throw "> SQR.Shader. Shader linking error: " + gl.getProgramInfoLog(program);
  108. return s;
  109. }
  110. s.inspect = function() {
  111. var gl = SQR.gl;
  112. var numAttr = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
  113. for (var i = 0; i < numAttr; i++) {
  114. var a = gl.getActiveAttrib(program, i);
  115. a.location = gl.getAttribLocation(program, a.name);
  116. attributes[a.name] = a;
  117. attrList.push(a);
  118. }
  119. var numUni = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS), id = 0;
  120. for (var i = 0; i < numUni; i++) {
  121. var u = gl.getActiveUniform(program, i);
  122. if(u.type == gl.SAMPLER_2D || u.type == gl.SAMPLER_CUBE) {
  123. u.texId = id++;
  124. uniformTextures.push(u);
  125. }
  126. // Special case for arrays
  127. if(u.name.indexOf('[') > -1) {
  128. var n = u.name.substring(0, u.name.indexOf('['));
  129. for(var j = 1; j < u.size; j++) {
  130. var ni = n + '[' + j + ']';
  131. var ui = {
  132. name: ni,
  133. location: gl.getUniformLocation(program, ni),
  134. type: u.type
  135. }
  136. uniforms[ui.name] = ui;
  137. uniformList.push(ui);
  138. }
  139. }
  140. u.location = gl.getUniformLocation(program, u.name);
  141. uniforms[u.name] = u;
  142. uniformList.push(u);
  143. }
  144. return s;
  145. }
  146. var stringType = 'string';
  147. s.getUniform = function(name) {
  148. return uniforms[name];
  149. }
  150. /**
  151. * @method hasUniform
  152. * @memberof SQR.Shader.prototype
  153. *
  154. * @returns {Object} true if the shader has a uniform that has this name, null otherwise. The object returned has 3 properties: name, location, type.
  155. */
  156. s.hasUniform = function(name) {
  157. return uniforms[name] != null;
  158. }
  159. /**
  160. * @method setUniform
  161. * @memberof SQR.Shader.prototype
  162. *
  163. * @description using setUniform is recommended for uniforms that do not change much or unifors that have the same
  164. * value for all the objects rendered with this shader. If you need to as uniforms that are different per object
  165. * (ex. a 100 balls rendered with the same shader, but each with a different color) then it is better to use the
  166. * <code>uniforms</code> object attached to each instance od <code>SQR.Transform</code>.
  167. * Please refer to the {@tutorial understanding-shaders} for more info.
  168. *
  169. * @param {string} uniform The name of the uniform.
  170. * By convection all uniforms in SQR start with a lowercas u and the a capitalized/camelcase name follows.
  171. * Example of good uniform names: <code>uIntensity, uLightColor</code>. Not good: <code>uintensity, color</code>.
  172. *
  173. * @param value the value of the uniform to set. It will expect a different object depending on the type of the uniform,
  174. * but there are a few rules as shown in the example below.
  175. *
  176. * @example
  177. var sh = SQR.Shader(glslCodeString);
  178. // glslCodeString = the code loaded from a file or wherever you get it from
  179. // ALWAYS DO THIS FIRST!
  180. sh.use();
  181. // for floats/ints just a number is ok
  182. sh.setUniform('uSpeed', 2);
  183. // ... but a one element array will do too
  184. sh.setUniform('uIntensity', [0.2]);
  185. // for vectors, regular Array or Float32Array is ok
  186. sh.setUniform('uDirection', [0.2, 0.5, 0.3]);
  187. // for matrices, pass in the data property of any Matrix class
  188. sh.setUniform('uBoneMatrix', boneMatrix.data);
  189. // textures expect an instance of SQR.Texture or SQR.Cubemap
  190. sh.setUniform('uNormalMap', SQR.Texture('assets/normalMap.jpg'));
  191. // in all the above cases any object that has a method called 'toUniform' works too
  192. // SQR.V2, SQR.V3 and SQR.Color have that, so:
  193. sh.setUniform('uCenter', new SQR.V3(12, 45, 33));
  194. sh.setUniform('uColor', SQR.Color().fromHex('#ff8000'));
  195. // or, assuming that light is an SQR.Transform:
  196. sh.setUniform('uLighPosition', light.position)
  197. */
  198. s.setUniform = function(uniform, value) {
  199. var gl = SQR.gl;
  200. var n = (typeof uniform == stringType) ? uniforms[uniform] : uniform;
  201. var v = value;
  202. if(!n) {
  203. var f = uniforms[uniform + '[0]'];
  204. if(f) {
  205. for(var i = 0; i < f.size; i++) {
  206. if(value[i]) s.setUniform(uniform + '[' + i + ']', value[i]);
  207. }
  208. return;
  209. }
  210. if(SQR.WARN_UNIFORM_NOT_PRESENT) {
  211. console.warn("> SQR.Shader attempt to set uniform that does not exist: " + uniform);
  212. console.trace();
  213. }
  214. return s;
  215. }
  216. if(v && v.toUniform) v = v.toUniform(n.type);
  217. switch (n.type) {
  218. case gl.BYTE:
  219. gl.uniform1i(n.location, v);
  220. break;
  221. case gl.UNSIGNED_BYTE:
  222. gl.uniform1i(n.location, v);
  223. break;
  224. case gl.SHORT:
  225. gl.uniform1i(n.location, v);
  226. break;
  227. case gl.UNSIGNED_SHORT:
  228. gl.uniform1i(n.location, v);
  229. break;
  230. case gl.INT:
  231. gl.uniform1i(n.location, v);
  232. break;
  233. case gl.INT_VEC2:
  234. gl.uniform2iv(n.location, v);
  235. break;
  236. case gl.INT_VEC3:
  237. gl.uniform3iv(n.location, v);
  238. break;
  239. case gl.INT_VEC4:
  240. gl.uniform4iv(n.location, v);
  241. break;
  242. case gl.UNSIGNED_INT:
  243. gl.uniform1i(n.location, v);
  244. break;
  245. case gl.FLOAT:
  246. gl.uniform1f(n.location, v);
  247. break;
  248. case gl.FLOAT_VEC2:
  249. gl.uniform2fv(n.location, v);
  250. break;
  251. case gl.FLOAT_VEC3:
  252. gl.uniform3fv(n.location, v);
  253. break;
  254. case gl.FLOAT_VEC4:
  255. gl.uniform4fv(n.location, v);
  256. break;
  257. case gl.BOOL:
  258. gl.uniform1i(n.location, v);
  259. break;
  260. case gl.BOOL_VEC2:
  261. gl.uniform2iv(n.location, v);
  262. break;
  263. case gl.BOOL_VEC3:
  264. gl.uniform3iv(n.location, v);
  265. break;
  266. case gl.BOOL_VEC4:
  267. gl.uniform4iv(n.location, v);
  268. break;
  269. case gl.FLOAT_MAT2:
  270. gl.uniformMatrix2fv(n.location, false, v.data || v);
  271. break;
  272. case gl.FLOAT_MAT3:
  273. gl.uniformMatrix3fv(n.location, false, v.data || v);
  274. break;
  275. case gl.FLOAT_MAT4:
  276. gl.uniformMatrix4fv(n.location, false, v.data || v);
  277. break;
  278. case gl.SAMPLER_2D:
  279. setTexture(n, v);
  280. break;
  281. case gl.SAMPLER_CUBE:
  282. setTextureCube(n, v);
  283. break;
  284. default:
  285. console.warn("> SQR.Shader > WARNING! Unknown uniform type ( 0x" + n.type.toString(16) + " )");
  286. break;
  287. }
  288. return s;
  289. }
  290. var setTexture = function(uniform, texture) {
  291. var gl = SQR.gl, id = uniform.texId;
  292. uniform.texref = texture;
  293. gl.activeTexture(gl.TEXTURE0 + id); // 33984
  294. if(texture) {
  295. gl.bindTexture(gl.TEXTURE_2D, texture.tex || texture);
  296. if(texture.isAnimated) texture.update();
  297. } else {
  298. gl.bindTexture(gl.TEXTURE_2D, null);
  299. }
  300. gl.uniform1i(uniform.location, id);
  301. }
  302. var setTextureCube = function(uniform, texture) {
  303. var gl = SQR.gl, id = uniform.texId;
  304. uniform.texref = texture;
  305. gl.activeTexture(gl.TEXTURE0 + id);
  306. gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture.tex || texture);
  307. gl.uniform1i(uniform.location, id);
  308. }
  309. s.updateTextures = function() {
  310. var gl = SQR.gl;
  311. for(var i = 0, tl = uniformTextures.length; i < tl; i++) {
  312. var t = uniformTextures[i];
  313. if(t.texref) s.setUniform(t, t.texref);
  314. }
  315. return s;
  316. }
  317. /**
  318. * @method use
  319. * @memberof SQR.Shader.prototype
  320. *
  321. * @description Sets this shader as the current program in GL. This function needs to be called before any uniforms are set.
  322. */
  323. s.use = function() {
  324. SQR.gl.useProgram(program);
  325. return s;
  326. }
  327. s.attribPointers = function(geo) {
  328. var gl = SQR.gl, al = attrList.length;
  329. geo = geo.buffer || geo;
  330. for(var i = 0; i < al; i++) {
  331. var a = attrList[i];
  332. var ga = geo.attributes[a.name];
  333. if(!ga) throw "> SQR.Shader expects attribute " + a.name + " but geometry doesn't provide it";
  334. if(!a.enabled) gl.enableVertexAttribArray(a.location);
  335. gl.vertexAttribPointer(a.location, ga.size, gl.FLOAT, false, geo.strideByteSize, ga.byteOffset);
  336. a.enabled = true;
  337. }
  338. return s;
  339. }
  340. if(!options || !options.doNotCompile) {
  341. s.compile();
  342. s.inspect();
  343. }
  344. return s;
  345. }