Giter Club home page Giter Club logo

three-customshadermaterial's Introduction

Custom Shader Material

Hero

Extend Three.js standard materials with your own shaders!


Waves Points Caustics

The demo is real, you can click it! It contains full code, too. ๐Ÿ“ฆ


Chat on Twitter


Custom Shader Material (CSM) lets you extend Three.js' material library with your own Vertex and Fragment shaders. It Supports both Vanilla and React!

Show Vanilla example
import CustomShaderMaterial from 'three-custom-shader-material/vanilla'

function Box() {
  const geometry = new THREE.BoxGeometry()
  const material = new CustomShaderMaterial({
    baseMaterial: THREE.MeshPhysicalMaterial,
    vertexShader: /* glsl */ ` ... `,
    fragmentShader: /* glsl */ ` ... `,
    silent: true, // Disables the default warning if true
    uniforms: {
      uTime: {
        value: 0,
      },
    },
    flatShading: true,
    color: 0xff00ff,
  })

  return new THREE.Mesh(geometry, material)
}
Show React example
import CustomShaderMaterial from 'three-custom-shader-material'

function Cube() {
  const materialRef = useRef()

  useFrame((state) => {
    if (materialRef.current) {
      materialRef.current.uniforms.uTime.value = state.clock.elapsedTime
    }
  })

  return (
    <mesh>
      <boxGeometry />
      <CustomShaderMaterial
        ref={materialRef}
        baseMaterial={THREE.MeshPhysicalMaterial}
        vertexShader={/* glsl */ ` ... `}
        fragmentShader={/* glsl */ ` ... `}
        {/*silent parameter to true disables the default warning if needed*/}
        silent
        uniforms={{
          uTime: {
            value: 0,
          },
        }}
        flatShading
        color={0xff00ff}
        // ...
      />
    </mesh>
  )
}
Show Vue (Tresjs) example

You need to have installed Cientos pkg to use this component.

<script setup>
import { CustomShaderMaterial } from '@tresjs/cientos'

</script>
<template>
  <TresMesh>
    <TresTorusKnotGeometry :args="[1, 0.3, 512, 32]" />
    <CustomShaderMaterial
      :baseMaterial="THREE.MeshPhysicalMaterial"
      :vertexShader="yourGLSLVertex"
      :fragmentShader="yourGLSLFragment"
      :uniforms="yourUniforms"
      silent
    />
  </TresMesh>
</template>

Installation

npm install three-custom-shader-material
yarn add three-custom-shader-material

Output Variables

CSM provides the following output variables, all of them are optional but you MUST use these variables like you would use standard GLSL output variables to see results.

Variable Type Description Available In Notes
csm_Position vec3 Custom vertex position. Vertex Shader csm_Position will be projected furthur down the line. Thus, no projection is needed here.
csm_PositionRaw vec3 Direct equivalent of gl_Position. Vertex Shader
csm_Normal vec3 Custom vertex normals. Vertex Shader
csm_PointSize float Custom gl_PointSize. Vertex Shader Only available in PointsMaterial
- - - - -
csm_DiffuseColor vec4 Custom diffuse color. Fragment Shader
csm_FragColor vec4 Direct equivalent of gl_FragColor. Fragment Shader csm_FragColor will override any shading applied by a base material. To preserve shading and other effects like roughness and metalness, use csm_DiffuseColor
csm_Emissive vec3 Custom emissive color. Fragment Shader Only available in MeshPhysicalMaterial and MeshStandardMaterial
csm_Roughness float Custom roughness. Fragment Shader Only available in MeshPhysicalMaterial and MeshStandardMaterial
csm_Metalness float Custom metalness. Fragment Shader Only available in MeshPhysicalMaterial and MeshStandardMaterial
csm_AO float Custom AO. Fragment Shader Only available in materials with an aoMap.
csm_Bump vec3 Custom Bump. Fragment Shader Only available in materials with a bumpMap.
csm_DepthAlpha float Custom alpha for depth material. Lets you control customDepthMaterial with the same shader as your regular materials. Fragment Shader
// gl_Position = projectionMatrix * modelViewPosition * position * vec3(2.0);
csm_Position = position * vec3(2.0);

Custom overrides

You can define any custom overrides you'd like using the patchMap prop. The prop is used as shown below.

