当前位置:   article > 正文

WebGL2.0:渲染到纹理_webgl2 texture

webgl2 texture

RTT(Render to Texture)

RTT(Render to Texture)即渲染到纹理。在普通的图形渲染流程中,最终结果是渲染到帧缓存中,然后才会显示到屏幕上。而RTT则是将场景渲染到一张纹理上,并且在之后进行使用。

1. 创建纹理对象

要进行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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

需要注意的是,这里只是创建了一个纹理对象,并没有提供数据,因此data设置成了null。

2. 创建帧缓冲

接下来,需要创建一个帧缓冲(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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3. 创建深度附件

在渲染到纹理时,可以选择不创建深度附件,这样就没有深度检测,渲染出来的纹理遮挡关系可能不正确。如果需要创建深度附件,则可以按照以下步骤进行:

// 创建一个深度纹理
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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

4. 进行渲染,生成纹理

在渲染之前需要先绑定帧缓冲以及设置视口。以下是进行渲染的代码:

// 绑定帧缓冲
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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

5. 使用纹理

渲染到纹理,目前我知道的使用场景有以下几种:
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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2. 读取纹理到数组,然后进一步使用, 可用于拾取对象、生成纹理、调试Shader等

// 读取纹理到数组
const pixels = new Uint8Array(targetTextureWidth * targetTextureHeight * 4);
gl.readPixels(0, 0, targetTextureWidth, targetTextureHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
  • 1
  • 2
  • 3

在这里,我们使用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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

有些开发者将Shader的中间值渲染到纹理用于调试。有些软件将场景物体的ID渲染到纹理,用于物体的拾取。
3. Shadowmap
在 Shadow Mapping 中,首先需要渲染一次场景,生成深度贴图(Depth Map),这个深度贴图实际上就是一张纹理,存储了从光源视角看到的场景的深度信息。然后,再将深度贴图应用到场景中进行阴影计算。渲染到纹理是实现 Shadow Mapping 的一种基础技术。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/727242
推荐阅读
  

闽ICP备14008679号