ทำดาว 100,000 ดวง

สวัสดี ผมชื่อ Michael Chang ทำงานร่วมกับทีม Data Arts ที่ Google เมื่อเร็วๆ นี้เราได้ทำการทดลอง 100,000 ดาว ซึ่งเป็นการทดลองของ Chrome ที่แสดงภาพดาวในบริเวณใกล้เคียง โปรเจ็กต์สร้างขึ้นด้วย THREE.js และ CSS3D ในกรณีศึกษานี้ ฉันจะอธิบายเกี่ยวกับกระบวนการค้นหา แชร์เทคนิคบางอย่างในการเขียนโปรแกรม และปิดท้ายด้วยความคิดเห็นเล็กๆ น้อยๆ เพื่อการปรับปรุงในอนาคต

หัวข้อที่กล่าวถึงในที่นี้ค่อนข้างกว้างและต้องอาศัยความรู้เรื่อง THREE.js เราหวังว่าคุณจะยังเพลิดเพลินกับความรู้เบื้องต้นทางเทคนิคได้ คุณข้ามไปยังส่วนที่สนใจได้โดยใช้ปุ่มสารบัญทางด้านขวา ก่อนอื่น เราจะแสดงส่วนการแสดงผลของโปรเจ็กต์ ตามด้วยการจัดการตัวปรับแสงเงา และสุดท้ายวิธีใช้ป้ายกำกับข้อความ CSS ร่วมกับ WebGL

ดาว 100,000 ดวง การทดลอง Chrome โดยทีม Data Arts
ดาว 100,000 ดวงใช้ THREE.js เพื่อแสดงดวงดาวที่อยู่ใกล้เคียงบนทางช้างเผือก

การค้นพบอวกาศ

หลังจากที่เราดำเนินการกับ Small Arms Globe เสร็จแล้วไม่นาน ฉันก็ทำการทดลองใช้อนุภาค THREE.js ที่มีระยะชัดลึก ฉันสังเกตเห็นว่าฉันสามารถเปลี่ยน "สเกล" ที่ตีความแล้วของฉากได้โดยปรับปริมาณของเอฟเฟ็กต์ที่ใช้ เมื่อเอฟเฟกต์ระยะชัดลึกสุดขีด วัตถุที่อยู่ไกลจะพร่ามัวมาก คล้ายกับการทำงานของการถ่ายภาพแบบทิลท์ชิฟท์ในการสร้างภาพลวงตาของการมองภาพในกล้องจุลทรรศน์ ในทางกลับกัน การลดเอฟเฟกต์ทำให้ดูเหมือนว่าคุณกำลังมองออกไปในห้วงลึกของอวกาศ

ฉันเริ่มตามล่าหาข้อมูลที่ฉันสามารถใช้เพื่อแทรกตำแหน่งอนุภาค เส้นทางที่นำฉันไปยังฐานข้อมูล HYG ของ astronexus.com การรวบรวมแหล่งข้อมูล 3 แหล่ง (Hipparcos, Yale Bright Star Catalog และ Gliese/Jahreiss Catalog) พร้อมพิกัด xyz Cartesian ที่คำนวณไว้ล่วงหน้า มาเริ่มกันเลย

การพล็อตข้อมูลดาว
ขั้นตอนแรกคือพล็อตดาวทุกดวงในแคตตาล็อกเป็นอนุภาคเดียว
ดาวที่มีชื่อ
ดาวบางดวงในแคตตาล็อกมีชื่อเฉพาะ โดยมีป้ายกำกับที่นี่

ต้องใช้เวลาประมาณ 1 ชั่วโมงในการแฮ็กสิ่งที่สร้างข้อมูลรูปดาวให้ปรากฏในพื้นที่ 3 มิติ ชุดข้อมูลมีดาว 119,617 ดวง ดังนั้นการแสดงดาวแต่ละดวงด้วยอนุภาคจึงไม่ใช่ปัญหาสำหรับ GPU สมัยใหม่ และยังมีดาวที่ระบุแยกกัน 87 ดวง ดังนั้นผมจึงสร้างการวางซ้อนเครื่องหมาย CSS โดยใช้เทคนิคเดียวกับที่อธิบายไว้ใน Small Arms Globe