const material = new CustomShaderMaterial({
   baseMaterial: THREE.MeshPhysicalMaterial,
   vertexShader: ` ... `,
   fragmentShader: ... `,
   uniforms: {...},
   patchMap={{
      "<KEYWORD>": {        // The keyword you will assign to in your custom shader
        "TO_REPLACE":       // The chunk you'd like to replace.
          "REPLACED_WITH"   // The chunk you'd like put in place of `TO_REPLACE`
      }
   }}
})

Extending already extended materials

CSM allows you to extend other CSM instances or materials that already use onBeforeCompile to extend base functionality such as MeshTransmissionMaterial. It is as simple as passing a ref to that material in as the baseMaterial:

Show Vanilla example
import CustomShaderMaterial from 'three-custom-shader-material/vanilla'

function Box() {
  const material1 = new CustomShaderMaterial({
    baseMaterial: THREE.MeshPhysicalMaterial,
    //...Any props
  })
  const material2 = new CustomShaderMaterial({
    baseMaterial: material1,
    //...Any props
  })

  // OR
  const material1 = new MeshTransmissionMaterial()
  const material2 = new CustomShaderMaterial({
    baseMaterial: material1,
    //...Any props
  })
}
Show React example
import CustomShaderMaterial from 'three-custom-shader-material'
import CustomShaderMaterialImpl from 'three-custom-shader-material/vanilla'

function Cube() {
  const [materialRef, setMaterialRef] = useState()
  // OR
  const materialRef = useMemo(
    () =>
      new CustomShaderMaterialImpl({
        baseMaterial: THREE.MeshPhysicalMaterial,
      }),
    []
  )

  return (
    <>
      <CustomShaderMaterial
        ref={setMaterialRef}
        baseMaterial={THREE.MeshPhysicalMaterial}
        //...Any props
      />

      {materialRef && (
        <CustomShaderMaterial
          baseMaterial={materialRef}
          //...Any props
        />
      )}
    </>
  )
}
Show Vue (Tresjs) example
<script setup>
import {ref } from 'vue'
import { CustomShaderMaterial } from '@tresjs/cientos'

const materialBase = ref()
</script>

<template>
  <TresMesh>
    <TresTorusKnotGeometry :args="[1, 0.3, 512, 32]" />
    <CustomShaderMaterial
        ref="materialBase"
        :baseMaterial="THREE.MeshPhysicalMaterial"
        :vertexShader="yourGLSLVertex"
        :fragmentShader="yourGLSLFragment"
        :uniforms="yourUniforms"
        silent
        />
  </TresMesh>

  <TresMesh v-if="materialBase">
    <TresSphereGeometry :args="[0.5, 16, 16]" />
    <CustomShaderMaterial
      :base-material="new CustomShaderMaterialImpl({
        baseMaterial: materialBase.value,
      })"
    />
  </TresMesh>
</template>

Gotchas

  • When extending already extended material, variables, uniforms, attributes, varyings and functions are NOT scoped to the material they are defined in. Thus, you WILL get redefinition errors if you do not manually scope these identifiers.

  • When using an instance of CSM as the baseMaterial, or chining multiple CSM instances, or when extending any material that uses onBeforeCompile the injection order is as follows:

    void main() {
      // shader A
      // shader B
      // shader C
      // shader D
    
      // original shader
    }

    The last injected shader will contain values set in all the shaders injected before it.

Performance

CSM is great for small overrides. Even though doable, if you find yourself lost in a patchMap, it's often simpler to just make a ShaderMaterial with the necessary #includes.

CSM has a non-negligible startup cost, i.e it does take more milliseconds than a plain ShaderMaterial to load. However, once loaded it is practically free. It also uses caching so reusing materials will not incur any extra cost. Couple important notes about performance:

  • Changing these props will rebuild the material

    • baseMaterial
    • fragmentShader
    • vertexShader
    • uniforms
    • cacheKey
  • <meshPhysicalMaterial /> and <CSM baseMaterial={meshPhysicalMaterial}> are the same, and will use the same cached shader program. The default cache key is such:

    hash(stripSpaces(fragmentShader) + stripSpaces(vertexShader) + serializedUniforms)

    You can provide your own cache key function via the cacheKey prop.

Note: CSM will only rebuild if the reference to the above props change, for example, in React, doing uniforms={{...}} means that the uniforms object is unstable, i.e. it is re-created, with a new reference every render. Instead, condsider memoizing the uniforms prop const uniforms = useMemo(() -> ({...}));. The uniforms object will then have the same refrence on every render.

If the uniforms are memoized, changing their value by doing uniforms.foo.value = ... will not cause CSM to rebuild, as the refrence of uniforms does not change.

Development

git clone https://github.com/FarazzShaikh/THREE-CustomShaderMaterial
cd THREE-CustomShaderMaterial
yarn
yarn dev

Will watch for changes in package/ and will hot-reload examples/debug.

License

MIT License

Copyright (c) 2021 Faraz Shaikh

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

three-customshadermaterial's People

Contributors

chasedavis avatar farazzshaikh avatar hmans avatar jaimetorrealba avatar madou avatar seantai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

three-customshadermaterial's Issues

Textures

I'm having trouble adding new textures to a material.

I see them in the uniforms, if I add the texture using map (or any of the other slots) they work fine but if I try to access the textures in the shader, nothing.

I'm going to assume this is a problem with something I am doing, but maybe you can see a mistake.

const uniforms = {
myTexture: DEFAULT_TEXTURE // just a data texture that goes from white to black
}

export const MaterialProps = {
  baseMaterial: MeshStandardMaterial,
  vertexShader, // set elsewhere
  fragmentShader, // set elsewhere
  uniforms,
};

<CustomShaderMaterial 
    map={myTexture}
    myTexture={myTexture}
    {...MaterialProps} 
/> 

Then in the frag shader, this won't throw an error but the texture will not show up.

uniform sampler2D myTexture; 
 void main() {
  csm_DiffuseColor = texture2D(myTexture, vUUv); // a custom UV confirmed to work
 }

if I replace this with this whetever is assigned to map shows up.

uniform sampler2D map; 
 void main() {
  csm_DiffuseColor = texture2D(map, vUUv);
 }

So it just doesn't like the new texture uniform.

Any help or direction is greatly appreciated.

Shaders not working

Hi there,

Sorry for posting this as an issue, but I've been trying to create a Fresnel effect on a mesh and didn't manage to accomplish anything. So, I'm trying not to waste any more hours.

Does anyone have an example of how I can add the Fresnel using CustomShaderMaterial?

My current code is this:

<mesh position={[15, 0, 0]}>
    <sphereGeometry args={[5, 100, 100]} />
    <CustomShaderMaterial
        baseMaterial={THREE.MeshPhysicalMaterial}
        color={'#612700'}
        vertexShader={`
            varying vec3 vPositionW;
            varying vec3 vNormalW;
            void main() {
                vPositionW = normalize(vec3(modelViewMatrix * vec4(position, 1.0)).xyz);
                vNormalW = normalize(normalMatrix * normal);
                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            }
        `}
        fragmentShader={`
            varying vec3 vPositionW;
            varying vec3 vNormalW;
            void main() {   
                float fresnelTerm = ( 1.0 - -min(dot(vPositionW, normalize(vNormalW) ), 0.0) );    
                gl_FragColor = vec4(1.0,0.0,0.0,1.0) * fresnelTerm;
            }
        `}
        roughness={0.65}
        reflectivity={1}
    />
</mesh>

But it doesn't have any effect.
Any help would be greatly appreciated since this has been driving me crazy.

Thank you.

Warning: Function copy already exists on CSM, renaming to base_copy

image

I have often this warning in the console, I wonder if there is a way to disable it?

If not, would you agree to add a verbose property that we can set to false that would disable any warning in the console ?

For instance:

<CustomShaderMaterial
              baseMaterial={MeshPhongMaterial}
              uniforms={{}}
              verbose={false}
/>

Thank you in advance.

.clone() does not seem to copy shaders

The material created by the .clone() function on CustomShaderMaterial instances seems to be missing shaders.

CSB available at https://codesandbox.io/s/transformcontrols-and-makedefault-forked-863utu?file=/src/App.js
On the CSB, clicking any mesh switches colors, when it shouldn't. The code switches between material1 which correctly appears red, and its clone material2, which incorrectly appears white.

Ideally, clone should copy shaders without recompiling them. At a minimum, it should copy with recompile. Currently, it does neither.

ERROR: 0:645: 'csm_Normal' undeclared identifier

Version 3.5.3 throws shader compilation errors with lamina.

image

ERROR: 0:645: 'csm_Normal' : undeclared identifier
ERROR: 0:645: 'assign' : l-value required (can't modify a const)
ERROR: 0:645: '=' : dimension mismatch
ERROR: 0:645: 'assign' : cannot convert from 'highp 3-component vector of float' to 'const highp float'

Excellent work on this lib by the way!
I humbly recommend following semvar to the letter if you've got inter dependencies between your own libs like this one and lumina.
I mention this only because I read you here:

I like to keep breaking changes to minor releases.
per pmndrs/lamina#38 (comment)

Be very careful with this approach. Any breaking changes should change the major version for your own sake. The package managers have built-in support for semvar, but will not support a new paradigm that looks like semvar (in moving breaking changes from major to minor, people may be caught unawares, even you the creator!!; perhaps the life you save can be your own <3).

Examples not working

I've just thought about the possibility to extend the standard materials with custom shaders today, and then I stumbled upon this. Seems to be exactly what I've been thinking of, and seems to be well done. Unfortunately, the two external examples you have provided have some issues that prohibit them to work. Please check them.

CSM doesn't source texture maps

When creating a CSM based off MeshStandardMaterial and assigning a map prop referencing a texture, the texture will only be used when not passing a fragment shader chunk to the CSM, or passing a fragment shader chunk that doesn't have a main entrypoint. As soon as you pass a chunk with a main, the diffuse/fragment colors will always reset to red (or whatever color you end up setting yourself in the shader.)

Currently, you need to work around this by sourcing the texture yourself:

void main() {
  csm_FragColor = texture2D(map, vUv);
}

Problem with using csm_PositionRaw and InstancedMesh

Hello, @FarazzShaikh, thank you for awsome extension of three.js.

I found a bug with using csm_PositionRaw with InstancedMesh. See screenshots:

1) vertex shader:

void main() {
    csm_PositionRaw = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

all vertices are set to the zero position
image

2) vertex shader (empty):

void main() {
}

or

void main() {
    csm_Position = position;
}

works fine:
image

Failed to resolve module specifier "crypto"

I get this error after importing, what's going on, I don't have crypto in my project

error message๏ผšFailed to resolve module specifier "crypto"

version๏ผšthree-custom-shader-material 5.3.8

CustomShaderMaterial does not support clone()

Problem

Trying to clone() CustomShaderMaterial does not work currently and gives this error:

TypeError: Cannot destructure property 'baseMaterial' of 'undefined' as it is undefined.

Cause

The reason is that the clone method is inherited from THREE.Material and looks like this

	clone() {
		return new this.constructor().copy( this );
	}

But the constructor looks like this (it destructures its first argument, which is not provided by clone).

  constructor({
    baseMaterial,
    fragmentShader,
    vertexShader,
    uniforms,
    patchMap,
    cacheKey,
    ...opts
  }) {
    ...
  }

Workaround

/**
 * This class is a workaround to make CustomShaderMaterial support clone.
 */
class CustomShaderMaterial2 extends CustomShaderMaterial {
  parameters: iCSMParams;
  constructor(parameters: iCSMParams) {
    super(parameters);
    this.parameters = parameters;
  }
  clone() {
    return new this.constructor(this.parameters).copy(this);
  }
}

Is it possible to output a normal from a fragment shader?

Hello and thanks for this great software.

I am extending MeshStandardMaterial and would like to output a normal per pixel from my fragment shader but I see that csm_Normal is vertex-shader-only.

How can I address this? My goal is to build a material that has an animating normal map but leverages all of the lighting and other potential of the standard material.

Please tell me it's possible. Perhaps with patchMap? I do not see docs and I'm on my phone and can't look at the example code easily.

Thank you!

Support macro defines like THREE.ShaderMaterial

Hello!

THREE.ShaderMaterial supports custom macro definitions: https://threejs.org/docs/?q=shader#api/en/materials/ShaderMaterial.defines

When I try to do the same thing with this library, the shader fails to compile. I'm guessing the materials are compiled before threejs injects the custom defines (or maybe they're just ignored currently).

A basic example (using vanilla threejs but works the same with React):

let material= new CustomShaderMaterial({
  baseMaterial: THREE.MeshStandardMaterial,
  fragmentShader: `
    void main() {
#if RED
      csm_DiffuseColor = vec4(1, 0, 0, 1);
#else
      csm_DiffuseColor = vec4(0, 1, 0, 1);
#endif
    }
  `,
  defines: {
    RED: false,
  }
});

Fails to compile the shader when you use it with:

ERROR: 0:1253: 'RED' : unexpected token after conditional expression

With ShaderMaterial defines, #define RED false would be injected at the top of the shader so things would compile. Obviously changes to defines would need to also trigger a recompilation.

How to supply the fragment shader uniforms?

Hi!
After choosing a material (for example here I'll use MeshPhysicalMaterial; as that's my primary use case), it is unclear how to access or supply the usual parameters for that material. For the built in materials, I would usually specify them at the time of creation such as:

new MeshPhysicalMaterial({ map: myTexture, transparent:true, clearcoat:1, envMap: myEnvMap, side: DoubleSide, });

Or, set them afterwards via something like

material.color.set(0x00FFFF);

If its helpful - I have found that you may access and modify uniforms of the CustomShaderMaterial via material.unforrms, for example after declaring a material of the type CustomShaderMaterial, and running the following:

    material.uniforms.clearcoat.value=1.;
    material.uniforms.opacity.value=0.5;
    material.uniforms.map.value=myTexture;
    console.log(material.uniforms);

I see that the uniform values are appropriately updated in the console log. But then should I use this material in a mesh, none of these changes are visible, so either these are not the correct values for me to be updating, or the updates are not making it to the fragment shader somehow?

UPDATE:
If I follow the above by
material.uniformsNeedUpdate=true;
before creating a mesh with the material, it appears I can indeed change the opacity, but so far nothing else visibly, despite the values in material.uniforms appearing changed.

One thing that leads me to think that I am going about this wrong is that (I think) at the end of the day, as far as THREEJS knows, the material I have made is a ShaderMaterial, so while I could access the map of the actual PhysicalMaterial via

material.map

(ie directly, without 'uniforms') trying such a thing on the custom material returns 'undefined'

Passing uniforms to shader

Hi , I was having a problem with using TypeScript and sending uniforms to custom shader so I tried your package.
I did a setup like youre typescript example, except if i tried to use physical material as base nothing was seen on screen happened, so I put ShaderMaterial then I see red cube. I put one of the rgb channels as sin(uTime) but nothing happens. So uniform is not reaching shader ?


function Box(props: JSX.IntrinsicElements['mesh']) {
  const materialRef = useRef<CustomShaderMaterialType | null>(null)


  useFrame((state) => {
    if (materialRef.current) {
      materialRef.current.uniforms.uTime.value = state.clock.elapsedTime
    }
  })

  return (
    <mesh>
      <boxGeometry args={[1, 1, 1]} />
      <CustomShaderMaterial
        ref={materialRef}
        baseMaterial={ShaderMaterial}
        vertexShader={vertex}
        fragmentShader={fragment}
        uniforms={{
          uTime: {
            value: 0,
          },
        }}
        // ...
      />
    </mesh>
  )
}
        
export const vertex =`
      precision mediump float;
      uniform float uTime;

      void main(){
        vec4 pos = vec4(position, 1.0);
        vec4 modelPosition = modelMatrix * pos;
        vec4 viewPosition = viewMatrix * modelPosition;
        vec4 projectedPosition = projectionMatrix * viewPosition;

        gl_Position = projectedPosition;
      }
    `
export const fragment = `
      precision mediump float;
      uniform float uTime;


      void main() {

        vec3 color = vec3(0.0, 0.0, sin(uTime));

        gl_FragColor = vec4(color, 1.0);
      }
    `

Material gets recreated on every render loop

This thing is amazing - but I've noticed the material gets recreated each loop - even if the props are the same :(

if you add a console.log(material.current) to your water example it should happen there too.

I am not sure how, but I believe the problem is here: https://github.com/FarazzShaikh/THREE-CustomShaderMaterial/blob/master/src/index.tsx#L18-L22

If I add the material to my tree as customerShaderMaterial and pass the args array the way the constructor expects it works.

TresJs implementation

Hi! would you accept a PR, to contain the implementation for using CSM with TresJs?

TresJs is the R3F of Vue.

I have already playing with your library but using the vainilla version example
The import will do like:
import CustomShaderMaterial from 'three-custom-shader-material/tresjs' (or similar)

And you can also be use easily with the Vue 3D comunity :)

"Multiple instances of Three.js being imported" issue

Hey I'm using your package in a project I'm working on, great library btw, and I get this warning.

Screen Shot 2022-09-27 at 4 07 28 PM

I don't know too much about package creation but I think all you have to do to fix it is not export your node_modules along with the npm, I think. At least when I remove the node_modules and reinstall the warning goes away.

Screen Shot 2022-09-27 at 5 37 24 PM

Shader compilation failure if csm_Feature used is not compatible with base material

Certain csm features like emissive, metalness, roughness, ao, bump only work with certain materials, makes sense!

It'd be great if rather than a shader compilation issue, there was graceful failure when those features are attempted to be used with an incompatible base material. A big use case for this would be for building material editors or otherwise offering some configuration options for materials as is popular.

I believe it can be worked around through patchmaps but might also be feasible or easy to consider doing nothing with the variable assignment (meaning, it's still declared in CSM compilation).

Thoughts?

Screen Shot 2023-04-18 at 11 28 02 PM

pictured: trying to use metalness/roughness with MeshBasicMaterial as base

Example use: Complex Function Grapher

Not an issue but an example putting the custom shader material to use (I think this is where you said to post it still?). Starting with PlaneGeometry, rewrite the vertex shader to map points to a parametric surface (here, a projection to R3 of the graph of some complex polynomials in R4).
The result is then textured using a fragment shader whose output is used as the map: on a MeshPhysicalMaterial.

Screen Shot 2021-05-15 at 10 53 32

Screen Shot 2021-05-15 at 10 54 05

Screen Shot 2021-05-15 at 10 59 37

As a comparison with a similar program I wrote doing this with THREE.ParametricGeometry (re-creating the geometry when the function changes), CustomShaderMaterial let me subdivide the domain 100x finer, at which point the result was still running 4x faster (60fps) than my original version.

Accessing csm_FragColor values in if statement causes white rendering

Hi!

I'm attempting to achieve a specific effect on my mesh, where objects beyond a certain distance have full opacity, and those nearer to the viewer than the threshold have reduced opacity. So far, it seems like a straightforward task. I've created a new CustomShaderMaterial, based on the existing material of the mesh:

const material = new CustomShaderMaterial({
    baseMaterial: child.material,
    fragmentShader: `
    void main() {
        if (csm_FragColor[0] > 0.0) {
            // Some operation
        }
    }
});

