正如《WebGL 编程指南》中所说的:
传统的三维图形程序通常使用 C 或 C++ 等语言开发,并为特定的平台被编译成二进制的可执行文件。这意味着程序不能跨平台运行。相比之下,WebGL 程序由 HTML 和 JavaScript 组成,只需要放在 Web 上即可执行。WebGL 由 OpenGL ES 衍生而来。
OpenGL 是底层的驱动级的图形接口,但是这种底层接口是浏览器中的 JavaScript 无法调用的。2010 年 WebGL 被推出来了之后,它允许工程师使用 JavaScript 去调用部分封装过的 OpenGL ES2.0 标准接口去提供硬件级别的 3D 图形加速功能。所以 GLSL 程序真正运行的时候还是跑在 OpenGL 驱动上的。
一、DOM
介绍了背景之后,下面说下怎么在网页上跑起 shader 代码来,先看 DOM 怎么写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webgl</title>
<style>
body {margin: 0; overflow: hidden;}
</style>
</head>
<body onload="main()">
<canvas id="album" width="300" height="300">
Please use a browser that supports "canvas"
</canvas>
<script src="./js/lib/webgl-utils.js"></script>
<script src="./js/lib/webgl-debug.js"></script>
<script src="./js/lib/cuon-utils.js"></script>
<script src="./js/lib/cuon-matrix.js"></script>
<script src="./index.js"></script>
</body>
</html>
复制代码
Ok,简单的不能再简单的 DOM 结构了,整个页面只需要一个 <canvas>
的节点即可。Canvas 是 HTML5 提供的一个特性,你可以把它当做一个载体,简单的说就是一张白纸。而 Canvas 2D 相当于获取了内置的二维图形接口,也就是二维画笔。Canvas 3D 是获取基于 WebGL 的图形接口,相当于三维画笔(当然你也可以画二维的东西)。
其中还引用了这么几个文件,这几个文件是来自《WebGL 编程指南》里提供的工具库:
1
2
3
4
5
<script src="./js/lib/webgl-utils.js"></script>
<script src="./js/lib/webgl-debug.js"></script>
<script src="./js/lib/cuon-utils.js"></script>
<script src="./js/lib/cuon-matrix.js"></script>
复制代码
下面分别把他们的代码列出来,它们的作用就是封装代码、兼容版本、提供工具函数等,这里先不细究:
1
2
3
// webgl-utils.js
WebGLUtils=function(){var o=function(e,n){for(var t=["webgl","experimental-webgl","webkit-3d","moz-webgl"],i=null,o=0;o<t.length;++o){try{i=e.getContext(t[o],n)}catch(e){}if(i)break}return i};return{create3DContext:o,setupWebGL:function(e,n,t){t=t||function(e){var n=document.getElementsByTagName("body")[0];if(n){var t=window.WebGLRenderingContext?'It doesn't appear your computer can support WebGL.<br/><a href="http://get.webgl.org">Click here for more information.</a>':'This page requires a browser that supports WebGL.<br/><a href="http://get.webgl.org">Click here to upgrade your browser.</a>';e&&(t+="<br/><br/>Status: "+e),n.innerHTML='<div style="margin: auto; width:500px;z-index:10000;margin-top:20em;text-align:center;">'+t+"</div>"}},e.addEventListener&&e.addEventListener("webglcontextcreationerror",function(e){t(e.statusMessage)},!1);var i=o(e,n);return i||(window.WebGLRenderingContext,t("")),i}}}(),window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e,n){window.setTimeout(e,1e3/60)}),window.cancelAnimationFrame||(window.cancelAnimationFrame=window.cancelRequestAnimationFrame||window.webkitCancelAnimationFrame||window.webkitCancelRequestAnimationFrame||window.mozCancelAnimationFrame||window.mozCancelRequestAnimationFrame||window.msCancelAnimationFrame||window.msCancelRequestAnimationFrame||window.oCancelAnimationFrame||window.oCancelRequestAnimationFrame||window.clearTimeout);
复制代码
1
2
3
// webgl-debug.js
WebGLDebugUtils=function(){var a={enable:{0:!0},disable:{0:!0},getParameter:{0:!0},drawArrays:{0:!0},drawElements:{0:!0,2:!0},createShader:{0:!0},getShaderParameter:{1:!0},getProgramParameter:{1:!0},getVertexAttrib:{1:!0},vertexAttribPointer:{2:!0},bindTexture:{0:!0},activeTexture:{0:!0},getTexParameter:{0:!0,1:!0},texParameterf:{0:!0,1:!0},texParameteri:{0:!0,1:!0,2:!0},texImage2D:{0:!0,2:!0,6:!0,7:!0},texSubImage2D:{0:!0,6:!0,7:!0},copyTexImage2D:{0:!0,2:!0},copyTexSubImage2D:{0:!0},generateMipmap:{0:!0},bindBuffer:{0:!0},bufferData:{0:!0,2:!0},bufferSubData:{0:!0},getBufferParameter:{0:!0,1:!0},pixelStorei:{0:!0,1:!0},readPixels:{4:!0,5:!0},bindRenderbuffer:{0:!0},bindFramebuffer:{0:!0},checkFramebufferStatus:{0:!0},framebufferRenderbuffer:{0:!0,1:!0,2:!0},framebufferTexture2D:{0:!0,1:!0,2:!0},getFramebufferAttachmentParameter:{0:!0,1:!0,2:!0},getRenderbufferParameter:{0:!0,1:!0},renderbufferStorage:{0:!0,1:!0},clear:{0:!0},depthFunc:{0:!0},blendFunc:{0:!0,1:!0},blendFuncSeparate:{0:!0,1:!0,2:!0,3:!0},blendEquation:{0:!0},blendEquationSeparate:{0:!0,1:!0},stencilFunc:{0:!0},stencilFuncSeparate:{0:!0,1:!0},stencilMaskSeparate:{0:!0},stencilOp:{0:!0,1:!0,2:!0},stencilOpSeparate:{0:!0,1:!0,2:!0,3:!0},cullFace:{0:!0},frontFace:{0:!0}},r=null;function o(e){if(null==r)for(var t in r={},e)"number"==typeof e[t]&&(r[e[t]]=t)}function n(){if(null==r)throw"WebGLDebugUtils.init(ctx) not called"}function f(e){n();var t=r[e];return void 0!==t?t:"*UNKNOWN WebGL ENUM (0x"+e.toString(16)+")"}function u(e,t,r){var n=a[e];return void 0!==n&&n[t]?f(r):r.toString()}function S(e){var t=e.getParameter(e.MAX_VERTEX_ATTRIBS),r=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,r);for(var n=0;n<t;++n)e.disableVertexAttribArray(n),e.vertexAttribPointer(n,4,e.FLOAT,!1,0,0),e.vertexAttrib1f(n,0);e.deleteBuffer(r);var a=e.getParameter(e.MAX_TEXTURE_IMAGE_UNITS);for(n=0;n<a;++n)e.activeTexture(e.TEXTURE0+n),e.bindTexture(e.TEXTURE_CUBE_MAP,null),e.bindTexture(e.TEXTURE_2D,null);for(e.activeTexture(e.TEXTURE0),e.useProgram(null),e.bindBuffer(e.ARRAY_BUFFER,null),e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,null),e.bindFramebuffer(e.FRAMEBUFFER,null),e.bindRenderbuffer(e.RENDERBUFFER,null),e.disable(e.BLEND),e.disable(e.CULL_FACE),e.disable(e.DEPTH_TEST),e.disable(e.DITHER),e.disable(e.SCISSOR_TEST),e.blendColor(0,0,0,0),e.blendEquation(e.FUNC_ADD),e.blendFunc(e.ONE,e.ZERO),e.clearColor(0,0,0,0),e.clearDepth(1),e.clearStencil(-1),e.colorMask(!0,!0,!0,!0),e.cullFace(e.BACK),e.depthFunc(e.LESS),e.depthMask(!0),e.depthRange(0,1),e.frontFace(e.CCW),e.hint(e.GENERATE_MIPMAP_HINT,e.DONT_CARE),e.lineWidth(1),e.pixelStorei(e.PACK_ALIGNMENT,4),e.pixelStorei(e.UNPACK_ALIGNMENT,4),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!1),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),e.UNPACK_COLORSPACE_CONVERSION_WEBGL&&e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL,e.BROWSER_DEFAULT_WEBGL),e.polygonOffset(0,0),e.sampleCoverage(1,!1),e.scissor(0,0,e.canvas.width,e.canvas.height),e.stencilFunc(e.ALWAYS,0,4294967295),e.stencilMask(4294967295),e.stencilOp(e.KEEP,e.KEEP,e.KEEP),e.viewport(0,0,e.canvas.clientWidth,e.canvas.clientHeight),e.clear(e.COLOR_BUFFER_BIT|e.DEPTH_BUFFER_BIT|e.STENCIL_BUFFER_BIT);e.getError(););}return{init:o,mightBeEnum:function(e){return n(),void 0!==r[e]},glEnumToString:f,glFunctionArgToString:u,makeDebugContext:function(t,a){o(t),a=a||function(e,t,r){for(var n,a="",i=0;i<r.length;++i)a+=(0==i?"":", ")+u(t,i,r[i]);n="WebGL error "+f(e)+" in "+t+"("+a+")",window.console&&window.console.log&&window.console.log(n)};var i={};function e(r,n){return function(){var e=r[n].apply(r,arguments),t=r.getError();return 0!=t&&(i[t]=!0,a(t,n,arguments)),e}}var r={};for(var n in t)"function"==typeof t[n]?r[n]=e(t,n):r[n]=t[n];return r.getError=function(){for(var e in i)if(i[e])return i[e]=!1,e;return t.NO_ERROR},r},makeLostContextSimulatingContext:function(r){var e={},a=1,n=!1,i=[],t=void 0,o=void 0,f=void 0,u={};function c(e,t){var r=e[t];return function(){if(!n)return function(e){for(var t=0;t<e.length;++t){var r=e[t];if((n=r)instanceof WebGLBuffer||n instanceof WebGLFramebuffer||n instanceof WebGLProgram||n instanceof WebGLRenderbuffer||n instanceof WebGLShader||n instanceof WebGLTexture)return r.__webglDebugContextLostId__==a}var n;return!0}(arguments)?r.apply(e,arguments):void(u[e.INVALID_OPERATION]=!0)}}for(var l in r)"function"==typeof r[l]?e[l]=c(r,l):e[l]=r[l];function b(e){return{statusMessage:e}}e.loseContext=function(){if(!n){for(n=!0,++a;r.getError(););!function(){for(var e=Object.keys(u),t=0;t<e.length;++t)delete glErrorShdow_[e]}(),u[r.CONTEXT_LOST_WEBGL]=!0,setTimeout(function(){t&&t(b("context lost"))},0)}},e.restoreContext=function(){if(n){if(!o)throw"You can not restore the context without a listener";setTimeout(function(){if(function(){for(var e=0;e<i.length;++e){var t=i[e];t instanceof WebGLBuffer?r.deleteBuffer(t):t instanceof WebctxFramebuffer?r.deleteFramebuffer(t):t instanceof WebctxProgram?r.deleteProgram(t):t instanceof WebctxRenderbuffer?r.deleteRenderbuffer(t):t instanceof WebctxShader?r.deleteShader(t):t instanceof WebctxTexture&&r.deleteTexture(t)}}(),S(r),n=!1,o){var e=o;o=f,f=void 0,e(b("context restored"))}},0)}},e.getError=function(){if(!n)for(;e=r.getError();)u[e]=!0;for(var e in u)if(u[e])return delete u[e],e;return r.NO_ERROR};for(var s=["createBuffer","createFramebuffer","createProgram","createRenderbuffer","createShader","createTexture"],g=0;g<s.length;++g)e[A=s[g]]=function(t){return function(){if(n)return null;var e=t.apply(r,arguments);return e.__webglDebugContextLostId__=a,i.push(e),e}}(r[A]);var E=["getActiveAttrib","getActiveUniform","getBufferParameter","getContextAttributes","getAttachedShaders","getFramebufferAttachmentParameter","getParameter","getProgramParameter","getProgramInfoLog","getRenderbufferParameter","getShaderParameter","getShaderInfoLog","getShaderSource","getTexParameter","getUniform","getUniformLocation","getVertexAttrib"];for(g=0;g<E.length;++g)e[A=E[g]]=function(e){return function(){return n?null:e.apply(r,arguments)}}(e[A]);var d,m,T,R=["isBuffer","isEnabled","isFramebuffer","isProgram","isRenderbuffer","isShader","isTexture"];for(g=0;g<R.length;++g){var A;e[A=R[g]]=function(e){return function(){return!n&&e.apply(r,arguments)}}(e[A])}function x(t){return"function"==typeof t?t:function(e){t.handleEvent(e)}}return e.checkFramebufferStatus=(d=e.checkFramebufferStatus,function(){return n?r.FRAMEBUFFER_UNSUPPORTED:d.apply(r,arguments)}),e.getAttribLocation=(m=e.getAttribLocation,function(){return n?-1:m.apply(r,arguments)}),e.getVertexAttribOffset=(T=e.getVertexAttribOffset,function(){return n?0:T.apply(r,arguments)}),e.isContextLost=function(){return n},e.registerOnContextLostListener=function(e){t=x(e)},e.registerOnContextRestoredListener=function(e){n?f=x(e):o=x(e)},e},resetToInitialState:S}}();
复制代码
1
2
3
// cuon-utils.js
function initShaders(e,r,a){var t=createProgram(e,r,a);return t?(e.useProgram(t),e.program=t,!0):(console.log("Failed to create program"),!1)}function createProgram(e,r,a){var t=loadShader(e,e.VERTEX_SHADER,r),o=loadShader(e,e.FRAGMENT_SHADER,a);if(!t||!o)return null;var l=e.createProgram();if(!l)return null;if(e.attachShader(l,t),e.attachShader(l,o),e.linkProgram(l),!e.getProgramParameter(l,e.LINK_STATUS)){var n=e.getProgramInfoLog(l);return console.log("Failed to link program: "+n),e.deleteProgram(l),e.deleteShader(o),e.deleteShader(t),null}return l}function loadShader(e,r,a){var t=e.createShader(r);if(null==t)return console.log("unable to create shader"),null;if(e.shaderSource(t,a),e.compileShader(t),!e.getShaderParameter(t,e.COMPILE_STATUS)){var o=e.getShaderInfoLog(t);return console.log("Failed to compile shader: "+o),e.deleteShader(t),null}return t}function getWebGLContext(e,r){var a=WebGLUtils.setupWebGL(e);return a?((arguments.length<2||r)&&(a=WebGLDebugUtils.makeDebugContext(a)),a):null}
复制代码
1
2
3
// cuon-matrix.js
var Matrix4=function(t){var e,r,n;if(t&&"object"==typeof t&&t.hasOwnProperty("elements")){for(r=t.elements,n=new Float32Array(16),e=0;e<16;++e)n[e]=r[e];this.elements=n}else this.elements=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])};Matrix4.prototype.setIdentity=function(){var t=this.elements;return t[0]=1,t[4]=0,t[8]=0,t[12]=0,t[1]=0,t[5]=1,t[9]=0,t[13]=0,t[2]=0,t[6]=0,t[10]=1,t[14]=0,t[3]=0,t[7]=0,t[11]=0,t[15]=1,this},Matrix4.prototype.set=function(t){var e,r,n;if((r=t.elements)!==(n=this.elements)){for(e=0;e<16;++e)n[e]=r[e];return this}},Matrix4.prototype.concat=function(t){var e,r,n,o,i,s,a,u;if(r=this.elements,n=this.elements,r===(o=t.elements))for(o=new Float32Array(16),e=0;e<16;++e)o[e]=r[e];for(e=0;e<4;e++)i=n[e],s=n[e+4],a=n[e+8],u=n[e+12],r[e]=i*o[0]+s*o[1]+a*o[2]+u*o[3],r[e+4]=i*o[4]+s*o[5]+a*o[6]+u*o[7],r[e+8]=i*o[8]+s*o[9]+a*o[10]+u*o[11],r[e+12]=i*o[12]+s*o[13]+a*o[14]+u*o[15];return this},Matrix4.prototype.multiply=Matrix4.prototype.concat,Matrix4.prototype.multiplyVector3=function(t){var e=this.elements,r=t.elements,n=new Vector3,o=n.elements;return o[0]=r[0]*e[0]+r[1]*e[4]+r[2]*e[8]+e[12],o[1]=r[0]*e[1]+r[1]*e[5]+r[2]*e[9]+e[13],o[2]=r[0]*e[2]+r[1]*e[6]+r[2]*e[10]+e[14],n},Matrix4.prototype.multiplyVector4=function(t){var e=this.elements,r=t.elements,n=new Vector4,o=n.elements;return o[0]=r[0]*e[0]+r[1]*e[4]+r[2]*e[8]+r[3]*e[12],o[1]=r[0]*e[1]+r[1]*e[5]+r[2]*e[9]+r[3]*e[13],o[2]=r[0]*e[2]+r[1]*e[6]+r[2]*e[10]+r[3]*e[14],o[3]=r[0]*e[3]+r[1]*e[7]+r[2]*e[11]+r[3]*e[15],n},Matrix4.prototype.transpose=function(){var t,e;return e=(t=this.elements)[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this},Matrix4.prototype.setInverseOf=function(t){var e,r,n,o,i;if(r=t.elements,n=this.elements,(o=new Float32Array(16))[0]=r[5]*r[10]*r[15]-r[5]*r[11]*r[14]-r[9]*r[6]*r[15]+r[9]*r[7]*r[14]+r[13]*r[6]*r[11]-r[13]*r[7]*r[10],o[4]=-r[4]*r[10]*r[15]+r[4]*r[11]*r[14]+r[8]*r[6]*r[15]-r[8]*r[7]*r[14]-r[12]*r[6]*r[11]+r[12]*r[7]*r[10],o[8]=r[4]*r[9]*r[15]-r[4]*r[11]*r[13]-r[8]*r[5]*r[15]+r[8]*r[7]*r[13]+r[12]*r[5]*r[11]-r[12]*r[7]*r[9],o[12]=-r[4]*r[9]*r[14]+r[4]*r[10]*r[13]+r[8]*r[5]*r[14]-r[8]*r[6]*r[13]-r[12]*r[5]*r[10]+r[12]*r[6]*r[9],o[1]=-r[1]*r[10]*r[15]+r[1]*r[11]*r[14]+r[9]*r[2]*r[15]-r[9]*r[3]*r[14]-r[13]*r[2]*r[11]+r[13]*r[3]*r[10],o[5]=r[0]*r[10]*r[15]-r[0]*r[11]*r[14]-r[8]*r[2]*r[15]+r[8]*r[3]*r[14]+r[12]*r[2]*r[11]-r[12]*r[3]*r[10],o[9]=-r[0]*r[9]*r[15]+r[0]*r[11]*r[13]+r[8]*r[1]*r[15]-r[8]*r[3]*r[13]-r[12]*r[1]*r[11]+r[12]*r[3]*r[9],o[13]=r[0]*r[9]*r[14]-r[0]*r[10]*r[13]-r[8]*r[1]*r[14]+r[8]*r[2]*r[13]+r[12]*r[1]*r[10]-r[12]*r[2]*r[9],o[2]=r[1]*r[6]*r[15]-r[1]*r[7]*r[14]-r[5]*r[2]*r[15]+r[5]*r[3]*r[14]+r[13]*r[2]*r[7]-r[13]*r[3]*r[6],o[6]=-r[0]*r[6]*r[15]+r[0]*r[7]*r[14]+r[4]*r[2]*r[15]-r[4]*r[3]*r[14]-r[12]*r[2]*r[7]+r[12]*r[3]*r[6],o[10]=r[0]*r[5]*r[15]-r[0]*r[7]*r[13]-r[4]*r[1]*r[15]+r[4]*r[3]*r[13]+r[12]*r[1]*r[7]-r[12]*r[3]*r[5],o[14]=-r[0]*r[5]*r[14]+r[0]*r[6]*r[13]+r[4]*r[1]*r[14]-r[4]*r[2]*r[13]-r[12]*r[1]*r[6]+r[12]*r[2]*r[5],o[3]=-r[1]*r[6]*r[11]+r[1]*r[7]*r[10]+r[5]*r[2]*r[11]-r[5]*r[3]*r[10]-r[9]*r[2]*r[7]+r[9]*r[3]*r[6],o[7]=r[0]*r[6]*r[11]-r[0]*r[7]*r[10]-r[4]*r[2]*r[11]+r[4]*r[3]*r[10]+r[8]*r[2]*r[7]-r[8]*r[3]*r[6],o[11]=-r[0]*r[5]*r[11]+r[0]*r[7]*r[9]+r[4]*r[1]*r[11]-r[4]*r[3]*r[9]-r[8]*r[1]*r[7]+r[8]*r[3]*r[5],o[15]=r[0]*r[5]*r[10]-r[0]*r[6]*r[9]-r[4]*r[1]*r[10]+r[4]*r[2]*r[9]+r[8]*r[1]*r[6]-r[8]*r[2]*r[5],0===(i=r[0]*o[0]+r[1]*o[4]+r[2]*o[8]+r[3]*o[12]))return this;for(i=1/i,e=0;e<16;e++)n[e]=o[e]*i;return this},Matrix4.prototype.invert=function(){return this.setInverseOf(this)},Matrix4.prototype.setOrtho=function(t,e,r,n,o,i){var s,a,u,h;if(t===e||r===n||o===i)throw"null frustum";return a=1/(e-t),u=1/(n-r),h=1/(i-o),(s=this.elements)[0]=2*a,s[1]=0,s[2]=0,s[3]=0,s[4]=0,s[5]=2*u,s[6]=0,s[7]=0,s[8]=0,s[9]=0,s[10]=-2*h,s[11]=0,s[12]=-(e+t)*a,s[13]=-(n+r)*u,s[14]=-(i+o)*h,s[15]=1,this},Matrix4.prototype.ortho=function(t,e,r,n,o,i){return this.concat((new Matrix4).setOrtho(t,e,r,n,o,i))},Matrix4.prototype.setFrustum=function(t,e,r,n,o,i){var s,a,u,h;if(t===e||n===r||o===i)throw"null frustum";if(o<=0)throw"near <= 0";if(i<=0)throw"far <= 0";return a=1/(e-t),u=1/(n-r),h=1/(i-o),(s=this.elements)[0]=2*o*a,s[1]=0,s[2]=0,s[3]=0,s[4]=0,s[5]=2*o*u,s[6]=0,s[7]=0,s[8]=(e+t)*a,s[9]=(n+r)*u,s[10]=-(i+o)*h,s[11]=-1,s[12]=0,s[13]=0,s[14]=-2*o*i*h,s[15]=0,this},Matrix4.prototype.frustum=function(t,e,r,n,o,i){return this.concat((new Matrix4).setFrustum(t,e,r,n,o,i))},Matrix4.prototype.setPerspective=function(t,e,r,n){var o,i,s,a;if(r===n||0===e)throw"null frustum";if(r<=0)throw"near <= 0";if(n<=0)throw"far <= 0";if(t=Math.PI*t/180/2,0===(s=Math.sin(t)))throw"null frustum";return i=1/(n-r),a=Math.cos(t)/s,(o=this.elements)[0]=a/e,o[1]=0,o[2]=0,o[3]=0,o[4]=0,o[5]=a,o[6]=0,o[7]=0,o[8]=0,o[9]=0,o[10]=-(n+r)*i,o[11]=-1,o[12]=0,o[13]=0,o[14]=-2*r*n*i,o[15]=0,this},Matrix4.prototype.perspective=function(t,e,r,n){return this.concat((new Matrix4).setPerspective(t,e,r,n))},Matrix4.prototype.setScale=function(t,e,r){var n=this.elements;return n[0]=t,n[4]=0,n[8]=0,n[12]=0,n[1]=0,n[5]=e,n[9]=0,n[13]=0,n[2]=0,n[6]=0,n[10]=r,n[14]=0,n[3]=0,n[7]=0,n[11]=0,n[15]=1,this},Matrix4.prototype.scale=function(t,e,r){var n=this.elements;return n[0]*=t,n[4]*=e,n[8]*=r,n[1]*=t,n[5]*=e,n[9]*=r,n[2]*=t,n[6]*=e,n[10]*=r,n[3]*=t,n[7]*=e,n[11]*=r,this},Matrix4.prototype.setTranslate=function(t,e,r){var n=this.elements;return n[0]=1,n[4]=0,n[8]=0,n[12]=t,n[1]=0,n[5]=1,n[9]=0,n[13]=e,n[2]=0,n[6]=0,n[10]=1,n[14]=r,n[3]=0,n[7]=0,n[11]=0,n[15]=1,this},Matrix4.prototype.translate=function(t,e,r){var n=this.elements;return n[12]+=n[0]*t+n[4]*e+n[8]*r,n[13]+=n[1]*t+n[5]*e+n[9]*r,n[14]+=n[2]*t+n[6]*e+n[10]*r,n[15]+=n[3]*t+n[7]*e+n[11]*r,this},Matrix4.prototype.setRotate=function(t,e,r,n){var o,i,s,a,u,h,p,c,l,f,m,M;return t=Math.PI*t/180,o=this.elements,i=Math.sin(t),s=Math.cos(t),0!==e&&0===r&&0===n?(e<0&&(i=-i),o[0]=1,o[4]=0,o[8]=0,o[12]=0,o[1]=0,o[5]=s,o[9]=-i,o[13]=0,o[2]=0,o[6]=i,o[10]=s,o[14]=0,o[3]=0,o[7]=0,o[11]=0):0===e&&0!==r&&0===n?(r<0&&(i=-i),o[0]=s,o[4]=0,o[8]=i,o[12]=0,o[1]=0,o[5]=1,o[9]=0,o[13]=0,o[2]=-i,o[6]=0,o[10]=s,o[14]=0,o[3]=0,o[7]=0,o[11]=0):0===e&&0===r&&0!==n?(n<0&&(i=-i),o[0]=s,o[4]=-i,o[8]=0,o[12]=0,o[1]=i,o[5]=s,o[9]=0,o[13]=0,o[2]=0,o[6]=0,o[10]=1,o[14]=0,o[3]=0,o[7]=0,o[11]=0):(1!==(a=Math.sqrt(e*e+r*r+n*n))&&(e*=u=1/a,r*=u,n*=u),h=1-s,p=e*r,c=r*n,l=n*e,f=e*i,m=r*i,M=n*i,o[0]=e*e*h+s,o[1]=p*h+M,o[2]=l*h-m,o[3]=0,o[4]=p*h-M,o[5]=r*r*h+s,o[6]=c*h+f,o[7]=0,o[8]=l*h+m,o[9]=c*h-f,o[10]=n*n*h+s,o[11]=0,o[12]=0,o[13]=0,o[14]=0),o[15]=1,this},Matrix4.prototype.rotate=function(t,e,r,n){return this.concat((new Matrix4).setRotate(t,e,r,n))},Matrix4.prototype.setLookAt=function(t,e,r,n,o,i,s,a,u){var h,p,c,l,f,m,M,y,x,v,w,A;return p=n-t,c=o-e,l=i-r,m=(c*=f=1/Math.sqrt(p*p+c*c+l*l))*u-(l*=f)*a,M=l*s-(p*=f)*u,y=p*a-c*s,v=(M*=x=1/Math.sqrt(m*m+M*M+y*y))*l-(y*=x)*c,w=y*p-(m*=x)*l,A=m*c-M*p,(h=this.elements)[0]=m,h[1]=v,h[2]=-p,h[3]=0,h[4]=M,h[5]=w,h[6]=-c,h[7]=0,h[8]=y,h[9]=A,h[10]=-l,h[11]=0,h[12]=0,h[13]=0,h[14]=0,h[15]=1,this.translate(-t,-e,-r)},Matrix4.prototype.lookAt=function(t,e,r,n,o,i,s,a,u){return this.concat((new Matrix4).setLookAt(t,e,r,n,o,i,s,a,u))},Matrix4.prototype.dropShadow=function(t,e){var r=new Matrix4,n=r.elements,o=t[0]*e[0]+t[1]*e[1]+t[2]*e[2]+t[3]*e[3];return n[0]=o-e[0]*t[0],n[1]=-e[1]*t[0],n[2]=-e[2]*t[0],n[3]=-e[3]*t[0],n[4]=-e[0]*t[1],n[5]=o-e[1]*t[1],n[6]=-e[2]*t[1],n[7]=-e[3]*t[1],n[8]=-e[0]*t[2],n[9]=-e[1]*t[2],n[10]=o-e[2]*t[2],n[11]=-e[3]*t[2],n[12]=-e[0]*t[3],n[13]=-e[1]*t[3],n[14]=-e[2]*t[3],n[15]=o-e[3]*t[3],this.concat(r)},Matrix4.prototype.dropShadowDirectionally=function(t,e,r,n,o,i,s,a,u){var h=n*t+o*e+i*r;return this.dropShadow([t,e,r,-h],[s,a,u,0])};var Vector3=function(t){var e=new Float32Array(3);t&&"object"==typeof t&&(e[0]=t[0],e[1]=t[1],e[2]=t[2]),this.elements=e};Vector3.prototype.normalize=function(){var t=this.elements,e=t[0],r=t[1],n=t[2],o=Math.sqrt(e*e+r*r+n*n);return o?1==o||(o=1/o,t[0]=e*o,t[1]=r*o,t[2]=n*o):(t[0]=0,t[1]=0,t[2]=0),this};var Vector4=function(t){var e=new Float32Array(4);t&&"object"==typeof t&&(e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3]),this.elements=e};
复制代码
二、JavaScript
接下来就是写 JS 了,是的就是这么快。接下来我们在 index.js 写进我们的代码,大家可以留意到 DOM 中绑定了一个事件<body onload="main()">
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// index.js
// 顶点着色器代码
var VSHADER_SOURCE =`
attribute vec4 a_Position;
varying vec2 uv;
void main() {
gl_Position = vec4(vec2(a_Position), 0.0, 1.0);
uv = vec2(0.5, 0.5) * (vec2(a_Position) + vec2(1.0, 1.0));
}
`
// 片元着色器代码
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
void main() {
gl_FragColor = vec4(0.,0.,0.,1.);
}
`;
function main() {
var canvas = document.getElementById('album');
// 这里的宽高按实际情况设置
canvas.width = 375;
canvas.height = 667;
// 获取 webgl 上下文(getWebGLContext 是前面引入的工具库预设的)
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// 初始化着色器(initShaders 是工具库定义的函数,传入上下文,顶点/片元着色器代码)
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// 设置顶点数据(initVertexBuffers 函数详见下面)
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
// 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制顶点(三个点决定一个矩形,四个点可以绘制两个三角形,组成为矩形,也就是我们的画布)
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
// 初始化顶点缓冲区
function initVertexBuffers(gl) {
// 顶点坐标(画布的四个点)
var verticesTexCoords = new Float32Array([
1.0, -1.0,
1.0, 1.0,
-1.0, 1.0,
-1.0, -1.0
]);
// 顶点数量(4个点决定一个矩形)
var n = 4;
// 创建顶点缓冲区
var vertexTexCoordBuffer = gl.createBuffer();
if (!vertexTexCoordBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// 绑定缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
// 获取数组每个元素的大小
var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
// 获取 a_Position 的存储位置并设置缓冲区
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 2, 0);
gl.enableVertexAttribArray(a_Position);
return n;
}
复制代码
有了这些代码之后,我们就可以通过修改片元着色器代码添加我们的效果了。
通过修改片元着色器代码(把颜色修改为红色),刷新后确实看到有所改变。那假设我们想要做动画呢?
很简单,我们在片元着色器代码中添加一个关于时间的变量:
1
2
3
4
5
6
7
8
9
10
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
uniform float time;
// 变化时间
void main() {
gl_FragColor = vec4(0.,0.,0.,1.);
}
`;
复制代码
然后在 main()
函数最后新增以下代码:
1
2
3
4
5
6
7
8
9
var t = 0;
var time = gl.getUniformLocation(gl.program, 'time');
if (time < 0) {
console.log('Failed to get the storage location of time');
return -1;
}
gl.uniform1f(time, .0);
render(gl, time, t);
复制代码
在 index.js 中新增一个render 函数:
1
2
3
4
5
6
7
8
9
10
function render(gl, time, t) {
t += 0.01;
gl.uniform1f(time, t);
// 每次渲染都把最新的时间传入片元着色器中
// 每次都需要清空画布再绘制
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
requestAnimationFrame(render.bind(this, gl, time, t));
}
复制代码
添加完了之后,我们修改一下片元着色器(引入时间变化),看看是否有效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
uniform float time;
// 变化时间
void main() {
float r = uv.x;
float g = uv.y;
float b = abs(sin(time));
gl_FragColor = vec4(r,g,b,1.);
}
`;
复制代码
PS:渐变录成 GIF 真是个灾难:
三、加载材质
当然我们不能一直跟像素打交道,视频或者图片都可以以贴图的形式放在画布中进行加工。下面讲讲如何把贴图展示在画布中。首先简单的预加载我们想要的贴图资源:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function main() {
var canvas = document.getElementById('album');
// 这里的宽高按实际情况设置
canvas.width = 375;
canvas.height = 667;
// 在这里把贴图资源预加载了
var imgList = [];
preload(imgList, [
'./images/img1.jpg',
'./images/img2.jpg'
], function() {
// 加载完成后再开始初始化 WebGL
// 获取 webgl 上下文
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// ...
})
}
// 图片预加载
function preload(imgList, arrayOfImages, callback) {
var sum = arrayOfImages.length;
var count = 0;
arrayOfImages.forEach(function(value){
var image = new Image()
image.src = value;
image.onload = function() {
imgList.push(image)
if (++count == sum) {
callback && callback()
}
}
})
}
复制代码
接下来就是初始化材质内容,在资源预加载的回调函数底部增加我们之前写好的 WebGL 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
function main() {
var canvas = document.getElementById('album');
// 这里的宽高按实际情况设置
canvas.width = 375;
canvas.height = 667;
// 在这里把贴图资源预加载了(这里加载两张图,方便做转场)
var imgList = [];
preload(imgList, [
'./images/img8.jpg',
'./images/img9.jpg'
], function() {
// ...
// 绘制顶点(这一步留到后面)
// gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
// 新增材质初始化函数
if (!initTextures(gl, imgList)) {
console.log('Failed to intialize the texture.');
return;
}
var t = 0;
var time = gl.getUniformLocation(gl.program, 'time');
if (time < 0) {
console.log('Failed to get the storage location of time');
return -1;
}
gl.uniform1f(time, .0);
render(gl, time, t);
})
}
// 初始化材质
function initTextures(gl, imgList) {
// 有两张图,所以创建两个材质
var texture0 = gl.createTexture();
var texture1 = gl.createTexture();
if (!texture0 && !texture1) {
console.log('Failed to create the texture object');
return false;
}
var u_Sampler0 = gl.getUniformLocation(gl.program, 'u_Sampler0');
if (!u_Sampler0) {
console.log('Failed to get the storage location of u_Sampler0');
return false;
}
var u_Sampler1 = gl.getUniformLocation(gl.program, 'u_Sampler1');
if (!u_Sampler1) {
console.log('Failed to get the storage location of u_Sampler1');
return false;
}
// 加载我们的材质并初始化(函数在下面有定义)
loadTexture(gl, texture0, u_Sampler0, imgList[0], 0);
loadTexture(gl, texture1, u_Sampler1, imgList[1], 1);
return true;
}
// 加载材质
function loadTexture(gl, texture, u_Sampler, image, index) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
gl.activeTexture(gl['TEXTURE'+index])
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(u_Sampler, index);
return true;
}
复制代码
如果这个时候执行代码你会发现报错,因为我们新增了两个变量u_Sampler0
和 u_Sampler1
用于存放两张材质图,所以需要在片元着色器中加上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 片元着色器代码
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
uniform float time;
// 变化时间
uniform sampler2D u_Sampler0;
uniform sampler2D u_Sampler1;
void main() {
// 同时使用内置的 mix() 函数做两张图的线性插值渐变效果
vec4 color = mix(texture2D(u_Sampler0, uv), texture2D(u_Sampler1, uv), abs(sin(time)));
gl_FragColor = vec4(color);
}
`;
复制代码
其实这就是普通的转场效果,我们还可以通过一些内置函数做更多转场效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 片元着色器代码
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
uniform float time;
// 变化时间
uniform sampler2D u_Sampler0;
uniform sampler2D u_Sampler1;
void main() {
// 同时使用内置的 mix() 函数做两张图的线性插值渐变效果
vec4 color = mix(texture2D(u_Sampler0, uv), texture2D(u_Sampler1, uv), abs(sin(time)));
gl_FragColor = vec4(color);
}
`;
复制代码
再来个复杂的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 片元着色器代码
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
uniform float time;
// 变化时间
uniform sampler2D u_Sampler0;
uniform sampler2D u_Sampler1;
void main() {
float m = smoothstep(-0.5, 0., uv.x - abs(sin(time))*1.5);
gl_FragColor = mix(texture2D(u_Sampler0, (uv - 0.5) * (1.0 - m) + 0.5), texture2D(u_Sampler1, (uv - 0.5) * m + 0.5), m);
}
`;
复制代码
再来个缩放的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 片元着色器代码
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
uniform float time;
// 变化时间
uniform sampler2D u_Sampler0;
uniform sampler2D u_Sampler1;
void main() {
st -= .5;
st *= 1.-smoothstep(0., 1., abs(sin(time)));
st += .5;
vec4 color = mix(texture2D(u_Sampler0, st), texture2D(u_Sampler1, uv), abs(sin(time)));
}
`;
复制代码
这里附上 indexjs 所有代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// 顶点着色器代码
var VSHADER_SOURCE =`
attribute vec4 a_Position;
varying vec2 uv;
void main() {
gl_Position = vec4(vec2(a_Position), 0.0, 1.0);
uv = vec2(0.5, 0.5) * (vec2(a_Position) + vec2(1.0, 1.0));
}
`
// 片元着色器代码
var FSHADER_SOURCE =`
precision mediump float;
varying vec2 uv;
uniform float time;
// 变化时间
uniform sampler2D u_Sampler0;
uniform sampler2D u_Sampler1;
void main() {
float m = smoothstep(-0.5, 0., uv.x - abs(sin(time))*1.5);
gl_FragColor = mix(texture2D(u_Sampler0, (uv - 0.5) * (1.0 - m) + 0.5), texture2D(u_Sampler1, (uv - 0.5) * m + 0.5), m);
}
`;
function main() {
var canvas = document.getElementById('album');
// 这里的宽高按实际情况设置
canvas.width = 375;
canvas.height = 667;
// 在这里把贴图资源预加载了
var imgList = [];
preload(imgList, [
'./images/img8.jpg',
'./images/img9.jpg'
], function() {
// 获取 webgl 上下文(getWebGLContext 是前面引入的工具库预设的)
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// 初始化着色器(initShaders 是工具库定义的函数,传入上下文,顶点/片元着色器代码)
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// 设置顶点数据(initVertexBuffers 函数详见下面)
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
// 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制顶点
// gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
// 设置贴图
if (!initTextures(gl, imgList)) {
console.log('Failed to intialize the texture.');
return;
}
var t = 0;
var time = gl.getUniformLocation(gl.program, 'time');
if (time < 0) {
console.log('Failed to get the storage location of time');
return -1;
}
gl.uniform1f(time, .0);
render(gl, time, t);
})
}
// 初始化顶点缓冲区
function initVertexBuffers(gl) {
// 顶点坐标
var verticesTexCoords = new Float32Array([
1.0, -1.0,
1.0, 1.0,
-1.0, 1.0,
-1.0, -1.0
]);
// 顶点数量
var n = 4;
// 创建顶点缓冲区
var vertexTexCoordBuffer = gl.createBuffer();
if (!vertexTexCoordBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// 绑定缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
// 获取数组每个元素的大小
var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
// 获取 a_Position 的存储位置并设置缓冲区
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 2, 0);
gl.enableVertexAttribArray(a_Position);
return n;
}
function render(gl, time, t) {
t += 0.01;
gl.uniform1f(time, t);
// 每次都需要清空画布再绘制
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
requestAnimationFrame(render.bind(this, gl, time, t));
}
// 图片预加载
function preload(imgList, arrayOfImages, callback) {
var sum = arrayOfImages.length;
var count = 0;
arrayOfImages.forEach(function(value){
var image = new Image()
image.src = value;
image.onload = function() {
imgList.push(image)
if (++count == sum) {
callback && callback()
}
}
})
}
// 初始化材质
function initTextures(gl, imgList) {
var texture0 = gl.createTexture();
var texture1 = gl.createTexture();
if (!texture0 && !texture1) {
console.log('Failed to create the texture object');
return false;
}
var u_Sampler0 = gl.getUniformLocation(gl.program, 'u_Sampler0');
if (!u_Sampler0) {
console.log('Failed to get the storage location of u_Sampler0');
return false;
}
var u_Sampler1 = gl.getUniformLocation(gl.program, 'u_Sampler1');
if (!u_Sampler1) {
console.log('Failed to get the storage location of u_Sampler1');
return false;
}
loadTexture(gl, texture0, u_Sampler0, imgList[0], 0);
loadTexture(gl, texture1, u_Sampler1, imgList[1], 1);
return true;
}
// 加载材质
function loadTexture(gl, texture, u_Sampler, image, index) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
gl.activeTexture(gl['TEXTURE'+index])
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(u_Sampler, index);
return true;
}
复制代码
最后
以上就是平常便当最近收集整理的关于WebGL Shader 环境搭建的全部内容,更多相关WebGL内容请搜索靠谱客的其他文章。
发表评论 取消回复