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

การค้นพบ Space
หลังจากทำ Small Arms Globe เสร็จไม่นาน ผมก็ทดลองใช้เดโมอนุภาค THREE.js ที่มีระยะชัดลึก ฉันสังเกตเห็นว่าสามารถเปลี่ยน "สเกล" ที่ตีความของฉากได้โดยการปรับปริมาณเอฟเฟกต์ที่ใช้ เมื่อเอฟเฟกต์ระยะชัดลึกมีความชัดเจนมาก วัตถุที่อยู่ไกลจะเบลอมาก ซึ่งคล้ายกับวิธีที่การถ่ายภาพแบบทิลต์ชิฟต์ทำงานเพื่อสร้างภาพลวงตาให้ดูเหมือนกำลังมองฉากขนาดเล็ก ในทางกลับกัน การลดเอฟเฟกต์จะทำให้ดูเหมือนว่าคุณกำลังมองไปยังห้วงอวกาศอันไกลโพ้น
ฉันเริ่มค้นหาข้อมูลที่ใช้เพื่อแทรกตำแหน่งอนุภาคได้ ซึ่งนำไปสู่ฐานข้อมูล HYG ของ astronexus.com ซึ่งเป็นการรวบรวมแหล่งข้อมูล 3 แหล่ง (Hipparcos, Yale Bright Star Catalog และ Gliese/Jahreiss Catalog) พร้อมด้วยพิกัดคาร์ทีเซียน xyz ที่คำนวณไว้ล่วงหน้า มาเริ่มกันเลย


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

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

ฉันตัดสินใจตั้งแต่เนิ่นๆ ว่าจะแทนหน่วย GL หนึ่งหน่วย ซึ่งก็คือพิกเซลในรูปแบบ 3 มิติด้วยระยะทาง 1 ปีแสง ซึ่งเป็นธรรมเนียมที่รวมตำแหน่งของทุกสิ่งทุกอย่างที่มองเห็นได้ และน่าเสียดายที่ทำให้ฉันเจอปัญหาความแม่นยำที่ร้ายแรงในภายหลัง
อีกอย่างที่ฉันตัดสินใจทำคือการหมุนฉากทั้งหมดแทนที่จะย้ายกล้อง ซึ่งเป็นสิ่งที่ฉันเคยทำในโปรเจ็กต์อื่นๆ มาบ้างแล้ว ข้อดีอย่างหนึ่งคือทุกอย่างจะวางอยู่บน "แท่นหมุน" เพื่อให้การลากเมาส์ไปทางซ้ายและขวาหมุนออบเจ็กต์ที่เป็นปัญหา แต่การซูมเข้าเป็นเพียงการเปลี่ยน camera.position.z
ขอบเขตการมองเห็น (FOV) ของกล้องก็เป็นแบบไดนามิกเช่นกัน เมื่อดึงออกไปด้านนอก มุมมองจะกว้างขึ้น ทำให้เห็นกาแล็กซีมากขึ้นเรื่อยๆ ในทางตรงกันข้าม เมื่อเคลื่อนที่เข้าหาดาว ขอบเขตการมองเห็นจะแคบลง ซึ่งจะช่วยให้กล้องมองเห็นสิ่งเล็กๆ (เมื่อเทียบกับกาแล็กซี) ได้โดยการบีบ FOV ให้มีลักษณะคล้ายแว่นขยายของเทพเจ้าโดยไม่ต้องกังวลกับปัญหาการตัดระนาบใกล้

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

การแสดงผลดวงอาทิตย์เป็นเรื่องยาก ผมต้องโกงด้วยเทคนิคกราฟิกแบบเรียลไทม์เท่าที่รู้ พื้นผิวของดวงอาทิตย์เป็นฟองพลาสมาที่ร้อนและจำเป็นต้องมีการเปลี่ยนแปลงอยู่ตลอดเวลา โดยจำลองผ่านพื้นผิวบิตแมปของภาพอินฟราเรดของพื้นผิวสุริยะ Surface Shader จะทำการค้นหาสีตามระดับสีเทาของเท็กซ์เจอร์นี้ และทำการค้นหาใน Color Ramp แยกต่างหาก เมื่อการค้นหานี้เปลี่ยนไปตามเวลา ก็จะทำให้เกิดการบิดเบือนคล้ายลาวานี้
เราใช้เทคนิคที่คล้ายกันกับโคโรนาของดวงอาทิตย์ แต่จะใช้การ์ดสไปรต์แบบแบนที่หันหน้าเข้าหากล้องเสมอโดยใช้ https://github.com/mrdoob/three.js/blob/master/src/extras/core/Gyroscope.js

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

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

ภาพจักรวาลเป็นที่ที่ผมรู้สึกว่าใช้เลนส์แฟลร์มากเกินไปได้ THREE.LensFlare ตอบโจทย์จุดประสงค์นี้ สิ่งที่ฉันต้องทำก็แค่ใส่หกเหลี่ยมแบบอนามอร์ฟิกและเติมกลิ่นอายของ JJ Abrams ลงไป ข้อมูลโค้ดด้านล่างแสดงวิธีสร้างในฉาก
// 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;
}
}
วิธีง่ายๆ ในการเลื่อนพื้นผิว

สำหรับ "ระนาบการวางแนวเชิงพื้นที่" เราได้สร้าง THREE.CylinderGeometry() ขนาดมหึมาและวางไว้ตรงกลางดวงอาทิตย์ หากต้องการสร้าง "คลื่นแสง" ที่แผ่ออกไปด้านนอก ฉันได้แก้ไขออฟเซ็ตของพื้นผิวตามเวลาดังนี้
mesh.material.map.needsUpdate = true;
mesh.material.map.onUpdate = function(){
this.offset.y -= 0.001;
this.needsUpdate = true;
}
map
คือพื้นผิวที่เป็นของวัสดุ ซึ่งมีฟังก์ชัน onUpdate ที่คุณเขียนทับได้ การตั้งค่าออฟเซ็ตจะทำให้เท็กซ์เจอร์ "เลื่อน" ไปตามแกนนั้น และการสแปม needsUpdate = true จะบังคับให้ลักษณะการทำงานนี้วนซ้ำ
การใช้แถบสี
ดาวแต่ละดวงมีสีแตกต่างกันตาม "ดัชนีสี" ที่นักดาราศาสตร์กำหนดให้ โดยทั่วไปแล้ว ดาวสีแดงจะเย็นกว่า และดาวสีน้ำเงิน/ม่วงจะร้อนกว่า การไล่ระดับสีนี้มีแถบสีขาวและสีส้มระดับกลาง
เมื่อแสดงผลดาว ฉันต้องการให้แต่ละอนุภาคมีสีของตัวเองตามข้อมูลนี้ วิธีทำคือใช้ "แอตทริบิวต์" ที่กำหนดให้กับวัสดุ Shader ที่ใช้กับอนุภาค
var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: datastarUniforms,
attributes: datastarAttributes,
/_ ... etc _/
});
var datastarAttributes = {
size: { type: 'f', value: [] },
colorIndex: { type: 'f', value: [] },
};
การกรอกข้อมูลอาร์เรย์ colorIndex จะทำให้แต่ละอนุภาคมีสีที่ไม่ซ้ำกันใน Shader โดยปกติแล้วเราจะส่ง vec3 ของสี แต่ในกรณีนี้ฉันจะส่ง float เพื่อให้ได้การค้นหา Color Ramp ในท้ายที่สุด

โดยการไล่ระดับสีมีลักษณะดังนี้ แต่ฉันต้องการเข้าถึงข้อมูลสีบิตแมปจาก 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;
}
จากนั้นจะใช้วิธีเดียวกันนี้ในการระบายสีดาวแต่ละดวงในมุมมองโมเดลดาว

การจัดการ Shader
ตลอดทั้งโปรเจ็กต์ ฉันพบว่าต้องเขียน Shader มากขึ้นเรื่อยๆ เพื่อให้ได้เอฟเฟกต์ภาพทั้งหมด ฉันเขียนโปรแกรมโหลดเชดเดอร์ที่กำหนดเองเพื่อจุดประสงค์นี้เพราะเบื่อที่จะต้องมีเชดเดอร์อยู่ใน 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') );
}
}
ฟังก์ชัน loadShaders() จะรับรายการชื่อไฟล์ Shader (คาดหวัง .fsh สำหรับ Fragment และ .vsh สำหรับ Vertex Shader) พยายามโหลดข้อมูล แล้วแทนที่รายการด้วยออบเจ็กต์ ผลลัพธ์สุดท้ายจะอยู่ใน THREE.js uniforms ซึ่งคุณสามารถส่งผ่าน Shader ไปยัง THREE.js uniforms ได้ดังนี้
var galacticShaderMaterial = new THREE.ShaderMaterial( {
vertexShader: shaderList.galacticstars.vertex,
fragmentShader: shaderList.galacticstars.fragment,
/_..._/
});
ฉันอาจใช้ require.js ได้ แต่จะต้องมีการประกอบโค้ดใหม่เพื่อจุดประสงค์นี้โดยเฉพาะ แม้ว่าโซลูชันนี้จะง่ายกว่ามาก แต่ฉันคิดว่ายังปรับปรุงได้อีก อาจจะในรูปแบบส่วนขยายของ THREE.js ก็ได้ โปรดแจ้งให้เราทราบหากคุณมีคำแนะนำหรือวิธีปรับปรุงเรื่องนี้ให้ดียิ่งขึ้น
ป้ายกำกับข้อความ CSS บน THREE.js
ในโปรเจ็กต์ล่าสุดของเราอย่าง Small Arms Globe ฉันได้ลองทำให้ป้ายกำกับข้อความปรากฏที่ด้านบนของฉาก THREE.js วิธีที่ฉันใช้จะคำนวณตำแหน่งโมเดลสัมบูรณ์ของตำแหน่งที่ต้องการให้ข้อความปรากฏ จากนั้นจะแปลงตำแหน่งหน้าจอโดยใช้ THREE.Projector() และสุดท้ายจะใช้ CSS "top" และ "left" เพื่อวางองค์ประกอบ CSS ในตำแหน่งที่ต้องการ
การทำซ้ำในช่วงแรกๆ ของโปรเจ็กต์นี้ใช้วิธีเดียวกันนี้ แต่ฉันก็อยากลองวิธีอื่นที่ Luis Cruz อธิบายไว้
แนวคิดพื้นฐานคือการจับคู่การแปลงเมทริกซ์ของ CSS3D กับกล้องและฉากของ THREE แล้วคุณจะ "วาง" องค์ประกอบ CSS ใน 3 มิติได้ราวกับว่าอยู่เหนือฉากของ THREE อย่างไรก็ตาม ฟีเจอร์นี้มีข้อจำกัด เช่น คุณจะวางข้อความไว้ใต้ออบเจ็กต์ THREE.js ไม่ได้ ซึ่งยังคงเร็วกว่าการพยายามจัดเลย์เอาต์โดยใช้แอตทริบิวต์ CSS "top" และ "left" มาก

คุณดูเดโม (และโค้ดในมุมมองแหล่งที่มา) สำหรับฟีเจอร์นี้ได้ที่นี่ อย่างไรก็ตาม เราพบว่าลำดับเมทริกซ์มีการเปลี่ยนแปลงสำหรับ 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 มิติแล้วทำให้ป้ายกำกับนั้นส่องสว่างพร้อมเงาตกกระทบ

เมื่อซูมเข้า ฉันพบว่าการปรับขนาดของตัวอักษรทำให้เกิดปัญหาในการจัดตำแหน่ง อาจเป็นเพราะระยะห่างระหว่างตัวอักษรและระยะขอบของข้อความใช่ไหม ปัญหาอีกอย่างคือข้อความจะกลายเป็นพิกเซลเมื่อซูมเข้า เนื่องจากโปรแกรมแสดงผล DOM จะถือว่าข้อความที่แสดงเป็นสี่เหลี่ยมพื้นผิว ซึ่งเป็นสิ่งที่ควรทราบเมื่อใช้วิธีนี้ เมื่อมองย้อนกลับไป ฉันอาจใช้ข้อความขนาดแบบอักษรขนาดใหญ่ก็ได้ และบางทีนี่อาจเป็นสิ่งที่ควรสำรวจในอนาคต ในโปรเจ็กต์นี้ ฉันยังใช้ป้ายกำกับข้อความตำแหน่ง CSS "top/left" ที่อธิบายไว้ก่อนหน้านี้สำหรับองค์ประกอบขนาดเล็กมากที่มาพร้อมกับดาวเคราะห์ในระบบสุริยะ
การเล่นเพลงและการเล่นวนซ้ำ
เพลงที่เปิดใน "แผนที่กาแล็กซี" ของ Mass Effect แต่งโดย Sam Hulick และ Jack Wall ซึ่งเป็นนักแต่งเพลงของ Bioware และมีอารมณ์แบบที่ฉันอยากให้ผู้เข้าชมได้สัมผัส เราต้องการใส่เพลงในโปรเจ็กต์เพราะคิดว่าเพลงเป็นส่วนสำคัญของบรรยากาศ ซึ่งจะช่วยสร้างความรู้สึกทึ่งและประหลาดใจที่เราพยายามจะสื่อ
Valdean Klump โปรดิวเซอร์ของเราได้ติดต่อ Sam ซึ่งมีเพลงที่ "ถูกตัดทิ้ง" จาก Mass Effect อยู่จำนวนหนึ่ง และเขาก็อนุญาตให้เราใช้เพลงเหล่านั้น แทร็กนี้มีชื่อว่า "In a Strange Land"
ฉันใช้แท็กเสียงสำหรับการเล่นเพลง แต่แม้ใน Chrome แอตทริบิวต์ "loop" ก็ไม่น่าเชื่อถือ บางครั้งก็ไม่วนซ้ำ ในท้ายที่สุด เราได้ใช้เคล็ดลับแท็กเสียงคู่เพื่อตรวจสอบจุดสิ้นสุดของการเล่นและวนไปยังแท็กอื่นเพื่อเล่น สิ่งที่น่าผิดหวังคือภาพนี้ยังคงวนซ้ำไม่สมบูรณ์ตลอดเวลา แต่ฉันรู้สึกว่านี่เป็นสิ่งที่ดีที่สุดที่ฉันทำได้แล้ว
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 เพื่อนร่วมงานของเรายังได้ใช้เวลาสร้าง "เสียงอวกาศ" ที่ยอดเยี่ยมซึ่งสร้างขึ้นมาเอง แต่ต้องตัดออกเนื่องจาก Web Audio API ไม่เสถียรและทำให้ Chrome ขัดข้องเป็นครั้งคราว น่าเสียดายที่เกิดเหตุการณ์นี้ขึ้น แต่ก็ทำให้เราคิดถึงเรื่องเสียงมากขึ้นสำหรับการทำงานในอนาคต ณ เวลาที่เขียนบทความนี้ เราได้รับแจ้งว่ามีการแก้ไข Web Audio API แล้ว ดังนั้นจึงเป็นไปได้ว่าตอนนี้ฟีเจอร์นี้จะใช้งานได้แล้ว โปรดคอยติดตามในอนาคต
องค์ประกอบการพิมพ์ที่จับคู่กับ WebGL ยังคงเป็นความท้าทาย และฉันไม่แน่ใจ 100% ว่าสิ่งที่เราทำอยู่นี้เป็นวิธีที่ถูกต้อง แต่ก็ยังรู้สึกเหมือนเป็นการแฮ็กอยู่ดี บางทีในอนาคตเราอาจใช้ THREE เวอร์ชันใหม่ที่มี CSS Renderer ที่กำลังจะเปิดตัวเพื่อเชื่อมโยงทั้ง 2 โลกนี้ให้ดียิ่งขึ้น
เครดิต
ขอขอบคุณ Aaron Koblin ที่อนุญาตให้เราทำโปรเจ็กต์นี้ Jono Brandel สำหรับการออกแบบและการติดตั้งใช้งาน UI ที่ยอดเยี่ยม การจัดรูปแบบข้อความ และการติดตั้งใช้งานทัวร์ Valdean Klump ที่ตั้งชื่อโปรเจ็กต์และเขียนข้อความทั้งหมด Sabah Ahmed ที่ช่วยเคลียร์สิทธิ์การใช้งานหลายตันสำหรับแหล่งข้อมูลและแหล่งที่มาของรูปภาพ Clem Wright ที่ติดต่อบุคคลที่เหมาะสมเพื่อเผยแพร่ Doug Fritz สำหรับความเป็นเลิศด้านเทคนิค George Brower ที่สอน JS และ CSS ให้ฉัน และแน่นอนว่าต้องขอบคุณ Mr. Doob สำหรับ THREE.js