The issue arises when I try to access the csm_FragColor values (even without writing to them). Strangely, the mesh renders fully white. However, if I comment out or remove the if statement (or any other statement that accesses csm_FragColor), the rendering appears correct. Is this a bug, or am I overlooking something simple?

Thanks!

Override fragment normals?

Would be nice to override normals in the fragment shader as well.

In the custom shader I was trying to port over to CSM I sample one or more normal maps and blend them together:

Screen Shot 2022-08-14 at 1 32 04 PM

more specifically updating the variable mapN or Normal is what I care about I guess: https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js#L23
https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js#L28

Can you disable projection in the vertex shader?

Hello! I'm trying to implement a billboard effect for trees (example video of the effect below) and am running into issues with how Three handles custom shaders (in essence: all or nothing!) which makes things way harder than they need to be. Your library looks great but I'm unable to use it currently because of the "projection happening down the line" part.

Screen.Recording.2022-10-31.at.9.07.38.pm.mov

In my vertex shader I project into MV space to then apply the offset. Is it possible to do the projection ourselves?

How to change the opacity of extended material ?

Hello

I would like to change only the alpha component of final color but cannot figure out how to do so.

Is this something that is currently easily feasible ?

To illustrate what I'm trying to achieve, here's to different pseudo-code solutions that would both satisfy my use case.

