问题描述
我正在尝试在WebGL中实现选择.我有很多物体(大约500个),我希望允许每个物体被选中.为此,我做了一个循环,该循环为每个对象分配了唯一的颜色(参见选择原理):
for (var i = 0, len = objects.length; i < len; i++) { framecolors[count++] = i % 256 / 256; //Red framecolors[count++] = Math.floor(i/256) / 256; //Green framecolors[count++] = Math.floor(i/(256*256)) / 256; //Blue }然后在经典的缓冲区中使用
framecolors检查每个对象是否具有不同的红色阴影.有效.
现在,我想使用对象的原始颜色,以及背景中红色阴影的框架缓冲.我经历了一些代码,我有点困惑.
这是我到目前为止尝试过的.
挑选之前调用的函数:
//Creates texture colorTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, colorTexture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 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, 400, 400, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); //Creates framebuffer fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fb); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0); gl.bindTexture(gl.TEXTURE_2D, colorTexture); gl.enable(gl.DEPTH_TEST); gl.bindFramebuffer(gl.FRAMEBUFFER, fb); gl.clear(gl.DEPTH_BUFFER_BIT); gl.drawArrays(gl.POINTS, 0, vertexPositionBuffer.numItems);
函数以下调用:
gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindTexture(gl.TEXTURE_2D, colorTexture); gl.drawArrays(gl.POINTS, 0, vertexPositionBuffer.numItems);
您可能可以理解,我对Framebuffers不太满意,即使我读了很多有关它们的信息,我也没有真正了解它们的工作方式.我不知道如何将framecolors链接到框架缓冲器.有办法吗?
谢谢, r.
推荐答案
framebuffer是附件的集合(RenderBuffers和/或纹理).它的工作方式就像没有框架的渲染一样. (实际上,浏览器在内部使用Framebuffer来实现WebGL的画布)
在您的情况下,您会缺少几件事.您很可能需要附加深度缓冲区
// create renderbuffer depthBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); // allocate renderbuffer gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); // attach renderebuffer gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
通常,您还应该检查Framebuffer的作品.连接所有随附的附件后gl.checkFramebufferStatus
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { alert("this combination of attachments does not work"); return; }
出于多种原因,框架缓冲器可能是不完整的.最常见的是附件不同的大小或GPU不支持附件的组合.注意:在WebGL中,需要某些组合才能工作,但是您以后可能会更改代码以使用不同格式,因此检查可能仍然是一个好主意.
您还需要通过调用gl.viewport时设置视口.
gl.bindFramebuffer(gl.FRAMEBUFFER, someFramebuffer); gl.viewport(0, 0, someFramebufferWidth, someFramebufferHeight);
,其中包括将东西放回画布
时将其放回原处gl.bindFramebuffer(gl.FRAMEBUFFER, null); // render to canvas gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
最后,上面的代码中有一个错误,因为您只需清除framebuffer的深度缓冲区,您可以在其中调用gl.clear.您想致电
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
由于您将稍后阅读颜色,否则将遗留旧颜色.
最后,我想你知道这一点.您找出哪个像素对应于鼠标单击并调用
var colorPicked = new Uint8Array(4); gl.readPixels(pickX, pickY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, colorPicked);
请注意,当您致电gl.readPixels时,必须将框架绑定到gl.bindFramebuffer或gl.readPixels时,将从画布中读取.
其他推荐答案
非常感谢Gman.只是为了完成您的答案,即如何使用我的Framebuffer使用framecolors,这很简单.在着色器中,我添加了一个专用于Framebuffer颜色的变量:
if (!offscreen) { gl_FragColor = normalColors; } else { gl_FragColor = frameColors; }
现在,在确定我选择的readPixels()函数选择哪个对象之前,我使用以下方式切换回Framebuffer:
function renderFrame() { gl.bindFramebuffer(gl.FRAMEBUFFER, fb); //fb = framebuffer gl.uniform1i(shaderProgram.uOffscreen, true); //uOffscreen = uniform boolean in shader draw(); //function called to draw objects (contains gl.clear and gl.viewport) }
类似地,我在调用功能后切换回通常的缓冲区.
我试图在单击鼠标时试图显示帧缓冲机而不是通常的缓冲区.我遇到麻烦,但是如果我发现的话,我会稍后发布解决方案.
编辑:解决方案:只需删除与Framebuffer关联的深度缓冲器(渲染缓冲区).然后显示缓冲区如下所述: webgl display framebuffer?
问题描述
I am trying to implement picking in WebGL. I have a lot of objects (around 500), and I'd like each one to be allowed to be picked. In order to do that, I did a loop which assigns a unique colour to each object (cf. picking principle):
for (var i = 0, len = objects.length; i < len; i++) { framecolors[count++] = i % 256 / 256; //Red framecolors[count++] = Math.floor(i/256) / 256; //Green framecolors[count++] = Math.floor(i/(256*256)) / 256; //Blue }
framecolors was then used in a classical buffer to check whether each object had a different shade of red. It worked.
Now, I want to use my objects' original colours, AND a framebuffer with the shades of red in the background. I've been through some code, and I am a bit confused.
Here's what I have tried so far.
Function called before picking:
//Creates texture colorTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, colorTexture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 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, 400, 400, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); //Creates framebuffer fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fb); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0); gl.bindTexture(gl.TEXTURE_2D, colorTexture); gl.enable(gl.DEPTH_TEST); gl.bindFramebuffer(gl.FRAMEBUFFER, fb); gl.clear(gl.DEPTH_BUFFER_BIT); gl.drawArrays(gl.POINTS, 0, vertexPositionBuffer.numItems);
Function called after:
gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindTexture(gl.TEXTURE_2D, colorTexture); gl.drawArrays(gl.POINTS, 0, vertexPositionBuffer.numItems);
As you can probably understand, I am not very comfortable with framebuffers, I just don't really get how they work, even though I read a lot about them. I have no idea how I can link framecolors to the framebuffer. Is there a way?
Thanks, R.
推荐答案
A framebuffer is a collection of attachments (renderbuffers and/or textures). It works just like rendering without a framebuffer. (in fact the browser is internally using a framebuffer to implement WebGL's canvas)
In your case you're missing a few things. You most likely need a depth buffer attached otherwise when you render your scene you won't get zBuffering and the wrong objects will appear in front.
// create renderbuffer depthBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); // allocate renderbuffer gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); // attach renderebuffer gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
In general you should also check your framebuffer works. After attaching all the attachments you call gl.checkFramebufferStatus
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { alert("this combination of attachments does not work"); return; }
A framebuffer can be incomplete for any number of reasons. The most common is the attachments are not same size or the GPU doesn't support that combination of attachments. Note: In WebGL certain combinations are required to work but you since you might change the code later to use different formats it's probably still a good idea to check.
You also need to set the viewport by calling gl.viewport whenever you switch framebuffers.
gl.bindFramebuffer(gl.FRAMEBUFFER, someFramebuffer); gl.viewport(0, 0, someFramebufferWidth, someFramebufferHeight);
That includes putting it back when setting things back to the canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // render to canvas gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
Finally there's a bug in the code above in that you're only clearing the depth buffer of the framebuffer where you call gl.clear. You want to call
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
Since you're going to read the colors later otherwise old colors will be left over.
Finally, and I guess you know this. You figure out which pixel corresponds to the mouse click and call
var colorPicked = new Uint8Array(4); gl.readPixels(pickX, pickY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, colorPicked);
Note that when you call gl.readPixels you must have your framebuffer bound with gl.bindFramebuffer or gl.readPixels will read from the canvas.
其他推荐答案
Thanks a lot gman. Just to complete your answer, about how to use framecolors with my framebuffer, it's quite simple. In the shaders, I added a variable dedicated to the framebuffer's colors:
if (!offscreen) { gl_FragColor = normalColors; } else { gl_FragColor = frameColors; }
Now, before determing which object I selected thanks to the readPixels() function, I switch back to the framebuffer using:
function renderFrame() { gl.bindFramebuffer(gl.FRAMEBUFFER, fb); //fb = framebuffer gl.uniform1i(shaderProgram.uOffscreen, true); //uOffscreen = uniform boolean in shader draw(); //function called to draw objects (contains gl.clear and gl.viewport) }
and similarly, I switch back to the usual buffer after calling the function.
I am trying to display the framebuffer instead of the usual buffer when the mouse is clicked. I am having troubles, but I'll post the solution later if I ever find it.
EDIT : Solution : just remove the depth-buffer (render buffer) associated with the framebuffer. Then display the buffer as explained in : WebGL display framebuffer?