Перейти ко вступительной информации

Видео+код: #2/7. Shaders в Three.JS

Статья создана: Видео+код: #2/7. Shaders в Three.JS

Видео: 9. Создаём Shaders в Three JS

урок 9 по Three JS / урок 7 по планете

Файлы из урока 7 по 3D планете

02-6-meshline+test.zip

Найдите книгу по шейдерам

«GLSL Essentials» (2013 г. издания) | ISBN: 978-1-84969-800-9

Ссылки, о которых идёт речь в видео:

Ссылочка на оригинал «бум»

Форум по threejs (мой вопрос)

Как работает WebGL

WebGL Shaders and GLSL

WebGL and OpenGL Differences (как бы сайт создателей технологии WebGL)

Код из видео (!на threejs-webpack-starter!)

В код я добавил два шейдерных матрериала, чтобы Вы могли их поклацать. Оба они эмитируют атмосферу планеты
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

// Вам необходимо установить three.meshline
// npm i three.meshline
import { MeshLine, MeshLineMaterial } from 'three.meshline';

// Также из предыдущих уроков animejs ...
import * as animejs from 'animejs/lib/anime'

// Это стандартная штука ThreeJS, её просто необходимо подклчить
import {BufferGeometryUtils} from 'three/examples/jsm/utils/BufferGeometryUtils'
// Урок 2-7import {TWEEN} from 'three/examples/jsm/libs/tween.module.min'
const canvas = document.querySelector('canvas.webgl')
const scene = new THREE.Scene()

/** НАШ КОД ... */
  // 1-й урок по планете
    // Создание группы для СВЕТОВ!
  const lightHolder = new THREE.Group();

  // Создание простого Света!
  const aLight=new THREE.DirectionalLight(0xffffff,2);

  // Установка позиции для этого света
  aLight.position.set(-1.5,1.7,.7);

  // Прикрепляем к удержателю позиции света, чтобы он дальше не крутился вместе с объектами на сцене
  //!!! Раскомментируйте, если нужна «голубая сфера» | Код ниже добавляет Свет на сцену
  //lightHolder.add(aLight);

  // Второй дополнительный свет
  const aLight2=new THREE.DirectionalLight(0xffffff,2);
  aLight2.position.set(-1.5,0.3,.7);
  //!!! Раскомментируйте, если нужна «голубая сфера» | Код ниже добавляет Свет на сцену
  //lightHolder.add(aLight2);

  // Создание геометрии сферы (которая икосахедрон) — для того, чтобы прикрепить к нему все остальные объекты — сам он будет невидим на сцене
  const geometry = new THREE.IcosahedronGeometry(1.0,2);

  // Создание материала для икосахедрона (сферы)
  const materialIcosahedron = new THREE.MeshBasicMaterial({
    opacity: 0,
    transparent: true
  });

  // Создание некоторого абстрактного объекта (переводится — сетка)
  const mesh = new THREE.Mesh(geometry,materialIcosahedron);
  // Установим родителя для всех элементов, к которым будет далее применена некоторая анимация...
  const parent=mesh;

  // Создание сферы, которую мы будем видеть — для скрытия заднего вида самой карты
  const geomHide = new THREE.SphereBufferGeometry(1.0499, 64, 36);
  const matHide=new THREE.MeshStandardMaterial({color:new THREE.Color(0x091e5a)});
  const meshHide= new THREE.Mesh(geomHide, matHide);

  //Добавляем объекты на сцену
  scene.add(meshHide);
  scene.add(lightHolder);