// In this pseudo-code example, the (imaginary) csm_Alpha is used to set the final gl_FragColor alpha component
void main() {
    csm_Alpha = 0.5;
}

// In this other pseudo-code example, I am able to retrieve the "temporary color" and update its alpha component.
// The cms_TemporaryColor is not a "real property", here it would hold the color value as set by the extended material, before any CSM modification.
void main() {
    csm_DiffuseColor = vec4(cms_TemporaryColor.r, cms_TemporaryColor.g, cms_TemporaryColor.b, 0.5);
}

Using the Material's opacity property is not a viable option in my case because I am working with an InstancedBufferGeometry and would like every instance to have a different alpha value.

Thanks!

Couldn't extend `<MeshReflectorMaterial/>` from `drei`

I tried to extend the <MeshReflectorMaterial/> from drei, but it results in black material. I'm not sure what cause the problem, as I did haven't used the fragment shader yet.

<MeshReflectorMaterial
  ref={setMaterialRef}
  ...
/>

<mesh position={[0, -0, 0]} rotation-x={-Math.PI / 2}>
  <planeGeometry args={[50, 50]} />
  {materialRef && (
    <OceanMaterial
      baseMaterial={materialRef}
      ...
    />
  )}
</mesh>

I have replicate the issue here: https://codesandbox.io/p/sandbox/ocean-material-889c76?file=%2Fsrc%2FScene.tsx%3A2%2C10-2%2C31

