在 WebGL 中使用附加混合绘制多个帧缓冲区到纹理

时间:2021-03-08 15:41:23

标签: glsl webgl

我正在尝试使用 WebGL 实现 2D 绽放/godrays 效果。出于演示的目的,我将 64x64 gl.POINT 渲染到帧缓冲区,然后通过多次迭代对其应用高斯模糊。以下是我正在采取的步骤:

  1. 将点渲染到帧缓冲区 1
  2. 将帧缓冲区 1 的结果渲染到帧缓冲区 2 作为全屏四边形的纹理并应用水平模糊
  3. 交换帧缓冲区 1 和帧缓冲区 2
  4. 在每次新的迭代中,应用水平或垂直模糊并慢慢增加模糊半径

Gaussian blur with WebGL steps illustrated

这行得通,但正如你所见,结果最终变得非常乏味。

const BLUR_ITERATIONS_COUNT = 20

console.clear()

const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl')

canvas.width = innerWidth * devicePixelRatio
canvas.height = innerHeight * devicePixelRatio
canvas.style.width = `${innerWidth}px`
canvas.style.height = `${innerHeight}px`
document.body.appendChild(canvas)

/* Particle */
const particleVShader = makeShader(gl.VERTEX_SHADER, `
  attribute vec4 position;
  void main () {
    gl_Position = position;
    gl_PointSize = 64.0;
  }
`)
const particleFShader = makeShader(gl.FRAGMENT_SHADER, `
  precision highp float;
  void main () {
    float dist = distance(gl_PointCoord.xy, vec2(0.5));
    if (dist < 0.5) {
      gl_FragColor = vec4(1.0);
    } else {
      discard;
    }
  }
`)
const particleProgram = makeProgram(particleVShader, particleFShader)

const particleVertices = new Float32Array([0, 0, 0])
const pVerticesBuffer = gl.createBuffer()

const particlePosAttribLoc = gl.getAttribLocation(particleProgram, 'position')

gl.bindBuffer(gl.ARRAY_BUFFER, pVerticesBuffer)
gl.bufferData(gl.ARRAY_BUFFER, particleVertices, gl.STATIC_DRAW)

/* Fullscreen quad */
const { vertices: quadVertices, uv: quadUvs } = makeFullscreenQuad()

const fullscreenQuadVShader = makeShader(gl.VERTEX_SHADER, `
  attribute vec4 position;
  attribute vec2 uv;
  varying vec2 v_uv;
  void main () {
    gl_Position = position;
    v_uv = uv;
  }
`)
const fullscreenQuadFShader = makeShader(gl.FRAGMENT_SHADER, `
  precision highp float;

  uniform sampler2D diffuse;
  uniform vec2 blurDirection;
  
  varying vec2 v_uv;
    
  vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
    vec4 color = vec4(0.0);
    vec2 off1 = vec2(1.3846153846) * direction;
    vec2 off2 = vec2(3.2307692308) * direction;
    color += texture2D(image, uv) * 0.2270270270;
    color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162;
    color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162;
    color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703;
    color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703;
    return color;
  }
  
  void main () {
    vec2 resolution = vec2(${innerWidth}.0, ${innerHeight}.0);
    gl_FragColor = blur9(diffuse, v_uv, resolution, blurDirection);
  }
`)
const fullscreenQuadProgram = makeProgram(fullscreenQuadVShader, fullscreenQuadFShader)

gl.useProgram(fullscreenQuadProgram)

const diffuseUniformLoc = gl.getUniformLocation(fullscreenQuadProgram, 'diffuse')
const blurDirectionUniformLoc = gl.getUniformLocation(fullscreenQuadProgram, 'blurDirection')

gl.uniform1i(diffuseUniformLoc, 0)
gl.uniform2f(blurDirectionUniformLoc, 0, 1)
gl.useProgram(null)

const fullQuadVertexAttribLoc = gl.getAttribLocation(fullscreenQuadProgram, 'position')
const fullQuadUVAttribLoc = gl.getAttribLocation(fullscreenQuadProgram, 'uv')

const verticesBuffer = gl.createBuffer()
const uvsBuffer = gl.createBuffer()

gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer)
gl.bufferData(gl.ARRAY_BUFFER, quadVertices, gl.STATIC_DRAW)

gl.bindBuffer(gl.ARRAY_BUFFER, uvsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, quadUvs, gl.STATIC_DRAW)

/* Textures & Framebuffer */
let textureBlurX = createTexture()
let textureBlurY = createTexture()

let framebufferBlurX = createFramebuffer(textureBlurX)
let framebufferBlurY = createFramebuffer(textureBlurY)

requestAnimationFrame(renderFrame)

