一.简介
本文主要记录纹理与FBO相关的用法,第一个是将FBO渲染到纹理(RTT),可以借此实现倒影,小地图等效果,第二种是借助FBO实现GPU选择
除此之外,纹理和FBO结合可以实现各种后期处理效果,有机会慢慢记录
二.渲染到纹理(RTT)
2.1 创建一个纹理,用于FBO的颜色关联对象
/**
* 使用图片创建纹理
* */
function createTexture(image, width, height) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.activeTexture(gl.TEXTURE0);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
if (image) {
//给纹理赋值图片
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGB,
gl.RGB,
gl.UNSIGNED_BYTE,
image
);
} else {
//若无图片,创建一个空纹理,用于FBO写入
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGB,
width,
height,
0,
gl.RGB,
gl.UNSIGNED_BYTE,
null
);
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 设置参数,让我们可以绘制任何尺寸的图像
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);
return texture;
}
在创建FBO纹理时,只需要将image传为null即可
2.2 创建一个FBO,关联深度渲染缓冲区和颜色纹理
/**
* 创建帧缓冲区
* */
function createFBO(width, height) {
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
//创建颜色附件
const texture = createTexture(null, width, height);
//将颜色附件绑定到FBO上
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
0
);
//把数据写入纹理
fbo.texture = texture;
//创建渲染缓冲区对象并设置其尺寸和参数
const depthBuffer = gl.createRenderbuffer();
if (!depthBuffer) {
console.log("无法创建渲染缓冲区对象");
return error();
}
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
gl.renderbufferStorage(
gl.RENDERBUFFER,
gl.DEPTH_COMPONENT16,
width,
height
);
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER,
depthBuffer
);
const result = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (gl.FRAMEBUFFER_COMPLETE !== result) {
throw new Error("帧缓冲区绑定有误");
}
gl.bindFramebuffer(gl.FRAMEBUFFER,null)
return fbo;
}
2.3 创建box,绘制到FBO
function drowObj(
obj,
program,
fbo,
viewProjectMatrix,
currentAngle,
texture
) {
gl.useProgram(program.program);
if (fbo) {
//绘制到FBO上
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
//清空FBO颜色和深度缓冲区
gl.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
//绑定box vao
vaoExt.bindVertexArrayOES(obj.vao);
//使用viewPorject矩阵
const viewProjectLoc = program.uniformLocs.u_viewProjectMatrixLoc;
gl.uniformMatrix4fv(viewProjectLoc, false, viewProjectMatrix.elements);
obj.modelMatrix.setRotate(currentAngle, 0, 1, 0);
const modelMatrixLoc = program.uniformLocs.u_modelMatrixLoc;
gl.uniformMatrix4fv(modelMatrixLoc, false, obj.modelMatrix.elements);
if(texture){
const textureLoc = program.uniformLocs.u_textureLoc;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(textureLoc, 0);
}else{
//设置batchId
const rgb=encodeToRGB(plane.id)
console.log(rgb)
gl.uniform3fv(program.uniformLocs.u_batchIdLoc, rgb)
}
gl.drawElements(gl.TRIANGLES, obj.length, gl.UNSIGNED_BYTE, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
}
2.4 再次绘制plane到屏幕上,并将FBO的颜色附件作为纹理
gl.clearColor(0, 0, 0, 1);
function draw() {
currentAngle += 0.5;
//设置相机参数
viewProjectMatrix.set(camera.projectMatrix);
viewProjectMatrix.multiply(camera.viewMatrix);
gl.enable(gl.DEPTH_TEST)
//把fbo的纹理赋值给plane
// gl.bindTexture(gl.TEXTURE_2D,fboTexture)
////////////////////////////画中画/////////////////////////
drowObj(
box,
textureProgram,
fbo,
viewProjectMatrix,
currentAngle,
boxTexture,
);
const fboTexture=fbo.texture
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
drowObj(
plane,
textureProgram,
null,
viewProjectMatrix,
currentAngle,
fboTexture,
);
}
效果如下:
二.GPU选择
使用GPU选择原理如下:
- 首先给每个object赋值一个id,并将id编码为RGB值
- 绑定FBO,绘制物体,物体的颜色使用编码出来的RGB值
- 解绑缓冲区,按照物体的实际颜色绘制
- 点击时,绑定FBO,使用
gl.readpixel读取颜色值,将颜色解码为id,遍历,寻找物体
2.1. 给出一个简单编码,将id编码为RGB,和将RGB解码为id
function encodeToRGB(id){
const color=[]
let i=2
while(i>0){
const delta=Math.pow(255,i)
color.push(Math.floor(id/delta))
id=id%delta
--i
}
color.push(id)
return color
}
function decodeRGBA(color){
let value=0
for(let i=1;i<=3;i++){
const delta=Math.pow(255,3-i)
value+=color[i-1]*delta
}
return value
}
2.2 使用颜色值绘制到FBO,然后解绑FBO,使用实际颜色绘制
const vs = /*glsl*/ `
attribute vec3 a_position;
attribute vec2 a_uv;
uniform mat4 u_viewProjectMatrix;
uniform mat4 u_modelMatrix;
uniform int batchId;
varying vec2 v_uv;
void main(){
gl_Position=u_viewProjectMatrix*u_modelMatrix*vec4(a_position,1.0);
v_uv=a_uv;
}
`;
const fs = /*glsl*/ `
precision mediump float;
uniform sampler2D texture;
varying vec2 v_uv;
void main(){
vec4 color=texture2D(texture,v_uv);
gl_FragColor=color;
}
`;
const colorFS = /*glsl*/ `
precision mediump float;
uniform vec3 u_batchId;
void main(){
gl_FragColor=vec4(u_batchId/255.0,1.0);
}
`;
const textureProgram = createShaderProgram(vs, fs);
const colorProgram=createShaderProgram(vs,colorFS)
const box = initBox(textureProgram.attributeLocs, vaoExt);
box.id=1000000
const plane = initPlane(textureProgram.attributeLocs, vaoExt);
plane.id=2000000
function draw() {
currentAngle += 0.5;
//设置相机参数
viewProjectMatrix.set(camera.projectMatrix);
viewProjectMatrix.multiply(camera.viewMatrix);
gl.enable(gl.DEPTH_TEST)
if (ispick) {
drowObj(
plane,
colorProgram,
fbo,
viewProjectMatrix,
currentAngle,
null,
);
}
drowObj(
plane,
textureProgram,
null,
viewProjectMatrix,
currentAngle,
boxTexture,
);
}
function createShaderProgram(vs, fs) {
const program = createProgram(gl, vs, fs);
//获取位置
const a_positionLoc = gl.getAttribLocation(program, "a_position");
const a_uvLoc = gl.getAttribLocation(program, "a_uv");
const u_viewProjectMatrixLoc = gl.getUniformLocation(
program,
"u_viewProjectMatrix"
);
const u_modelMatrixLoc = gl.getUniformLocation(program, "u_modelMatrix");
const u_textureLoc = gl.getUniformLocation(program, "texture");
const u_batchIdLoc = gl.getUniformLocation(program, "u_batchId");
return {
program,
attributeLocs: {
a_positionLoc,
a_uvLoc,
},
uniformLocs: {
u_viewProjectMatrixLoc,
u_modelMatrixLoc,
u_textureLoc,
u_batchIdLoc,
},
};
}
2.3 点击时,绑定FBO,读取颜色并解码,之后遍历场景中的物体,即可知道选择了哪个物体
document.getElementById("canvas").onclick = (e) => {
const x = e.offsetX;
const y = e.offsetY;
ispick=true
draw();
gl.bindFramebuffer(gl.FRAMEBUFFER,fbo)
const pixels = new Uint8Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
gl.bindFramebuffer(gl.FRAMEBUFFER,null)
ispick = false;
alert('id是'+decodeRGBA(pixels))
};