CustomShaderMaterial error

Paper:

   <div>
     <MainLayoutSeo title={'Home'}>
       <Paper script={main} />
     </MainLayoutSeo>
   </div>
main: 
    import * as THREE from 'three';
import { CustomShaderMaterial, TYPES } from 'three-custom-shader-material';
import { loadShadersCSM } from 'gl-noise';
import { initScene } from './setup.js';
import lights from './light';


const v = {
  defines: './static/shaders/particle_defines.glsl',
  header: './static/shaders/particle_header.glsl',
  main: './static/shaders/particle_main.glsl',
};

const f = {
  defines: './static/shaders/frag/defines.glsl',
  header: './static/shaders/frag/header.glsl',
  main: './static/shaders/frag/main.glsl',
};

export async function main(canvas) {
  const { defines: vdefines, header: vheader, main: vmain } = await loadShadersCSM(v);
  const { defines: fdefines, header: fheader, main: fmain } = await loadShadersCSM(f);

  const { scene, renderer, camera, controls } = initScene(canvas);
  camera.position.set(10, 10, 10);

  lights(scene);

  const loader = new THREE.TextureLoader();
  const disk = loader.load('./static/textures/circle-sprite.png');

  const geometry = new THREE.IcosahedronGeometry(4, 32);
  console.log(geometry.attributes.position.count);
  const material = new CustomShaderMaterial({
    baseMaterial: TYPES.POINTS,

    vShader: {
      defines: vdefines,
      header: vheader,
      main: vmain,
    },
    fShader: {
      defines: fdefines,
      header: fheader,
      main: fmain,
    },
    uniforms: {
      uShift: {
        value: 0,
      },
      uShape: {
        value: disk,
      },
      uScale: {
        value: window.innerHeight / 2,
      },
      uTime: {
        value: 0,
      },
      uTargetPos: {
        value: new THREE.Vector3(0),
      },
    },
    passthrough: {
      size: 0.1,
    },
  });

  const points = new THREE.Points(geometry, material);

  scene.add(points);

  const targetPos = new THREE.Vector3();

  renderer.domElement.addEventListener('pointermove', (event) => {
    var vec = new THREE.Vector3(); // create once and reuse
    var pos = new THREE.Vector3(); // create once and reuse
    vec.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
    vec.unproject(camera);
    vec.sub(camera.position).normalize();
    var distance = -camera.position.z / vec.z;
    pos.copy(camera.position).add(vec.multiplyScalar(distance));
    targetPos.x = pos.x;
    targetPos.y = pos.y;
    targetPos.z = pos.z;
  });

  function render(time) {
    if (material && material.uniforms) {
      material.uniforms.uTime.value = time * 0.001;
      material.uniforms.uTargetPos.value = targetPos;
    }

    controls.update();

    renderer.render(scene, camera);
  }

  return { render, cleanup };
}

