Opengl es 在离散层中渲染,然后合成

Opengl es 在离散层中渲染,然后合成,opengl-es,webgl,three.js,Opengl Es,Webgl,Three.js,在我的项目中,我知道有3个谨慎的层 出身背景 中间地带 前景 这些层中的每一层都可能具有非常不同的单位比例和可能重叠的几何体。但我不想让它们呈现为占用相同的空间。我希望它们以逻辑层顺序渲染,而不是以3D空间的顺序渲染 例如,假设背景是一个半径为10个单位的窄管,我将其覆盖在相机上以实现隧道状效果。然后,我想在远离摄影机的场景中,放置一个大立方体作为前景,每侧100个单位 在此场景中,立方体和隧道相交并相互遮挡。我正在寻找一种方法来渲染整个隧道,然后渲染整个立方体,然后将渲染的立方体放在渲染隧道的

在我的项目中,我知道有3个谨慎的层

出身背景 中间地带 前景 这些层中的每一层都可能具有非常不同的单位比例和可能重叠的几何体。但我不想让它们呈现为占用相同的空间。我希望它们以逻辑层顺序渲染,而不是以3D空间的顺序渲染

例如,假设背景是一个半径为10个单位的窄管,我将其覆盖在相机上以实现隧道状效果。然后,我想在远离摄影机的场景中,放置一个大立方体作为前景,每侧100个单位

在此场景中,立方体和隧道相交并相互遮挡。我正在寻找一种方法来渲染整个隧道,然后渲染整个立方体,然后将渲染的立方体放在渲染隧道的顶部。我希望该立方体中的纹理/着色器中的任何alpha透明度都能清晰地合成,显示透明像素后面的渲染通道

因此:

我是在描述一种存在的技术或特征吗?如果是,那叫什么? WebGL能做到这一点吗? three.js可以这样做吗? 这会不会导致一次性渲染整个帧的性能大幅下降? 如何在three.js中构建我的渲染来设置它?
仍然是新的*GL图形编程,如果我的词汇不准确,很抱歉。学究式的词汇更正是非常感谢的,因为它将帮助我谷歌

我想在对这个例子进行了一些反向工程之后,我成功地做到了这一点:

基本上

renderer.autoClear = false;
然后,不是像这样渲染一个场景:

render: function() {
  renderer.render(scene, camera);
}
我在渲染多个场景之前手动清除:

render: function() {
  renderer.clear()
  renderer.render(background, camera);
  renderer.render(midground,  camera);
  renderer.render(foreground, camera);
}

但是,仍然不能完全确定性能影响

我想在对这个例子进行了一些反向工程之后,我成功地做到了这一点:

基本上

renderer.autoClear = false;
然后,不是像这样渲染一个场景:

render: function() {
  renderer.render(scene, camera);
}
我在渲染多个场景之前手动清除:

render: function() {
  renderer.clear()
  renderer.render(background, camera);
  renderer.render(midground,  camera);
  renderer.render(foreground, camera);
}

但是,仍然不能完全确定性能影响

首先,您可以渲染3个不同的画布,只需设置它们的z索引和位置,使它们重叠,浏览器就会合成它们

如果你想在一张画布上完成这一切,那么基本上你只需要在画完一些东西后清除深度缓冲区

drawStuffInBack();

// clear the depth (and stencil buffers)
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

drawStuffInMiddle();

// clear the depth (and stencil buffers)
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

drawStuffInFront();

首先,您可以渲染3个不同的画布,只需设置它们的z索引和位置,使它们重叠,浏览器将合成它们

如果你想在一张画布上完成这一切,那么基本上你只需要在画完一些东西后清除深度缓冲区

drawStuffInBack();

// clear the depth (and stencil buffers)
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

drawStuffInMiddle();

// clear the depth (and stencil buffers)
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

drawStuffInFront();

下面是一个工作示例,它有一个VisualYers类用于管理任意数量的层,并使用renderer.autoClear=false技术和清除深度技术,正如Alex Waynes和gman的回答所暗示的那样

这种方法很好,因为对象的renderOrder没有被修改,这是另一种方法,因此不会引入其他问题

尝试使用UI中的选项查看其功能:

