pmndrs / lamina

🍰 An extensible, layer based shader material for ThreeJS
MIT License
1.02k stars 42 forks source link

Add mask layers #33

Open supermoos opened 2 years ago

supermoos commented 2 years ago

Hi, is it possible to make a layer that's last in line in the fragment shader renderer. The reason I ask is because I'm trying to create a an AlphaGradient Layer, essentially the same as the Gradient layer, except it works as a "mask" for the whole object, so you should be able to use it to fade out an object selectively using the same params as you use to control the Gradient Layer.

Here's a test were I just tried hardcoding the alpha value to be 0.0 to make it invisible, however this quick test revealed that the alpha value doesn't seem to propegate / override the main material?

import { Abstract } from 'lamina/vanilla'

export default class AlphaGradient extends Abstract {
  static u_colorA = 'white'
  static u_colorB = 'black'
  static u_alpha = 1

  static u_start = 1
  static u_end = -1
  static u_contrast = 1

  static vertexShader = `
        varying vec3 v_position;

        vod main() {
      v_position = lamina_mapping_template;
        }
  `

  static fragmentShader = `
    uniform vec3 u_colorA;
    uniform vec3 u_colorB;
    uniform vec3 u_axis;
    uniform float u_alpha;
    uniform float u_start;
    uniform float u_end;
    uniform float u_contrast;

        varying vec3 v_position;

    void main() {

      float f_step = smoothstep(u_start, u_end, v_position.axes_template * u_contrast);
      vec3 f_color = mix(u_colorA, u_colorB, f_step);

      return vec4(f_color, 0.0);
    }
  `

  axes: 'x' | 'y' | 'z' = 'x'
  mapping: 'local' | 'world' | 'uv' = 'local'

  constructor(props?) {
    super(
      AlphaGradient,
      {
        name: 'AlphaGradient',
        ...props,
      },
      (self: AlphaGradient) => {
        self.schema.push({
          value: self.axes,
          label: 'axes',
          options: ['x', 'y', 'z'],
        })

        self.schema.push({
          value: self.mapping,
          label: 'mapping',
          options: ['uv', 'world', 'local'],
        })

        const mapping = AlphaGradient.getMapping(self.mapping)

        self.vertexShader = self.vertexShader.replace('lamina_mapping_template', mapping || 'local')
        self.fragmentShader = self.fragmentShader.replace('axes_template', self.axes || 'x')
      }
    )
  }

  private static getMapping(type?: string) {
    switch (type) {
      default:
      case 'local':
        return `position`
      case 'world':
        return `(modelMatrix * vec4(position,1.0)).xyz`
      case 'uv':
        return `vec3(uv, 0.)`
    }
  }
}
FarazzShaikh commented 2 years ago

Nope the alpha for all layers along with the base material must be <0 for the overall material to be transparent. Masking is currently not supported. PRs are welcome though 😊

supermoos commented 2 years ago

Got it! do you mean <1 ?

FarazzShaikh commented 2 years ago

Ah yes sorry! <1 Oops 😅

supermoos commented 2 years ago

Could you give me a pointer on how to start something like an override layer, i.e. a layer that runs last in the shader and can modify the final color output?

FarazzShaikh commented 2 years ago

For a layer to appear last it needs to be composed laast in the layer stack.

In react:

<LayerMaterial>
    { /* Some other layers */ }
    <YourLastLayer />
</LayerMaterial>

In vanialla:

new LayerMaterial({
    layers: [
        // Some other layers
        new YourLastLayer()
    ]
})

With the nomal blend mode, the last layer will override all the previous ones

supermoos commented 2 years ago

Right, but it only overrides the previous layers right, not the basematerial or did I miss something?

FarazzShaikh commented 2 years ago

If you use the basic (default) lighting mode then yeah it will override the base material. In other lighting modes there's shading, so you can think of it only overriding the Diffuse color.

Please reach out on the pmndrs discord for discussions/questions. Link is in the readme