function renderFrame (ts) {
  gl.clearColor(0.1, 0.1, 0.1, 1.0)

  gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferBlurX)
  gl.viewport(0, 0, innerWidth, innerHeight)
  
  gl.clear(gl.COLOR_BUFFER_BIT)
  
  gl.bindBuffer(gl.ARRAY_BUFFER, pVerticesBuffer)
  gl.enableVertexAttribArray(particlePosAttribLoc)
  gl.vertexAttribPointer(particlePosAttribLoc, 3, gl.FLOAT, false, 0, 0)

  gl.useProgram(particleProgram)
  gl.drawArrays(gl.POINTS, 0, 1)
  gl.useProgram(null)

  gl.bindFramebuffer(gl.FRAMEBUFFER, null)
  
  gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer)
  gl.enableVertexAttribArray(fullQuadVertexAttribLoc)
  gl.vertexAttribPointer(fullQuadVertexAttribLoc, 2, gl.FLOAT, false, 0, 0)

  gl.bindBuffer(gl.ARRAY_BUFFER, uvsBuffer)
  gl.enableVertexAttribArray(fullQuadUVAttribLoc)
  gl.vertexAttribPointer(fullQuadUVAttribLoc, 2, gl.FLOAT, false, 0, 0)
  
  for (let i = 0; i < BLUR_ITERATIONS_COUNT; i++) {
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferBlurY)
    gl.bindTexture(gl.TEXTURE_2D, textureBlurX)

    gl.useProgram(fullscreenQuadProgram)

    const radius = BLUR_ITERATIONS_COUNT - i - 1
    
    if (i % 2 === 0) {
      gl.uniform2f(blurDirectionUniformLoc, radius, 0)
    } else {
      gl.uniform2f(blurDirectionUniformLoc, 0, radius)
    }
    gl.drawArrays(gl.TRIANGLES, 0, 6)
    gl.useProgram(null)    
  
    let t = framebufferBlurX
    framebufferBlurX = framebufferBlurY
    framebufferBlurY = t
    
    t = textureBlurX
    textureBlurX = textureBlurY
    textureBlurY = t
  }
  
  gl.bindFramebuffer(gl.FRAMEBUFFER, null)
  
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)

  gl.bindTexture(gl.TEXTURE_2D, textureBlurY)
  gl.useProgram(fullscreenQuadProgram)
  gl.drawArrays(gl.TRIANGLES, 0, 6)
  gl.useProgram(null)    
  
  gl.bindTexture(gl.TEXTURE_2D, null)
  
  requestAnimationFrame(renderFrame)
}

/* ------- WebGL Utils ------- */

function createFramebuffer (texture) {
  const fb = gl.createFramebuffer()
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb)
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)
  
  gl.bindFramebuffer(gl.FRAMEBUFFER, null)
  return fb
}

function createTexture (width = innerWidth, height = innerHeight) {
  const texture = gl.createTexture()
const level = 0;
  const internalFormat = gl.RGBA;
  const border = 0;
  const format = gl.RGBA;
  const type = gl.UNSIGNED_BYTE;
  const data = null;
  gl.bindTexture(gl.TEXTURE_2D, texture)
  gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, 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)
  gl.bindTexture(gl.TEXTURE_2D, null)
  return texture
}

function makeFullscreenQuad() {
  return {
    vertices: new Float32Array([1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1]),
    uv: new Float32Array([1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1]),
  }
}

function makeProgram (vShader, fShader) {
  const program = gl.createProgram()
  gl.attachShader(program, vShader)
  gl.attachShader(program, fShader)
  gl.linkProgram(program)
  if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
    return program
  }
  console.error(`
    error when linking program:
    ${gl.getProgramInfoLog(program)}
  `)
}

function makeShader (type, src) {
  const shader = gl.createShader(type)
  gl.shaderSource(shader, src)
  gl.compileShader(shader)
  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    return shader
  }
  console.error(`
    error in ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader:
    ${gl.getShaderInfoLog(shader)}
  `)
}
* { margin: 0; padding: 0; }

我想添加添加剂混合来实现像 this tutorial 这样的真正的上帝。

但是,我在理解如何准确地完成此操作时遇到了困难。在本教程中,作者使用了单独的 THREE.AdditiveBlending pass (link),它具有以下片段着色器:

  'uniform sampler2D tDiffuse;',
  'uniform sampler2D tAdd;',
  'uniform float fCoeff;',
  'varying vec2 vUv;',
  'void main() {',
  'vec4 texel = texture2D( tDiffuse, vUv );',
  'vec4 add = texture2D( tAdd, vUv );',
  'gl_FragColor = texel + add * fCoeff;',
'}'

我怀疑 tDiffuse sampler2D 统一是之前所有通道混合在一起的结果,但无法理解这些通道是如何排列的?在我看来,作者以某种方式将每个高斯模糊步骤渲染到另一个帧缓冲区而不清除它?

感谢任何帮助

0 个答案:

没有答案