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