3D网页小实验-WebGL2中的TransformFeedback
OpenGL4.x中,TransformFeedback用来分流顶点着色器或几何着色器的输出,并可以将它返回计算机的内存做一些操作。如此,我们就可以使用显卡的并行计算能力来进行一些计算加速,比如深度学习或区块链。这里记录如何使用WebGL2在浏览器中使用TransformFeedback。
1、原生TransformFeedback的基本流程:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>原生WebGL例子-反写内存</title> 6 <style> 7 html, body { 8 overflow: hidden; 9 width: 100%; 10 height: 100%; 11 margin: 0; 12 padding: 0; 13 } 14 #renderCanvas { 15 width: 100%; 16 height: 100%; 17 touch-action: none; 18 } 19 </style> 20 </head> 21 <body> 22 <canvas id="renderCanvas" touch-action="none"></canvas> 23 </body> 24 <script> 25 window.onload=webGLStart; 26 var canvas,gl; 27 28 function webGLStart() 29 { 30 canvas = document.getElementById("renderCanvas");//canvas是html5下的绘图标签,可以支持3D绘图 31 gl=initGL(canvas);//初始化“绘制上下文”,以后的绘制都要通过它进行 32 var vertexShaderSrc="#version 300 es\n" +//这个版本标志必须有!这里必须有换行符 33 "precision highp float;" +//如果你要在程序中书写浮点数,则这个精度设置是必须的,但这里其实不需要 34 "in float inValue;\n" +//这个换行符不是必须的,输入值《-注意!浏览器端输入的数据单元可能是一个浮点数或2到4元向量,但glsl只会按这里设置的数据类型处理输入,比如输入3元向量型的数据,但这里只取向量的第一个分量处理。 35 "out float outValue;\n" +//一方面输出到片元着色器,一方面输出到TransformFeedback 36 "void main()\n" + 37 "{\n" + 38 " outValue=sqrt(inValue);\n" +//取平方根 39 "}\n" 40 var vertexShader=gl.createShader(gl.VERTEX_SHADER);//建立顶点着色器 41 gl.shaderSource(vertexShader, vertexShaderSrc); 42 gl.compileShader(vertexShader); 43 //先不建立片元着色器 44 var shaderProgram = gl.createProgram();//建立着色器程序对象, 45 gl.attachShader(shaderProgram, vertexShader); 46 //注意!对于OpenGL和OpenGL ES来说,允许只建立顶点着色器,然后链接即可计算,但WebGL必须有片元着色器才可链接 47 48 var fragmentShaderSrc="#version 300 es\n" + 49 "precision highp float;\n" + 50 "precision highp int;" + 51 52 "out vec4 outValue2;" +//这个光栅化后输出到屏幕,和TransformFeedback没关系 53 "void main(){" + 54 " outValue2=vec4(1.0,0.5,0.5,1.0);" + 55 "}" 56 var fragmentShader=gl.createShader(gl.FRAGMENT_SHADER); 57 gl.shaderSource(fragmentShader, fragmentShaderSrc); 58 gl.compileShader(fragmentShader); 59 gl.attachShader(shaderProgram, fragmentShader); 60 61 //也先不连接程序 62 gl.transformFeedbackVaryings(shaderProgram,["outValue"], gl.SEPARATE_ATTRIBS);//gl.SEPARATE_ATTRIBS,//gl.INTERLEAVED_ATTRIBS 63 //console.log(activeInfo);//上一行设置“变换反馈”与顶点着色器中的哪个out变量关联,这里只有一个 64 65 gl.linkProgram(shaderProgram); 66 console.log(gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)); 67 console.log(gl.getShaderInfoLog(vertexShader)); 68 console.log(gl.getShaderInfoLog(fragmentShader)); 69 console.log(gl.getProgramInfoLog(shaderProgram));//The program must contain objects to form both a vertex and fragment shader. 70 71 72 var verts = [2,0,0,4,0,0,6,0,0];//准备一点数据 73 var vertexBuffer=gl.createBuffer(); 74 gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer); 75 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);//准备浏览器端的缓存 76 var inputAttrib =gl.getAttribLocation(shaderProgram, "inValue");//找到显卡中变量的位置(此时尚未传递数据到显卡) 77 78 79 80 var transformFeedbackBuffer=gl.createBuffer();//用于读取变换反馈结果的缓存 81 gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER,transformFeedbackBuffer); 82 gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, 3* Float32Array.BYTES_PER_ELEMENT, gl.STREAM_READ);//STREAM_READ,DYNAMIC_READ,STATIC_READ,静态则只读一次? 83 84 var transformFeedback=gl.createTransformFeedback();//建立变换反馈对象 85 gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback); 86 gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, transformFeedbackBuffer);//把变换反馈对象和缓存关联起来 87 88 gl.useProgram(shaderProgram);//启用链接好的着色器程序 89 gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer); 90 gl.enableVertexAttribArray(inputAttrib); 91 gl.vertexAttribPointer(inputAttrib,3,gl.FLOAT,false,0,0);//3是vertSize,这里3个数一组的发送到显卡 92 93 gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback); 94 gl.beginTransformFeedback(gl.POINTS);//这两个必须一样 95 gl.drawArrays(gl.POINTS,0,3);//TRIANGLE_STRIP,TRIANGLES 96 //var activeInfo = gl.getTransformFeedbackVarying(shaderProgram, 0); 97 //console.log(activeInfo); 98 gl.endTransformFeedback(); 99 //读取时控制台出现警告:performance warning: READ-usage buffer was read back without waiting on a fence. This caused a graphics pipeline stall. 100 //但是添加了延时后仍然有警告,不知是什么原因 101 102 // requestAnimFrame( 103 // function(){ 104 // gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER,transformFeedbackBuffer); 105 // //var arr=new Float32Array(3); 106 // var arrBuffer = new ArrayBuffer(3 * Float32Array.BYTES_PER_ELEMENT); 107 // var arr=new Float32Array(arrBuffer); 108 // gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER,0,arr) 109 // console.log(arr); 110 // //console.log(arr.values()); 111 // } 112 // ) 113 //gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER,transformFeedbackBuffer); 114 setTimeout(function(){ 115 116 //var arr=new Float32Array(3); 117 var arrBuffer = new ArrayBuffer(3 * Float32Array.BYTES_PER_ELEMENT); 118 var arr=new Float32Array(arrBuffer); 119 gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER,0,arr)//ARRAY_BUFFER,TRANSFORM_FEEDBACK_BUFFER 120 console.log(arr);//读取到了变换反馈信息。 121 //console.log(arr.values()); 122 },1000) 123 124 //var activeInfo = gl.getTransformFeedbackVarying(shaderProgram, 0); 125 //console.log(activeInfo); 126 } 127 function initGL(canvas){ 128 gl; 129 try 130 { 131 gl = canvas.getContext("webgl2",{antialias:true});//从canvas中获取webgl上下文 132 //https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/getContext 133 gl.viewport(0,0,canvas.width,canvas.height);//设置视口 134 } 135 catch(e) 136 { 137 var msg="Error creating WebGL Context!: "+ e.toString(); 138 alert(msg); //弹出错误信息 139 } 140 return gl; 141 } 142 window.requestAnimFrame = (function() { 143 return window.requestAnimationFrame || 144 window.webkitRequestAnimationFrame || 145 window.mozRequestAnimationFrame || 146 window.oRequestAnimationFrame || 147 window.msRequestAnimationFrame || 148 function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { 149 window.setTimeout(callback, 1000/60); 150 }; 151 })(); 152 </script> 153 </html>
在js的语法里,我们就是飞行在天空的上帝,我们可以俯瞰大地上的所有角落,并随时降临在任意位置;但在操作显卡时,我们是轨道上的宇航员,我们要在移动到特定坐标时才能看到相应位置(bind),并且要在做好充足准备后(buffer)才能在有限的区域登陆。
2、Babylon.js训练场中的一个略微复杂一些的例子:
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> 5 <title>01Babylon里的的原生例子</title> 6 <style> 7 html, body { 8 overflow: hidden; 9 width: 100%; 10 height: 100%; 11 margin: 0; 12 padding: 0; 13 } 14 #renderCanvas { 15 width: 100%; 16 height: 100%; 17 touch-action: none; 18 } 19 </style> 20 <script src="../../JS/LIB/babylon50.min.js"></script> 21 <!--<script src="../../JS/LIB/recast.js"></script><!–基于wasm编译而来的导航库–>--> 22 <!--<script src="../../JS/LIB/dat.gui.min.js"></script>--> 23 </head> 24 <body> 25 <canvas id="renderCanvas" touch-action="none"></canvas> <!-- touch-action="none" for best results from PEP --> 26 <script> 27 const canvas = document.getElementById("renderCanvas"); // Get the canvas element 获取画布标签 28 const engine = new BABYLON.Engine(canvas, true, 29 { preserveDrawingBuffer: true, stencil: true, disableWebGL2Support: false}); // Generate the BABYLON 3D engine 建立BABYLON 3D引擎 30 //var navmeshdebug; 31 32 const createScene = () => { 33 // This creates a basic Babylon Scene object (non-mesh) 34 var scene = new BABYLON.Scene(engine); 35 var babylonE = scene.getEngine();//未使用 36 37 // This creates and positions a free camera (non-mesh) 38 var camera = new BABYLON.ArcRotateCamera("camera", BABYLON.Tools.ToRadians(90), BABYLON.Tools.ToRadians(65), 2000, BABYLON.Vector3.Zero(), scene); 39 //var camera = new BABYLON.FreeCamera("camera", BABYLON.Vector3.Zero(), scene); 40 // This targets the camera to scene origin 41 camera.setTarget(BABYLON.Vector3.Zero()); 42 camera.maxZ = 1000000;//因为操作的是同一个gl,所以这个相机也能生效! 43 44 // This attaches the camera to the canvas 45 camera.attachControl(canvas, true); 46 47 // This creates a light, aiming 0,1,0 - to the sky (non-mesh) 48 var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene); 49 50 // Default intensity is 1. Let's dim the light a small amount 51 light.intensity = 0.7;//光照也生效 52 53 54 var time = 0; 55 56 // Our built-in 'ground' shape. 57 var ground = BABYLON.MeshBuilder.CreateGround("ground", { 58 width: 1000, 59 height: 1000, 60 subdivisions: 500 61 }, scene 62 );//建立了一个“地面网格”,它有很多个顶点 63 let data = ground.getVertexBuffer(BABYLON.VertexBuffer.PositionKind).getData();//取出顶点数据 64 65 var newData = new Float32Array(data.length);//把顶点数据放在一个缓存对象里 66 67 const gl = canvas.getContext('webgl2') || canvas.getContext('experimental-webgl2'); 68 console.log(gl);//取原生gl对象 69 setupCustomGLProgram(gl, new Float32Array(data), newData);//使用显卡并行处理大量顶点,应该会比cpu更快 70 71 console.log(newData); 72 73 ground.setVerticesData(BABYLON.VertexBuffer.PositionKind, newData);//把处理完的顶点放回地面网格里 74 ground.createNormals(false); 75 76 77 78 scene.registerBeforeRender(function(){ 79 time += 0.1;//也可以让顶点们随时间变化!! 80 }); 81 82 return scene; 83 } 84 85 86 87 function setupCustomGLProgram(gl, dataIn, dataOut)//自定义gpu计算 88 { 89 const VERTEX_COUNT = dataIn.length; 90 91 gl.enable(gl.RASTERIZER_DISCARD);//这个有什么用?《-让这个gpu计算中的draw不生效!只用Babylon.js的! 92 //使用GL_RASTERIZER_DISCARD标志作为参数调用glEnable()函数,告诉渲染管线在transform feedback可选阶段之后和到达光栅器前抛弃所有的图元。 93 const program = gl.createProgram();//和上个例子类似的操作 94 gl.attachShader(program, getShader(gl, voronoiVertex, gl.VERTEX_SHADER)); 95 gl.attachShader(program, getShader(gl, voronoiFragment, gl.FRAGMENT_SHADER)); 96 gl.transformFeedbackVaryings(program, ['outPosition'], gl.SEPARATE_ATTRIBS); 97 98 gl.linkProgram(program); 99 console.log('program:', gl.getProgramInfoLog(program)); 100 gl.useProgram(program); 101 102 103 //buffers 104 //input 105 let inputBuffer = gl.createBuffer(); 106 gl.bindBuffer(gl.ARRAY_BUFFER, inputBuffer); 107 gl.bufferData(gl.ARRAY_BUFFER, dataIn, gl.STATIC_DRAW); 108 109 //output 110 let resultBuffer = gl.createBuffer(); 111 112 // Create a TransformFeedback object 113 var transformFeedback = gl.createTransformFeedback(); 114 gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback); 115 116 gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, resultBuffer); 117 gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, VERTEX_COUNT * Float32Array.BYTES_PER_ELEMENT, gl.STATIC_DRAW); 118 119 120 // Attribute position 121 const inputAttribLocation = gl.getAttribLocation(program, 'position'); 122 123 gl.enableVertexAttribArray(inputAttribLocation); 124 gl.bindBuffer(gl.ARRAY_BUFFER, inputBuffer); 125 gl.vertexAttribPointer( 126 inputAttribLocation, // index 127 3, // size 128 gl.FLOAT, // type 129 gl.FALSE, // normalized? 130 0, // stride 131 0 // offset 132 ); 133 134 135 // Activate the transform feedback 136 gl.beginTransformFeedback(gl.POINTS); 137 gl.drawArrays(gl.POINTS, 0, Math.floor(VERTEX_COUNT/3)); 138 gl.endTransformFeedback(); 139 140 141 // Read back 142 gl.getBufferSubData( 143 gl.TRANSFORM_FEEDBACK_BUFFER, // target 144 0, // srcByteOffset 145 dataOut, // dstData 146 ); 147 148 gl.disable(gl.RASTERIZER_DISCARD); 149 } 150 151 152 153 function getShader(gl, source, type){ 154 let sh = gl.createShader(type); 155 gl.shaderSource(sh, source); 156 gl.compileShader(sh); 157 console.log('Shader:', gl.getShaderInfoLog(sh)); 158 return sh; 159 } 160 161 162 163 164 165 166 167 //用模板字符串,更好换行 168 const voronoiVertex = 169 `#version 300 es 170 precision highp float; 171 172 173 174 175 in vec3 position; 176 out vec3 outPosition; 177 178 void main(void) { 179 vec3 finalPos = position; 180 181 182 183 outPosition = finalPos; 184 if(finalPos[0]<0.0) 185 { 186 outPosition[1]=10.0; 187 } 188 } 189 `; 190 191 192 const voronoiFragment = 193 `#version 300 es 194 precision highp float;//这一句可省 195 196 void main(void) { 197 198 } 199 ` 200 201 202 203 204 BABYLON.Effect.ShadersStore["customVertexShader"] = voronoiVertex 205 206 BABYLON.Effect.ShadersStore["customFragmentShader"] = voronoiFragment; 207 208 // Add your code here matching the playground format 按照训练场的格式添加你自己的代码 209 const scene = createScene(); //Call the createScene function 调用createScene方法 210 211 scene.debugLayer.show(); 212 // Register a render loop to repeatedly render the scene 注册一个渲染循环,来重复地渲染场景 213 engine.runRenderLoop(function () { 214 scene.render(); 215 }); 216 // Watch for browser/canvas resize events 监听浏览器或画布的尺寸改变事件 217 window.addEventListener("resize", function () { 218 engine.resize(); 219 }); 220 221 </script> 222 </body> 223 </html>
以上就是一个通过并行计算提高计算效率的例子。