FBX + Material.onBeforeCompile()

Hi, community!

This time I’ve tried to use .onBeforeCompile().
I’ve achieved the desired result, but I wouldn’t say it was much easier than re-creation of a default material with THREE.ShaderMaterial().

As a base, I took the official example with FBX and turned it into New Retro Wave style :slight_smile:
Have fun with the scene, music is included (but doesn’t start automatically in Chrome)

9 Likes

OMG I love this!! :heart_eyes::heart_eyes::heart_eyes:

Regarding the use of .onBeforeCompile(), I guess the main reason to use this method is that it’s less verbose than recreating a built-in material like in the webgl_buffergeometry_instancing_lambert example.

2 Likes

@looeee
Party hard!!! :smile:

Oh, yeah, I know this example. From my point of view, it’s so weird to have a full copy of shaders of a material, it simply confuses beginners, makes them think that using of shaders is something like rocket science :slight_smile:

Recently, I’ve answered a question, where the author was, I don’t know what word to apply to this - scared - of using buffer geometry with shaders :slight_smile:
Just read all the thread

1 Like

Yeah, perhaps. But personally I’ve found that example useful on a couple of occasions recently.

I don’t think it’s the idea that every example is just for beginners .

2 Likes

Also true :slight_smile: :beers:

As much as i love modifying GLSL when there’s a lot of GLSL already doing the stuff i want, i absolutely detest onBeforeCompile the whole “compile” process in three.js makes little sense and this just adds fuel to the fire. I do find it easier to work with than rewriting the entire shader, but thats about the absolute worst rout one can take in my view.

Could you elaborate a bit more on your experience, why didn’t you like it?

Regarding the use of .onBeforeCompile() , I guess the main reason to use this method is that it’s less verbose than recreating a built-in material like in the…

Sorry but no. I’ve been following this feature for about a year before it was even conceived. The only reason this method exists in the library seems to be that it was possible to add it with three lines of code. This is a very poor reason to add something to a library like this IMHO.

Yeah, perhaps. But personally I’ve found that example useful on a couple of occasions recently.

I would really appreciate if you could share the use case. I haven’t seen much “production” level stuff, at most some creative experiments such as this. I used it for various things that i wanted to make as add-ons for three.js (like better cross-sections, better transparency, normal maps that actually work etc). Something like onBeforeCompile would be supe beneficial for all of that, obc just plain doesnt work :frowning:

Also, i’m going to try this today, but i think i’ve found a fix for the non-deterministic “ill randomly choose a material for you” behavior that onBeforeCompile is designed to have :slight_smile:

const _obc = shader => {...}
const obc = `
function (shader){
  const _${ hash() }
  ${ _obc.toString() }
  _obc(shader)
}
`
material.onBeforeCompile = eval(obc)

Now this is something that would feel really weird if it worked, but c’est la vie, i want my meshes to use the materials that i tell them, not at random.

Don’t ever use eval.

Aside from that, I don’t have any strong opinions around any of the methods of extending built in materials, but since it seems like you do, @pailhead, perhaps it would be more constructive here if you show how to recreate @prisoner849’s funky example in the way you think it should be done?

It can’t be shown in a simple example since it’s all rather philosophical. I’ll simplify the code in the pen:

mat.onBeforeCompile = shader=>{
   shader.uniform.foo = foo
   shader.vertexShader = 'uniform <type> foo; \n' + shader.vertexShader //why switch to GLSL to declare type, why \n why string manipulation (+ restOfShader) just add {type:'v4'} to uniform and {target: 'vert'} 
   shader.vertexShader = shader.vertexShader.replace(
     '#include <bar>`,
     '#include <bar>' + myGLSL
   )
}

Going top to bottom:

  1. It’s a function that has side effects, returns nothing. It mutates this shader object.
  2. Why do you have to wait for WebGLRenderer to call this. The shader argument is exactly the same as config in new THREE.ShaderMaterial(config). Having the renderer create this and not be able to do myBuiltInMat.toShaderMaterial() makes no sense to me. I mean i get it why this would happen automatically and invoked by the renderer, but just not why it’s so encapsulated and private.
  3. Why repeat yourself by prepending uniforms to strings. The renderer already does so many things, i want to just declare a uniform once, and have it magically appear in the shader. The {value, type} uniform works great here, i’d just add a {target: 'vert | frag'}.
  4. You always either swap out a chunk, or add something before or after it. It’s really verbose. Where do you store myGLSL here anyway? In @prisoner849 example they are inlined in the callback. This is whats being endlessly discussed on github, regarding cloning materials and such. Your GLSL is trapped in that callback. How about this?
mat.userData.chunks = { myChunk }

And then a generic obc parser

mat.obc = shader => { Object.keys(mat.userData.chunks).forEach(replaceChunk(mat)) }

