【问题标题】:Use 2 meshes + shader materials with each a different fragment shader in 1 scene (three.js)在 1 个场景中使用 2 个网格 + 着色器材质,每个都有不同的片段着色器(three.js)
【发布时间】:2020-03-29 08:07:55
【问题描述】:

我有 2 个网格,每个网格都有一个 shaderMaterial,每个网格都有一个不同的片段着色器。当我将两个网格都添加到场景中时,只会出现一个。您可以在下面找到我的 2 个片段着色器(查看两张图片以了解它们的外观)。它们基本相同。

我想要实现的目标:使用mesh1 作为掩码,并将另一个mesh2(紫色斑点)放在掩码上。

紫色斑点:

// three.js code
const geometry1 = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
const material1 = new THREE.ShaderMaterial({
  uniforms: this.uniforms,
  vertexShader,
  fragmentShader,
  defines: {
    PR: window.devicePixelRatio.toFixed(1)
  }
});

const mesh1 = new THREE.Mesh(geometry1, material1);
this.scene.add(mesh1);


// fragment shader
void main() {
  vec2 res = u_res * PR;
  vec2 st = gl_FragCoord.xy / res.xy - 0.5;

  st.y *= u_res.y / u_res.x * 0.8;

  vec2 circlePos = st;
  float c = circle(circlePos, 0.2 + 0. * 0.1, 1.) * 2.5;

  float offx = v_uv.x + sin(v_uv.y + u_time * .1);
  float offy = v_uv.y * .1 - u_time * 0.005 - cos(u_time * .001) * .01;
  float n = snoise3(vec3(offx, offy, .9) * 2.5) - 2.1;

  float finalMask = smoothstep(1., 0.99, n + pow(c, 1.5));

  vec4 bg = vec4(0.12, 0.07, 0.28, 1.0);
  vec4 bg2 = vec4(0., 0., 0., 0.);

  gl_FragColor = mix(bg, bg2, finalMask);
}

蓝色面具

// three.js code
const geometry2 = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
const material2 = new THREE.ShaderMaterial({
  uniforms,
  vertexShader,
  fragmentShader,
  defines: {
    PR: window.devicePixelRatio.toFixed(1)
  }
});

const mesh2 = new THREE.Mesh(geometry2, material2);
this.scene.add(mesh2);

// fragment shader
void main() {
  vec2 res = u_res * PR;
  vec2 st = gl_FragCoord.xy / res.xy - 0.5;

  st.y *= u_res.y / u_res.x * 0.8;

  vec2 circlePos = st;
  float c = circle(circlePos, 0.2 + 0. * 0.1, 1.) * 2.5;

  float offx = v_uv.x + sin(v_uv.y + u_time * .1);
  float offy = v_uv.y * .1 - u_time * 0.005 - cos(u_time * .001) * .01;
  float n = snoise3(vec3(offx, offy, .9) * 2.5) - 2.1;

  float finalMask = smoothstep(1., 0.99, n + pow(c, 1.5));

  vec4 bg = vec4(0.12, 0.07, 0.28, 1.0);
  vec4 bg2 = vec4(0., 0., 0., 0.);

  gl_FragColor = mix(bg, bg2, finalMask);
}

渲染目标代码

this.rtWidth = window.innerWidth;
this.rtHeight = window.innerHeight;
this.renderTarget = new THREE.WebGLRenderTarget(this.rtWidth, this.rtHeight);

this.rtCamera = new THREE.PerspectiveCamera(
  this.camera.settings.fov,
  this.camera.settings.aspect,
  this.camera.settings.near,
  this.camera.settings.far
);
this.rtCamera.position.set(0, 0, this.camera.settings.perspective);

this.rtScene = new THREE.Scene();
this.rtScene.add(this.purpleBlob);

const geometry = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight, 1);
const material = new THREE.MeshPhongMaterial({
  map: this.renderTarget.texture,
});

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

this.scene.add(this.mesh);

我还是个着色器新手,所以请耐心等待。 :-)

