Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 646
Vasiliy Stavenko Avatar asked Jun 17 '14 08:06

Vasiliy Stavenko


1 Answers

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.

like image 175
Vasiliy Stavenko Avatar answered Oct 15 '22 11:10

Vasiliy Stavenko