Search code examples
javascriptthree.jsglslshader

How to apply custom shader to sprite in THREE.js


I want to be able to apply some procedural structures to faces. First task, when I faced such demand is to create billboard, on which is drawn nuclear blast in open space. I hoped to make it as a animated radial gradient and I have succeed partly.

The main thing is for each fragment shader - to have access to UV as to uniform var.

Seems like the main thing about rendering sprites - is to access to camera projection matrix in the vertex shader.

Here's example http://goo.gl/A7pY01!

Now I want to draw this onto the billboard sprite. I supposed to use THREE.Sprite for this with THREE.ShaderMaterial, but had no luck in this. It seemed, that THREE.SpriteMaterial is only good material for sprites. And after inspecting some source-code I revealed why Sprites are draw in one special way using plugins.

So, before I found myself inventing my own bicycle, I felt needness to ask people how to place my own custom shader on my own custom sprite without hacking THREE.js?


Solution

  • So. After a small research and work I have considered THREE.ShaderMaterial is the best option to complete this little task. Thanks to /extras/renderers/plugins/SpritePlugin, I realized how to form and position sprites using vertex shaders. I still have some question, but I found one good solution.

    To accomplish my task, firstly I create a simple plane geometry:

     var geometry = new THREE.PlaneGeometry( 1, 1 );
    

    And use it in mesh with ShaderMaterial:

                uniforms = {
                    cur_time: {type:"f", value:1.0},
                    beg_time:{type:"f", value:1.0},
                    scale:{type: "v3", value:new THREE.Vector3()}
                };
    
                var material = new THREE.ShaderMaterial( {
    
                    uniforms: uniforms,
                    vertexShader: document.getElementById( 'vertexShader' ).textContent,
                    fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
                    transparent: true,
                    blending:THREE.AdditiveBlending // It looks like real blast with Additive blending!!!
    
                } );
    
    
     var mesh = new THREE.Mesh( geometry, material );
    

    Here's my shaders: Vertex shader:

            varying vec2 vUv;
            uniform vec3 scale;
    
            void main() {
                vUv = uv;
                float rotation = 0.0;
    
                vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, position.z*scale.z);
    
                vec2 pos = alignedPosition.xy;
    
                vec2 rotatedPosition;
                rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;
                rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;
    
                vec4 finalPosition;
    
                finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );
                finalPosition.xy += rotatedPosition;
                finalPosition = projectionMatrix * finalPosition;
    
                gl_Position =  finalPosition;
    
            }
    

    I got vertex shader from original Sprite Plugin source code, and changed it slightly. BTW, changing += to = makes sprite screen-sticky. This thing wasted a lot of my time.

    And this is my fragment shader:

            uniform float cur_time;
            uniform float beg_time;
    
            varying vec2 vUv;
    
            void main() {
                float full_time = 5000.;
    
                float time_left = cur_time - beg_time;
                float expl_step0 = 0.;
                float expl_step1 = 0.3;
                float expl_max   = 1.;
    
                float as0 = 0.;
                float as1 = 1.;
                float as2 = 0.;
    
                float time_perc = clamp( (time_left / full_time), 0., 1. ) ;
                float alphap; 
                alphap = mix(as0,as1, smoothstep(expl_step0, expl_step1, time_perc));
                alphap = mix(alphap,as2, smoothstep(expl_step1, expl_max, time_perc));
    
    
                vec2 p = vUv;
                vec2 c = vec2(0.5, 0.5);
                float max_g = 1.;
                float dist = length(p - c) * 2. ;
    
                float step1 = 0.;
                float step2 = 0.2;
                float step3 = 0.3;
    
                vec4 color;
                float a0 = 1.;
                float a1 = 1.;
                float a2 = 0.7;
                float a3 = 0.0;
    
    
                vec4 c0 = vec4(1., 1., 1., a0 * alphap);
                vec4 c1 = vec4(0.9, 0.9, 1., a1 * alphap);
                vec4 c2 = vec4(0.7, 0.7, 1., a2 * alphap);
                vec4 c3 = vec4(0., 0., 0., 0.);
    
    
    
                color = mix(c0, c1, smoothstep(step1, step2, dist));
                color = mix(color, c2, smoothstep(step2, step3, dist));
                color = mix(color, c3, smoothstep(step3, max_g, dist));
    
                gl_FragColor = color;
       }
    

    Here's example of how to make multipoint gradient, animated by time. There's a lot to optimize and several thoughts how to make this even more beautiful.

    But this one is almost what I wanted.