【问题讨论】:

  • 一种解决方案是使用单独的场景和/或render targets 来实现这一点。然后,您可以通过渲染目标将遮罩渲染到纹理,然后渲染第二个将带有遮罩的纹理传递到第二个着色器,类似于how the EffectsComposer works
  • @gman 谢谢,我想最简单的选择是创建一个额外的场景还是我错了?另外,如果我有多个场景,性能会更差吗?
  • 是的,创建另一个场景(和一个渲染目标,以便您可以从第一个场景生成遮罩纹理)。不,它不会更慢。虽然这取决于你真正想要做什么。如果您想对事物进行硬编码,您可以将两个着色器组合成一个着色器。将掩码中的main 更改为mainMask 并让它返回最后一个值,而不是将其分配给gl_FragColor。现在从第二个着色器调用它以获取蒙版。
  • @gman 我为渲染目标添加了代码。由于我使用紫色 blob 进行页面转换,这将出现在每个页面上,因此我想将其用作我的渲染目标。你介意帮助我在我的蒙版着色器中传递纹理吗?
  • 在您的问题中以snippet 发布可运行代码。您可以看到很多可运行的three.js sn-ps 示例,例如this one

标签: three.js shader


【解决方案1】:

three.js 中可能有无数种掩码方法。这里有一些

  1. 使用模板缓冲区

    模板缓冲区类似于深度缓冲区,因为它对于画布或渲染目标中的每个像素都有一个对应的模板像素。你需要告诉three.js你想要一个模板缓冲区,然后你可以在渲染时告诉它在你绘制东西时如何处理模板缓冲区。

    Material上的模板设置

    你告诉三个.js

    • 如果您绘制的像素未能通过模板测试怎么办
    • 如果您的绘图像素未通过深度测试怎么办
    • 如果您绘制的像素通过了深度测试,该怎么办。

    你可以告诉它为每个条件做的事情是保持(什么都不做)、递增、递减、递增环绕、递减环绕、设置为特定值。

    您还可以通过设置Material.stencilFunc 来指定模板测试是什么

    因此,例如,您可以将模板缓冲区清除为 0(默认值?),将模板测试设置为始终通过,并设置条件,以便如果深度测试通过,则将模板设置为 1。然后绘制一堆东​​西。现在,无论在哪里绘制它们,然后模板缓冲区中都会有一个 1。

    现在您更改模板测试,使其仅在等于 1(或 0)时通过,然后绘制更多内容,现在只会在模板等于您设置的值的地方绘制内容

    This exmaple uses the stencil

  2. 带有 alpha 蒙版的蒙版

    在这种情况下,您需要 2 个颜色纹理和一个 alpha 纹理。你如何得到这些取决于你。例如,您可以从图像中加载所有 3 个。或者您可以使用 3 个渲染目标生成所有 3 个。最后,您将所有 3 个传递给一个着色器,将它们混合在一起,如

    gl_FragColor = mix(colorFromTexture1, colorFromTexture2, valueFromAlphaTexture);
    

    This example uses this alpha mixing method

    请注意,如果您的 2 种颜色纹理之一具有 Alpha 通道,则您可以只使用 2 个纹理。您只需传递其中一种颜色纹理作为您的蒙版。

    当然,您也可以根据一幅图像或另一幅图像或两者的颜色计算遮罩。例如

    // assume you have function that converts from rgb to hue,saturation,value
    vec3 hsv = rgb2hsv(colorFromTexture1.rgb);
    float hue = hsv.x;
    // pick one or the other if color1 is close to green
    float mixAmount = step(abs(hue - 0.33), 0.05); 
    gl_FragColor = mix(colorFromTexture1, colorFromTexture2, mixAmount);
    

    这里的重点不是那个确切的代码,而是你可以根据你想要的任何东西,颜色,位置,随机数学,基于时间的正弦波,一些生成斑点的公式,为面具制作任何你想要的公式, 任何。最常见的是一些代码,它只是从纹理中查找 mixAmount,这就是上面链接示例所做的。

  3. ShaderToy 风格

    您上面的代码似乎是一个shadertoy 风格的着色器,它正在绘制一个全屏四边形。您可以在同一个着色器中绘制它们而不是绘制 2 个单独的东西

    vec4 computeBlueBlob() {
      ...
      return blueBlobColor;
    }
    
    vec4 computeWhiteBlob() {
      ...
      return whtieBlobColor;
    }
    
    vec4 main() {
      vec4 color1 = computeBlueBlob();
      vec4 color2 = computeWhiteBlob();
      float mixAmount = color.a;  // note: color2.a could be any
                                  // formula to decide which colors
                                  // to draw
      gl_FragColor = mix(color1, color2, mixAmount);
    }
    

    请注意,就像上面一样,您如何计算 mixAmount 取决于您。基于任何东西,color1.r,color2.r,一些公式,一些色调,一些其他 blob 生成函数,等等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-17
    • 2017-04-14
    • 1970-01-01
    • 2020-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多