error: Cannot read properties of undefined (reading 'POINTS')

Cannot use both csm_DiffuseColor and csm_FragColor in if/else

When in opaque mode, my shader uses diffuse color to allow there to be lighting on the mesh. When not in opaque mode, my shader forms a neon overlay. But using both, even in an exclusive if/else statement, doesn't work. Heck, even commenting "// csm_FragColor" anywhere breaks the code.

uniform bool opaque;

void main() {
  if (opaque) {
    csm_DiffuseColor = vec4(1.0);
  else {
    csm_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
  }
}

Extending ShadowMaterial

I'd like to extend the three.js ShadowMaterial so I can apply shadows to my custom shader. When I try to do this my shader code gets ignored and the default ShadowMaterial is rendered (ie. the fragments in shadow render as black, the rest of the mesh is transparent).

Looking at the source code for ShadowMaterial, I can see it explicitly sets the gl_FragColor, whereas the other materials use outgoingLight as their output:

gl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );

Am I approaching this wrong or is this not possible with the library currently?

If the base material has a map, a CSM from it throws an error on three >= 0.151.0

Managed to repro here:
https://codesandbox.io/s/customshadermaterial-hero-forked-0976xh?file=/src/App.jsx
you do need to open your browser console to see this as it doesn't show up in CSB's console.