ระหว่างนี้ฉันเพิ่งดูซีรีส์ Mass Effect จบไป ในเกม ผู้เล่นจะได้รับเชิญให้ออกสำรวจกาแล็กซีและสแกนดาวเคราะห์ต่างๆ และอ่านประวัติของสิ่งมีชีวิตที่ฟังดูเป็นวิกิพีเดีย ซึ่งสมมติสมบูรณ์ เช่น สายพันธุ์ที่เติบโตบนโลก ประวัติศาสตร์ทางธรณีวิทยา และอื่นๆ อีกมากมาย

เมื่อเรารู้ข้อมูลที่มีอยู่จริงเกี่ยวกับดวงดาวมากมาย ก็น่าจะให้ข้อมูลที่แท้จริงเกี่ยวกับกาแล็กซีได้ในลักษณะเดียวกัน เป้าหมายสูงสุดสำหรับโปรเจ็กต์นี้คือการทำให้ข้อมูลนี้มีชีวิต เปิดโอกาสให้ผู้ชมได้สำรวจกาแล็กซี à la Mass Effect เพื่อเรียนรู้เกี่ยวกับดวงดาวและการกระจายตัว และหวังว่าจะเป็นตัวจุดประกายความน่าเกรงขามและความสงสัยเกี่ยวกับอวกาศ ในที่สุด

ฉันควรจะเกริ่นนำส่วนที่เหลือของกรณีศึกษานี้ด้วยการบอกว่าฉันไม่ได้เป็นนักดาราศาสตร์ และนี่เป็นผลงานของงานวิจัยมือสมัครเล่นที่ได้รับการสนับสนุนโดยคำแนะนำจากผู้เชี่ยวชาญภายนอก โปรเจ็กต์นี้ต้องตีความว่าเป็นการตีความด้านอวกาศโดยศิลปินอย่างแน่นอน

การสร้างกาแล็กซี

แผนของผมคือการสร้างแบบจำลองของกาแล็กซีที่สามารถแสดงข้อมูลของดาวได้ในบริบท และหวังว่าจะให้ทัศนียภาพที่ยอดเยี่ยมของสถานที่ของเราในทางช้างเผือก

ต้นแบบระยะต้นของกาแล็กซี
ต้นแบบยุคแรกๆ ของระบบอนุภาคทางช้างเผือก

ในการสร้างทางช้างเผือก ฉันสร้างอนุภาค 100,000 ชิ้นและนำมาวางเป็นเกลียวโดยการจำลองการก่อตัวของแขนกาแล็กซี ฉันไม่ได้กังวลเรื่องเทคนิคการก่อตัวของแขนเกลียวมากนัก เพราะนี่เป็นโมเดลตัวแทน ไม่ใช่รูปแบบทางคณิตศาสตร์ อย่างไรก็ตาม ฉันพยายามทำให้จำนวนแขนเกลียวถูกต้องมากขึ้นหรือน้อยลง และหมุนไปใน "ทิศทางที่ถูกต้อง"

ในแบบจำลองทางช้างเผือกเวอร์ชันต่อๆ ไป ผมได้ลดความสำคัญของการใช้อนุภาคโดยให้ภาพระนาบของกาแล็กซีร่วมไปกับอนุภาคเหล่านี้ โดยหวังว่าจะทำให้มีลักษณะเป็นภาพถ่ายมากขึ้น ภาพจริงเป็นภาพกาแล็กซีกังหัน NGC 1232 ที่อยู่ห่างจากเราประมาณ 70 ล้านปีแสง โดยปรับแต่งให้ดูเหมือนทางช้างเผือก

กำลังคำนวณสเกลของกาแล็กซี
ทุก GL ทุกหน่วยเป็นปีแสง ในกรณีนี้ ทรงกลมมีความกว้าง 110,000 ปีแสงโดยรวมระบบอนุภาค

ผมตัดสินใจตั้งแต่แรกเริ่มที่จะเป็นตัวแทนหน่วย GL หนึ่งหน่วย ซึ่งก็คือพิกเซลใน 3 มิติเป็นปีแสง 1 หน่วย ซึ่งเป็นรูปแบบที่การรวมการวางตำแหน่งโฆษณาไว้เป็นหนึ่งเดียวสำหรับทุกสิ่งที่เห็นภาพ และก็ต้องขออภัยที่เกิดปัญหาเกี่ยวกับความแม่นยำที่ร้ายแรงในภายหลัง

