渲染对象

有时候我们需要多次渲染,比如先将一些内容渲染到FrameBuffer中,然后再输出到屏幕。

在3D扩展中,我们可以使用RenderTarget对象来创建一块可绘制的FrameBuffer。

  1. const vertex = /* glsl */ `
  2. precision highp float;
  3. precision highp int;
  4. attribute vec3 position;
  5. attribute vec3 normal;
  6. attribute vec2 uv;
  7. uniform mat4 modelViewMatrix;
  8. uniform mat4 projectionMatrix;
  9. uniform mat3 normalMatrix;
  10. varying vec2 vUv;
  11. varying vec3 vNormal;
  12. void main() {
  13. vUv = uv;
  14. vNormal = normalize(normalMatrix * normal);
  15. gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  16. }
  17. `;
  18. const fragment = /* glsl */ `
  19. precision highp float;
  20. precision highp int;
  21. uniform sampler2D tMap;
  22. varying vec2 vUv;
  23. varying vec3 vNormal;
  24. void main() {
  25. vec3 normal = normalize(vNormal);
  26. float lighting = 0.2 * dot(normal, normalize(vec3(-0.3, 0.8, 0.6)));
  27. vec3 tex = texture2D(tMap, vUv).rgb;
  28. gl_FragColor.rgb = tex + lighting + vec3(vUv - 0.5, 0.0) * 0.1;
  29. gl_FragColor.a = 1.0;
  30. }
  31. `;
  32. const {Scene} = spritejs;
  33. const {Cube, RenderTarget} = spritejs.ext3d;
  34. const container = document.getElementById('container');
  35. const scene = new Scene({
  36. container,
  37. displayRatio: 2,
  38. });
  39. const layer = scene.layer3d('fglayer', {
  40. camera: {
  41. fov: 35,
  42. },
  43. });
  44. layer.camera.attributes.pos = [0, 1, 5];
  45. layer.camera.lookAt([0, 0, 0]);
  46. const texture = layer.createTexture({
  47. image: new Uint8Array([
  48. 191, 25, 54, 255,
  49. 96, 18, 54, 255,
  50. 96, 18, 54, 255,
  51. 37, 13, 53, 255,
  52. ]),
  53. width: 2,
  54. height: 2,
  55. magFilter: layer.gl.NEAREST,
  56. });
  57. const program = layer.createProgram({
  58. vertex,
  59. fragment,
  60. texture,
  61. });
  62. const target = new RenderTarget(layer.gl, {
  63. width: 512,
  64. height: 512,
  65. camera: {
  66. fov: 35,
  67. },
  68. });
  69. target.camera.attributes.pos = [0, 1, 5];
  70. target.camera.lookAt([0, 0, 0]);
  71. const mesh = new Cube(program);
  72. target.append(mesh);
  73. const targetProgram = layer.createProgram({
  74. vertex,
  75. fragment,
  76. texture: target.texture,
  77. });
  78. target.addEventListener('beforerender', (e) => {
  79. layer.gl.clearColor(0.15, 0.05, 0.2, 1);
  80. });
  81. target.addEventListener('afterrender', (e) => {
  82. layer.gl.clearColor(1, 1, 1, 0);
  83. });
  84. const mesh2 = new Cube(targetProgram);
  85. layer.append(mesh2);
  86. layer.tick(() => {
  87. layer.renderTarget(target);
  88. mesh.attributes.rotateY -= 1.5;
  89. mesh2.attributes.rotateY -= 0.9;
  90. mesh2.attributes.rotateX -= 1;
  91. });

我们可以通过layer.renderTarget来绘制内容到FrameBuffer上,然后通过将target.texture绑定为program的texture的方式使用。

我们可以创建多个RenderTarget将内容叠加,比如:

  1. const target1 = new RenderTarget(layer.gl, {
  2. width: 512,
  3. height: 512,
  4. camera: {
  5. fov: 35,
  6. },
  7. });
  8. const target2 = new RenderTarget(layer.gl, {
  9. width: 512,
  10. height: 512,
  11. camera: {
  12. fov: 35,
  13. },
  14. });
  15. const target3 = new RenderTarget(layer.gl, {
  16. width: 512,
  17. height: 512,
  18. camera: {
  19. fov: 35,
  20. },
  21. });
  22. ...
  23. const root = new Mesh3d(...);
  24. layer.tick(() => {
  25. // 我们可以不将root添加到renderTarget上,这样就可以针对不同的renderTarget复用root
  26. layer.renderTarget(target1, {root});
  27. root.program.uniforms.tMap.value = target1.texture;
  28. layer.renderTarget(target2, {root});
  29. root.program.uniforms.tMap.value = target2.texture;
  30. layer.renderTarget(target3, {root});
  31. root.program.uniforms.tMap.value = target2.texture;
  32. ...
  33. });

但是这样的话,要处理多次就要创建多个RenderTarget,比较消耗内存。实际上,如果我们多次处理,可以只创建两个target,依次交换他们

  1. const root = new Mesh3d(...);
  2. layer.tick(() => {
  3. // 我们可以不将root添加到renderTarget上,这样就可以针对不同的renderTarget复用root
  4. layer.renderTarget(target1, {root});
  5. root.program.uniforms.tMap.value = target1.texture;
  6. layer.renderTarget(target2, {root});
  7. root.program.uniforms.tMap.value = target2.texture;
  8. layer.renderTarget(target1, {root});
  9. ...
  10. });

RenderTarget在创建的时候还可以传一个配置项buffer,设为true,则启用双缓冲,这样就不需要创建两个Target,代码就可以简化为:

  1. const root = new Mesh3d(...);
  2. layer.tick(() => {
  3. // 我们可以不将root添加到renderTarget上,这样就可以针对不同的renderTarget复用root
  4. layer.renderTarget(target, {root});
  5. root.program.uniforms.tMap.value = target.texture;
  6. target.swap();
  7. layer.renderTarget(target, {root});
  8. root.program.uniforms.tMap.value = target.texture;
  9. target.swap();
  10. layer.renderTarget(target, {root});
  11. ...
  12. });

默认情况下,执行layer.renderTarget会清空之前缓冲区中的内容,如果我们不希望这么做,可以设置layer.autoClear为false,那么就可以保留缓冲区的内容。