//@ts支票 //////////////////////// //层系 //////////////////////// /**@typedef{{name:string,backingScene:THREE.Scene,order:number}层*/ 类可视层{ /** *@type{Array} *@私人 */ __层=[]; 建造师 /**@private@type{THREE.WebGLRenderer}*/\uu renderer, /**@private@type{typeof THREE.Scene}*/\u Scene=THREE.Scene { 这个.\uuuu渲染器=\uuuu渲染器; 这个._场景=_场景; } 定义层/**@type{string}*/name,/**@type{number=}*/order=0{ const layer=这个; //默认图层的顺序始终为0。 const previousOrder=layer.order; layer.order=name==默认值?0:顺序; //仅在订单更改时进行排序。 如果上一个订单!==layer.order 这个.uuu layers.sorta,b=>a.order-b.order; 返回层; } /** *按名称获取图层如果该图层不存在,则以默认顺序0创建该图层。 *@私人 */ __getLayer/**@type{string}*/name{ 让layer=this.\uu layers.findl=>l.name==name; 如果!层{ 层={name,backingScene:newthis.\u场景,顺序:0}; layer.backingScene.autoUpdate=false; 这个._layers.pushlayer; } 返回层; } removeLayer/**@type{string}*/name{ const index=这个。\uuu layers.findIndexl=>{ 如果l.name==name{ l、 backingScene.children.length=0; 返回true; } 返回false; }; 如果索引>=0,则为该层。拼接索引,1; } hasLayer/**@type{string}*/name{ 返回此值。uu layers.somel=>l.name===name; } /**@readonly*/ 获得分层计数{ 返回此项。\uuuu layers.length; } addObjectToLayer /**@type{THREE.Object3D}*/obj, /**@type{string | string[]}*/layers { 如果Array.isArraylayers{ 对于层的常量名称,请参见此。\uuu addObjectToLayerobj,名称; 回来 } 这是.u_addObjectToLayerobj,图层; } addObjectsToLayer /**@type{THREE.Object3D[]}*/objects, /**@type{string | string[]}*/layers { 用于对象的常量对象{ this.addObjectToLayerobj,层; } } /**@private@readonly*/ __emptyArray=Object.freeze[]; /**@私人*/ __addObjectToLayer /**@type{THREE.Object3D}*/obj, /**@type{string}*/name { const layer=这个; const proxy=Object.createobj{ 孩子:{get:=>这个。uu emptyArray} }; layer.backingScene.children.pushproxy; } removeObjectFromLayer /**@type{THREE.Object3D}*/obj, /**@type{string | string[]}*/nameOrNames { 如果Array.isArrayNames{ 对于名称或名称的常量名称{ const layer=this.\uu layers.findl=>l.name==name; 如果!层继续; 此项。从图层Robj、图层中删除对象; } 回来 } const layer=this.\uu layers.findl=>l.name===nameOrNames; 如果!层返回; 此项。从图层Robj、图层中删除对象; } /**@私人*/ __removeObjectFromLayer /**@type{THREE.Object3D}*/obj, /**@type{Layer}*/Layer { const children=layer.backingScene.children; 常量索引=children.findIndex proxy=>/**@type{any}*/proxy.\uuu proto\uuuu===obj ; 如果索引>=0{ children[index]=children[children.length-1]; 儿童流行音乐; } } removeObjectsFromAllLayers/**@type{THREE.Object3D[]}*/objects{ 用于此的常量层。\uu层{ 用于对象的常量对象{ 此项。从图层Robj、图层中删除对象; } } } 提供 /**@type{THREE.Camera}*/Camera, /**@type{layerName:string=>void}*/beforeach, /**@type{layerName:string=>void}*/afterEach { 用于此的常量层。\uu层{ beforeachlayer.name; 这是.\uu renderer.renderlayer.backingScene,摄影机; afterEachlayer.name; } } } ////////////////////// //瓦尔斯 ////////////////////// 让摄影机、统计信息、几何体、材质、对象、对象2、根; 设时间=0; /**@type{THREE.Scene}*/ 让现场; /**@type{THREE.WebGLRenderer}*/ 让渲染器; /**@type{VisualLayers}*/ 让观察家; 常数时钟=新的三个时钟; 常量绿色=27ae60; 常量选项={ 是的, 中间盒:是的, 是的, 第二层顺序:2 }; ////////////////////// //初始化 ////////////////////// ~function init{ setup3D; renderLoop; }; //////////////////////////////// //设置3D //////////////////////////////// 功能设置3D{ const container=document.createElementdiv; container.id=容器; document.body.appendChildcontainer; //摄像机 摄像机=新的三视角摄像机 70, window.innerWidth/window.innerHeight, 1. 10000 ; 摄像机位置x=0; 摄像机位置z=500; 摄像机位置y=0; 场景=新的三个场景; //渲染器 renderer=new-THREE.WebGLRenderer{antialas:true,alpha:true}; renderer.setClearColor0x111111; container.appendChildrender.doElement; //层 visualLayers=新的VisualLayersrenderer; //不必定义图层。向图层添加对象将 //自动创建顺序为0的图层。但让我们定义顺序为0的图层 //顺序值。 visualLayers.DefineLayer1,1; visualLayers.DefineLayer2,2; visualLayers.DefineLayer3,3; //灯光 const directionalLight=新的3.DirectionalLight0xffffff,0.5; directionalLight.position.set300,0,300; scene.adddirectionalLight; visualLayers.addObjectToLayerdirectionalLight[ 第一层, 第二层, 第3层 ]; const ambientLight=新的三个。AmbientLight0xffffff,0.4; scene.addambientLight; visualLayers.addObjectToLayerArbientLight,[第1层、第2层、第3层]; //几何学 根=新的3.Object3D; scene.addroot; 几何体=新的三箱几何体100100100; 材质=新的3.0网格材质{ 颜色:绿色, 透明:假, 不透明度:1 }; 对象=新的三个。网格几何体,材质; root.addobject; visualLayers.addObjectToLayerobject,第1层; object.position.y=80; object.position.z=-20; //object.rotation.y=-Math.PI/5 object2=新的三个。网格几何体,材质; object.addobject2; visualLayers.addObjectToLayerobject2,第2层; 对象2.位置y-=80; object2.position.z=-20; object2.rotation.y=-Math.PI/5; const object3=新的三个网格几何体,材质; object2.addobject3; visualLayers.addObjectToLayerobject3,第3层; 对象3.位置y-=80; object3.position.z=-20; object3.rotation.y=-Math.PI/5; //桂 常量窗格=新窗格{ 标题:视觉专家 }; pane.addInputoptions,useLayers,{label:use layers}; pane.addInputoptions,showMiddleBox,{label:showMiddleBox}; 窗格。添加自动选项,旋转; 窗玻璃 .添加自动选项、图层2顺序、{ 标签:第二层订单, 选项:{ 0: 0, 2: 2, 4: 4 } } .onchange,=>visualLayers.definelayer2,options.layer2顺序; //统计数据 //见:https://github.com/mrdoob/stats.js 统计数据=新统计数据; stats.domeElement.style.position=绝对值; stats.domeElement.style.left=0px; stats.domeElement.style.top=0px; stats.setMode0; document.body.appendChildstats.doElement; } ////////////////////// //调整大小 ////////////////////// window.onresize=函数事件{ camera.aspect=window.innerWidth/window.innerHeight; camera.updateProjectionMatrix; renderer.setSizewindow.innerWidth,window.innerHeight; }; ////////////////////// //RAF渲染循环 ////////////////////// 函数渲染{ stats.begin; 如果选项为“旋转”{ time+=clock.getDelta; object.rotation.y+=0.02; root.rotation.y=Math.PI/2+Math.PI/6*Math.sintime*0.001; } object2.visible=options.showmidlebox; if options.useLayers{ scene.updateWorldMatrix,true; renderer.autoClear=false; 1.1.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2; visualLayers.rendercamera,在每个层之前,在每个层之后; }否则{ renderer.autoClear=true; renderer.renderscene,摄影机; } 统计数据。结束; } 函数renderLoop{ 提供 requestAnimationFramerenderLoop; } 函数beforeachlayerrenderlayer{} 每层下层之后的函数{ 1.clearDepth; } html, 身体 容器{ 边际:0px; 填充:0px; 宽度:100%; 身高:100%; } 帆布{ 背景:透明; 显示:块; 宽度:100%; 身高:100%; 位置:绝对位置; 左:0; 排名:0; }
下面是一个工作示例,它有一个VisualYers类用于管理任意数量的层,并使用renderer.autoClear=false技术和清除深度技术,正如Alex Waynes和gman的回答所暗示的那样