// КОД НИЖЕ — добавляет некое свечение или атмосферу планеты — 1-й вариант
  /* function mmm(intensity, fade) {    // Custom glow shader from https://github.com/stemkoski/stemkoski.github.com/tree/master/Three.js    let glowMaterial = new THREE.ShaderMaterial({        uniforms: {            'c': {                type: 'f',                value: intensity            },            'p': {                type: 'f',                value: fade            },            glowColor: {                type: 'c',                value: new THREE.Color(0x091e5a)            }        },        vertexShader: `    uniform float c;    uniform float p;    varying float intensity;    void main() {      vec3 vNormal = normalize( normalMatrix * normal );      vec3 vNormel = normalize( normalMatrix * vec3(1.,1.,1.) );      intensity = pow( c - dot(vNormal, vNormel), p );      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );    }`,        fragmentShader: `    uniform vec3 glowColor;    varying float intensity;    void main()     {      vec3 glow = glowColor * intensity;      gl_FragColor = vec4( vec3(glow.r,glow.g,glow.b), 1. );    }`,        side: THREE.FrontSide,        depthWrite: false,        depthTest: false,        // wireframe:true,        blending: THREE.AdditiveBlending,        //blending: THREE.SubtractiveBlending,        transparent: true    });    return glowMaterial;}// const geometrySphere=new THREE.IcosahedronGeometry(1.0575,9);// const geometrySphere=new THREE.IcosahedronGeometry(1.051,9);const geometrySphere=new THREE.SphereBufferGeometry(1.051, 64, 36)const meshGeo=new THREE.Mesh(geometrySphere,mmm( .7, 2));//meshGeo.rotation.y=1.5;//meshGeo.rotation.x=-1.5;meshGeo.rotation.y=.4;meshGeo.rotation.x=-3; //scene.add(meshGeo)lightHolder.add(meshGeo);*/
  /* !!!WARN!!! Planet 2-2 */
    scene.add(mesh) // Добавил основной прозрачный (скрытый от глаз объект на сцену), он послужит «родителем» для остальных...
    // Функция добавления данных на карту планеты
    function addMapInf(posCil1,posCir2,main=false){
      // Принимает парамерты:
        //posCil1 => array(1,2,3)
        //posCil2 => array(1,2,3)
        //main => boolean
      let mainSize=null// если main = true, то значит это ПЕРВЫЙ «флагшток» (освновная позиция на карте)
      let mSC=null// размер круга под цилиндром
      let color=0x008DFB;//цвет по умолчанию — это цвет НЕглавных «флагштоков»
      if(main){// если это первый «флагшток»
          mainSize=[.004,.004,.3,3];
          mSC=[.017,24];
          color=0x86c3f9
      }else{ // если остальные флагштоки, то их размер чуть меньше основного
          mainSize=[.002,.002,.16,4]
          mSC=[.01,12]
      };
      // Создание цилиндра
      const cyl=new THREE.CylinderBufferGeometry(mainSize[0],mainSize[1],mainSize[2],mainSize[3]);
      const cylinder=new THREE.Mesh(
        cyl,
        new THREE.MeshBasicMaterial({color})
      );
      
      // Нет необходимости направлять цилиндр к центру
      //cylinder.lookAt(new THREE.Vector3());

      // Установим позицию цилиндра, которая приходит из заданных нами координат
      cylinder.position.set(posCil1[0],posCil1[1],posCil1[2]);

      //scene.add(cylinder);// Добавим на сцену — это можно НЕ делать, так как мы и так добавим это на сцену кодом ниже
      parent.add(cylinder);// Добавим к родительскому элементу для дальнейшей анимации (в других уроках)

      // Видимо, далее по коду моей планеты, есть место, где мне необходим только лишь цилиндр (без круга внизу)
      if(posCir2==''){return [cylinder]}

      // Создаём окружность под цилиндром
      const circLocation = new THREE.CircleBufferGeometry(mSC[0],mSC[1]);

      // «Засунем» цилиндр в mesh и применим к нему материал...
      const circleLocation = new THREE.Mesh(
          circLocation,
          new THREE.MeshBasicMaterial({color, side: THREE.DoubleSide})
      );

      // Устанавливаем ему позицию — с помощью заранее определённых данных
      circleLocation.position.set(posCir2[0],posCir2[1],posCir2[2]);

      //Указываем ему «смотреть» в начало координат (нулевую точку), чтобы он как бы был над поверхностью планеты
      circleLocation.lookAt(new THREE.Vector3());

      //scene.add(circleLocation);

      // Добавляем окружность под цилиндром
      parent.add(circleLocation);

      // Функция возвращает два объекта в виде массива
      // Объекты представляют из себя ранее созданные 3D-объекты — JS Object
      return [cylinder,circleLocation]
    }

    // Вызываю функцию создания элемнтов карты («флагшток №1»)
    //                данные определил заранее, руками, попробуйте их менять — увидите, как это трудно
