mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.6k stars 35.36k forks source link

Per material GLSL ShaderChunk includes #10789

Closed pailhead closed 7 years ago

pailhead commented 7 years ago
Description of the problem

I was able to do magical things with a few undocumented features Material.defines , Material.customDepthMaterial and Material.customDistanceMaterial.

https://github.com/mrdoob/three.js/issues/10764

I'm wondering now if this whole system could be extended by having the renderer use a chunk provided by the material instead of THREE.ShaderChunks.

It's possible to modify the shader chunks so that they don't break by using the preprocessor.

This can then easily be monkey patched into three. Before the first render call is made, THREE.ShaderChunk[ chunk ] is modified. Then, that branch can be controlled by assigning a define to a material instance's defines.

The shader chunk mechanism is already so neatly broken up, that it could probably cover most cases.

For example, something that modifies the geometry can use uv_pars_vertex to safely be included in all the needed shaders. Depth shader doesnt need to render maps, but it needs to fetch a texture if it uses displacement, so it includes the uvs. Something that modifies lighting, can make a branch in one of the lighting includes.

I'm thinking, monkey patching and branching can be avoided if the renderer would look up a chunk provided by something like Material.shader.entryPoint['uv_pars_vertex'].

In addition to this, i'd add more entry points, at least a global_vertex and global_fragment, or a define #IS_FRAGMENT so then any global stuff could be appended to common.

Confused though why are parameters being passed here: https://github.com/mrdoob/three.js/blob/master/src/renderers/webgl/WebGLProgram.js#L496

tl:dr;

would this be a good idea?

    vertexShader = parseIncludes( vertexShader, material.shader.entryPoints );

https://github.com/mrdoob/three.js/blob/master/src/renderers/webgl/WebGLProgram.js#L153

function parseIncludes( string , materialIncludes ) {

    var pattern = /#include +<([\w\d.]+)>/g;

    function replace( match, include ) {

        var replace = materialIncludes[ include ] ?  materialIncludes[ include ] : ShaderChunk[ include ];

        if ( replace === undefined ) {

            throw new Error( 'Can not resolve #include <' + include + '>' );

        }

        return parseIncludes( replace );

    }

    return string.replace( pattern, replace );

}

@WestLangley @mrdoob

Three.js version
Browser
OS
Hardware Requirements (graphics card, VR Device, ...)
WestLangley commented 7 years ago

I assume you want to inject code into a built-in material's shader on a per-instance basis.

One hack was posted here.

I assume you are looking for an nicer API for doing so.

Is that correct?

pailhead commented 7 years ago

Yes, to not override the global chunks. See the pull request. The hack takes care of the defines, but then the defines are further hacked by adding conditional branching in the preprocessor. You have to take care it's coordinated with the defines correctly, and that it doesn't break other stuff. With this there would be no need for branching.

https://github.com/mrdoob/three.js/pull/10791

pailhead commented 7 years ago

In the future, i'd consider an api where you can inject glsl in more abstract entry points. lighting, transform_vertices etc.

As much as i hated working with scenekit, i really liked this api