อีกวิธีการหนึ่งที่ผมตัดสินใจคือหมุนทั้งฉากแทนที่จะหมุนกล้อง ซึ่งเป็นสิ่งที่ผมทำแล้วในโครงการอื่นๆ อีก 2-3 โปรเจ็กต์ ข้อดีอย่างหนึ่งคือการวางทุกอย่างลงใน "เทิร์นเทเบิล" การลากเมาส์ไปทางซ้ายและขวาเพื่อหมุนวัตถุที่ต้องการ แต่การซูมเข้าเป็นเพียงการเปลี่ยน Camera.position.z เท่านั้น

ขอบเขตการมองเห็น (หรือ FOV) ของกล้องจะเป็นแบบไดนามิกด้วย เมื่อมองออก ขอบเขตการมองเห็นจะกว้างขึ้น พร้อมเข้าถึงกาแล็กซีมากขึ้นเรื่อยๆ แต่เมื่อเลื่อนเข้าหาดาว ขอบเขตการมองเห็นจะแคบลง ซึ่งช่วยให้กล้องดูสิ่งที่ไม่มีที่สิ้นสุดได้ (เมื่อเทียบกับกาแล็กซี) โดยยัด FOV ลงไปยังแว่นขยายที่เหมือนพระเจ้า โดยไม่ต้องจัดการกับปัญหาภาพหลุดใกล้ระนาบ

วิธีต่างๆ ในการแสดงภาพกาแล็กซี
(ด้านบน) กาแล็กซีอนุภาคอนุภาคต้น (ด้านล่าง) อนุภาคที่มีระนาบรูปภาพ

จากที่นี่ ผมสามารถ "วาง" ดวงอาทิตย์ที่ห่างจากแกนกลางกาแล็กซี หลายหน่วย ผมยังสามารถแสดงภาพขนาดสัมพัทธ์ของระบบสุริยะด้วยการทำแผนที่รัศมีของหน้าผา Kuiper (สุดท้ายแล้วผมเลือกแสดงภาพเมฆออริก) แทน ภายในระบบสุริยะนี้ ผมยังสามารถดูวงโคจรของโลกอย่างง่าย และรัศมีจริงของดวงอาทิตย์เมื่อเปรียบเทียบกัน

ระบบสุริยะ
ดวงอาทิตย์โคจรผ่านดาวเคราะห์และทรงกลมที่เป็นตัวแทนของแถบไคเปอร์

ดวงอาทิตย์แสดงผลได้ยาก ผมต้องใช้เทคนิคกราฟิกแบบเรียลไทม์หลายอย่างที่ผมรู้จัก พื้นผิวของดวงอาทิตย์คือฟองอากาศร้อนของพลาสมา และจำเป็นต้องมีการเคลื่อนตัวของแสงอาทิตย์ที่เปลี่ยนแปลงไปตามกาลเวลา การจำลองนี้จำลองผ่านพื้นผิวบิตแมปของภาพอินฟราเรดของพื้นผิวพลังงานแสงอาทิตย์ ตัวปรับเฉดสีพื้นผิวจะสร้างสีตามโทนสีเทาของพื้นผิวนี้ และดำเนินการค้นหาในระดับสีแยกต่างหาก เมื่อการค้นหานี้ถูกเปลี่ยนเมื่อเวลาผ่านไป จะทําให้เกิดความบิดเบี้ยวในลักษณะลาวา

ส่วนเทคนิคโคโรนาของดวงอาทิตย์ก็ใช้เทคนิคที่คล้ายกัน แต่จะเป็นการ์ดสไปรท์แบบแบนราบที่หันเข้าหากล้องเสมอโดยใช้ https://github.com/mrdoob/three.js/blob/master/src/extras/core/Gyroscope.js

โซลูชันการแสดงผล
เวอร์ชันแรกของดวงอาทิตย์

แสงอาทิตย์ถูกสร้างขึ้นผ่านจุดยอดมุมและตัวปรับเฉดสีแบบเศษส่วนที่ใช้กับทรงห่วง โดยหมุนรอบขอบของพื้นผิวพลังงานแสงอาทิตย์ ตัวปรับแสงเงาจุดยอดมีฟังก์ชันนอยส์ที่ทำให้ค่อยๆ ตัดเป็นรูปคล้ายหยด

ตรงนี้เองที่ผมเริ่มพบปัญหาระนาบขัดแย้งเนื่องจากความแม่นยำของ GL ตัวแปรสำหรับความแม่นยำทั้งหมดกำหนดไว้ล่วงหน้าใน THREE.js ผมเลยเพิ่มความแม่นยำให้ไม่ได้ถ้าต้องทำงานอะไรมากมาย ปัญหาเรื่องความแม่นยําไม่ได้เลวร้ายเท่าต้นทาง แต่เมื่อฉันเริ่มสร้างโมเดลระบบดาวอื่นๆ ปัญหาก็เริ่มเกิดขึ้น

โมเดลดาว
ในภายหลังมีการสร้างโค้ดการแสดงดวงอาทิตย์ในรูปแบบทั่วไปเพื่อแสดงดวงดาวอื่นๆ

ที่ผมใช้ก็มีเคล็ดลับอยู่ 2-3 อย่างเพื่อลดการต่อสู้กับ Z Material.polygonoffset ของ THREE เป็นพร็อพเพอร์ตี้ที่อนุญาตให้แสดงผลรูปหลายเหลี่ยมในตำแหน่งต่างๆ ที่รับรู้ได้ (เท่าที่ฉันเข้าใจ) ซึ่งใช้ในการบังคับให้ระนาบโคโรนาแสดงผลบนพื้นผิวดวงอาทิตย์เสมอ ส่วนเบื้องล่างนี้มี "รัศมี" ของดวงอาทิตย์เพื่อให้แสงที่ส่องออกจากทรงกลม

ปัญหาอีกอย่างที่เกี่ยวข้องกับความแม่นยำคือ นางแบบดาวจะเริ่มกระตุกขณะที่ซูมเข้า เพื่อแก้ไขปัญหานี้ ฉันต้อง "ลบ" การหมุนฉาก แล้วหมุนโมเดลดาวและแผนที่สภาพแวดล้อมแยกกันเพื่อสร้างภาพลวงตาว่าคุณกำลังโคจรรอบดาว

กำลังสร้าง Lensflare

พลังที่ยิ่งใหญ่มาพร้อมกับความรับผิดชอบที่ใหญ่ยิ่ง
พลังที่ยิ่งใหญ่มาพร้อมกับความรับผิดชอบที่ยิ่งใหญ่

การแสดงภาพอวกาศเป็นจุดที่ผมรู้สึกว่าสามารถหลบเลี่ยงการใช้แสงแฟลร์มากเกินไป THREE.LensFlare ทำหน้าที่นี้ สิ่งที่ผมต้องทำคือใส่หกเหลี่ยมอนามอร์ฟิกและ JJ Abrams 1 ขีด ตัวอย่างด้านล่างแสดงวิธีสร้างขึ้นในฉากของคุณ

// This function returns a lesnflare THREE object to be .add()ed to the scene graph
function addLensFlare(x,y,z, size, overrideImage){
var flareColor = new THREE.Color( 0xffffff );

lensFlare = new THREE.LensFlare( overrideImage, 700, 0.0, THREE.AdditiveBlending, flareColor );

// we're going to be using multiple sub-lens-flare artifacts, each with a different size
lensFlare.add( textureFlare1, 4096, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );

// and run each through a function below
lensFlare.customUpdateCallback = lensFlareUpdateCallback;

lensFlare.position = new THREE.Vector3(x,y,z);
lensFlare.size = size ? size : 16000 ;
return lensFlare;
}

// this function will operate over each lensflare artifact, moving them around the screen
function lensFlareUpdateCallback( object ) {
var f, fl = this.lensFlares.length;
var flare;
var vecX = -this.positionScreen.x _ 2;
var vecY = -this.positionScreen.y _ 2;
var size = object.size ? object.size : 16000;

var camDistance = camera.position.length();

for( f = 0; f < fl; f ++ ) {
flare = this.lensFlares[ f ];

flare.x = this.positionScreen.x + vecX * flare.distance;
flare.y = this.positionScreen.y + vecY * flare.distance;

flare.scale = size / camDistance;
flare.rotation = 0;

}
}

วิธีง่ายๆ ในการเลื่อนพื้นผิว

ได้รับแรงบันดาลใจจาก Homeworld
เครื่องบินแบบ Cartesian ที่จะช่วยในการกำหนดทิศทางตามพื้นที่ในอวกาศ

สำหรับ "ระนาบการวางแนวเชิงพื้นที่" ได้มีการสร้างขึ้น THREE.CylinderGeometry() ขนาดมหึมาและมีศูนย์กลางอยู่ที่ดวงอาทิตย์ เพื่อสร้าง "คลื่นแสง" ที่พัดออกมารอบข้างออกมา ฉันจึงปรับเปลี่ยนออฟเซ็ตพื้นผิวของพื้นผิวเมื่อเวลาผ่านไปดังนี้

mesh.material.map.needsUpdate = true;
mesh.material.map.onUpdate = function(){
this.offset.y -= 0.001;
this.needsUpdate = true;
}

map คือพื้นผิวที่เป็นของวัสดุ ซึ่งจะมีฟังก์ชัน onUpdate ซึ่งคุณเขียนทับได้ การตั้งค่าออฟเซ็ตจะทำให้พื้นผิวมีการ "เลื่อน" ไปตามแกนนั้น และการส่งสแปม needsUpdate = true จะทำให้การทำงานนี้วนซ้ำ

การใช้ระดับสี

ดาวแต่ละดวงจะมีสีที่แตกต่างกันตาม "ดัชนีสี" ที่นักดาราศาสตร์กำหนดให้ โดยทั่วไป ดาวสีแดงจะเย็นกว่า และดาวสีน้ำเงิน/ม่วงจะร้อนกว่า แถบสีขาวและสีส้มระดับกลางจะอยู่ในการไล่ระดับสีนี้

ฉันอยากให้แต่ละอนุภาคมีสีของตนเองตามข้อมูลนี้เมื่อแสดงภาพดาว วิธีการคือใช้ "แอตทริบิวต์" ที่ระบุให้กับวัสดุให้เฉดสีที่ใช้กับอนุภาค

var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: datastarUniforms,
attributes: datastarAttributes,
/_ ... etc _/
});
var datastarAttributes = {
size: { type: 'f', value: [] },
colorIndex: { type: 'f', value: [] },
};

การเติมอาร์เรย์ colorIndex จะทำให้แต่ละอนุภาคมีสีที่ไม่ซ้ำกันในตัวปรับสี ปกติจะมีตัวแปรสี vec3 แต่ในตัวอย่างนี้ ผมจะส่งผ่านในแบบลอยเพื่อทำการค้นหาระดับสีในที่สุด

ระดับสี
ระดับสีที่ใช้เพื่อค้นหาสีที่มองเห็นได้จากดัชนีสีของดาว

ระดับสีเป็นแบบนี้ แต่ฉันต้องเข้าถึงข้อมูลสีบิตแมปจาก JavaScript วิธีที่ผมทำก็คือโหลดรูปภาพลงใน DOM ก่อน วาดลงในองค์ประกอบ Canvas แล้วเข้าถึงบิตแมปของ Canvas

// make a blank canvas, sized to the image, in this case gradientImage is a dom image element
gradientCanvas = document.createElement('canvas');
gradientCanvas.width = gradientImage.width;
gradientCanvas.height = gradientImage.height;

// draw the image
gradientCanvas.getContext('2d').drawImage( gradientImage, 0, 0, gradientImage.width, gradientImage.height );

// a function to grab the pixel color based on a normalized percentage value
gradientCanvas.getColor = function( percentage ){
return this.getContext('2d').getImageData(percentage \* gradientImage.width,0, 1, 1).data;
}

จากนั้นจึงใช้วิธีการเดียวกันนี้ในการระบายสีดาวแต่ละดวงในมุมมองโมเดลดาว

ตาตัวเอง!
เทคนิคเดียวกันนี้ยังใช้ในการค้นหาสีเพื่อหาคลาสสเปกตรัมของดาว

การปรับแสงเงา

ตลอดทำโปรเจ็กต์ ฉันพบว่าฉันต้องเขียนเฉดสีมากขึ้นเพื่อให้เอฟเฟกต์ภาพทั้งหมดสำเร็จ ฉันเขียนตัวโหลดตัวปรับเฉดสีที่กำหนดเองสำหรับวัตถุประสงค์นี้เพราะฉันเบื่อหน่ายกับการมีตัวปรับเฉดสีอยู่ใน index.html

// list of shaders we'll load
var shaderList = ['shaders/starsurface', 'shaders/starhalo', 'shaders/starflare', 'shaders/galacticstars', /*...etc...*/];

// a small util to pre-fetch all shaders and put them in a data structure (replacing the list above)
function loadShaders( list, callback ){
var shaders = {};

var expectedFiles = list.length \* 2;
var loadedFiles = 0;

function makeCallback( name, type ){
return function(data){
if( shaders[name] === undefined ){
shaders[name] = {};
}

    shaders[name][type] = data;

    //  check if done
    loadedFiles++;
    if( loadedFiles == expectedFiles ){
    callback( shaders );
    }

};

}

for( var i=0; i<list.length; i++ ){
var vertexShaderFile = list[i] + '.vsh';
var fragmentShaderFile = list[i] + '.fsh';

//  find the filename, use it as the identifier
var splitted = list[i].split('/');
var shaderName = splitted[splitted.length-1];
$(document).load( vertexShaderFile, makeCallback(shaderName, 'vertex') );
$(document).load( fragmentShaderFile,  makeCallback(shaderName, 'fragment') );

}
}

_ ผลลัพธ์สุดท้ายจะอยู่ในเครื่องแบบ THREE.js ของคุณ ซึ่งคุณสามารถส่งตัวให้เฉดสีไปที่เครื่องแบบ THREE.js ได้โดยทำดังนี้

var galacticShaderMaterial = new THREE.ShaderMaterial( {
vertexShader: shaderList.galacticstars.vertex,
fragmentShader: shaderList.galacticstars.fragment,
/_..._/
});

ฉันน่าจะใช้ required.js ก็ได้แม้ว่าอาจต้องการประกอบโค้ดบางอย่างอีกครั้งเพื่อวัตถุประสงค์นี้ แม้ว่าจะง่ายกว่านี้ แต่เราสามารถปรับปรุงโซลูชันนี้ ซึ่งอาจจะเป็นส่วนขยาย THREE.js ก็ได้ โปรดแจ้งให้เราทราบหากคุณมีคำแนะนำหรือวิธีพัฒนาให้ดียิ่งขึ้น

ป้ายกำกับข้อความ CSS ที่ด้านบนของ THREE.js

ในโครงการล่าสุดของเรา Small Arms Globe ผมชอบทำให้ป้ายกำกับข้อความปรากฏขึ้นที่ด้านบนของฉาก THREE.js วิธีที่ฉันใช้จะคำนวณตำแหน่งโมเดลสัมบูรณ์ของตำแหน่งที่ฉันต้องการให้ข้อความปรากฏ จากนั้นกำหนดตำแหน่งหน้าจอโดยใช้ THREE.Projector() และสุดท้ายใช้ CSS "top" และ "left" เพื่อวางองค์ประกอบ CSS ในตำแหน่งที่ต้องการ

การทำซ้ำช่วงแรกๆ ในโปรเจ็กต์นี้ใช้เทคนิคเดียวกันนี้ แต่ฉันอยากลองใช้วิธีอื่นที่ Luis Cruz อธิบายไว้

แนวคิดเบื้องต้น: จับคู่การแปลงเมทริกซ์ของ CSS3D เป็นกล้องและฉากของ 3 มิติ จากนั้นคุณจะ "วาง" องค์ประกอบ CSS ในแบบ 3 มิติ ราวกับว่าอยู่บนฉากของ 3 มิติได้ แต่มีข้อจำกัดบางประการ ตัวอย่างเช่น คุณจะไม่สามารถให้ข้อความอยู่ใต้ออบเจ็กต์ THREE.js ซึ่งยังคงเร็วกว่าการพยายามทำเลย์เอาต์โดยใช้แอตทริบิวต์ CSS "top" และ "left" มาก

ป้ายกำกับข้อความ
การใช้การแปลง CSS3D เพื่อวางป้ายกำกับข้อความที่ด้านบนของ WebGL

ดูการสาธิต (และโค้ดในซอร์สโค้ด) เพื่อดูข้อมูลนี้ได้ที่นี่ อย่างไรก็ตาม เราพบว่าลำดับเมทริกซ์มีการเปลี่ยนแปลงสำหรับ THREE.js ฟังก์ชันที่ฉันได้อัปเดต:

