我正在尝试使用 WebGL 实现 2D 绽放/godrays 效果。出于演示的目的,我将 64x64 gl.POINT 渲染到帧缓冲区,然后通过多次迭代对其应用高斯模糊。以下是我正在采取的步骤:
这行得通,但正如你所见,结果最终变得非常乏味。
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 统一是之前所有通道混合在一起的结果,但无法理解这些通道是如何排列的?在我看来,作者以某种方式将每个高斯模糊步骤渲染到另一个帧缓冲区而不清除它?
感谢任何帮助