I believe this is related to this change https://github.com/mrdoob/three.js/pull/25740/files#diff-202a730a51aad3c22e059efd980a6cd5c5d0637a6abd240b3bdee3e5ff30aaa2L530 from this PR mrdoob/three.js#25740

Relevant error (line numbers may be different):

FRAGMENT ERROR: 0:1553: 'vUv' : undeclared identifier

  1548:     #if defined IS_UNKNOWN || defined IS_SHADERMATERIAL || defined IS_MESHDEPTHMATERIAL || defined IS_MESHNORMALMATERIAL || defined IS_SHADOWMATERIAL
  1549:         vec4 csm_DiffuseColor = vec4(1., 0., 1., 1.);
  1550:         vec4 csm_FragColor = vec4(1., 0., 1., 1.);
  1551:     #else
  1552:         #ifdef USE_MAP
> 1553:             vec4 _csm_sampledDiffuseColor = texture2D(map, vUv);
  1554: 
  1555:             #ifdef DECODE_VIDEO_TEXTURE

I'm not entirely sure this can be fixed on this end... thanks! :)

PS: opened corresponding issue on three's repo: mrdoob/three.js#25742

Add csm_Bump?

I successfully used CSM patchmaps to add surface bump in the fragment shader without need of map / bump texture. After chatting with Faraz, I'm putting my findings here to discuss what it could look like to add bump natively to CSM.