/*     const c1=addMapInf([.66,.95,-.28],[.662,.8,-.28],true)

    // Анимирую появление «флагштока» — высокого цилиндра
    animejs({
      targets:c1[0].scale,// указываем цель анимации — «scale» — увеличение чего-то
      x:[0,1],// увеличивает с 0 до 1 по оси X
      y:[0,1],// увеличивает с 0 до 1 по оси Y
      z:[0,1],// увеличивает с 0 до 1 по оси Z
      duration:2000,// время выполнения самой анимации
      delay:1100,// задержка перед выполнением анимации
      easing:'easeOutBounce' // тип перехода анимации — лучше всего выбирать «linear»
    }); */

    // Анимирую появление круга под цилиндром
//    animejs({targets:c1[1].scale,x:[0,1],y:[0,1],z:[0,1],duration:2000,easing:'linear'});

  /* \ !!!WARN!!! Planet 2-2 */

    /* !!!WARN!!! Planet 2-3 */
    /* Text */
    const fontLoader=new THREE.FontLoader();
    fontLoader.load('fonts/font-roboto.json', font =>{
        function createText(text,pos,rot,size,font,color=0xffffff){
          text=new String(text);
          const textGeo = new THREE.TextGeometry(text,{
            font,
            size,
            height: .004,
            curveSegments: 12,
            /* bevelEnabled: true,
            bevelThickness: 10,
            bevelSize: 8,
            bevelOffset: 0,
            bevelSegments: 5 */
          } );
          const textMaterial=new THREE.MeshBasicMaterial({
              color,
              side:THREE.FrontSide
          });
          text=new THREE.Mesh(textGeo,textMaterial);
          text.position.set(pos[0],pos[1],pos[2]);
          text.rotation.set(rot[0],rot[1],rot[2]);
          /* text.updateMatrix(); */
          scene.add(text);
          parent.add(text);
          return text;
      }
        const txt1=createText('3D',[.6,1.1,-.48],[0,1.95,0],.05,font)
        const txt2=createText('Planet',[.6,1.0,-.48],[0,1.95,0],.05,font,0x0086ff);

        const mainPos=[.662,.8,-.28];
        animejs.timeline().add({
            targets:txt1.scale,x:[0,1],y:[0,1],z:[0,1],duration:600,easing:'linear'
        }).add({
            targets:txt2.scale,x:[0,1],y:[0,1],z:[0,1],duration:600,delay:1000,easing:'linear',complete:()=>{
                //(main)
                let c1=addMapInf([.66,.95,-.28],mainPos,true);
                animejs({targets:c1[0].scale,x:[0,1],y:[0,1],z:[0,1],duration:1000,delay:100,easing:'linear'});
                animejs({targets:c1[1].scale,x:[0,1],y:[0,1],z:[0,1],duration:1000,easing:'linear'});
            }
          })
    });
    //\TEXT+
    /* \ !!!WARN!!! Planet 2-3 */

      /* !!!WARN!!! Planet 2-4 */

      const loader = new THREE.TextureLoader();

      // load a resource
      loader.load(
          'media/pine-tree.png',
          function ( texture ) {
              const material = new THREE.MeshBasicMaterial( {
                  map: texture,
                  side: THREE.DoubleSide,
                  alphaTest:.5
              });
              const meshTexture = new THREE.Mesh(
                  new THREE.PlaneGeometry(.235,.235),
                  material
              );
              meshTexture.position.set(.62,1,-.37);
              meshTexture.rotation.set(0,1.95,0);
              meshTexture.scale.set(0,0,0);
              scene.add(meshTexture)
              parent.add(meshTexture)
              animejs({targets:meshTexture.scale,x:[0,.7],y:[0,.7],z:[0,1],duration:600,easing:'linear'})
          },
          undefined,
          function ( e ) {
              console.error( e );
          }
      );

      /* \ !!!WARN!!! Planet 2-4 */

        /* !!!WARN!!! Planet 2-5 */
        // Массив точек для «бум» — это в след. уроках..
        const circlePointsAr=[
          //(main)
          [.662,.775,-.28],
          [.63,.84,-.13],//lux
          [.89,.55,-.2139],
          [.54,.75,.5],//Lond
          [-.2138805, .773827135, .692131996],//usa 2
          [-.7738271,.69213199,.21388055],//usa
          [.25,.33,-.968],//hong
          [.53,-.02,-.92]
      ];

        let meshCircles=null; // Переменная для самой карты
        /* Строим саму карту планеты из «кружочков» */
        const obj={};// Создадим объект, чтобы в него «складывать» переменные
        obj.w=360;// Обозначим кратную размеру map.png ширину будущего canvas
        obj.h=180;// ~ высоту ~
        obj.d=document;// Для псевдонима document (чтобы каждый раз его не писать)
        obj.c=obj.d.createElement('canvas');// Создание canvas, в который будем помещать точки из PNG изображения и брать их для нашей карты планеты
        obj.cnt=obj.c.getContext('2d');// Установим контекст 2d, а не webgl
        obj.c.width=obj.w;// Ширина canvas
        obj.c.height=obj.h;// Высота ~
        obj.c.classList.add('tmpCanvas');// Добавим класс для нового объекта canvas в HTML коде страницы, чтобы обратиться к нему далее
        obj.d.body.appendChild(obj.c);// Добавим его в документ
    
        obj.s=obj.d.createElement('style');// Создадим стиль
        obj.s.innerText=`.tmpCanvas{position:absolute;z-index:-9;width:0;height:0;overflow:hidden}`;// Сам CSS-код позиционирования нового canvas — скрываем его с глаз
        obj.d.body.appendChild(obj.s);// Добавляем стили в document
        obj.img=new Image();// Создадим объект класса Image (нативный JS)
        obj.img.src='media/map.png';// Присвоем ему путь к изображению
        obj.img.onload=()=>{// Когда загрузится... выполним код ниже
            obj.cnt.drawImage(obj.img,0,0,obj.w,obj.h) // Нарисуем изображение на canvas из PNG файла
            obj.data = obj.cnt.getImageData(0, 0, obj.w, obj.h)  
            obj.data = obj.data.data;// Возьмём точки из canvas
            obj.ar=[];
            // ** Код ниже для shader
            const impacts = [];// Некий пустой массив, куда будут добавлены данные с координатами «бум»
            for (let i = 0; i < circlePointsAr.length; i++) {// пройдёмся по заранее указанным точкам
                impacts.push({// Добавим в массив для шейдера данные
                  // позиция «бума»
                    impactPosition:new THREE.Vector3(circlePointsAr[i][0],circlePointsAr[i][1],circlePointsAr[i][2]),
                  //радиус бума (задаётся случайное число от 0.0001 до 0.002)
                    impactMaxRadius: THREE.Math.randFloat(0.0001, 0.002),
                  // некоторый коэфициент
                    impactRatio: .01
                });
            }
            // необходимо для шейдера
            let uniforms = {
              // передаём этому объекту «impacts» данные из массива «impacts»
                impacts: {value: impacts}
            }
            // \ **

            // Важный код. Наполним массив точками из данных из canvas
            for(let y = 0; y < obj.w; y++) {// по оси Y
                for(let x = 0; x < obj.w; x++) {// по оси X
                    const a=obj.data[((obj.w*y)+x)*4+3];// берём только n-нные значения
                    if(a>200){
                        obj.ar.push([x-obj.w,y-obj.w/6.2])// здесь 6.2 — это как бы «отступ от севера»
                    }
                }
            }
            // https://r105.threejsfundamentals.org/threejs/lessons/threejs-optimize-lots-of-objects.html
            // RU: https://stepik.org/lesson/582241/step/1?unit=576975
            const lonHelper = new THREE.Object3D();
            scene.add(lonHelper);
            // We rotate the latHelper on its X axis to the latitude
            const latHelper = new THREE.Object3D();
            lonHelper.add(latHelper);
            // The position helper moves the object to the edge of the sphere
            const positionHelper = new THREE.Object3D();
            positionHelper.position.z = .5;
            // positionHelper.position.z = Math.random();
            latHelper.add(positionHelper);
            // Used to move the center of the cube so it scales from the position Z axis
            const originHelper = new THREE.Object3D();
            originHelper.position.z=.5;
            positionHelper.add(originHelper);
            const lonFudge=Math.PI*.5;
            const latFudge=Math.PI*-0.135;
            const geometries=[];
    

            obj.nAr=[];
            obj.counter=0;
            obj.counter2=0;

            // Материал с шейдером, который поможет скруглить PlaneBufferGeometry и анимировать, сделать «бум»
            const materialCircles=new THREE.MeshBasicMaterial({
                //color:0xffffff, // Можно НЕ указывать здесь цвет, так как он формируется FragmentShader'ом
                side:THREE.FrontSide,// Видимая часть — передняя (THREE.FrontSide) | THREE.DoubleSide | THREE.BackSide
                onBeforeCompile: shader => {// позиционный или точечный шейдер
                    shader.uniforms.impacts = uniforms.impacts;
                    shader.vertexShader = `
            struct impact {
              vec3 impactPosition;
              float impactMaxRadius;
              float impactRatio;
            };
            uniform impact impacts[${circlePointsAr.length}];
            attribute vec3 center;
            ${shader.vertexShader}
          `.replace(
                        `#include <begin_vertex>`,
                        `#include <begin_vertex>
            float finalStep = 0.0;
            for (int i = 0; i < ${circlePointsAr.length};i++){
    
              float dist = distance(center, impacts[i].impactPosition);
              float curRadius = impacts[i].impactMaxRadius * impacts[i].impactRatio/2.;
              float sstep = smoothstep(0., curRadius*1.8, dist) - smoothstep(curRadius - ( .8 * impacts[i].impactRatio ), curRadius, dist);
              sstep *= 1. - impacts[i].impactRatio;
              finalStep += sstep;
    
            }
            finalStep = clamp(finalStep*.5, 0., 1.);
            transformed += normal * finalStep * 0.25;
            `
                    );
                    //console.log(shader.vertexShader);
                    // Этот кусочек кода отвечает за «цветовой» шейдер, который и будет скруглять наш PlaneBufferGeometry
                    // и задавать ему определённый цвет
                    shader.fragmentShader = shader.fragmentShader.replace(
                        `vec4 diffuseColor = vec4( diffuse, opacity );`,
                        `
            if (length(vUv - 0.5) > 0.5) discard;
            vec4 diffuseColor = vec4( vec3(.7,.7,.7), 1.0 );
            `);
                }
    
            });
            materialCircles.defines = {"USE_UV" : ""};//https://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences
    
            let uty0=0
            // Проходимся по массиву наших точек («кружочков»)
            obj.ar.forEach(e=>{
                uty0++
                obj.counter2++;
                const geometry=new THREE.PlaneBufferGeometry(0.005,0.005);
                // Позиционирование «кружочков»
                // +15 — вращаем на 15 градусов западнее, хотя это можно было сделать иначе — вращать уже весь объект, а не каждый из «кружочков»
                // degToRad — https://threejs.org/docs/#api/en/math/MathUtils.degToRad
                lonHelper.rotation.y = THREE.MathUtils.degToRad(e[0])+lonFudge+15;
                const w=latHelper.rotation.x = THREE.MathUtils.degToRad(e[1])+latFudge;
                if(w-obj.prewLatX===0/*&&obj.counter2%2==0*/){
                    originHelper.updateWorldMatrix(true,false);// ЭТА
                    geometry.applyMatrix4(originHelper.matrixWorld);// и ЭТА штуки необходимы для обновления позиции отдельного «кружочка»
                    // Код ниже для анимирования «бум»
                    geometry.setAttribute("center", new THREE.Float32BufferAttribute(geometry.attributes.position.array, 3));
                    // Добавим вновь созданный «кружочек» в массив
                    geometries.push(geometry);
                }
                obj.prewLatX=w;
            });
            //Сформируем лишь одну буферную геометрию (которая по-идее должна обрабатываться на видео карте)
            //из массива ранее сформированных «кружочков»
            const geometryCircles = BufferGeometryUtils.mergeBufferGeometries(geometries, false);
            meshCircles = new THREE.Mesh(geometryCircles, materialCircles);
            // ниже тестовый материал, чтобы можно было увидеть НЕ «кружочки», а реальные PlaneBufferGeometry
            //meshCircles = new THREE.Mesh(geometryCircles, new THREE.MeshBasicMaterial({color:0xffffff}));

            // Добавим на сцену наш новый объект (саму карту)
            scene.add(meshCircles);

            //Добавим новый объект (саму карту) к родительскому элементу
            parent.add(meshCircles);

            // Немного увеличим наш новый объект, чтобы все «кружочки» были над поверхностью планеты
            meshCircles.scale.set(1.051,1.051,1.051)
    
            obj.c.remove();// Удалим временный canvas из которого брали точки
            obj.s.remove()// Удалим временные стили
                /* !!!WARN!!! Planet 2-7 (продолжение 2-5) */                const tweens2 = [];// Некий массив для анимаций (твинов)                for (let i = 0; i < circlePointsAr.length; i++) {// проходимся по массиву с заранее заданным точками «бума»                    tweens2.push({// добавляем в массив анимаций эту точку и указываем, что именно анимировать                        runTween:()=>{                            const tween=new TWEEN.Tween({value:0})                                .to(                                  { value: 1 },// желательно оставиь 1                                  THREE.Math.randInt(2500,5000) // формируем некое случайное числов от 2500 до 5000 — время анимации «бум» | попробуйте поставить 5000, 15000                                )                                .onUpdate(val=>{// во время обновления также меняем коэффициент                                    uniforms.impacts.value[i].impactRatio = val.value;                                })                                .onComplete(()=>{                                    uniforms.impacts.value[i].impactPosition=new THREE.Vector3(circlePointsAr[i][0],circlePointsAr[i][1],circlePointsAr[i][2]);// указываем позицию из заранее опредеёлнных точек                                    uniforms.impacts.value[i].impactMaxRadius = 5 * THREE.Math.randFloat(0.5, 0.75);                                    tweens2[i].runTween();// указываем расстояние, на сколько будет разлетаться «бум» | попробуйте поставить 0.75,1.2                                });                            tween.start();//запускаем эту анимацию                        }                    });                }                tweens2.forEach(t=>{t.runTween()});// запускаем цепочку анимаций                ///!!! смотрим 754 строку !!!              /* \ !!!WARN!!! Planet 2-7 */          }
        /* \ !!!WARN!!! Planet 2-5 */

          /* !!!WARN!!! Planet 2-6 */
            // Функция создания точек для передачи в MeshLine и создании на их основе линий
            // Принимает в себя объект, в котором есть три точки {q:[x,y,z],w:[x,y,z],e:[x,y,z]}
            //Curve
            function createCurve(q){
              // Эти штуки необходимы для позиционирования над поверхностью планеты
              const lonHelper = new THREE.Object3D();
              scene.add(lonHelper);
              // We rotate the latHelper on its X axis to the latitude
              const latHelper = new THREE.Object3D();
              lonHelper.add(latHelper);
              // The position helper moves the object to the edge of the sphere
              const positionHelper = new THREE.Object3D();
              positionHelper.position.z = .5;
              latHelper.add(positionHelper);
              // Used to move the center of the cube so it scales from the position Z axis
              const originHelper = new THREE.Object3D();
              originHelper.position.z = 0.5;
              positionHelper.add(originHelper);
              // QuadraticBezierCurve3 — создаёт из трёх и более точек кривую Безье
              const curve = new THREE.QuadraticBezierCurve3(
                  new THREE.Vector3(q.q[0],q.q[1],q.q[2]),
                  new THREE.Vector3(q.w[0],q.w[1],q.w[2]),
                  new THREE.Vector3(q.e[0],q.e[1],q.e[2])
              );
              // Возвращаю константу, хотя можно было и просто возвратить результат. Здесь просто её можно залогировать, чтобы понять, что происходит
              const pointsCurve = curve.getPoints(24);
              // ... например, так:
              // console.log(pointsCurve)
              return pointsCurve;
            }
            //\Curve

            const lineMesh=[]; // Это будет массив, где будут находиться все линии, чтобы анимировать их

            // Функция создания самой линии (MeshLine)
            // Принимает в себя значение результата выполнения функции выше
            // а именно точки Vector3 (из кривой Безье)
            function createMeshLine(dataFromCreateCurve,flat=null){
                // Строим геометрию
                // let color=new THREE.Color(1,getRandomFloat(.5,1.),1);
                // let color=new THREE.Color(.2,.7,1);
                // let color=new THREE.Color(.2,getRandomFloat(.5,.8),1);
                // Здесь я делаю цвета линий немного разными, чтобы разнообразить их
                let color=new THREE.Color(.2,THREE.Math.randFloat(.5,.8),1);
                let dashRatio=.5,
                    lineWidth=.005
                if(flat){// это линии, которые белые — летят из нашего центра в другие стороны, в отличии от синих линий, которые летят К ЦЕНТРУ (нашему условному центру)
                    color=new THREE.Color(0xffffff);
                    dashRatio=.9
                    lineWidth=.003
                }

                const line = new MeshLine();// экземпляр MeshLine
                line.setGeometry(dataFromCreateCurve);// Передаём ему геометрию из функции выше
                const geometryl = line.geometry;
                // Построить материал с параметрами, чтобы оживить его.
                const materiall = new MeshLineMaterial({
                    transparent: true, // Необходимо, чтобы была видна анимация, если false, то линия просто будет залита определённым цветом и не будет видна анимация
                    lineWidth,
                    color,
                    dashArray: 2, // всегда должен быть
                    dashOffset: 0, // начать с dash к zero
                    dashRatio, // видимая минута ряда длины. Мин: 0.5, Макс: 0.99
                });
                // Построение сетки
                const lineMeshMat = new THREE.Mesh(geometryl, materiall);// Создаём саму линию (Mesh)
                lineMeshMat.lookAt(new THREE.Vector3())// Здесь можно и не писать это
                scene.add(lineMeshMat); // Добавим её на сцену
                //parent.add(lineMeshMat);
                lineMesh.push(lineMeshMat); // Добавим эту одну линию, созданную выше, в массив для их анимаций
                /*function update() {
                    // Проверьте, есть ли dash, чтобы остановить анимацию.
                    // Уменьшить значение dashOffset анимировать dash.
                    lineMesh.material.uniforms.dashOffset.value -= 0.01;
                    // requestAnimationFrame(update)
                }
                update()*/

            }

            // Позиция основной точки нашей планеты (где сейчас «флагшток»)
            const mainPos=[.662,.8,-.28];

            // Пример использования функций выше
            createMeshLine(createCurve({q:[.63,.84,-.13],w:[.7,.8,-.2],e:mainPos}))

            // Тестовый код из примера на github
            const points = [];
            for (let j = 0; j < Math.PI; j += (2 * Math.PI) / 100) {
              points.push(Math.cos(j), Math.sin(j), 0);
            }
            let dashRatio=.5,
            lineWidth=.005
            let color=new THREE.Color(.2,THREE.Math.randFloat(.5,.8),1);
            const line = new MeshLine();
        
            line.setPoints(points);
        
            const materiall = new MeshLineMaterial({
                transparent: true,
                lineWidth,
                color,
                dashArray:2, // всегда должен быть
                dashOffset: 0, // начать с dash к zero
                dashRatio, // видимая минута ряда длины. Мин: 0.99, Макс: 0.5
            });
            //line.materiall.uniforms.dashOffset.value -= 0.01;
        
            const lineMeshMat = new THREE.Mesh(line, materiall);
            //lineMeshMat.lookAt(new THREE.Vector3())
            scene.add(lineMeshMat);
            // \ Тестовый код из примера на github
          /* \ !!!WARN!!! Planet 2-6 */
/** \ НАШ КОД */

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight
}