/_ Fixes the difference between WebGL coordinates to CSS coordinates _/
function toCSSMatrix(threeMat4, b) {
var a = threeMat4, f;
if (b) {
f = [
a.elements[0], -a.elements[1], a.elements[2], a.elements[3],
a.elements[4], -a.elements[5], a.elements[6], a.elements[7],
a.elements[8], -a.elements[9], a.elements[10], a.elements[11],
a.elements[12], -a.elements[13], a.elements[14], a.elements[15]
];
} else {
f = [
a.elements[0], a.elements[1], a.elements[2], a.elements[3],
a.elements[4], a.elements[5], a.elements[6], a.elements[7],
a.elements[8], a.elements[9], a.elements[10], a.elements[11],
a.elements[12], a.elements[13], a.elements[14], a.elements[15]
];
}
for (var e in f) {
f[e] = epsilon(f[e]);
}
return "matrix3d(" + f.join(",") + ")";
}

เนื่องจากทุกสิ่งมีการเปลี่ยนรูปแบบ ข้อความจึงไม่หันหน้าเข้าหากล้องอีกต่อไป วิธีแก้ไขคือการใช้ THREE.Gyroscope() ที่บังคับให้ Object3D "สูญเสีย" การวางแนวที่รับช่วงมาจากฉาก เทคนิคนี้เรียกว่า "บิลบอร์ด" และ Gyroscope เหมาะสำหรับการดำเนินการนี้

สิ่งที่ดีก็คือ DOM และ CSS ปกติทั้งหมดยังคงเล่นด้วยกัน เช่น สามารถเลื่อนเมาส์ไปวางเหนือป้ายกำกับข้อความ 3 มิติ และทำให้เงาตกกระทบสว่างไสว

ป้ายกำกับข้อความ
ให้ป้ายข้อความหันหน้าเข้าหากล้องเสมอโดยติดไว้กับ THREE.Gyroscope()

เมื่อซูมเข้า ฉันพบว่าการปรับขนาดตัวอักษรทำให้มีปัญหาในการกำหนดตำแหน่ง อาจเป็นเพราะการจัดช่องไฟและระยะห่างจากขอบของข้อความ ปัญหาอีกอย่างคือ ข้อความกลายเป็นพิกเซลเมื่อซูมเข้า เนื่องจากตัวแสดงผล DOM ถือว่าข้อความที่แสดงผลเป็นสี่เหลี่ยมที่มีพื้นผิว ซึ่งเป็นสิ่งที่ควรระวังเมื่อใช้วิธีนี้ เมื่อนึกทบทวนดู ฉันคิดว่าคงใช้แค่ข้อความตัวอักษรขนาดใหญ่ และบางทีนี่อาจเป็นไอเดียสำหรับการสำรวจในอนาคต ในโปรเจ็กต์นี้ฉันยังใช้ป้ายกำกับข้อความตำแหน่ง CSS "ด้านบน/ซ้าย" ที่อธิบายไว้ก่อนหน้านี้ สำหรับองค์ประกอบขนาดเล็กมากซึ่งมีดาวเคราะห์ควบคู่ในระบบสุริยะ

การเล่นเพลงและการวนซ้ำ

ผลงานเพลงที่เล่นระหว่าง "Galactic Map" ของ Mass Effect คือเพลงของ Sam Hulick และ Jack Wall นักประพันธ์เพลงไบโอแวร์ ซึ่งให้อารมณ์ที่ผมอยากให้ผู้เข้าชมได้สัมผัส เราอยากได้เพลงบางเพลงในโปรเจ็กต์เพราะรู้สึกว่าเป็นส่วนสำคัญของบรรยากาศ ซึ่งก็ช่วยสร้างความน่าเกรงขามและความมหัศจรรย์ที่เราพยายามจะมุ่งหวัง

โปรดิวเซอร์ Valdean Klump ได้ติดต่อแซมที่มีเพลง "ตัดพื้น" จาก Mass Effect มากๆ เขาอนุญาตให้เราใช้ แทร็กมีชื่อว่า "In a Strange Land"