It wont work by design (https://github.com/mrdoob/three.js/issues/13192) because you can’t hash these. Three.js is suuuuuper specific as to what you can put in obc.

A more complete example can be seen here:

This experiment allows you to nuke all the built in materials from WebGLRenderer. It’s basically that MeshStandardMaterial.toShaderMaterial() functionality.

You can play around with it, modify it, tweak it, but at the end of the day it does a few simple things:

  1. pipes uniforms into object properties ( get prop () { return this.uniforms.prop.value } etc)
  2. has chunks (can parse includes, has a library of glsl snippets) and can override chunks
  3. creates ShaderMaterial so WebGLRenderer doesnt need to know about any other materials…

There we go:

Don’t ever use eval.

Other people have ran into the hashing issue, if it is possible to theoretically solve it with eval and eval is evil, i’d argue that this is proof that the pattern is bad. It makes me really sad that this is ignored :crying_cat_face:

Main reason i removed the built in materials from the core is that it caused a forest of conditions, a better solutions than checking isMeshLambertMaterial etc. would have been using a method that is i mplemented on that material class, making it extenable from the outside regarding some material specific setups.

Anyway some conditions get really nasty like if ( material.isMeshLambertMaterial || material isMeshPhongMaterial... and this list goes very long often. Entirely rewriting a shader or replacing chunks doesn’t matter to the final program, since every new material will compile the code thing to a new program.

In a asset system, i reuse the programs as long as no constant changes/a recompile is required. A better sharing of programs would be nice to have by default in THREE too.

@pailhead
new Function('return function() {}') should be more save not operating in the current scope.

Elaborate please, i havent tested my eval snippet. The problem is three sees the same thing then it calls obc.toString()

Oh damn i see, just replace eval with different syntax, you still have to convert string to code to get around the design limitation.

There’s initiative, if it hasn’t been added already to copy the onBeforeCompile i wonder if that can lead to issues if you end up using it like this. Say someone writes a loader using someone elses obc that evaluates a function.

w00t, the evil eval() works here:

It however doesn’t like new Function(shader=>…) does anyone know how to convert this eval?

That’s not surprising, and of course it’s fine to use eval in experiments like this. The problem is that it’s never going to get accepted into any production code.

It’s true that onBeforeCompile is very limited, as is the entire current material system when it comes to extending materials.

As far as I can see onBeforeCompile was largely added as a stopgap measure while we transition towards a node based material system.

I haven’t examined Sunag’s node material in detail so I can’t comment on his implementation, but I do think that node based material is the way forward here rather than endlessly going over the limitations of the current system.

There’s a reason why Sketchfab, Unity, Blender, Unreal, Lightwave, Substance Designer and lots of others use node based materials rather than monolithic shaders like three.js.

For some background on why SketchFab made the transition check out their chapter in http://www.webglinsights.com/ (Chapter 12).

1 Like

By the way @prisoner849, Happy Christmas! :christmas_tree::santa:

It’s not, it’s actually fairly flexible IMHO, but to each their own :slight_smile: my concern is higher level than this, why so many opinions, why are some ways the “recommended” ways etc. All of these packages that you mention use some shader language under the hood. All of them have some “tool” that is used to assemble these directed graphs that in turn generate some shader code.

Why? The limitations are quite simple. I’d love to see them removed, i know it’s most likely not going to happen, but i’m really curious why :slight_smile:

You can do all kinds of stuff with the current system, it’s just very verbose, and some bugs are defended as features, this could easily change. Again, i’m talking about ShaderMaterial who or what makes it, i don’t really care about.

That’s not surprising, and of course it’s fine to use eval in experiments like this. The problem is that it’s never going to get accepted into any production code.

Do you know why? The way to avoid it is of course, just to make your own simple parser and not use three’s internal one, but then you have 150kb of the library sitting around doing nothing.

Did you read the article I linked above? It’s explained very clearly there.

it’s actually fairly flexible IMHO

Perhaps limited was the wrong word to use. Of course, the current system is infinitely flexible and extensible, it’s just awkward and complicated to do so. The people making Unity, Unreal, SketchFab etc.have discovered that node based material systems alleviate this and allow for much simpler flexibility as well as allowing for visual material editors which are easier for artists to use.

Again, I’d suggest that you read the chapter by Sketchfab in WebGL Insights. It’s very interesting to understand how SketchFab works under the hood (well, how it worked 4 years or so ago), and it might help you to see why some other people chose to switch to node based materials.

The limitations are quite simple. I’d love to see them removed,

The suggestions I’ve seen you make for removing them are anything but simple since they involve a complete refactoring of the material system, and in the PR you linked above you’re suggesting moving all the GLSL out of the core.

If you really have simple suggestions to make, then you should keep your suggestions simple.