1 /** 2 * Hilo 3 * Copyright 2015 alibaba.com 4 * Licensed under the MIT License 5 */ 6 7 /** 8 * Heavily inspired by PIXI's SpriteRenderer: 9 * https://github.com/pixijs/pixi.js/blob/v3.0.9/src/core/sprites/webgl/SpriteRenderer.js 10 */ 11 12 var DEG2RAD = Math.PI / 180; 13 /** 14 * @language=zh 15 * @class webgl画布渲染器。所有可视对象将渲染在canvas画布上。 16 * @augments Renderer 17 * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。 18 * @module hilo/renderer/WebGLRenderer 19 * @requires hilo/core/Class 20 * @requires hilo/renderer/Renderer 21 * @requires hilo/geom/Matrix 22 * @property {WebGLRenderingContext} gl webgl上下文。只读属性。 23 */ 24 var WebGLRenderer = Class.create(/** @lends WebGLRenderer.prototype */{ 25 Extends: Renderer, 26 Statics:/** @lends WebGLRenderer */{ 27 /** 28 * @language=zh 29 * 最大批渲染数量。 30 * @type {Number} 31 */ 32 MAX_BATCH_NUM:2000, 33 /** 34 * @language=zh 35 * 顶点属性数。只读属性。 36 * @type {Number} 37 */ 38 ATTRIBUTE_NUM:5, 39 /** 40 * @language=zh 41 * 是否支持WebGL。只读属性。 42 * @type {Boolean} 43 */ 44 isSupported:null 45 }, 46 renderType:'webgl', 47 gl:null, 48 constructor: function(properties){ 49 window.__render = this; 50 WebGLRenderer.superclass.constructor.call(this, properties); 51 var gl = this.gl = this.canvas.getContext("webgl")||this.canvas.getContext('experimental-webgl'); 52 53 this.maxBatchNum = WebGLRenderer.MAX_BATCH_NUM; 54 this.positionStride = WebGLRenderer.ATTRIBUTE_NUM * 4; 55 var vertexNum = this.maxBatchNum * WebGLRenderer.ATTRIBUTE_NUM * 4; 56 var indexNum = this.maxBatchNum * 6; 57 this.positions = new Float32Array(vertexNum); 58 this.indexs = new Uint16Array(indexNum); 59 for (var i=0, j=0; i < indexNum; i += 6, j += 4) 60 { 61 this.indexs[i + 0] = j + 0; 62 this.indexs[i + 1] = j + 1; 63 this.indexs[i + 2] = j + 2; 64 this.indexs[i + 3] = j + 1; 65 this.indexs[i + 4] = j + 2; 66 this.indexs[i + 5] = j + 3; 67 } 68 this.batchIndex = 0; 69 this.sprites = []; 70 71 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 72 gl.clearColor(0, 0, 0, 0); 73 gl.disable(gl.DEPTH_TEST); 74 gl.disable(gl.CULL_FACE); 75 gl.enable(gl.BLEND); 76 77 this._initShaders(); 78 this.defaultShader.active(); 79 80 this.positionBuffer = gl.createBuffer(); 81 this.indexBuffer = gl.createBuffer(); 82 83 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); 84 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indexs, gl.STATIC_DRAW); 85 86 gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); 87 gl.bufferData(gl.ARRAY_BUFFER, this.positions, gl.DYNAMIC_DRAW); 88 89 gl.vertexAttribPointer(this.a_position, 2, gl.FLOAT, false, this.positionStride, 0);//x, y 90 gl.vertexAttribPointer(this.a_TexCoord, 2, gl.FLOAT, false, this.positionStride, 2 * 4);//x, y 91 gl.vertexAttribPointer(this.a_alpha, 1, gl.FLOAT, false, this.positionStride, 4 * 4);//alpha 92 }, 93 94 context: null, 95 96 /** 97 * @private 98 * @see Renderer#startDraw 99 */ 100 startDraw: function(target){ 101 if(target.visible && target.alpha > 0){ 102 if(target === this.stage){ 103 this.clear(); 104 } 105 return true; 106 } 107 return false; 108 }, 109 /** 110 * @private 111 * @see Renderer#draw 112 */ 113 draw: function(target){ 114 var ctx = this.context, w = target.width, h = target.height; 115 116 //TODO:draw background 117 var bg = target.background; 118 119 //draw image 120 var drawable = target.drawable, image = drawable && drawable.image; 121 if(image){ 122 var gl = this.gl; 123 if(!image.texture){ 124 this.activeShader.uploadTexture(image); 125 } 126 127 var rect = drawable.rect, sw = rect[2], sh = rect[3], offsetX = rect[4], offsetY = rect[5]; 128 if(!w && !h){ 129 //fix width/height TODO: how to get rid of this? 130 w = target.width = sw; 131 h = target.height = sh; 132 } 133 134 if(this.batchIndex >= this.maxBatchNum){ 135 this._renderBatches(); 136 } 137 138 var vertexs = this._createVertexs(image, rect[0], rect[1], sw, sh, 0, 0, w, h); 139 var index = this.batchIndex * this.positionStride; 140 var positions = this.positions; 141 var alpha = target.__webglRenderAlpha; 142 positions[index + 0] = vertexs[0];//x 143 positions[index + 1] = vertexs[1];//y 144 positions[index + 2] = vertexs[2];//uvx 145 positions[index + 3] = vertexs[3];//uvy 146 positions[index + 4] = alpha;//alpha 147 148 positions[index + 5] = vertexs[4]; 149 positions[index + 6] = vertexs[5]; 150 positions[index + 7] = vertexs[6]; 151 positions[index + 8] = vertexs[7]; 152 positions[index + 9] = alpha; 153 154 positions[index + 10] = vertexs[8] 155 positions[index + 11] = vertexs[9] 156 positions[index + 12] = vertexs[10] 157 positions[index + 13] = vertexs[11] 158 positions[index + 14] = alpha; 159 160 positions[index + 15] = vertexs[12] 161 positions[index + 16] = vertexs[13] 162 positions[index + 17] = vertexs[14] 163 positions[index + 18] = vertexs[15] 164 positions[index + 19] = alpha; 165 166 var matrix = target.__webglWorldMatrix; 167 for(var i = 0;i < 4;i ++){ 168 var x = positions[index + i*5]; 169 var y = positions[index + i*5 + 1]; 170 171 positions[index + i*5] = matrix.a*x+matrix.c*y + matrix.tx; 172 positions[index + i*5 + 1] = matrix.b*x+matrix.d*y + matrix.ty; 173 } 174 175 target.texture = image.texture; 176 this.sprites[this.batchIndex++] = target; 177 } 178 }, 179 180 /** 181 * @private 182 * @see Renderer#endDraw 183 */ 184 endDraw: function(target){ 185 if(target === this.stage){ 186 this._renderBatches(); 187 } 188 }, 189 /** 190 * @private 191 * @see Renderer#transform 192 */ 193 transform: function(target){ 194 var drawable = target.drawable; 195 if(drawable && drawable.domElement){ 196 Hilo.setElementStyleByView(target); 197 return; 198 } 199 200 var ctx = this.context, 201 scaleX = target.scaleX, 202 scaleY = target.scaleY; 203 204 if(target === this.stage){ 205 var style = this.canvas.style, 206 oldScaleX = target._scaleX, 207 oldScaleY = target._scaleY; 208 209 if((!oldScaleX && scaleX != 1) || (oldScaleX && oldScaleX != scaleX)){ 210 target._scaleX = scaleX; 211 style.width = scaleX * target.width + "px"; 212 } 213 if((!oldScaleY && scaleY != 1) || (oldScaleY && oldScaleY != scaleY)){ 214 target._scaleY = scaleY; 215 style.height = scaleY * target.height + "px"; 216 } 217 target.__webglWorldMatrix = target.__webglWorldMatrix||new Matrix(1, 0, 0, 1, 0, 0); 218 }else{ 219 target.__webglWorldMatrix = target.__webglWorldMatrix||new Matrix(1, 0, 0, 1, 0, 0); 220 this._setConcatenatedMatrix(target, target.parent); 221 } 222 223 if(target.alpha > 0) { 224 if(target.parent && target.parent.__webglRenderAlpha){ 225 target.__webglRenderAlpha = target.alpha * target.parent.__webglRenderAlpha; 226 } 227 else{ 228 target.__webglRenderAlpha = target.alpha; 229 } 230 } 231 }, 232 233 /** 234 * @private 235 * @see Renderer#remove 236 */ 237 remove: function(target){ 238 var drawable = target.drawable; 239 var elem = drawable && drawable.domElement; 240 241 if(elem){ 242 var parentElem = elem.parentNode; 243 if(parentElem){ 244 parentElem.removeChild(elem); 245 } 246 } 247 }, 248 249 /** 250 * @private 251 * @see Renderer#clear 252 */ 253 clear: function(x, y, width, height){ 254 this.gl.clear(this.gl.COLOR_BUFFER_BIT); 255 }, 256 257 /** 258 * @private 259 * @see Renderer#resize 260 */ 261 resize: function(width, height){ 262 if(this.width !== width || this.height !== height){ 263 this.width = this.canvas.width = width; 264 this.height = this.canvas.height = height; 265 this.gl.viewport(0, 0, width, height); 266 267 this.canvasHalfWidth = width * .5; 268 this.canvasHalfHeight = height * .5; 269 270 this._uploadProjectionTransform(true); 271 } 272 }, 273 _renderBatches:function(){ 274 var gl = this.gl; 275 gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.positions.subarray(0, this.batchIndex * this.positionStride)); 276 var startIndex = 0; 277 var batchNum = 0; 278 var preTexture = null; 279 for(var i = 0;i < this.batchIndex;i ++){ 280 var sprite = this.sprites[i]; 281 if(preTexture && preTexture !== sprite.texture){ 282 this._renderBatch(startIndex, i); 283 startIndex = i; 284 batchNum = 1; 285 } 286 preTexture = sprite.texture; 287 } 288 this._renderBatch(startIndex, this.batchIndex); 289 this.batchIndex = 0; 290 }, 291 _renderBatch:function(start, end){ 292 var gl = this.gl; 293 var num = end - start; 294 if(num > 0){ 295 gl.bindTexture(gl.TEXTURE_2D, this.sprites[start].texture); 296 gl.drawElements(gl.TRIANGLES, num * 6, gl.UNSIGNED_SHORT, start * 6 * 2); 297 } 298 }, 299 _uploadProjectionTransform:function(force){ 300 if(!this._projectionTransformElements||force){ 301 this._projectionTransformElements = new Float32Array([ 302 1/this.canvasHalfWidth, 0, 0, 303 0, -1/this.canvasHalfHeight, 0, 304 -1, 1, 1, 305 ]); 306 } 307 308 this.gl.uniformMatrix3fv(this.u_projectionTransform, false, this._projectionTransformElements); 309 }, 310 _initShaders:function(){ 311 var VSHADER_SOURCE ='\ 312 attribute vec2 a_position;\n\ 313 attribute vec2 a_TexCoord;\n\ 314 attribute float a_alpha;\n\ 315 uniform mat3 u_projectionTransform;\n\ 316 varying vec2 v_TexCoord;\n\ 317 varying float v_alpha;\n\ 318 void main(){\n\ 319 gl_Position = vec4((u_projectionTransform * vec3(a_position, 1.0)).xy, 1.0, 1.0);\n\ 320 v_TexCoord = a_TexCoord;\n\ 321 v_alpha = a_alpha;\n\ 322 }\n\ 323 '; 324 325 var FSHADER_SOURCE = '\n\ 326 precision mediump float;\n\ 327 uniform sampler2D u_Sampler;\n\ 328 varying vec2 v_TexCoord;\n\ 329 varying float v_alpha;\n\ 330 void main(){\n\ 331 gl_FragColor = texture2D(u_Sampler, v_TexCoord) * v_alpha;\n\ 332 }\n\ 333 '; 334 335 this.defaultShader = new Shader(this, { 336 v:VSHADER_SOURCE, 337 f:FSHADER_SOURCE 338 },{ 339 attributes:["a_position", "a_TexCoord", "a_alpha"], 340 uniforms:["u_projectionTransform", "u_Alpha", "u_Sampler"] 341 }); 342 }, 343 _createVertexs:function(img, tx, ty, tw, th, x, y, w, h){ 344 var tempVertexs = this.__tempVertexs||[]; 345 var width = img.width; 346 var height = img.height; 347 348 tw = tw/width; 349 th = th/height; 350 tx = tx/width; 351 ty = ty/height; 352 353 w = w; 354 h = h; 355 x = x; 356 y = y; 357 358 if(tw + tx > 1){ 359 tw = 1 - tx; 360 } 361 362 if(th + ty > 1){ 363 th = 1 - ty; 364 } 365 366 var index = 0; 367 tempVertexs[index++] = x; tempVertexs[index++] = y; tempVertexs[index++] = tx; tempVertexs[index++] = ty; 368 tempVertexs[index++] = x+w;tempVertexs[index++] = y; tempVertexs[index++] = tx+tw; tempVertexs[index++] = ty; 369 tempVertexs[index++] = x; tempVertexs[index++] = y+h; tempVertexs[index++] = tx;tempVertexs[index++] = ty+th; 370 tempVertexs[index++] = x+w;tempVertexs[index++] = y+h;tempVertexs[index++] = tx+tw;tempVertexs[index++] = ty+th; 371 372 return tempVertexs; 373 }, 374 _setConcatenatedMatrix:function(view, ancestor){ 375 var mtx = view.__webglWorldMatrix; 376 var cos = 1, sin = 0, 377 rotation = view.rotation % 360, 378 pivotX = view.pivotX, pivotY = view.pivotY, 379 scaleX = view.scaleX, scaleY = view.scaleY; 380 381 if(rotation){ 382 var r = rotation * DEG2RAD; 383 cos = Math.cos(r); 384 sin = Math.sin(r); 385 } 386 387 mtx.a = cos*scaleX; 388 mtx.b = sin*scaleX; 389 mtx.c = -sin*scaleY; 390 mtx.d = cos*scaleY; 391 mtx.tx = view.x - mtx.a * pivotX - mtx.c * pivotY; 392 mtx.ty = view.y - mtx.b * pivotX - mtx.d * pivotY; 393 394 mtx.concat(ancestor.__webglWorldMatrix); 395 } 396 }); 397 398 /** 399 * @language=zh 400 * shader 401 * @param {WebGLRenderer} renderer [description] 402 * @param {Object} source 403 * @param {String} source.v 顶点shader 404 * @param {String} source.f 片段shader 405 * @param {Object} attr 406 * @param {Array} attr.attributes attribute数组 407 * @param {Array} attr.uniforms uniform数组 408 */ 409 var _cacheTexture = {}; 410 var Shader = function(renderer, source, attr){ 411 this.renderer = renderer; 412 this.gl = renderer.gl; 413 this.program = this._createProgram(this.gl, source.v, source.f); 414 415 attr = attr||{}; 416 this.attributes = attr.attributes||[]; 417 this.uniforms = attr.uniforms||[]; 418 } 419 420 Shader.prototype = { 421 active:function(){ 422 var that = this; 423 var renderer = that.renderer; 424 var gl = that.gl; 425 var program = that.program; 426 427 if(program && gl){ 428 renderer.activeShader = that; 429 gl.useProgram(program); 430 that.attributes.forEach(function(attribute){ 431 renderer[attribute] = gl.getAttribLocation(program, attribute); 432 gl.enableVertexAttribArray(renderer[attribute]); 433 }); 434 435 that.uniforms.forEach(function(uniform){ 436 renderer[uniform] = gl.getUniformLocation(program, uniform); 437 }); 438 439 if(that.width !== renderer.width || that.height !== renderer.height){ 440 that.width = renderer.width; 441 that.height = renderer.height; 442 renderer._uploadProjectionTransform(); 443 } 444 } 445 }, 446 uploadTexture:function(image){ 447 var gl = this.gl; 448 var renderer = this.renderer; 449 if(_cacheTexture[image.src]){ 450 image.texture = _cacheTexture[image.src]; 451 } 452 else{ 453 var texture = gl.createTexture(); 454 var u_Sampler = renderer.u_Sampler; 455 456 457 gl.activeTexture(gl.TEXTURE0); 458 gl.bindTexture(gl.TEXTURE_2D, texture); 459 460 // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); 461 gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); 462 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 463 464 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 465 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 466 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 467 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 468 gl.uniform1i(u_Sampler, 0); 469 gl.bindTexture(gl.TEXTURE_2D, null); 470 471 image.texture = texture; 472 _cacheTexture[image.src] = texture; 473 } 474 }, 475 _createProgram:function(gl, vshader, fshader){ 476 var vertexShader = this._createShader(gl, gl.VERTEX_SHADER, vshader); 477 var fragmentShader = this._createShader(gl, gl.FRAGMENT_SHADER, fshader); 478 if (!vertexShader || !fragmentShader) { 479 return null; 480 } 481 482 var program = gl.createProgram(); 483 if (program) { 484 gl.attachShader(program, vertexShader); 485 gl.attachShader(program, fragmentShader); 486 487 gl.linkProgram(program); 488 489 gl.deleteShader(fragmentShader); 490 gl.deleteShader(vertexShader); 491 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); 492 if (!linked) { 493 var error = gl.getProgramInfoLog(program); 494 console.log('Failed to link program: ' + error); 495 gl.deleteProgram(program); 496 return null; 497 } 498 } 499 return program; 500 }, 501 _createShader:function(gl, type, source){ 502 var shader = gl.createShader(type); 503 if(shader){ 504 gl.shaderSource(shader, source); 505 gl.compileShader(shader); 506 507 var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 508 if (!compiled) { 509 var error = gl.getShaderInfoLog(shader); 510 console.log('Failed to compile shader: ' + error); 511 gl.deleteShader(shader); 512 return null; 513 } 514 } 515 return shader; 516 } 517 }; 518 519 WebGLRenderer.isSupported = (function(){ 520 var canvas = document.createElement('canvas'); 521 if(canvas.getContext && (canvas.getContext('webgl')||canvas.getContext('experimental-webgl'))){ 522 return true; 523 } 524 else{ 525 return false; 526 } 527 })();