赞
踩
RTT(Render to Texture)即渲染到纹理。在普通的图形渲染流程中,最终结果是渲染到帧缓存中,然后才会显示到屏幕上。而RTT则是将场景渲染到一张纹理上,并且在之后进行使用。
要进行RTT,首先要创建一个纹理对象:
// 创建渲染对象 const targetTextureWidth = 256; // 纹理的长度与宽度 const targetTextureHeight = 256; const targetTexture = gl.createTexture(); // 创建纹理对象 gl.bindTexture(gl.TEXTURE_2D, targetTexture); // 进行bind,以下操作针对的是当前bind对象 { // 定义 0 级的大小和格式,不需要mipmap只需定义level 0即可 const level = 0; const internalFormat = gl.RGBA; // 每个像素使用RGBA表示 const border = 0; // 纹理的border,必须为0 const format = gl.RGBA; const type = gl.UNSIGNED_BYTE; // 像素每个通道的存储格式 const data = null; // 设置成null gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, targetTextureWidth, targetTextureHeight, border, format, type, data); 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); }
需要注意的是,这里只是创建了一个纹理对象,并没有提供数据,因此data设置成了null。
接下来,需要创建一个帧缓冲(framebuffer),帧缓冲是一个附件集,附件是纹理或renderbuffer。Renderbuffer与纹理很像,但支持一些纹理不支持的格式和可选项。不过,不能像纹理那样直接将renderbuffer提供给着色器。
// 创建并绑定帧缓冲
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// 附加纹理为第一个颜色附件
const attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D(
gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
在渲染到纹理时,可以选择不创建深度附件,这样就没有深度检测,渲染出来的纹理遮挡关系可能不正确。如果需要创建深度附件,则可以按照以下步骤进行:
// 创建一个深度纹理 const depthTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, depthTexture); // 设置深度缓冲的大小和targetTexture相同 { // 定义 0 级的大小和格式 const level = 0; const internalFormat = gl.DEPTH_COMPONENT24; const border = 0; const format = gl.DEPTH_COMPONENT; const type = gl.UNSIGNED_INT; const data = null; gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, targetTextureWidth, targetTextureHeight, border, format, type, data); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, level); }
在渲染之前需要先绑定帧缓冲以及设置视口。以下是进行渲染的代码:
// 绑定帧缓冲 gl.bindFramebuffer(gl.FRAMEBUFFER, fb); // 设置视口大小 gl.viewport(0, 0, targetTextureWidth, targetTextureHeight); // 清空画布颜色缓冲区和深度缓冲区 gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 设置相机 // ... // 设置光源 // ... // 绘制场景 // ...
渲染到纹理,目前我知道的使用场景有以下几种:
1. 直接交给着色器使用
生成的纹理可以直接传递给着色器,在着色器中使用它进行渲染。在进行渲染之前,需要将Framebuffer的绑定解除,并使用纹理对象进行绘制:
// 将帧缓冲的绑定解除
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// 使用生成的纹理进行绘制
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLES, 0, 6);
2. 读取纹理到数组,然后进一步使用, 可用于拾取对象、生成纹理、调试Shader等
// 读取纹理到数组
const pixels = new Uint8Array(targetTextureWidth * targetTextureHeight * 4);
gl.readPixels(0, 0, targetTextureWidth, targetTextureHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
在这里,我们使用gl.readPixels()函数将帧缓冲区中的像素数据读取到一个数组中。这个数组就是我们最终生成的纹理数据,可以用来在之后的渲染中使用。如果想用这个数组重新生成纹理,可按以下代码:
// 创建纹理对象
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// 设置纹理参数
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);
// 将像素数据绑定到纹理对象上
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, targetTextureWidth, targetTextureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
有些开发者将Shader的中间值渲染到纹理用于调试。有些软件将场景物体的ID渲染到纹理,用于物体的拾取。
3. Shadowmap
在 Shadow Mapping 中,首先需要渲染一次场景,生成深度贴图(Depth Map),这个深度贴图实际上就是一张纹理,存储了从光源视角看到的场景的深度信息。然后,再将深度贴图应用到场景中进行阴影计算。渲染到纹理是实现 Shadow Mapping 的一种基础技术。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。