这种方法很好,因为对象的renderOrder没有被修改,这是另一种方法,因此不会引入其他问题

尝试使用UI中的选项查看其功能:

//@ts支票 //////////////////////// //层系 //////////////////////// /**@typedef{{name:string,backingScene:THREE.Scene,order:number}层*/ 类可视层{ /** *@type{Array} *@私人 */ __层=[]; 建造师 /**@private@type{THREE.WebGLRenderer}*/\uu renderer, /**@private@type{typeof THREE.Scene}*/\u Scene=THREE.Scene { 这个.\uuuu渲染器=\uuuu渲染器; 这个._场景=_场景; } 定义层/**@type{string}*/name,/**@type{number=}*/order=0{ const layer=这个; //默认图层的顺序始终为0。 const previousOrder=layer.order; layer.order=name==默认值?0:顺序; //仅在订单更改时进行排序。 如果上一个订单!==layer.order 这个.uuu layers.sorta,b=>a.order-b.order; 返回层; } /** *按名称获取图层如果该图层不存在,则以默认顺序0创建该图层。 *@私人 */ __getLayer/**@type{string}*/name{ 让layer=this.\uu layers.findl=>l.name==name; 如果!层{ 层={name,backingScene:newthis.\u场景,顺序:0}; layer.backingScene.autoUpdate=false; 这个._layers.pushlayer; } 返回层; } removeLayer/**@type{string}*/name{ const index=这个。\uuu layers.findIndexl=>{ 如果l.name==name{ l、 backingScene.children.length=0; 返回true; } 返回false; }; 如果索引>=0,则为该层。拼接索引,1; } hasLayer/**@type{string}*/name{ 返回此值。uu layers.somel=>l.name===name; } /**@readonly*/ 获得分层计数{ 返回此项。\uuuu layers.length; } addObjectToLayer /**@type{THREE.Object3D}*/obj, /**@type{string | string[]}*/layers { 如果Array.isArraylayers{ 对于层的常量名称,请参见此。\uuu addObjectToLayerobj,名称; 回来 } 这是.u_addObjectToLayerobj,图层; } addObjectsToLayer / **@type{THREE.Object3D[]}*/objects, /**@type{string | string[]}*/layers { 用于对象的常量对象{ this.addObjectToLayerobj,层; } } /**@private@readonly*/ __emptyArray=Object.freeze[]; /**@私人*/ __addObjectToLayer /**@type{THREE.Object3D}*/obj, /**@type{string}*/name { const layer=这个; const proxy=Object.createobj{ 孩子:{get:=>这个。uu emptyArray} }; layer.backingScene.children.pushproxy; } removeObjectFromLayer /**@type{THREE.Object3D}*/obj, /**@type{string | string[]}*/nameOrNames { 如果Array.isArrayNames{ 对于名称或名称的常量名称{ const layer=this.\uu layers.findl=>l.name==name; 如果!层继续; 此项。从图层Robj、图层中删除对象; } 回来 } const layer=this.\uu layers.findl=>l.name===nameOrNames; 如果!层返回; 此项。从图层Robj、图层中删除对象; } /**@私人*/ __removeObjectFromLayer /**@type{THREE.Object3D}*/obj, /**@type{Layer}*/Layer { const children=layer.backingScene.children; 常量索引=children.findIndex proxy=>/**@type{any}*/proxy.\uuu proto\uuuu===obj ; 如果索引>=0{ children[index]=children[children.length-1]; 儿童流行音乐; } } removeObjectsFromAllLayers/**@type{THREE.Object3D[]}*/objects{ 用于此的常量层。\uu层{ 用于对象的常量对象{ 此项。从图层Robj、图层中删除对象; } } } 提供 /**@type{THREE.Camera}*/Camera, /**@type{layerName:string=>void}*/beforeach, /**@type{layerName:string=>void}*/afterEach { 用于此的常量层。\uu层{ beforeachlayer.name; 这是.\uu renderer.renderlayer.backingScene,摄影机; afterEachlayer.name; } } } ////////////////////// //瓦尔斯 ////////////////////// 让摄影机、统计信息、几何体、材质、对象、对象2、根; 设时间=0; /**@type{THREE.Scene}*/ 让现场; /**@type{THREE.WebGLRenderer}*/ 让渲染器; /**@type{VisualLayers}*/ 让观察家; 常数时钟=新的三个时钟; 常量绿色=27ae60; 常量选项={ 是的, 中间盒:是的, 是的, 第二层顺序:2 }; ////////////////////// //初始化 ////////////////////// ~function init{ setup3D; renderLoop; }; //////////////////////////////// //设置3D //////////////////////////////// 功能设置3D{ const container=document.createElementdiv; container.id=容器; document.body.appendChildcontainer; //摄像机 摄像机=新的三视角摄像机 70, window.innerWidth/window.innerHeight, 1. 10000 ; 摄像机位置x=0; 摄像机位置z=500; 摄像机位置y=0; 场景=新的三个场景; //渲染器 renderer=new-THREE.WebGLRenderer{antialas:true,alpha:true}; renderer.setClearColor0x111111; container.appendChildrender.doElement; //层 visualLayers=新的VisualLayersrenderer; //不必定义图层。向图层添加对象将 //自动创建顺序为0的图层。但让我们定义顺序为0的图层 //顺序值。 visualLayers.DefineLayer1,1; visualLayers.DefineLayer2,2; visualLayers.DefineLayer3,3; //灯光 const directionalLight=新的3.DirectionalLight0xffffff,0.5; directionalLight.position.set300,0,300; scene.adddirectionalLight; visualLayers.addObjectToLayerdirectionalLight[ 第一层, 第二层, 第3层 ]; const ambientLight=新的三个。AmbientLight0xffffff,0.4; scene.addambientLight; visualLayers.addObjectToLayerArbientLight,[第1层、第2层、第3层]; //几何学 根=新的3.Object3D; scene.addroot; 几何体=新的三箱几何体100100100; 材质=新的3.0网格材质{ 颜色:绿色, 透明:假, 不透明度:1 }; 对象=新的三个。网格几何体,材质; root.addobject; visualLayers.addObjectToLayerobject,第1层; object.position.y=80; object.position.z=-20; //object.rotation.y=-Math.PI/5 object2=新的三个。网格几何体,材质; object.addobject2; visualLayers.addObjectToLayerobject2,第2层; 对象2.位置y-=80; object2.position.z=-20; object2.rotation.y=-Math.PI/5; const object3=新的三个网格几何体,材质; object2.addobject3; visualLayers.addObjectToLayerobject3,第3层; 对象3.位置y-=80; 对象3.positi on.z=-20; object3.rotation.y=-Math.PI/5; //桂 常量窗格=新窗格{ 标题:视觉专家 }; pane.addInputoptions,useLayers,{label:use layers}; pane.addInputoptions,showMiddleBox,{label:showMiddleBox}; 窗格。添加自动选项,旋转; 窗玻璃 .添加自动选项、图层2顺序、{ 标签:第二层订单, 选项:{ 0: 0, 2: 2, 4: 4 } } .onchange,=>visualLayers.definelayer2,options.layer2顺序; //统计数据 //见:https://github.com/mrdoob/stats.js 统计数据=新统计数据; stats.domeElement.style.position=绝对值; stats.domeElement.style.left=0px; stats.domeElement.style.top=0px; stats.setMode0; document.body.appendChildstats.doElement; } ////////////////////// //调整大小 ////////////////////// window.onresize=函数事件{ camera.aspect=window.innerWidth/window.innerHeight; camera.updateProjectionMatrix; renderer.setSizewindow.innerWidth,window.innerHeight; }; ////////////////////// //RAF渲染循环 ////////////////////// 函数渲染{ stats.begin; 如果选项为“旋转”{ time+=clock.getDelta; object.rotation.y+=0.02; root.rotation.y=Math.PI/2+Math.PI/6*Math.sintime*0.001; } object2.visible=options.showmidlebox; if options.useLayers{ scene.updateWorldMatrix,true; renderer.autoClear=false; 1.1.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2; visualLayers.rendercamera,在每个层之前,在每个层之后; }否则{ renderer.autoClear=true; renderer.renderscene,摄影机; } 统计数据。结束; } 函数renderLoop{ 提供 requestAnimationFramerenderLoop; } 函数beforeachlayerrenderlayer{} 每层下层之后的函数{ 1.clearDepth; } html, 身体 容器{ 边际:0px; 填充:0px; 宽度:100%; 身高:100%; } 帆布{ 背景:透明; 显示:块; 宽度:100%; 身高:100%; 位置:绝对位置; 左:0; 排名:0; }
这有一个API:renderer.clear false,true,true;。在ThreeJS中可能有一个API,但在WebGL中,这是原始问题的第2部分,这是如何实现的。有一个API:renderer.clear false,true,true;。在ThreeJS中可能有一个API,但在WebGL中,这是原始问题的第2部分,这是如何实现的。