Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default material shader modifier #7581

Closed

Conversation

fernandojsg
Copy link
Collaborator

Many times I've tried to use some of the default materials like MeshPhongMaterial but at the same time I wanted to add some kind of custom transformation, like a simple mesh deformation. To do that I needed to create a completely new shader and copy the shader from MeshPhong, add my custom deformation code and create a new ShaderMaterial.
It's really a pain so I thought It could be useful to add the possibility to include custom code inside the default materials making this changes much easier.

I've added a new material param called shaderModifier that is a object with the following parameters:

  • preMainVertex: This code will be added before the main() in the shader code. Typically used to add uniforms declarations or custom functions.
  • preVertex: It's added just after the transformed = position; so you could add your custom code to play with the transformed vertex.
  • postVertex: It's added at the end of the vertex shader.
  • preMainFragment: Like preMainVertexit's added before the main() in the fragment shader code.
  • preFragment: The same as preVertex but for fragment shader.
  • postFragment: The same as postVertexbut for fragment shader.
  • uniforms: Custom uniforms added.

Having this new code we could add a custom vertex shader modifier (distortion) based on a new uniform variable called time:

material = new THREE.MeshPhongMaterial( { 
    color: 0xAAAAAA,
    specular: 0xffffff,
    shininess: 10,
    map: DiffuseMap,
    normalMap: NormalMap,
    shaderModifier: {
        preVertex: [
            'float angle = (150.*sin(time-position.y))*0.02;',
            'transformed = vec3(transformed.x * cos(angle) - transformed.z * sin(angle), transformed.y, transformed.x * sin(angle) + transformed.z*cos(angle));',
            'vNormal = vec3( vNormal.x * cos( angle ) - vNormal.z * sin( angle ), vNormal.y, vNormal.x * sin( angle ) + vNormal.z * cos( angle ) );'
         ].join("\n"),
        uniforms: { 
            "time": { type: "f", value: 1.0 }
        },
        preMainVertex: "uniform float time;",
    }
} );

Typically uses will require preVertex to do actual vertex transformation code on transformed point, preMainVertex to define the uniform, uniforms to access them and postFragmentto modify the gl_FragColor.

In the previous example we defined time uniform. If we want to access it on the render loop we could just simple use the following line:
material.shaderModifier.uniforms[ "time" ].value = time;

I've included also a simple example showing this functionality:
http://fernandojsg.com/lab/js/twist

Twister effect

@danielribeiro
Copy link
Contributor

Wow! This looks great. I wonder if anybody here knows the answer to this question: https://github.com/mrdoob/three.js/pull/7581/files#diff-e24610216374beea052f744c4643869bR86

@GGAlanSmithee
Copy link
Contributor

Yeah, it really does look good!

@fernandojsg
Copy link
Collaborator Author

@danielribeiro umm I thought I deleted that parameter, as I just copied from another example. I guess it really depends on how the normal maps is stored, but for the one on the example is not necessary.

@nn4e
Copy link

nn4e commented Nov 13, 2015

A few months ago was dismissed PR from my with the same functionality (but more dirty implementation)
#6885

Im afraid the same awaits this one

@mrdoob
Copy link
Owner

mrdoob commented Nov 13, 2015

@nn4e maybe the dirtiness had something to do, the prettier the solution the more chances it has to be merged 😊

@fernandojsg
Copy link
Collaborator Author

I just deleted the builds, sorry for that :)

@bhouston
Copy link
Contributor

This could be done in an elegant way via @sunag's node based material system? Basically this would be a customization of the normal parameter.

@mrdoob
Copy link
Owner

mrdoob commented Nov 17, 2015

API wise... How about something like this instead?

var material = new THREE.MeshPhongMaterial( color: 0xAAAAAA,
    specular: 0xffffff,
    shininess: 10,
    map: DiffuseMap,
    normalMap: NormalMap
);

var enhanced = new THREE.EnhancedMaterial( material, {
        uniforms: { 
            "time": { type: "f", value: 1.0 }
        },
        preVertex: [
            'float angle = (150.*sin(time-position.y))*0.02;',
            'transformed = vec3(transformed.x * cos(angle) - transformed.z * sin(angle), transformed.y, transformed.x * sin(angle) + transformed.z*cos(angle));',
            'vNormal = vec3( vNormal.x * cos( angle ) - vNormal.z * sin( angle ), vNormal.y, vNormal.x * sin( angle ) + vNormal.z * cos( angle ) );'
         ].join("\n"),
        preMainVertex: "uniform float time;",
    }
);

Actually, something like this would be better I think:

var material = new THREE.MeshPhongMaterial().toShaderMaterial();
material.vertexShader.replace( '%PRE_MAIN_VERTEX%', 'uniform float time;' );

@fernandojsg
Copy link
Collaborator Author

@mrdoob I like the second approach to convert the default material to a shader material but keeping something similar to the shadermodifier attributes. I believe it would be easier and more intuitive for the newbies than replacing strings in their own. And we could also keep track of those properties.
For example thinking on the editor, I believe having the properties object you could enable them or modify their values easier.

@fernandojsg
Copy link
Collaborator Author

This is an old PR but today I started to play again with this problem and tried @sunag's NodeMaterial. It's a very nice way to inject code using those shortcuts to the standard materials defined in three.js but I see some problems for use it in this kind of simple situations:

  • It's code is big and not part of the core
  • The code of the shaders from the core must be replicated and maintained, so everytime you change the core you must change the library.
  • It doesn't seems to allow include of raw expressions, something like a rawTextNode where you could write the code in a similar way I described before. Otherwise for simple code like the one in the example we should create several nodes and tweaking them won't be as easy as playing directly with the shader's code.

So I'm not sure what you think but maybe we could have this two approach together? Include something like this proposal in the base core: adding the shorcuts directly on the core and with a simple api where you could inject directly code on those shorcuts. Simple enough to prevent excesive changes on the core, so the users should know what they're doing and which attributes they're modifying.

What do you think?

@tschw
Copy link
Contributor

tschw commented Apr 25, 2016

This is an old PR but today I started to play again with this problem and tried @sunag's NodeMaterial. It's a very nice way to inject code using those shortcuts to the standard materials defined in three.js but I see some problems for use it in this kind of simple situations:
[...]
It's code is big and not part of the core

That.

It's possible to write something like this in-core with a minimum of code. However, in order for that to happen we'd have to clean up some more (see #8552).

@fernandojsg
Copy link
Collaborator Author

Following the discussion on #11475

@fernandojsg fernandojsg deleted the feature/material_shader_modifiers branch August 10, 2017 07:39
Repository owner deleted a comment from pailhead Mar 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants