Облака похожи на процедурные деревья, которые мы создали в прошлый раз. Разница в том, что мы не можем просто сложить вместе несколько объектов в форме облаков. Вместо этого мы объединим несколько сфер и дрожим вершины, чтобы они выглядели более крупными.

Эта статья является частью моей текущей серии руководств по ThreeJS средней сложности. Я давно хотел что-то среднее между уровнями вступления Как нарисовать куб и Давайте заполним экран шейдерным безумием. Итак, вот оно.

Для начала нам понадобятся три сферы. Как и в случае с деревьями, я объединил три объекта SphereGeometry в одну геометрию.

const geo = new THREE.Geometry()

const tuft1 = new THREE.SphereGeometry(1.5,7,8)
tuft1.translate(-2,0,0)
geo.merge(tuft1)

const tuft2 = new THREE.SphereGeometry(1.5,7,8)
tuft2.translate(2,0,0)
geo.merge(tuft2)

const tuft3 = new THREE.SphereGeometry(2.0,7,8)
tuft3.translate(0,0,0)
geo.merge(tuft3)

Чтобы нарисовать геометрию, мне нужна сетка с этой геометрией и материалом. Мне нужен низкополигональный вид с плоским затенением, поэтому мне нужно вызвать geo.computeFlatVertexNormals() и установить для flatShading значение true:

cloud = new THREE.Mesh(
    geo,
    new THREE.MeshLambertMaterial({
        color:'white',
        flatShading:true,
    })
)

Вот как это выглядит:

Чтобы грани действительно выделялись, я использую как рассеянный свет 0,3, так и направленный свет 0,7, идущий сбоку:

const light = new THREE.DirectionalLight( 0xffffff, 0.7 );
light.position.set( 1, 1, 0 ).normalize();
scene.add( light );
scene.add(new THREE.AmbientLight(0xffffff,0.3))

Дрожание

Один из способов сделать низкополигональный объект симпатичным - немного переместить точки, чтобы формы перестали быть идеально симметричными. Мы можем сделать это, случайным образом немного сместив вершины. Я создал функцию jitter, которая делает следующее:

// remap value from the range of [smin,smax] to [emin,emax]
const map = (val, smin, smax, emin, emax) => (emax-emin)*(val-smin)/(smax-smin) + emin
//randomly displace the x,y,z coords by the `per` value
const jitter = (geo,per) => geo.vertices.forEach(v => {
    v.x += map(Math.random(),0,1,-per,per)
    v.y += map(Math.random(),0,1,-per,per)
    v.z += map(Math.random(),0,1,-per,per)
})
jitter(geo,0.2)

Вызов jitter с 0,2 перемещает координаты x, y и z каждой вершины в геометрии на случайное значение между -0,2 и 0,2. Это перемещает точки достаточно, чтобы эффект был виден, не перемещая их настолько, чтобы зритель больше не мог сказать, что это сфера. Вот как это выглядит:

Конечно, у мультяшных облаков плоское дно. Мы можем легко сделать это с помощью функции chop, которая сглаживает любые точки ниже определенного значения:

const chopBottom = (geo,bottom) => geo.vertices.forEach(v => v.y = Math.max(v.y,bottom))
chopBottom(geo,-0.5)

В качестве последней детали, чтобы он выглядел симпатичнее, я добавил второй направленный свет с другой стороны, который излучает розоватое свечение. Это создает ощущение облаков на закате.

const light2 = new THREE.DirectionalLight( 0xff5566, 0.7 );
light2.position.set( -3, -1, 0 ).normalize();
scene.add( light2 );

Живая демонстрация

Вот и все, что есть. Просто объедините несколько примитивов, а затем немного поиграйте с вершинами, чтобы получить желаемый эффект. Этот же принцип можно использовать для многих других объектов, таких как здания, автомобили и даже животные. Обратите внимание на этого симпатичного низкополигонального льва, сделанного только из кубиков от Karim Maalou.

В следующий раз мы будем работать над созданным низкополигональным горным и водным ландшафтом.

Быть замеченным

Кстати, если вы работаете над классным интерфейсом WebVR, который вы хотели бы продемонстрировать прямо в Firefox Reality, дайте нам знать.