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>&lt;!&ndash;基于wasm编译而来的导航库&ndash;&gt;-->
 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>

以上就是一个通过并行计算提高计算效率的例子。

posted @ 2021-08-16 17:09  ljzc002  阅读(465)  评论(0编辑  收藏  举报