正如《WebGL 编程指南》中所说的:

传统的三维图形程序通常使用 C 或 C++ 等语言开发,并为特定的平台被编译成二进制的可执行文件。这意味着程序不能跨平台运行。相比之下,WebGL 程序由 HTML 和 JavaScript 组成,只需要放在 Web 上即可执行。WebGL 由 OpenGL ES 衍生而来。

OpenGL 是底层的驱动级的图形接口,但是这种底层接口是浏览器中的 JavaScript 无法调用的。2010 年 WebGL 被推出来了之后,它允许工程师使用 JavaScript 去调用部分封装过的 OpenGL ES2.0 标准接口去提供硬件级别的 3D 图形加速功能。所以 GLSL 程序真正运行的时候还是跑在 OpenGL 驱动上的。

一、DOM

介绍了背景之后,下面说下怎么在网页上跑起 shader 代码来,先看 DOM 怎么写:

<!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 编程指南》里提供的工具库:

<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>
复制代码

下面分别把他们的代码列出来,它们的作用就是封装代码、兼容版本、提供工具函数等,这里先不细究:

// 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);
复制代码
// 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}}();
复制代码
// 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}
复制代码
// 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()">

// 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;
}
复制代码

有了这些代码之后,我们就可以通过修改片元着色器代码添加我们的效果了。

通过修改片元着色器代码(把颜色修改为红色),刷新后确实看到有所改变。那假设我们想要做动画呢?

很简单,我们在片元着色器代码中添加一个关于时间的变量:

var FSHADER_SOURCE =`precision mediump float;varying vec2 uv;uniform float time;     // 变化时间void main() {gl_FragColor = vec4(0.,0.,0.,1.);}
`;
复制代码

然后在 main() 函数最后新增以下代码:

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 函数:

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));
}
复制代码

添加完了之后,我们修改一下片元着色器(引入时间变化),看看是否有效果:

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 真是个灾难:

三、加载材质

当然我们不能一直跟像素打交道,视频或者图片都可以以贴图的形式放在画布中进行加工。下面讲讲如何把贴图展示在画布中。首先简单的预加载我们想要的贴图资源:

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 代码:

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_Sampler0u_Sampler1 用于存放两张材质图,所以需要在片元着色器中加上:

// 片元着色器代码
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);}
`;
复制代码

其实这就是普通的转场效果,我们还可以通过一些内置函数做更多转场效果:

// 片元着色器代码
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);}
`;
复制代码

再来个复杂的:

// 片元着色器代码
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);}
`;
复制代码

再来个缩放的:

// 片元着色器代码
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 所有代码:

// 顶点着色器代码
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 环境搭建相关推荐

  1. Mac VSCode OpenGL环境搭建

    Mac VSCode OpenGL环境搭建 1.安装glfw brew install glfw 2.下载glad https://glad.dav1d.de/ 选择合适的版本后 拷贝glad文件夹到 ...

  2. 使用webgl(three.js)搭建3D智慧园区、3D大屏,3D楼宇,智慧灯杆三维展示,3D灯杆,web版3D,bim管理系统——第六课...

    前言: 今年是建国70周年,爱国热情异常的高涨,为自己身在如此安全.蓬勃发展的国家深感自豪. 我们公司楼下为庆祝国庆,拉了这样的标语,每个人做好一件事,就组成了我们强大的祖国. 看到这句话,深有感触, ...

  3. 3D仿真教程:ThingJS全套环境搭建方案

    在现实世界中,所有物体都不是独立存在的,周围都存在相应环境,常见的环境元素有雨.电.风.光束等,都是3D仿真的主要对象.ThingJS提供了全套环境搭建方案. 如何控制环境与效果?ThingJS 提供 ...

  4. Linux下PhysX3.4编译和环境搭建

    编译 源码链接: https://github.com/NVIDIAGameWorks/PhysX-3.4 使用随附的 Makefile 构建源代码: Makefiles 在 /PhysX_3.4/S ...

  5. 使用webgl(three.js)搭建一个3D建筑,3D消防模拟——第三课

    使用webgl(three.js)搭建一个3D建筑,3D消防模拟--第三课 项目背景 消防安全一直是各大都市关注的重要课题,在消防体系中,特别是高楼消防体系中,消防系统整体布控与监控,火情有效准确定位 ...

  6. 初识 ThreeJS (ThreeJS 相关环境搭建)

    初识 ThreeJS (初识 ThreeJS (ThreeJS 相关环境搭建) 参考 描述 ThreeJS 在本地搭建 NodeJS 的官方网站 获取 使用 安装依赖项 运行 官方文档 案例 场景编辑 ...

  7. cesium编程入门(二)环境搭建

    环境搭建 环境搭建 编译 node 安装 Node.js安装包及源码下载地址为:https://nodejs.org/en/download/. 安装完成后,打开命令行,输入:node -v,如果结果 ...

  8. Anaconda3+python3.7.10+TensorFlow2.3.0+PyQt5环境搭建

    Anaconda3+python3.7.10+TensorFlow2.3.0+PyQt5环境搭建 一.Anaconda 创建 python3.7环境 1.进入 C:\Users\用户名 目录下,找到 ...

  9. Windows10+Anaconda3+Pycharm环境搭建

    Windows10+Anaconda3+Pycharm环境搭建 一.安装Anaconda3 1.进入 anconda官网,下载Anaconda3 2.安装,选项默认继续安装(可以自行更改安装位置),等 ...

最新文章

  1. bp神经网络_BP 神经网络驱动的手写体数字识别软件 EasyOCR
  2. 基于PyTorch框架的多层全连接神经网络实现MNIST手写数字分类
  3. 从HBase中移除WAL?3D XPoint技术带来的变革
  4. ajax div 赋值重新渲染_30分钟全面解析图解AJAX原理
  5. java终结器_Java的终结器仍然存在
  6. 工作136:eachrt
  7. magento开启模板路径提示
  8. 理解numpy数组中的各个维度代表的具体意思(axis)
  9. [转]static和final的区别
  10. 用c语言编写车牌识别系统,这算干货吗?写点车牌识别系统方案
  11. 区块链技术视频网站EthCast.com上线
  12. 华为悦盒E6108无线投屏-机顶盒连接wifi-我的盒子-DLNA-手机视频有TV标识-点击TV标识
  13. 优化GPU显存不足,提高GPU利用率
  14. 【知识星球】视频分类模型和数据集板块汇总介绍
  15. SSH Tunnel隧道
  16. Unity3D开发游戏有没有流行的框架
  17. 多传感器融合定位六-惯性导航原理及误差分析
  18. 捉泥鳅用计算机怎么按,看看冬天是怎么捉泥鳅的,吓傻了
  19. c盘空间不足,删除 wer/reportqueue目录文件
  20. 06_NetBean主类使用库项目类中的方法

热门文章

  1. 031_div和span
  2. mongo备份索引_【MongoDB学习之四】索引 聚合 备份与恢复 监控
  3. gulp+自动化编译html,gulp自动化构建html静态资源路径版本号添加和替换
  4. 傅里叶分析斯坦恩中文版pdf_实分析(英文版)[(美)斯坦恩(Stein,E.M.) 著] 2013年版...
  5. 与通用计算机相比 单片机具体有哪些特点,嵌入式系统-复习大纲_彭荣
  6. 基于Java的RDMA高性能通信库(二):Java Socket Over RDMA
  7. thinkjs-定时任务
  8. LDAP和Implementation
  9. Ubuntu 14.04 下 OF-Config安装
  10. jquery实现加载更多效果