Как применить пользовательский шейдер к спрайту в THREE.js

#javascript #three.js #glsl #shader

#javascript #three.js #glsl #шейдер

Вопрос:

Я хочу иметь возможность применять некоторые процедурные структуры к лицам. Первая задача, когда я столкнулся с таким требованием, — создать рекламный щит, на котором нарисован ядерный взрыв в открытом космосе. Я надеялся сделать это в виде анимированного радиального градиента, и мне это частично удалось.

Главное для каждого фрагментного шейдера — иметь доступ к UV как к единой переменной.

Похоже, что главное в рендеринге спрайтов — это доступ к матрице проекции камеры в вершинном шейдере.

Вот пример http://goo.gl/A7pY01 !

Теперь я хочу нарисовать это на спрайте рекламного щита. Я предполагал использовать THREE.Sprite для этого with THREE.ShaderMaterial , но в этом мне не повезло. Казалось, это THREE.SpriteMaterial только хороший материал для спрайтов. И после проверки некоторого исходного кода я показал, почему спрайты рисуются одним особым способом с использованием плагинов.

Итак, прежде чем я обнаружил, что изобретаю свой собственный велосипед, я почувствовал необходимость спросить людей, как разместить мой собственный пользовательский шейдер на моем собственном пользовательском спрайте без взлома THREE.js ?

Комментарии:

1. Хороший вопрос. Рассмотрите возможность рендеринга вашего шейдера в текстуру, а затем использования текстуры в качестве map SpriteMaterial .

2. Да. Я думаю, я сделаю это как-нибудь таким образом, но в этом решении есть несколько недостатков. Я определенно пересмотрю метод рендеринга чуть позже. Ответ на этот вопрос покажет, как обстоят дела в будущем. (Возможно THREE.js нужен какой THREE.ShaderedSpritePlugin -то объект.. :))

Ответ №1:

Итак. После небольшого исследования и работы, которые я рассмотрел THREE.ShaderMaterial , это лучший вариант для выполнения этой небольшой задачи. Благодаря /extras/renderers/plugins/SpritePlugin , я понял, как формировать и позиционировать спрайты с помощью вершинных шейдеров. У меня все еще есть некоторые вопросы, но я нашел одно хорошее решение.

Чтобы выполнить свою задачу, сначала я создаю простую плоскую геометрию:

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

И использовать его в сетке с 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 );
  

Вот мои шейдеры:
Вершинный шейдер:

         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;

        }
  

Я получил вершинный шейдер из исходного кода плагина Sprite и немного изменил его.
Кстати, изменение = на = делает экран спрайта липким. Эта вещь отняла у меня много времени.

И это мой фрагментный шейдер:

         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;
   }
  

Вот пример того, как создать многоточечный градиент, анимированный по времени. Есть много оптимизаций и несколько мыслей, как сделать это еще более красивым.

Но это почти то, что я хотел.

Комментарии:

1. Было бы неплохо просто иметь возможность присоединять шейдер к SpriteMaterial — но в то же время спасибо за это, хотя, очень полезно!