For starters, bump in three.js is essentially the detail that is drawn in the fragment shader without actually changing vertex positions or geometry. It does this by "perturbing the normals" so that the perturbed normals can be used in lighting calculations.

Findings:
There are 4 references to bump currently in three.js shader chunks.

  1. bumpmap_pars_fragment.glsl.js --> this is where utility functions to perturb normals are defined when a bump map is present
  2. normal_fragment_maps.glsl.js --> this is where the normal actually gets perturbed when a bump map is present
  3. envmap_pars_vertex.glsl.js and envmap_pars_fragment.glsl.js --> conditionally defines ENV_WORLDPOS, refractionRatio, vWorldPosition, and vReflect depending on if bump map is used. I'm not sure how these are used downstream.

Basically in default three.js materials, none of the above bump logic takes place unless a bumpMap is provided as a parameter for a material (shown in WebGLProgram.js). In that case USE_BUMPMAP is defined.

I was hoping it could be straightforward to use a default patchmap to add csm_Bump to the fragshader in CSM, though the above findings make me less sure. Some ideas:

  • If csm_Bump is used in the fragment shader, override three material to include USE_BUMPMAP as a parameter. Then, you'd also expect bumpmap_pars_fragment.glsl.js to be patched as it relies on a bump texture lookup. Given that the motivation here is really for procedurallly generated bump, I suppose you could combine procedural bump with a predefined bump map as well.
  • You could patch all of the above 4 shader chunks to consider a csm_Bump in the calcuation per fragment similar to what the ao_map_fragment override does. I have a question though, does support for csm_AO in CSM mean that user defined ao maps get ignored @FarazzShaikh ?

I did learn one other thing, which is that it looks like clearcoat (and others?) in three.js specifically does not use perturbed normals for lighting calculations (see: mrdoob/three.js#12867), so we would not want csm_Bump to either?

Extended meshBasicMaterial does not have access to cameraPosition uniform

This is really minor and maybe not a CSM issue but rather a Three.js issue but I thought I would log it.

Three.js docs list cameraPosition as a uniform accessible from shader.

With that said, cameraPosition is not accessible from shader if the base material in CSM is MeshBasicMaterial. The value of cameraPosition is just vec3(0,0,0).

I don't think it's the sort of issue you'd run into with vanilla shaderMaterials in three however because you wouldn't be using MeshBasicMaterial in the first place, but maybe it's the fact that it's being extended which is causing it?

This issue on three's discourse explains the relevant source a bit better.

Issue is demonstrated here where I'm setting csm_DiffuseColor to vec4(normalize(cameraPosition)) and you can see that it's all black when base is MeshBasicMaterial

Screen.Recording.2023-08-04.at.12.07.54.AM.mov

Waves example seems to be broken

The waves example shown in the README seems to be broken: This is the result I see running it on both Firefox and Chrome:

image

The other examples work fine, just thought you'd want to know that the waves example seems to be down.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.