ฉันใช้แท็กเสียงสำหรับการเล่นเพลง อย่างไรก็ตาม แม้แต่ใน Chrome แอตทริบิวต์ "ลูป" ก็ไม่น่าเชื่อถือ บางครั้งก็ใช้การวนซ้ำไม่ได้ สุดท้าย การแฮ็กแบบแท็กเสียงคู่นี้ก็ใช้เพื่อตรวจหาการสิ้นสุดของการเล่นและการวนไปยังแท็กอื่นเพื่อเล่น สิ่งที่น่าผิดหวังคือ ตอนที่ยังนี้ไม่วนซ้ำแบบแนบเนียนตลอดเวลา ผมคิดว่านี่เป็นสิ่งที่ดีที่สุดที่ทำได้

var musicA = document.getElementById('bgmusicA');
var musicB = document.getElementById('bgmusicB');
musicA.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playB = function(){
musicB.play();
}
// make it wait 15 seconds before playing again
setTimeout( playB, 15000 );
}, false);

musicB.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playA = function(){
musicA.play();
}
// otherwise the music will drive you insane
setTimeout( playA, 15000 );
}, false);

// okay so there's a bit of code redundancy, I admit it
musicA.play();

สิ่งที่ต้องปรับปรุง

หลังจากได้ทำงานกับ THREE.js มาสักพักหนึ่งแล้ว ฉันรู้สึกว่าฉันมาจนถึงจุดที่ข้อมูลของฉันปะปนกับโค้ดมากเกินไป ตัวอย่างเช่น ตอนที่กำหนดวัสดุ พื้นผิว และคำสั่งเรขาคณิตในบทสนทนา ผมแค่เรียกว่า "การสร้างโมเดล 3 มิติด้วยโค้ด" ปัญหานี้ทำให้รู้สึกแย่มาก และเป็นส่วนที่ความพยายามในอนาคตกับ THREE.js ควรปรับปรุงให้ดีขึ้นได้อย่างมาก ตัวอย่างเช่น การกำหนดข้อมูลที่เป็นรูปธรรมในไฟล์แยกต่างหากคือ คุณควรดูและปรับเปลี่ยนได้ในบางบริบท และสามารถนำกลับมาใช้ในโครงการหลักได้

เพื่อนร่วมงานของเรา Ray McClure ยังได้ใช้เวลาสร้าง "เสียงรบกวนอวกาศ" ที่ยอดเยี่ยมและต้องตัดออกเนื่องจาก API เสียงในเว็บไม่เสถียร ทำให้ Chrome ขัดข้องอยู่บ่อยๆ น่าเสียดายจัง แต่นั่นก็ทำให้เราคิดถึงพื้นที่อย่างเต็มที่สำหรับการทำงานในอนาคต จากการเขียนนี้ เราได้รับแจ้งว่า Web Audio API ได้รับการแพตช์แล้ว จึงเป็นไปได้ที่การทดสอบจะทํางานได้ในตอนนี้ ซึ่งเป็นสิ่งที่รอดูในอนาคต

องค์ประกอบแบบตัวอักษรที่จับคู่กับ WebGL ยังคงเป็นเรื่องที่ท้าทาย และผมก็ไม่แน่ใจ 100% ว่าสิ่งที่เราทำอยู่นี้เป็นวิธีที่ถูกต้อง แต่ก็ยังดูเหมือนถูกแฮ็กอยู่ บางทีเวอร์ชันต่อๆ ไปของ THREE ที่มีตัวแสดงผล CSS ที่กำลังมาแรงสามารถใช้เพื่อรวม 2 โลกเข้าด้วยกันได้ดียิ่งขึ้น

เครดิต

ต้องขอบคุณ Aaron Koblin ที่ให้เราเดินทางไปเมืองด้วยโปรเจ็กต์นี้ Jono Brandel สำหรับการออกแบบ UI ที่ยอดเยี่ยม รวมถึงการใช้งาน การจัดการประเภท และการใช้งานการทัวร์ชม Valdean Klump ให้ชื่อและสำเนาทั้งหมดแก่โครงการ ซาบาห์ อาห์เหม็ดสำหรับการล้างเมตริกสิทธิ์การใช้จำนวนมากสำหรับแหล่งที่มาของข้อมูลและรูปภาพ Clem Wright ที่ติดต่อกลุ่มคนที่เหมาะสมสำหรับการเผยแพร่ Doug Fritz สำหรับความเป็นเลิศทางเทคนิค George Brower ช่วยสอน JS และ CSS ให้ฉัน และแน่นอน Mr. Doob สำหรับ THREE.js

รายการอ้างอิง