window.addEventListener('resize', () =>
{
    // Update sizes
    sizes.width = window.innerWidth
    sizes.height = window.innerHeight

    // Update camera
    camera.aspect = sizes.width / sizes.height
    camera.updateProjectionMatrix()

    // Update renderer
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Camera
 */
  // Камера
  const camera = new THREE.PerspectiveCamera(12,window.innerWidth / window.innerHeight,.01,100);
  // Позиция камеры
  camera.position.set(10.5,4,-3.5);
  //Как ей «смотреть» — смещаем «куда» она смотрит
  camera.setViewOffset(10, 10, -2, .5, 9, 9)



//MOON — 2-й вариант свечения планеты (атмосфера)//     const sphereGlow=new THREE.IcosahedronBufferGeometry(1.0573,9);const sphereGlow=new THREE.IcosahedronBufferGeometry(1.06,9);const materialGlow=	new THREE.ShaderMaterial({        uniforms:            {                "c":   { type: "f", value: 1.0 },                "p":   { type: "f", value: 1.4 },                glowColor: { type: "c", value: new THREE.Color(0x0086ff) },                viewVector: { type: "v3", value: camera.position }            },        vertexShader:`uniform vec3 viewVector;uniform float c;uniform float p;varying float intensity;void main() {vec3 vNormal = normalize( normalMatrix * normal );vec3 vNormel = normalize( normalMatrix * viewVector );intensity = pow( c - dot(vNormal, vNormel), p );gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );}`            ,        fragmentShader:        `uniform vec3 glowColor;varying float intensity;void main() {vec3 glow = glowColor * intensity;gl_FragColor = vec4( glow, .1 );}`        ,        side: THREE.FrontSide,        //blending: THREE.AdditiveBlending,        transparent: true    });const moonGlow = new THREE.Mesh( sphereGlow, materialGlow );moonGlow.rotation.y=-8.0;moonGlow.rotation.x=.9;//scene.add( moonGlow );lightHolder.add(moonGlow);//\ MOON

// Controls
 const controls = new OrbitControls(camera, canvas)
// controls.enableDamping = true

const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias:true
})

camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()

renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor('#000', 1);

const clock = new THREE.Clock()
const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()
    // Render
    renderer.render(scene, camera)

    lightHolder.quaternion.copy(camera.quaternion);

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)

    lineMesh.forEach(e=>{
        e.material.uniforms.dashOffset.value -= 0.01
    });
    TWEEN.update();}

tick()

Друзья, будьте бдительны и проверяйте свой код на максимально возможном количестве устройств.

Так выглядит шейдерный (вверху, который создаётся функцией mmm()) материал для планеты на ~10 яблофоне (возможно, это виднеется тот самый скрываемый нами от глаз основной родительский Mesh... но надругих уст-вах всё было отлично...)

Баг шейдера на iphone

Расшифровка временных меток видео:

00:00 Прывітанне, сябар
01:00 Для изучения шейдеров необходимо углубиться в информацию
04:47 Как изменить цвет в шейдере?
06:26 Точки в шейдерах очень важны
08:18 Шейдеры не поддаются глобальному освещению (внешним источникам света на сцене)
09:04 Вызываю ошибку в шейдере
09:17 Как устранить ошибку в шейдере?
10:21 FrontSide, DoubleSide, BackSide
10:46 Как можно заработать деньги с помощью canvas-sketch+three.js?
11:46 VertexShader — позиционный шейдер
13:02 Как выполняется анимация шейдеров на планете? Tween+Shader в ThreeJS
15:36 Итоги
17:00 shadertoy.com
18:53 Кот
19:44 MoonLight в Three JS (как бы атмосфера планеты Земля)
21:55 Отключаю свет на сцене
23:41 Ещё один вариант лунного свечения/атмосферы планеты
27:18 Выводы и почти финал по планете... Что будет дальше?
29:06 Шчаслівага кодынгу
copyleft 2023. we use: GatsbyJS, React, Google Analytics. Source code