同一页面上的多个 WebGL 模型 [英] Multiple WebGL models on the same page
问题描述
我的研究实验室正在开发一个网页,该网页显示一长串可滚动的 3d 模型列表,大约有 50 个左右.我们的第一个想法是使用单独的 THREE.js WebGL 上下文来做到这一点,但鉴于 WebGL 的架构,这似乎是不可取的,而且浏览器似乎将页面上的上下文数量限制为大约 2^4.
我不需要这些上下文来做任何令人印象深刻的事情:单个几何图形只有几百个三角形,没有纹理,并且一次只有一个在使用鼠标旋转其相机时动画.
我可以说服 WebGL 以浏览器不会抱怨的方式做我想做的事吗?我想也许有一个单一的大几何体,我所有的模型都排成一排,并且单独的画布带有视口,每个画布只显示一个模型.但似乎不受支持.(在同一上下文中允许多个视图,但这对我来说不是很有用.)
感谢您的任何想法!
不清楚为什么您认为需要多个 webgl 上下文.我猜是因为你想要这样的列表
1.[img] 描述描述2. [img] 说明描述3.【图片】说明描述
或者什么?
一些想法
为屏幕制作一个足够大的画布,设置它的 CSS 使其不会随着页面的其余部分滚动.绘制与您想要滚动的任何其他 HTML 对齐的模型.
制作一个离屏 webgl 画布并使用 canvas2d 元素进行显示.
为每个模型渲染模型然后调用
someCanvas2DContextForElementN.drawImage(webGLcanvasElement, ...);
鉴于可能只有几个画布可见,您只需要更新那些画布.事实上,回收它们可能是个好主意.换句话说,与其制作 12000 个画布或 12000 个元素列表,不如让它们足以适应屏幕并在滚动时更新它们.
如果我的页面设计允许的话,我个人可能会选择#1.似乎有效,见下文.
<小时>事实证明这真的很容易.我只是拿了这个绘制 100 个对象的样本,让它一次绘制一个对象.
清屏后开启剪刀测试
gl.enable(gl.SCISSOR_TEST);
然后,对于每个对象
//获取作为我们想要位置的占位符的元素//绘制对象var viewElement = obj.viewElement;//获取它相对于页面视口的位置var rect = viewElement.getBoundingClientRect();//检查它是否在屏幕外.如果是这样跳过它if (rect.bottom < 0 || rect.top > gl.canvas.clientHeight ||rect.right <0 ||rect.left >gl.canvas.clientWidth) {返回;//它在屏幕外}//设置视口var 宽度 = rect.right - rect.left;var 高度 = rect.bottom - rect.top;var left = rect.left;var 底部 = gl.canvas.clientHeight - rect.bottom - 1;gl.viewport(左,底部,宽度,高度);gl.scissor(左、底、宽、高);
我不是 100% 确定是否需要将宽度和高度加 1.我想我应该查一下.
在任何情况下,我都会为每个渲染对象计算一个新的投影矩阵,只是为了使代码通用.占位符 div 的大小可以不同.
更新:
最初发布在这里的解决方案在画布上使用了 position: fixed
以防止其滚动.新的解决方案使用 position: absolute
并在像这样渲染之前更新变换
gl.canvas.style.transform = `translateY(${window.scrollY}px)`;
使用之前的解决方案,在匹配位置重新绘制的形状可能会滞后于滚动.使用新的解决方案,画布会滚动,直到我们有时间更新它.这意味着如果我们不能足够快地绘制,形状可能会丢失几帧,但它看起来比滚动不匹配要好得多.
以下示例是更新后的解决方案.
"use strict";//使用 twgl.js 因为我很懒twgl.setAttributePrefix("a_");var m4 = twgl.m4;var gl = twgl.getWebGLContext(document.getElementById("c"));//编译着色器,链接程序,查找位置var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);//为每个形状调用 gl.creatBuffer、gl.bindBuffer、gl.bufferData//用于位置、法线、texcoordsvar 形状 = [twgl.primitives.createCubeBufferInfo(gl, 2),twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12),twgl.primitives.createPlaneBufferInfo(gl, 2, 2),twgl.primitives.createTruncatedConeBufferInfo(gl, 1, 0, 2, 24, 1),twgl.primitives.createCresentBufferInfo(gl, 1, 1, 0.5, 0.1, 24),twgl.primitives.createCylinderBufferInfo(gl, 1, 2, 24, 2),twgl.primitives.createDiscBufferInfo(gl, 1, 24),twgl.primitives.createTorusBufferInfo(gl, 1, 0.4, 24, 12),];函数兰特(最小,最大){return min + Math.random() * (max - min);}//共同的价值观var lightWorldPosition = [1, 8, -10];var lightColor = [1, 1, 1, 1];var 相机 = m4.identity();var view = m4.identity();var viewProjection = m4.identity();var tex = twgl.createTexture(gl, {分钟:gl.NEAREST,mag:gl.NEAREST,源代码:[255, 255, 255, 255,192, 192, 192, 255,192, 192, 192, 255,255, 255, 255, 255,],});var randColor = 函数(){var color = [Math.random(), Math.random(), Math.random(), 1];颜色[Math.random() * 3 |0] = 1;//使至少 1 个明亮返回颜色;};var 对象 = [];var numObjects = 100;var list = document.getElementById("list");var listItemTemplate = document.getElementById("list-item-template").text;for (var ii = 0; ii < numObjects; ++ii) {var listElement = document.createElement("div");listElement.innerHTML = listItemTemplate;listElement.className = "list-item";var viewElement = listElement.querySelector(".view");var 制服 = {u_lightWorldPos:lightWorldPosition,u_lightColor: 光色,u_diffuseMult: randColor(),u_specular: [1, 1, 1, 1],u_shininess: 50,u_specularFactor: 1,u_diffuse: tex,u_viewInverse:相机,u_world: m4.identity(),u_worldInverseTranspose: m4.identity(),u_worldViewProjection: m4.identity(),};对象.push({ySpeed: rand(0.1, 0.3),zSpeed: rand(0.1, 0.3),制服:制服,视图元素:视图元素,程序信息:程序信息,缓冲区信息:形状 [ii % 形状.长度],});list.appendChild(listElement);}var showRenderingArea = false;函数渲染(时间){时间 *= 0.001;twgl.resizeCanvasToDisplaySize(gl.canvas);gl.canvas.style.transform = `translateY(${window.scrollY}px)`;gl.启用(gl.DEPTH_TEST);gl.disable(gl.SCISSOR_TEST);gl.clearColor(0, 0, 0, 0);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);gl.启用(gl.SCISOR_TEST);如果(showRenderingArea){gl.clearColor(0, 0, 1, 1);}var 眼睛 = [0, 0, -8];var 目标 = [0, 0, 0];变量向上 = [0, 1, 0];m4.lookAt(眼睛,目标,向上,相机);m4.inverse(相机,视图);objects.forEach(function(obj, ndx) {var viewElement = obj.viewElement;//获取viewElement的位置var rect = viewElement.getBoundingClientRect();if (rect.bottom < 0 || rect.top > gl.canvas.clientHeight ||rect.right <0 ||rect.left >gl.canvas.clientWidth) {返回;//它在屏幕外}var 宽度 = rect.right - rect.left;var 高度 = rect.bottom - rect.top;var left = rect.left;var 底部 = gl.canvas.clientHeight - rect.bottom - 1;gl.viewport(左,底部,宽度,高度);gl.scissor(左、底、宽、高);如果(showRenderingArea){gl.clear(gl.COLOR_BUFFER_BIT);}var 投影 = m4.perspective(30 * Math.PI/180, 宽/高, 0.5, 100);m4.multiply(投影,视图,视图投影);var uni = obj.uniforms;var world = uni.u_world;m4.identity(世界);m4.rotateY(世界,时间* obj.ySpeed,世界);m4.rotateZ(world, time * obj.zSpeed, world);m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose);m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection);gl.useProgram(obj.programInfo.program);//调用 gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointertwgl.setBuffersAndAttributes(gl, obj.programInfo, obj.bufferInfo);//调用 gl.bindTexture, gl.activeTexture, gl.uniformXXXtwgl.setUniforms(obj.programInfo, uni);//调用 gl.drawArrays 或 gl.drawElementstwgl.drawBufferInfo(gl, obj.bufferInfo);});}if (true) {//动画var renderContinuously = 函数(时间){渲染(时间);requestAnimationFrame(renderContinuously);}requestAnimationFrame(renderContinuously);} 别的 {var requestId;var renderRequest = 函数(时间){渲染(时间);requestId = 未定义;}//如果动画var queueRender = function() {如果(!requestId){requestId = requestAnimationFrame(renderRequest);}}window.addEventListener('resize', queueRender);window.addEventListener('scroll', queueRender);队列渲染();}
* {box-sizing: 边框框;-moz-box-sizing: 边框框;}身体 {字体系列:等宽;边距:0;}#C {位置:绝对;顶部:0;宽度:100vw;高度:100vh;}#外{宽度:100%;z-索引:2;位置:绝对;顶部:0px;}#内容 {保证金:自动;填充:2em;}#b {宽度:100%;文本对齐:居中;}.项目清单 {边框:1px纯黑色;边距:2em;填充:1em;宽度:200px;显示:内联块;}.list-item .view {宽度:100px;高度:100px;向左飘浮;边距:0 1em 1em 0;}.list-item .description {填充左:2em;}@media only screen and (max-width : 500px) {#内容 {宽度:100%;}.项目清单 {边距:0.5em;}.list-item .description {填充左:0em;}}
<script src="//twgljs.org/dist/4.x/twgl-full.min.js"><;/脚本><身体><canvas id="c"></canvas><div id="外层"><div id="内容"><div id="b">项目列表</div><div id="列表"></div>