jiopjina
jiopjina

Reputation: 21

How to reuse the three.js fragment shader output

I'm new the the glsl shaders in three.js, now trying to make simple and fast blur effect on a mesh texture. This is my fragment shader code:

void blurh(in vec4 inh, out vec4 outh, int r) {
    for(int x = -r; x < r+1; x++) {
        float coordx = vUv[0] + float(x) / uResolution[0];
        coordx = max(0.0, min(1.0, coordx));
        inh += texture2D(uTexture, vec2 (coordx, vUv[1]));
    }
    outh = inh / float(r + r + 1);
}

void blurv(in vec4 inv, out vec4 outv, int r) {
    for(int y = -r; y < r+1; y++) {
        float coordy = vUv[1] + float(y) / uResolution[1];
        coordy = max(0.0, min(1.0, coordy));
        inv += texture2D(uTexture, vec2 (vUv[0], coordy));
    }
    outv = inv / float(r + r + 1);
}

void main() {
    int r = 200; // radius
    gl_FragColor = texture2D(uTexture, vUv);
    blurh(gl_FragColor, gl_FragColor, r);
    blurv(gl_FragColor, gl_FragColor, r);
}
the output result: it supposed to give me a box blur, but it is only bluring on the vertical direction. seems the vertical blur function is not using the horizontal blur result as input. so the first question is, how do I reuse horizontal blur function output as input of vertical blur function? Second is, how do I store/save some variable in fragment shader for later use?

Upvotes: 1

Views: 486

Answers (1)

Mugen87
Mugen87

Reputation: 31076

Notice that the repository already provides blur shaders. So you can use the post-processing pipeline to achieve the intended effect:

let camera, scene, composer;

let mesh;

init();
animate();

function init() {

  camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
  camera.position.z = 1;

  scene = new THREE.Scene();

  const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
  const material = new THREE.MeshNormalMaterial();

  mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  composer = new THREE.EffectComposer(renderer);

  const renderPass = new THREE.RenderPass(scene, camera);
  const effectHBlur = new THREE.ShaderPass(THREE.HorizontalBlurShader);
  const effectVBlur = new THREE.ShaderPass(THREE.VerticalBlurShader);
  effectHBlur.uniforms['h'].value = 1 / window.innerWidth;
  effectVBlur.uniforms['v'].value = 1 / window.innerHeight;

  composer.addPass(renderPass);
  composer.addPass(effectHBlur);
  composer.addPass(effectVBlur);

}

function animate() {

  requestAnimationFrame(animate);

  mesh.rotation.x += 0.01;
  mesh.rotation.y += 0.02;

  composer.render();

}
body {
      margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/EffectComposer.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/ShaderPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/RenderPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/shaders/HorizontalBlurShader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/shaders/VerticalBlurShader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/shaders/CopyShader.js"></script>

If you don't want to use the effect composer, you have to work with plain render targets. Meaning:

  • Render the scene into a render target first (beauty pass)
  • Apply the horizontal blur and render to the next render target
  • Apply the vertical blur and render to screen

So you have to split your current shader into two.

Upvotes: 2

Related Questions