Облака похожи на процедурные деревья, которые мы создали в прошлый раз. Разница в том, что мы не можем просто сложить вместе несколько объектов в форме облаков. Вместо этого мы объединим несколько сфер и дрожим вершины, чтобы они выглядели более крупными.
Эта статья является частью моей текущей серии руководств по 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, дайте нам знать.