portaldacalheta.pt
  • หลัก
  • การจัดการโครงการ
  • การเพิ่มขึ้นของระยะไกล
  • การบริหารโครงการ
  • เครื่องมือและบทช่วยสอน
เทคโนโลยี

กราฟิก 3 มิติ: บทช่วยสอน WebGL



โลกของกราฟิก 3 มิติในตอนแรกน่ากลัวมาก ไม่ว่าคุณจะต้องการสร้างโลโก้ 3 มิติแบบโต้ตอบหรือออกแบบเกมที่สมบูรณ์หากคุณไม่รู้หลักการของการเรนเดอร์ 3 มิติคุณอาจจมปลักอยู่ในห้องสมุดที่รวบรวมสิ่งต่างๆมากมาย

การใช้ห้องสมุดอาจเป็นเครื่องมือที่เหมาะสมและ JavaScript มีโอเพ่นซอร์สที่น่าทึ่งในรูปแบบของ three.js . มีข้อเสียบางประการในการใช้โซลูชันสำเร็จรูปแม้ว่า:



  • พวกเขาสามารถมีคุณสมบัติมากมายที่คุณไม่ได้วางแผนจะใช้ ขนาดของคุณสมบัติพื้นฐานที่ย่อขนาด tres.js อยู่ที่ประมาณ 500kB และคุณสมบัติเพิ่มเติมใด ๆ (การอัปโหลดไฟล์โมเดลจริงเป็นหนึ่งในนั้น) ทำให้เพย์โหลดใหญ่ขึ้น การถ่ายโอนข้อมูลจำนวนนั้นเพียงเพื่อแสดงโลโก้ที่หมุนเวียนบนเว็บไซต์ของคุณจะเป็นการสิ้นเปลือง
  • ชั้นนอกของสิ่งที่เป็นนามธรรมสามารถทำการปรับเปลี่ยนที่ปกติง่ายและทำได้ยาก วิธีการแรเงาวัตถุบนหน้าจอที่สร้างสรรค์ของคุณสามารถนำไปใช้งานได้ง่ายหรือต้องใช้เวลาหลายสิบชั่วโมงในการรวมเข้ากับเนื้อหาในห้องสมุดของคุณ
  • ในขณะที่ไลบรารีได้รับการปรับให้เหมาะสมเป็นอย่างดีในสถานการณ์ส่วนใหญ่คุณสามารถลบส่วนเสริมพิเศษสำหรับกรณีการใช้งานของคุณได้ ตัวแสดงผลสามารถทำให้ขั้นตอนบางอย่างรันบนกราฟิกการ์ดได้หลายล้านครั้ง ทุกคำสั่งที่นำออกจากขั้นตอนดังกล่าวหมายความว่าการ์ดแสดงผลที่อ่อนแอกว่าสามารถจัดการเนื้อหาของคุณได้โดยไม่มีปัญหา

แม้ว่าคุณจะตัดสินใจใช้ไลบรารีกราฟิกระดับสูง แต่การมีความเข้าใจพื้นฐานเกี่ยวกับสิ่งที่ซ่อนอยู่จะช่วยให้คุณใช้งานได้อย่างมีประสิทธิภาพมากขึ้น ไลบรารียังสามารถมีฟังก์ชันขั้นสูงเช่น ShaderMaterial ใน three.js. การรู้หลักการแสดงกราฟิกช่วยให้คุณสามารถใช้คุณสมบัติดังกล่าวได้



กฎเกสตัลต์ขององค์กรการรับรู้

ภาพประกอบโลโก้ 3D ApeeScape บนผืนผ้าใบ WebGL



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

รหัสสุดท้าย มีให้คุณทดลองเล่นด้วย



เป็นตัวแทนของโมเดล 3 มิติ

สิ่งแรกที่คุณต้องเข้าใจคือวิธีการแสดงผลโมเดล 3 มิติ แบบจำลองทำจากตาข่ายสามเหลี่ยม สามเหลี่ยมแต่ละอันจะแสดงด้วยจุดยอดสามจุดสำหรับแต่ละมุมของสามเหลี่ยม มีคุณสมบัติสามประการที่แนบมากับจุดยอดที่พบมากที่สุด

ตำแหน่งจุดยอด

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



จุดยอดปกติ

ทรงกลมที่มีโครงร่างเดียวกันโดยใช้แรเงาแบนเรียบ

พิจารณาสองรุ่นก่อนหน้านี้ ประกอบด้วยตำแหน่งจุดยอดเดียวกันแม้ว่าจะดูแตกต่างกันโดยสิ้นเชิงเมื่อเป็นตัวแทน เป็นไปได้อย่างไร?



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

การเปรียบเทียบระหว่างเวกเตอร์ปกติสำหรับการแรเงาแบบเรียบและแบบเรียบ



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

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



พิกัดพื้นผิว

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

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

รูปสามเหลี่ยมถูกวาดโดยใช้พิกัด UV ของจุดยอดแต่ละจุดในรูปสามเหลี่ยมและใช้ภาพที่จับระหว่างพิกัดเหล่านั้นบนพื้นผิว

การสาธิตการทำแผนที่ UV พร้อมด้วยแพทช์ที่โดดเด่นและการเย็บที่มองเห็นได้บนโมเดล

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

โครงร่างโครงร่างไม่ได้เป็นส่วนหนึ่งของพื้นผิว แต่ถูกวางซ้อนไว้ที่ด้านบนของภาพเพื่อให้คุณเห็นว่าสิ่งต่าง ๆ มีความสัมพันธ์กันอย่างไร

กำลังโหลดโมเดล OBJ

เชื่อหรือไม่ว่านี่คือสิ่งที่คุณต้องรู้เพื่อสร้างโมเดลโหลดเดอร์ง่ายๆของคุณเอง รูปแบบไฟล์ OBJ ง่ายพอที่จะใช้ตัวแยกวิเคราะห์ในโค้ดไม่กี่บรรทัด

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

ใบหน้าแสดงด้วยกลุ่มของจุดยอด แต่ละจุดยอดจะแสดงด้วยดัชนีของคุณสมบัติแต่ละอย่างดังนั้นดัชนีจึงเริ่มต้นที่ 1 มีหลายวิธีในการแทนค่านี้ แต่เราจะยึดติดกับ 'v1 / vt1 / vn1 v2 / vt2 / vn2 v3 / vt3 / vn3` กำหนดให้มีคุณสมบัติทั้งสามและ จำกัด จำนวนจุดยอดสำหรับแต่ละใบหน้าไว้ที่สาม ข้อ จำกัด ทั้งหมดเหล่านี้ถูกสร้างขึ้นเพื่อให้ตัวโหลดง่ายที่สุดเท่าที่จะเป็นไปได้เนื่องจากตัวเลือกอื่น ๆ ทั้งหมดต้องการการประมวลผลเล็กน้อยก่อนที่จะสามารถอยู่ในรูปแบบที่ WebGL ชอบได้

เราได้วางข้อกำหนดไว้มากมายสำหรับโปรแกรมโหลดไฟล์ของเรา นั่นอาจฟังดู จำกัด แต่แอปพลิเคชันการสร้างแบบจำลอง 3 มิติมักให้ความสามารถในการกำหนดข้อ จำกัด เหล่านั้นเมื่อส่งออกโมเดลเป็นไฟล์ OBJ

โค้ดต่อไปนี้จะแยกวิเคราะห์สตริงที่เป็นตัวแทนของไฟล์ OBJ และสร้างโมเดลในรูปแบบของใบหน้า

function Geometry (faces) this.faces = faces // Parses an OBJ file, passed as a string Geometry.parseOBJ = function (src) { var POSITION = /^vs+([d.+-eE]+)s+([d.+-eE]+)s+([d.+-eE]+)/ var NORMAL = /^vns+([d.+-eE]+)s+([d.+-eE]+)s+([d.+-eE]+)/ var UV = /^vts+([d.+-eE]+)s+([d.+-eE]+)/ var FACE = /^fs+(-?d+)/(-?d+)/(-?d+)s+(-?d+)/(-?d+)/(-?d+)s+(-?d+)/(-?d+)/(-?d+)(?:s+(-?d+)/(-?d+)/(-?d+))?/ lines = src.split(' ') var positions = [] var uvs = [] var normals = [] var faces = [] lines.forEach(function (line) { // Match each line of the file against various RegEx-es var result if ((result = POSITION.exec(line)) != null) { // Add new vertex position positions.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]))) } else if ((result = NORMAL.exec(line)) != null) { // Add new vertex normal normals.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]))) } else if ((result = UV.exec(line)) != null) { // Add new texture mapping point uvs.push(new Vector2(parseFloat(result[1]), 1 - parseFloat(result[2]))) } else if ((result = FACE.exec(line)) != null) { // Add new face var vertices = [] // Create three vertices from the passed one-indexed indices for (var i = 1; i <10; i += 3) { var part = result.slice(i, i + 3) var position = positions[parseInt(part[0]) - 1] var uv = uvs[parseInt(part[1]) - 1] var normal = normals[parseInt(part[2]) - 1] vertices.push(new Vertex(position, normal, uv)) } faces.push(new Face(vertices)) } }) return new Geometry(faces) } // Loads an OBJ file from the given URL, and returns it as a promise Geometry.loadOBJ = function (url) { return new Promise(function (resolve) { var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState == XMLHttpRequest.DONE) { resolve(Geometry.parseOBJ(xhr.responseText)) } } xhr.open('GET', url, true) xhr.send(null) }) } function Face (vertices) [] function Vertex (position, normal, uv) function Vector3 (x, y, z) 0 this.y = Number(y) function Vector2 (x, y)

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

ทำการแปลงเชิงพื้นที่

จุดทั้งหมดในโมเดลที่เราโหลดนั้นสัมพันธ์กับระบบพิกัดของมัน หากเราต้องการแปลหมุนและปรับขนาดโมเดลสิ่งที่เราต้องทำคือดำเนินการนี้บนระบบพิกัด ระบบพิกัด A สัมพันธ์กับระบบพิกัด B ถูกกำหนดโดยตำแหน่งของศูนย์กลางเป็นเวกเตอร์ p_ab และเวกเตอร์สำหรับแต่ละแกน x_ab, y_ab และ z_ab แทนทิศทางของแกนนั้น ดังนั้นหากจุดถูกย้ายไป 10 บน x จากระบบพิกัด A จากนั้น - ในระบบพิกัด B - มันจะเคลื่อนที่ไปในทิศทางของ x_ab คูณด้วย 10

ข้อมูลทั้งหมดนี้ถูกจัดเก็บในรูปแบบเมทริกซ์ต่อไปนี้:

x_ab.x y_ab.x z_ab.x p_ab.x x_ab.y y_ab.y z_ab.y p_ab.y x_ab.z y_ab.z z_ab.z p_ab.z 0 0 0 1

หากเราต้องการแปลงเวกเตอร์ 3 มิติ q เราจะต้องคูณเมทริกซ์การแปลงด้วยเวกเตอร์เท่านั้น:

q.x q.y q.z 1

สิ่งนี้ทำให้จุดเคลื่อนที่โดย q.x ตามแกนใหม่ x, โดย q.y ตามแกนใหม่ y และโดย q.z ตามแกนใหม่ z. ในที่สุดมันก็ทำให้จุดเคลื่อนที่ไปตามเวกเตอร์ p ซึ่งเป็นสาเหตุที่เราใช้จุดหนึ่งเป็นองค์ประกอบสุดท้ายของการคูณ

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

มีการเปลี่ยนแปลงหลายอย่างที่สามารถทำได้และเราจะมาดูการเปลี่ยนแปลงหลัก ๆ

ไม่มีการเปลี่ยนแปลง

หากไม่มีการเปลี่ยนแปลงเกิดขึ้นเวกเตอร์ p เป็นเวกเตอร์ศูนย์เวกเตอร์ x คือ [1, 0, 0], y คือ [0, 1, 0] และ z คือ [0, 0, 1]. จากนี้ไปเราจะอ้างถึงค่าเหล่านี้เป็นค่าเริ่มต้นสำหรับเวกเตอร์เหล่านี้ การประยุกต์ใช้ค่าเหล่านี้ทำให้เรามีเมทริกซ์เอกลักษณ์:

1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1

นี่เป็นจุดเริ่มต้นที่ดีสำหรับการเปลี่ยนโซ่

การแปล

วางการแปลงสำหรับการแปล

ในขณะที่ทำการแปลเวกเตอร์ทั้งหมดยกเว้นเวกเตอร์ p จะมีค่าเริ่มต้น ผลลัพธ์ในเมทริกซ์ต่อไปนี้:

1 0 0 p.x 0 1 0 p.y 0 0 1 p.z 0 0 0 1

การปรับ

วางการเปลี่ยนแปลงเพื่อการปรับเปลี่ยน

การติดตั้งโมเดลหมายถึงการลดจำนวนที่แต่ละพิกัดก่อให้เกิดตำแหน่งของจุด ไม่มีการกระจัดสม่ำเสมอที่เกิดจากความพอดีดังนั้นเวกเตอร์ p คงค่าเริ่มต้นไว้ เวกเตอร์แกนเริ่มต้นต้องคูณด้วยปัจจัยมาตราส่วนตามลำดับซึ่งส่งผลให้เมทริกซ์ต่อไปนี้:

s_x 0 0 0 0 s_y 0 0 0 0 s_z 0 0 0 0 1

ที่นี่ s_x, s_y และ s_z แสดงถึงการปรับใช้กับแต่ละแกน

การหมุน

การแปลงเฟรมสำหรับการหมุนรอบแกน Z

ภาพด้านบนแสดงสิ่งที่เกิดขึ้นเมื่อเราหมุนกรอบพิกัดรอบแกน Z

การหมุนไม่ส่งผลให้เกิดการกระจัดสม่ำเสมอดังนั้นเวกเตอร์ p คงค่าเริ่มต้นไว้ ตอนนี้สิ่งต่าง ๆ ซับซ้อนขึ้นเล็กน้อย การหมุนทำให้การเคลื่อนที่ตามแกนที่แน่นอนในระบบพิกัดเดิมเคลื่อนที่ไปในทิศทางอื่น ดังนั้นถ้าเราหมุนระบบพิกัด 45 องศารอบแกน Z โดยเคลื่อนที่ไปตามแกน x ของระบบพิกัดเดิมการเคลื่อนที่จะเกิดขึ้นในแนวทแยงระหว่างแกน x และแกน y ในระบบพิกัดใหม่

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

Around X: 1 0 0 0 0 cos(phi) sin(phi) 0 0 -sin(phi) cos(phi) 0 0 0 0 1 Around Y: cos(phi) 0 sin(phi) 0 0 1 0 0 -sin(phi) 0 cos(phi) 0 0 0 0 1 Around Z: cos(phi) -sin(phi) 0 0 sin(phi) cos(phi) 0 0 0 0 1 0 0 0 0 1

การนำไปใช้

ทั้งหมดนี้สามารถใช้เป็นคลาสที่เก็บตัวเลข 16 ตัวจัดเก็บอาร์เรย์ในไฟล์ ลำดับคอลัมน์หลัก .

function Transformation () { // Create an identity transformation this.fields = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } // Multiply matrices, to chain transformations Transformation.prototype.mult = function (t) { var output = new Transformation() for (var row = 0; row <4; ++row) { for (var col = 0; col < 4; ++col) { var sum = 0 for (var k = 0; k < 4; ++k) { sum += this.fields[k * 4 + row] * t.fields[col * 4 + k] } output.fields[col * 4 + row] = sum } } return output } // Multiply by translation matrix Transformation.prototype.translate = function (x, y, z) // Multiply by scaling matrix Transformation.prototype.scale = function (x, y, z) // Multiply by rotation matrix around X axis Transformation.prototype.rotateX = function (angle) // Multiply by rotation matrix around Y axis Transformation.prototype.rotateY = function (angle) // Multiply by rotation matrix around Z axis Transformation.prototype.rotateZ = function (angle)

มองผ่านกล้อง

ส่วนสำคัญของการนำเสนอวัตถุบนหน้าจอมีดังนี้: กล้องถ่ายรูป ส่วนประกอบหลักของกล้องมีสองส่วน ตำแหน่งของมันและวิธีที่มันฉายวัตถุที่สังเกตเห็นบนหน้าจอ

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

องค์ประกอบหลักที่สองคือวิธีฉายวัตถุที่สังเกตเห็นลงบนเลนส์ ใน WebGL ทุกสิ่งที่มองเห็นบนหน้าจอจะอยู่ในกล่อง ช่องมีช่วงระหว่าง -1 ถึง 1 ในแต่ละแกน ทุกสิ่งที่มองเห็นอยู่ในกล่องนั้น เราสามารถใช้วิธีเมทริกซ์การแปลงเดียวกันเพื่อสร้างเมทริกซ์การฉาย

การฉายภาพ Orthographic

พื้นที่สี่เหลี่ยมที่เปลี่ยนเป็นมิติ * framebuffer * ที่เหมาะสมโดยใช้การฉายภาพออร์โทกราฟิค

การฉายภาพที่ง่ายที่สุดคือ [orthographic projection] (https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographical-projection-matrix/orthographic-projection-matrix) กล่องถูกนำมาในช่องว่างที่ระบุความกว้างความสูงและความลึกโดยสมมติว่าจุดศูนย์กลางอยู่ที่ตำแหน่งศูนย์ จากนั้นการฉายภาพจะปรับขนาดกล่องให้พอดีกับกล่องที่อธิบายไว้ก่อนหน้านี้ซึ่ง WebGL สังเกตวัตถุ เนื่องจากเราต้องการปรับขนาดแต่ละมิติเป็นสองเราจึงตั้งค่าแต่ละแกนเป็น 2/size โดยที่ size คือขนาดของแกนตามลำดับ ข้อแม้เล็ก ๆ อย่างหนึ่งคือการที่เราคูณแกน Z ด้วยค่าลบ สิ่งนี้ทำเพราะเราต้องการพลิกทิศทางของมิตินั้น เมทริกซ์สุดท้ายมีรูปแบบนี้:

2/width 0 0 0 0 2/height 0 0 0 0 -2/depth 0 0 0 0 1

การฉายภาพมุมมอง

Frustum ถูกเปลี่ยนเป็นมิติ * framebuffer * ที่เหมาะสมโดยใช้การฉายภาพมุมมอง

เราจะไม่ลงรายละเอียดเกี่ยวกับวิธีการออกแบบการฉายภาพนี้ แต่ใช้ไฟล์ สูตรสุดท้าย ซึ่งเป็นมาตรฐานที่ดีสำหรับตอนนี้ เราสามารถทำให้มันง่ายขึ้นโดยการวางเส้นโครงไว้ที่ตำแหน่งศูนย์บนแกน x และ y ทำให้ขีด จำกัด ด้านขวา / ซ้ายและบน / ล่างเท่ากับ width/2 และ height/2 ตามลำดับ พารามิเตอร์ n และ f เป็นตัวแทนของเครื่องบินตัด near และ far ซึ่งเป็นระยะทางที่เล็กที่สุดและใหญ่ที่สุดที่กล้องจะจับจุดได้ พวกมันแสดงด้วยด้านคู่ขนานของ น่าผิดหวัง ในภาพด้านบน

การฉายภาพมุมมองมักจะแสดงด้วยไฟล์ มุมมอง (เราจะใช้แนวตั้ง) อัตราส่วนภาพ และระยะทางใกล้และไกลของเครื่องบิน ข้อมูลนั้นสามารถใช้ในการคำนวณ width และ height จากนั้นอาร์เรย์สามารถสร้างได้จากเทมเพลตต่อไปนี้:

2*n/width 0 0 0 0 2*n/height 0 0 0 0 (f+n)/(n-f) 2*f*n/(n-f) 0 0 -1 0

ในการคำนวณความกว้างและความสูงสามารถใช้สูตรต่อไปนี้:

height = 2 * near * Math.tan(fov * Math.PI / 360) width = aspectRatio * height

FOV (มุมมองภาพ) หมายถึงมุมแนวตั้งที่กล้องจับด้วยเลนส์ อัตราส่วนภาพแสดงถึงอัตราส่วนของความกว้างต่อความสูงของภาพและขึ้นอยู่กับขนาดของหน้าจอที่เรากำลังแสดง

การนำไปใช้

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

function Camera () { this.position = new Transformation() this.projection = new Transformation() } Camera.prototype.setOrthographic = function (width, height, depth) { this.projection = new Transformation() this.projection.fields[0] = 2 / width this.projection.fields[5] = 2 / height this.projection.fields[10] = -2 / depth } Camera.prototype.setPerspective = function (verticalFov, aspectRatio, near, far) { var height_div_2n = Math.tan(verticalFov * Math.PI / 360) var width_div_2n = aspectRatio * height_div_2n this.projection = new Transformation() this.projection.fields[0] = 1 / height_div_2n this.projection.fields[5] = 1 / width_div_2n this.projection.fields[10] = (far + near) / (near - far) this.projection.fields[10] = -1 this.projection.fields[14] = 2 * far * near / (near - far) this.projection.fields[15] = 0 } Camera.prototype.getInversePosition = function () { var orig = this.position.fields var dest = new Transformation() var x = orig[12] var y = orig[13] var z = orig[14] // Transpose the rotation matrix for (var i = 0; i <3; ++i) { for (var j = 0; j < 3; ++j) { dest.fields[i * 4 + j] = orig[i + j * 4] } } // Translation by -p will apply R^T, which is equal to R^-1 return dest.translate(-x, -y, -z) }

นี่เป็นชิ้นสุดท้ายที่เราต้องการก่อนที่จะเริ่มวาดภาพบนหน้าจอ

วาดวัตถุด้วยแชนเนลกราฟิก WebGL

พื้นผิวที่เรียบง่ายที่สุดที่คุณวาดได้คือสามเหลี่ยม ในความเป็นจริงสิ่งที่คุณวาดในอวกาศ 3 มิติส่วนใหญ่ประกอบด้วยสามเหลี่ยมจำนวนมาก

ดูพื้นฐานว่าขั้นตอนของช่องแผนภูมิทำอย่างไร

สิ่งแรกที่ต้องทำความเข้าใจคือการแสดงผลหน้าจอใน WebGL อย่างไร มันคือช่องว่าง 3 มิติซึ่งครอบคลุมระหว่าง -1 ถึง 1 บนแกน x , ย ย ด้วย . ตามค่าเริ่มต้นแกนนี้ ด้วย ไม่ได้ใช้ แต่คุณสนใจกราฟิก 3 มิติดังนั้นคุณควรเปิดใช้งานทันที

ด้วยเหตุนี้สิ่งต่อไปนี้จึงจำเป็นต้องมีสามขั้นตอนในการวาดสามเหลี่ยมบนพื้นผิวนี้

หลักการเกสตัลต์ของความใกล้ชิด

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

ตอนนี้คุณได้กำหนดจุดยอดให้กับ GPU แล้วคุณจะบอก GPU ว่าจะใช้ตรรกะอะไรเมื่อวางจุดยอดบนหน้าจอ ขั้นตอนนี้จะใช้เพื่อใช้การแปลงเมทริกซ์ของเรา GPU นั้นดีมากในการคูณเมทริกซ์ 4x4 จำนวนมากดังนั้นเราจะนำคุณสมบัตินั้นไปใช้งานได้ดี

ในขั้นตอนสุดท้าย GPU จะแรสเตอร์สามเหลี่ยมนั้น Rasterization เป็นกระบวนการในการถ่ายภาพกราฟิกแบบเวกเตอร์และกำหนดว่าจะต้องทาสีพิกเซลใดบนหน้าจอเพื่อให้วัตถุกราฟิกเวกเตอร์แสดง ในกรณีของเรา GPU กำลังพยายามกำหนดพิกเซลที่อยู่ภายในแต่ละสามเหลี่ยม สำหรับแต่ละพิกเซล GPU จะถามคุณว่าคุณต้องการทาสีเป็นสีอะไร

นี่คือสี่องค์ประกอบที่จำเป็นในการวาดสิ่งที่คุณต้องการและเป็นตัวอย่างที่ง่ายที่สุดของ a ช่องกราฟิก . สิ่งต่อไปนี้คือการดูแต่ละรายการและการนำไปใช้งานง่ายๆ

เฟรมบัฟเฟอร์ กำหนดไว้ล่วงหน้า

องค์ประกอบที่สำคัญที่สุดสำหรับแอปพลิเคชัน WebGL คือบริบท WebGL คุณสามารถเข้าถึงได้ด้วย gl = canvas.getContext ('webgl') หรือใช้ ’experimental-webgl' หรือในกรณีที่เบราว์เซอร์ที่ใช้ในปัจจุบันไม่รองรับฟังก์ชันทั้งหมดของ WebGL 'ผืนผ้าใบ' ที่เราอ้างถึงคือองค์ประกอบ DOM ของผืนผ้าใบที่เราต้องการวาด บริบทประกอบด้วยหลายสิ่งซึ่ง ได้แก่ ไฟล์ เฟรมบัฟเฟอร์ กำหนดไว้ล่วงหน้า

สามารถอธิบาย เฟรมบัฟเฟอร์ เช่นเดียวกับบัฟเฟอร์ (วัตถุ) ใด ๆ ที่คุณสามารถวาดได้ โดยค่าเริ่มต้นไฟล์ เฟรมบัฟเฟอร์ ค่าเริ่มต้นจะจัดเก็บสีของแต่ละพิกเซลบนผืนผ้าใบที่เชื่อมโยงบริบท WebGL ตามที่อธิบายไว้ในส่วนก่อนหน้าเมื่อเราวาดไฟล์ เฟรมบัฟเฟอร์ แต่ละพิกเซลจะอยู่ระหว่าง -1 ถึง 1 บนแกน x ย ย . สิ่งที่เรากล่าวถึงก็คือความจริงที่ว่าโดยค่าเริ่มต้น WebGL ไม่ได้ใช้แกน ด้วย . สามารถเปิดใช้งานฟังก์ชันดังกล่าวได้โดยเรียกใช้ gl.enable (gl.DEPTH_TEST) เยี่ยมมาก แต่การทดสอบเชิงลึกคืออะไร?

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

ภาพวาดที่คุณทำจะยังคงอยู่บนหน้าจอจนกว่าคุณจะบอกให้ล้าง ในการดำเนินการนี้คุณต้องโทร gl.clear (gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) ซึ่งจะลบทั้งสีและบัฟเฟอร์ความลึก ในการเลือกสีที่จะตั้งค่าพิกเซลที่ถูกลบให้ใช้ gl.clearColor (rojo, verde, azul, alfa)

มาสร้างเรนเดอร์ที่ใช้แคนวาสและล้างตามความต้องการ:

function Renderer (canvas) Renderer.prototype.setClearColor = function (red, green, blue) { gl.clearColor(red / 255, green / 255, blue / 255, 1) } Renderer.prototype.getContext = function () { return this.gl } Renderer.prototype.render = function () gl.DEPTH_BUFFER_BIT) var renderer = new Renderer(document.getElementById('webgl-canvas')) renderer.setClearColor(100, 149, 237) loop() function loop () { renderer.render() requestAnimationFrame(loop) }

การแนบสคริปต์นี้เข้ากับ HTML ต่อไปนี้จะทำให้เกิดสี่เหลี่ยมผืนผ้าสีฟ้าสดใสบนหน้าจอ

requestAnimationFrame

ที่เรียกว่า N ทำให้โหนดถูกเรียกอีกครั้งทันทีที่การเรนเดอร์ก่อนหน้าเสร็จสิ้นและการจัดการเหตุการณ์ทั้งหมดเสร็จสิ้น

Vertex Buffer Objects

สิ่งแรกที่คุณควรทำคือกำหนดจุดยอดที่คุณต้องการวาด คุณสามารถทำได้โดยอธิบายผ่านเวกเตอร์ในอวกาศ 3 มิติ หลังจากนั้นคุณต้องย้ายข้อมูลนั้นไปยัง GPU RAM สร้าง Buffer ใหม่ เวอร์เท็กซ์บัฟเฟอร์ (ก.พ. ).

ก วัตถุบัฟเฟอร์ โดยทั่วไปเป็นวัตถุที่เก็บอาร์เรย์ของหน่วยความจำบน GPU เป็นก ก.พ. สิ่งนี้บ่งบอกถึงสิ่งที่ GPU สามารถใช้หน่วยความจำได้เท่านั้น โดยส่วนใหญ่แล้ววัตถุบัฟเฟอร์ที่คุณสร้างจะเป็น VBO .

คุณสามารถกรอก ก.พ. รับจุดยอดทั้งหมด 3N ที่เรามีและสร้างเมทริกซ์ของ ลอย ด้วยองค์ประกอบ 2N สำหรับตำแหน่งจุดยอด y VBO บรรทัดฐานจุดยอดและ Geometry สำหรับพิกัดพื้นผิว ก.พ. . กลุ่มละสามคน ลอย หรือสอง ลอย สำหรับพิกัด UV แสดงถึงพิกัดแต่ละจุดของจุดยอด จากนั้นเราส่งอาร์เรย์เหล่านี้ไปยัง GPU และจุดยอดของเราก็พร้อมสำหรับส่วนที่เหลือของช่อง

เนื่องจากตอนนี้ข้อมูลอยู่ใน RAM ของ GPU แล้วคุณจึงสามารถลบข้อมูลนั้นออกจาก RAM ที่ใช้งานทั่วไปได้ นั่นคือเว้นแต่คุณต้องการแก้ไขในภายหลังและโหลดซ้ำ การแก้ไขแต่ละครั้งจะต้องตามด้วยการอัปโหลดเนื่องจากการแก้ไขอาร์เรย์ JS ของเราใช้ไม่ได้กับ VBO ใน RAM จริงของ GPU

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

นอกจากนี้เรายังเพิ่มการทำให้เป็นอนุกรมในชั้นเรียนของเรา Geometry.prototype.vertexCount = function () { return this.faces.length * 3 } Geometry.prototype.positions = function () { var answer = [] this.faces.forEach(function (face) { face.vertices.forEach(function (vertex) { var v = vertex.position answer.push(v.x, v.y, v.z) }) }) return answer } Geometry.prototype.normals = function () { var answer = [] this.faces.forEach(function (face) { face.vertices.forEach(function (vertex) { var v = vertex.normal answer.push(v.x, v.y, v.z) }) }) return answer } Geometry.prototype.uvs = function () { var answer = [] this.faces.forEach(function (face) { face.vertices.forEach(function (vertex) { var v = vertex.uv answer.push(v.x, v.y) }) }) return answer } //////////////////////////////// function VBO (gl, data, count) { // Creates buffer object in GPU RAM where we can store anything var bufferObject = gl.createBuffer() // Tell which buffer object we want to operate on as a VBO gl.bindBuffer(gl.ARRAY_BUFFER, bufferObject) // Write the data, and set the flag to optimize // for rare changes to the data we're writing gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW) this.gl = gl this.size = data.length / count this.count = count this.data = bufferObject } VBO.prototype.destroy = function () { // Free memory that is occupied by our buffer object this.gl.deleteBuffer(this.data) } และองค์ประกอบภายใน

VBO

ชนิดข้อมูล gl สร้างไฟล์ ก.พ. ในบริบท WebGL ที่ส่งผ่านโดยยึดตามอาร์เรย์ที่ส่งผ่านเป็นพารามิเตอร์ที่สอง

คุณสามารถดูการเรียกบริบทได้สามครั้ง createBuffer () โทร bindBuffer () สร้างไฟล์ กันชน . โทร ARRAY_BUFFER สั่งให้เครื่องสถานะ WebGL ใช้หน่วยความจำเฉพาะนี้เป็นหน่วยความจำปัจจุบัน ก.พ. (bufferData ()) สำหรับการดำเนินการทั้งหมดในอนาคตจนกว่าจะระบุไว้เป็นอย่างอื่น หลังจากนั้นเราตั้งค่าของ ก.พ. ปัจจุบันกับข้อมูลที่ให้มาพร้อมกับ deleteBuffer() .

นอกจากนี้เรายังมีวิธีการทำลายที่ลบวัตถุบัฟเฟอร์ GPU RAM ของเราโดยใช้ function Mesh (gl, geometry) { var vertexCount = geometry.vertexCount() this.positions = new VBO(gl, geometry.positions(), vertexCount) this.normals = new VBO(gl, geometry.normals(), vertexCount) this.uvs = new VBO(gl, geometry.uvs(), vertexCount) this.vertexCount = vertexCount this.position = new Transformation() this.gl = gl } Mesh.prototype.destroy = function () { this.positions.destroy() this.normals.destroy() this.uvs.destroy() } .

คุณสามารถใช้สาม VBO และการเปลี่ยนแปลงเพื่ออธิบายคุณสมบัติทั้งหมดของตาข่ายพร้อมกับตำแหน่ง

Geometry.loadOBJ('/assets/model.obj').then(function (geometry) { var mesh = new Mesh(gl, geometry) console.log(mesh) mesh.destroy() })

ตัวอย่างเช่นนี่คือวิธีที่เราสามารถโหลดโมเดลเก็บคุณสมบัติไว้ในตาข่าย ( ตาข่าย ) แล้วทำลายมัน:

atributo

เฉดสี

ต่อไปนี้เป็นกระบวนการสองขั้นตอนที่อธิบายไว้ก่อนหน้านี้ซึ่งเกี่ยวข้องกับการย้ายจุดไปยังตำแหน่งที่ต้องการและวาดภาพพิกเซลทั้งหมด ในการทำเช่นนี้เราเขียนโปรแกรมที่ทำงานบนกราฟิกการ์ดหลาย ๆ ครั้ง โดยปกติโปรแกรมนี้ประกอบด้วยอย่างน้อยสองส่วน ส่วนแรกคือไฟล์ Vertex Shader ซึ่งดำเนินการสำหรับแต่ละจุดยอดและเอาต์พุตที่เราควรวางจุดยอดบนหน้าจอเหนือสิ่งอื่นใด ส่วนที่สองคือ ชิ้นส่วน Shader ซึ่งทำงานสำหรับแต่ละพิกเซลที่สามเหลี่ยมครอบคลุมบนหน้าจอและสร้างสีที่ควรทาสีพิกเซล

เฉดสี ของจุดยอด

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

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

เฉดสี พวกเขาเขียนด้วย GLSL มีองค์ประกอบที่ไม่ซ้ำกันมากมายในภาษานี้ แต่ไวยากรณ์ส่วนใหญ่คล้ายกับ C มากดังนั้นคนส่วนใหญ่จึงควรเข้าใจ

มีตัวแปรสามประเภทที่เข้าและออกจากไฟล์ เงา ของจุดยอดและทั้งหมดนี้รองรับการใช้งานเฉพาะ:

วิธีเขียนโค้ด html ที่สะอาด
  • uniforme - เป็นอินพุตที่มีคุณสมบัติเฉพาะของจุดยอด ก่อนหน้านี้เราได้อธิบายตำแหน่งของจุดยอดเป็นแอตทริบิวต์ในรูปแบบของเวกเตอร์สามองค์ประกอบ คุณสามารถดูแอตทริบิวต์เป็นค่าที่อธิบายจุดยอด
  • uniforme - เป็นอินพุตที่เหมือนกันสำหรับแต่ละจุดยอดภายในการเรียกใช้การแสดงผลเดียวกัน สมมติว่าเราต้องการย้ายโมเดลของเรากำหนดเมทริกซ์การเปลี่ยนแปลง คุณสามารถใช้ตัวแปร variaciones เพื่ออธิบายว่า คุณยังสามารถใช้ทรัพยากร GPU เช่นพื้นผิว คุณสามารถดูเครื่องแบบเหล่านี้เป็นค่าที่อธิบายโมเดลหรือส่วนหนึ่งของโมเดล
  • attribute vec3 position; attribute vec3 normal; attribute vec2 uv; uniform mat4 model; uniform mat4 view; uniform mat4 projection; varying vec3 vNormal; varying vec2 vUv; void main() { vUv = uv; vNormal = (model * vec4(normal, 0.)).xyz; gl_Position = projection * view * model * vec4(position, 1.); } - นี่คือผลลัพธ์ที่เราส่งผ่านไปยังส่วนของ เงา . เนื่องจากสามเหลี่ยมของจุดยอดอาจมีหลายพันพิกเซลแต่ละพิกเซลจะได้รับค่าที่สอดแทรกสำหรับตัวแปรนี้ขึ้นอยู่กับตำแหน่ง ดังนั้นหากจุดยอดหนึ่งส่ง 500 เป็นเอาต์พุตและอีก 100 พิกเซลที่อยู่ตรงกลางของสิ่งเหล่านี้จะได้รับ 300 เป็นอินพุตสำหรับตัวแปรนั้น คุณสามารถดูรูปแบบต่างๆเป็นค่าที่อธิบายพื้นผิวระหว่างจุดยอด

สมมติว่าคุณต้องการสร้างไฟล์ เงา ของจุดยอดที่ได้รับตำแหน่งปกติและพิกัด uv สำหรับแต่ละจุดยอดและตำแหน่งมุมมอง (ตำแหน่งกล้องถอยหลัง) และเมทริกซ์การฉายสำหรับแต่ละวัตถุที่แสดง สมมติว่าคุณต้องการวาดแต่ละพิกเซลตามพิกัด uv และค่ามาตรฐาน คุณจะถามตัวเองว่า 'รหัสนั้นจะเป็นอย่างไร'

main

รายการเหล่านี้ส่วนใหญ่ควรอธิบายได้ด้วยตนเอง สิ่งที่สำคัญที่สุดที่ควรทราบคือไม่มีค่าส่งคืนในฟังก์ชัน variantes ค่าทั้งหมดที่เราต้องการส่งคืนจะถูกกำหนดให้กับตัวแปร gl_Position หรือตัวแปรพิเศษ ในที่นี้เรากำหนดให้ vec4 ซึ่งเป็นเวกเตอร์สี่มิติดังนั้นมิติสุดท้ายจะต้องอยู่ในรูปเดียวเสมอ สิ่งแปลก ๆ อีกอย่างที่คุณอาจสังเกตเห็นคือวิธีที่เราสร้าง vec4 เวกเตอร์ไม่อยู่ในตำแหน่ง คุณสามารถสร้าง float ใช้สี่ vec2 s, สอง variables s หรือชุดค่าผสมอื่น ๆ ที่ให้ผลลัพธ์เป็นสี่องค์ประกอบ มีการหล่อแบบแปลก ๆ มากมายที่ดูสมเหตุสมผลเมื่อคุณคุ้นเคยกับเมทริกซ์การเปลี่ยนแปลง

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

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

ส่วนของ เฉดสี

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

หลักการที่อยู่เบื้องหลังการใช้แฟรกเมนต์เชเดอร์นั้นคล้ายกับหลักการมาก เฉดสี ของจุดยอด อย่างไรก็ตามมีความแตกต่างใหญ่สามประการ:

  • ไม่มีเอาต์พุตเพิ่มเติม atributos และอินพุต gl_FragColor ถูกแทนที่ด้วยรายการ 'ผสม' เราเพิ่งไปที่ช่องของเราและสิ่งที่ส่งออกในไฟล์ เงา จุดยอดเป็นรายการในส่วนของ เงา .
  • ผลลัพธ์เดียวของเราตอนนี้คือ vec4 ซึ่งก็คือ #ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec2 clampedUv = clamp(vUv, 0., 1.); gl_FragColor = vec4(clampedUv, 1., 1.); } องค์ประกอบแสดงถึงสีแดงเขียวน้ำเงินและอัลฟา (RGBA) ตามลำดับโดยมีตัวแปรอยู่ในช่วง 0 ถึง 1 คุณควรให้อัลฟ่าอยู่ที่ 1 เว้นแต่คุณจะใช้ความโปร่งใส อย่างไรก็ตามความโปร่งใสเป็นแนวคิดที่ก้าวหน้าพอสมควรดังนั้นเราจึงยึดติดกับวัตถุทึบแสง
  • ที่จุดเริ่มต้นของส่วนของ เงา คุณต้องตั้งค่าความแม่นยำของลูกลอยซึ่งเป็นสิ่งสำคัญสำหรับการแก้ไข ในเกือบทุกกรณีให้ปฏิบัติตามบรรทัดต่อไปนี้ เงา .

ด้วยเหตุนี้คุณจึงสามารถเขียนไฟล์ เงา ซึ่งวาดช่องสีแดงตามตำแหน่ง U ช่องสีเขียวตามตำแหน่ง V และตั้งค่าช่องสีน้ำเงินเป็นค่าสูงสุด

clamp

ฟังก์ชั่น function ShaderProgram (gl, vertSrc, fragSrc) { var vert = gl.createShader(gl.VERTEX_SHADER) gl.shaderSource(vert, vertSrc) gl.compileShader(vert) if (!gl.getShaderParameter(vert, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(vert)) throw new Error('Failed to compile shader') } var frag = gl.createShader(gl.FRAGMENT_SHADER) gl.shaderSource(frag, fragSrc) gl.compileShader(frag) if (!gl.getShaderParameter(frag, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(frag)) throw new Error('Failed to compile shader') } var program = gl.createProgram() gl.attachShader(program, vert) gl.attachShader(program, frag) gl.linkProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(program)) throw new Error('Failed to link program') } this.gl = gl this.position = gl.getAttribLocation(program, 'position') this.normal = gl.getAttribLocation(program, 'normal') this.uv = gl.getAttribLocation(program, 'uv') this.model = gl.getUniformLocation(program, 'model') this.view = gl.getUniformLocation(program, 'view') this.projection = gl.getUniformLocation(program, 'projection') this.vert = vert this.frag = frag this.program = program } // Loads shader files from the given URLs, and returns a program as a promise ShaderProgram.load = function (gl, vertUrl, fragUrl) { return Promise.all([loadFile(vertUrl), loadFile(fragUrl)]).then(function (files) { return new ShaderProgram(gl, files[0], files[1]) }) function loadFile (url) { return new Promise(function (resolve) { var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState == XMLHttpRequest.DONE) { resolve(xhr.responseText) } } xhr.open('GET', url, true) xhr.send(null) }) } } เพียง จำกัด ทั้งหมด ลอย บนวัตถุให้อยู่ในขอบเขตที่กำหนด ส่วนที่เหลือของโค้ดควรตรงไปตรงมา

เมื่อคำนึงถึงสิ่งนี้สิ่งที่ต้องทำคือการนำสิ่งนี้ไปใช้ใน WebGL

การรวม Shaders ในโปรแกรม

ขั้นตอนต่อไปคือการรวมเฉดสีลงในโปรแกรม:

ShaderProgram.prototype.use = function () { this.gl.useProgram(this.program) }

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

วาดแบบจำลองจริงๆ

สุดท้าย แต่ไม่ท้ายสุดวาดแบบจำลอง

ก่อนอื่นให้เลือกโปรแกรม เงา คุณต้องการใช้อะไร

Transformation.prototype.sendToGpu = function (gl, uniform, transpose) gl.uniformMatrix4fv(uniform, transpose Camera.prototype.use = function (shaderProgram) { this.projection.sendToGpu(shaderProgram.gl, shaderProgram.projection) this.getInversePosition().sendToGpu(shaderProgram.gl, shaderProgram.view) }

จากนั้นจะส่งเครื่องแบบที่เกี่ยวข้องกับกล้องทั้งหมดไปยัง GPU เครื่องแบบเหล่านี้จะเปลี่ยนเพียงครั้งเดียวสำหรับการเปลี่ยนกล้องหรือการเคลื่อนไหวแต่ละครั้ง

VBO.prototype.bindToAttribute = function (attribute) { var gl = this.gl // Tell which buffer object we want to operate on as a VBO gl.bindBuffer(gl.ARRAY_BUFFER, this.data) // Enable this attribute in the shader gl.enableVertexAttribArray(attribute) // Define format of the attribute array. Must match parameters in shader gl.vertexAttribPointer(attribute, this.size, gl.FLOAT, false, 0, 0) }

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

drawArrays ()

จากนั้นจึงกำหนดเมทริกซ์ของสาม ลอย สำหรับเครื่องแบบ เครื่องแบบแต่ละประเภทมีลายเซ็นที่แตกต่างกันดังนั้น เอกสารประกอบ และอื่น ๆ เอกสารประกอบ พวกเขาเป็นเพื่อนของคุณที่นี่ สุดท้ายวาดเมทริกซ์สามเหลี่ยมบนหน้าจอ มันบอกการเรียกดึง [TRIÁNGULOS] (https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawArrays) จากจุดยอดที่สามารถเริ่มต้นได้และจำนวนจุดยอดที่จะวาด . พารามิเตอร์แรกที่ส่งผ่านจะบอก WebGL ว่าจะตีความอาร์เรย์จุดยอดอย่างไร ใช้ PUNTOS ใช้จุดยอดสามเท่าสามจุดแล้ววาดสามเหลี่ยมสำหรับแต่ละสามเท่า ใช้ Mesh.prototype.draw = function (shaderProgram) { this.positions.bindToAttribute(shaderProgram.position) this.normals.bindToAttribute(shaderProgram.normal) this.uvs.bindToAttribute(shaderProgram.uv) this.position.sendToGpu(this.gl, shaderProgram.model) this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount) } มันจะดึงจุดสำหรับจุดยอดแต่ละจุดที่ส่งผ่านเท่านั้น มีตัวเลือกอื่น ๆ อีกมากมาย แต่ไม่จำเป็นต้องค้นพบทุกสิ่งในเวลาเดียวกัน ด้านล่างนี้คือรหัสสำหรับวาดวัตถุ:

Renderer.prototype.setShader = function (shader) { this.shader = shader } Renderer.prototype.render = function (camera, objects) { this.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) var shader = this.shader if (!shader) { return } shader.use() camera.use(shader) objects.forEach(function (mesh) { mesh.draw(shader) }) }

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

var renderer = new Renderer(document.getElementById('webgl-canvas')) renderer.setClearColor(100, 149, 237) var gl = renderer.getContext() var objects = [] Geometry.loadOBJ('/assets/sphere.obj').then(function (data) { objects.push(new Mesh(gl, data)) }) ShaderProgram.load(gl, '/shaders/basic.vert', '/shaders/basic.frag') .then(function (shader) { renderer.setShader(shader) }) var camera = new Camera() camera.setOrthographic(16, 10, 10) loop() function loop () { renderer.render(camera, objects) requestAnimationFrame(loop) }

เราสามารถรวมองค์ประกอบทั้งหมดที่เรามีเพื่อวาดบางสิ่งบนหน้าจอได้ในที่สุด:

#ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); gl_FragColor = vec4(brown, 1.); }

วัตถุที่วาดบนผืนผ้าใบโดยมีสีขึ้นอยู่กับพิกัด UV

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

#ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); vec3 sunlightDirection = vec3(-1., -1., -1.); float lightness = -clamp(dot(normalize(vNormal), normalize(sunlightDirection)), -1., 0.); gl_FragColor = vec4(brown * lightness, 1.); }

วัตถุสีน้ำตาลวาดบนผ้าใบ

ดูไม่ค่อยน่าเชื่อเท่าไหร่ ดูเหมือนว่าฉากนั้นต้องการเอฟเฟกต์การแรเงาบางอย่าง

การเพิ่มแสง

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

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

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

หากคุณสร้างผลิตภัณฑ์จุดระหว่างเวกเตอร์ที่เป็นมาตรฐานสำหรับรังสีของแสงและพื้นผิวปกติคุณจะได้รับ -1 หากรังสีตกกระทบพื้นผิวในแนวตั้งฉากอย่างสมบูรณ์ 0 หากรังสีขนานกับพื้นผิวและ 1 หากมีการส่องสว่างจาก ด้านตรงข้าม ดังนั้นสิ่งที่อยู่ระหว่าง 0 ถึง 1 ไม่ควรเพิ่มแสงในขณะที่ตัวเลขระหว่าง 0 ถึง -1 ควรค่อยๆเพิ่มปริมาณแสงที่ตกกระทบวัตถุ คุณสามารถทดสอบสิ่งนี้ได้โดยการเพิ่มแสงทึบในโค้ด เงา .

#ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); vec3 sunlightDirection = vec3(-1., -1., -1.); float lightness = -clamp(dot(normalize(vNormal), normalize(sunlightDirection)), -1., 0.); float ambientLight = 0.3; lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(brown * lightness, 1.); }

วัตถุสีน้ำตาลในแสงแดดและแสงโดยรอบ

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

#ifdef GL_ES precision highp float; #endif uniform vec3 lightDirection; uniform float ambientLight; varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.); lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(brown * lightness, 1.); }

วัตถุที่มีพื้นผิวพร้อมเอฟเฟกต์แสง

คุณสามารถบรรลุเอฟเฟกต์เดียวกันนี้ได้โดยการเข้าสู่ระดับแสงที่เก็บทิศทางของแสงและความเข้มของแสงโดยรอบ จากนั้นคุณสามารถเปลี่ยนตัวอย่างข้อมูลจาก เงา เพื่อรองรับการเพิ่มนั้น

ตอนนี้เขา เงา กลายเป็น:

function Light () { this.lightDirection = new Vector3(-1, -1, -1) this.ambientLight = 0.3 } Light.prototype.use = function (shaderProgram) { var dir = this.lightDirection var gl = shaderProgram.gl gl.uniform3f(shaderProgram.lightDirection, dir.x, dir.y, dir.z) gl.uniform1f(shaderProgram.ambientLight, this.ambientLight) }

ตอนนี้คุณสามารถกำหนดแสง:

this.ambientLight = gl.getUniformLocation(program, 'ambientLight') this.lightDirection = gl.getUniformLocation(program, 'lightDirection')

ในคลาสโปรแกรม เงา เพิ่มเครื่องแบบที่จำเป็น:

Renderer.prototype.render = function (camera, light, objects) { this.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) var shader = this.shader if (!shader) { return } shader.use() light.use(shader) camera.use(shader) objects.forEach(function (mesh) { mesh.draw(shader) }) }

ในโปรแกรมให้เพิ่มการโทรไปยังไฟใหม่ในตัวแสดงภาพ:

var light = new Light() loop() function loop () { renderer.render(camera, light, objects) requestAnimationFrame(loop) }

ปมจะเปลี่ยนไปเล็กน้อย:

sampler2D

หากคุณทำทุกอย่างถูกต้องภาพที่แสดงควรจะเหมือนกับภาพสุดท้าย

ขั้นตอนสุดท้ายที่ควรพิจารณาคือการเพิ่มพื้นผิวจริงให้กับแบบจำลองของเรา มาทำตอนนี้

การเพิ่มพื้นผิว

HTML5 รองรับการโหลดรูปภาพได้อย่างดีเยี่ยมจึงไม่จำเป็นต้องทำการวิเคราะห์รูปภาพที่เข้มข้นมากนัก ภาพจะถูกส่งไปยัง GLSL เป็น sampler2D บอก เงา พื้นผิวที่เชื่อมโยงกับตัวอย่างใด มีพื้นผิวจำนวน จำกัด ที่สามารถเชื่อมโยงได้และขีด จำกัด จะขึ้นอยู่กับฮาร์ดแวร์ที่ใช้ ก #ifdef GL_ES precision highp float; #endif uniform vec3 lightDirection; uniform float ambientLight; uniform sampler2D diffuse; varying vec3 vNormal; varying vec2 vUv; void main() { float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.); lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(texture2D(diffuse, vUv).rgb * lightness, 1.); } สามารถสอบถามสีได้ในบางตำแหน่ง นี่คือจุดที่พิกัด UV เข้ามา นี่คือตัวอย่างที่เราแทนที่สีน้ำตาลด้วยสีตัวอย่าง

this.diffuse = gl.getUniformLocation(program, 'diffuse')

จะต้องเพิ่มเครื่องแบบใหม่ในรายการในโปรแกรมของ เงา :

function Texture (gl, image) { var texture = gl.createTexture() // Set the newly created texture context as active texture gl.bindTexture(gl.TEXTURE_2D, texture) // Set texture parameters, and pass the image that the texture is based on gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image) // Set filtering methods // Very often shaders will query the texture value between pixels, // and this is instructing how that value shall be calculated gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) this.data = texture this.gl = gl } Texture.prototype.use = function (uniform, binding) Texture.load = function (gl, url) { return new Promise(function (resolve) { var image = new Image() image.onload = function () { resolve(new Texture(gl, image)) } image.src = url }) }

สุดท้ายเราจะใช้การโหลดพื้นผิว ตามที่ระบุไว้ก่อนหน้านี้ HTML5 มีสิ่งอำนวยความสะดวกสำหรับการโหลดรูปภาพ สิ่งที่เราต้องทำคือส่งภาพไปยัง GPU:

sampler2D

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

ตอนนี้สิ่งที่คุณต้องทำคือขยายชั้นเรียน function Mesh (gl, geometry, texture) { // added texture var vertexCount = geometry.vertexCount() this.positions = new VBO(gl, geometry.positions(), vertexCount) this.normals = new VBO(gl, geometry.normals(), vertexCount) this.uvs = new VBO(gl, geometry.uvs(), vertexCount) this.texture = texture // new this.vertexCount = vertexCount this.position = new Transformation() this.gl = gl } Mesh.prototype.destroy = function () { this.positions.destroy() this.normals.destroy() this.uvs.destroy() } Mesh.prototype.draw = function (shaderProgram) { this.positions.bindToAttribute(shaderProgram.position) this.normals.bindToAttribute(shaderProgram.normal) this.uvs.bindToAttribute(shaderProgram.uv) this.position.sendToGpu(this.gl, shaderProgram.model) this.texture.use(shaderProgram.diffuse, 0) // new this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount) } Mesh.load = function (gl, modelUrl, textureUrl) { // new var geometry = Geometry.loadOBJ(modelUrl) var texture = Texture.load(gl, textureUrl) return Promise.all([geometry, texture]).then(function (params) { return new Mesh(gl, params[0], params[1]) }) } ในการจัดการพื้นผิวด้วย:

var renderer = new Renderer(document.getElementById('webgl-canvas')) renderer.setClearColor(100, 149, 237) var gl = renderer.getContext() var objects = [] Mesh.load(gl, '/assets/sphere.obj', '/assets/diffuse.png') .then(function (mesh) { objects.push(mesh) }) ShaderProgram.load(gl, '/shaders/basic.vert', '/shaders/basic.frag') .then(function (shader) { renderer.setShader(shader) }) var camera = new Camera() camera.setOrthographic(16, 10, 10) var light = new Light() loop() function loop () { renderer.render(camera, light, objects) requestAnimationFrame(loop) }

และสคริปต์สุดท้ายหลักจะมีลักษณะดังนี้:

function loop () { renderer.render(camera, light, objects) camera.position = camera.position.rotateY(Math.PI / 120) requestAnimationFrame(loop) }

หัวหมุนระหว่างการเคลื่อนไหวของกล้อง

แม้แต่การเชียร์ก็สามารถทำได้ง่ายในจุดนี้ หากคุณต้องการให้กล้องหมุนรอบวัตถุของเราคุณสามารถทำได้โดยเพิ่มโค้ดเพียงบรรทัดเดียว:

ขนาดคิวรีสื่อสำหรับการออกแบบที่ตอบสนอง
void main() { float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.); lightness = lightness > 0.1 ? 1. : 0.; // new lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(texture2D(diffuse, vUv).rgb * lightness, 1.); }

ใช้ไฟรูปการ์ตูน

คุณมีอิสระที่จะเล่นด้วย เฉดสี . การเพิ่มบรรทัดของโค้ดจะทำให้แสงที่สมจริงนี้กลายเป็นการ์ตูน

|_+_|

ทำได้ง่ายเพียงแค่บอกให้แสงไปที่ระดับสุดขั้วโดยขึ้นอยู่กับว่ามันเกินเกณฑ์ที่ตั้งไว้หรือไม่

จะไปที่ไหนต่อไป

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

ไม่เรียงตามลำดับใด ๆ ต่อไปนี้เป็นแหล่งข้อมูลที่ดีเยี่ยมสำหรับข้อมูลโดยละเอียดสำหรับทั้ง WebGL และ OpenGL

  • ข้อมูลเบื้องต้นเกี่ยวกับ WebGL
  • การเรียนรู้ WebGL
  • บทแนะนำที่มีรายละเอียดมาก บทช่วยสอน OpenGL จะแนะนำคุณผ่านหลักการพื้นฐานทั้งหมดที่อธิบายไว้ที่นี่อย่างช้าๆและละเอียด
  • และมี มากมาย , มากมาย ไซต์อื่น ๆ ที่ทุ่มเทเพื่อสอนหลักการของคอมพิวเตอร์กราฟิก
  • เอกสาร MDN สำหรับ WebGL
  • ข้อกำหนด Khronos WebGL 1.0 หากคุณสนใจที่จะเข้าใจรายละเอียดทางเทคนิคเพิ่มเติมเกี่ยวกับวิธีการทำงานของ WebGL API ในทุกกรณี

สร้างเว็บแอปที่ทันสมัยด้วยวัสดุเชิงมุม

ส่วนหน้าของเว็บ

สร้างเว็บแอปที่ทันสมัยด้วยวัสดุเชิงมุม
ความท้าทายของการเข้าสู่ตลาดครั้งแรก

ความท้าทายของการเข้าสู่ตลาดครั้งแรก

วงจรชีวิตของผลิตภัณฑ์

โพสต์ยอดนิยม
ตลาด Crowdfunding Equity ของสหรัฐมีการเติบโตขึ้นตามความคาดหวังหรือไม่?
ตลาด Crowdfunding Equity ของสหรัฐมีการเติบโตขึ้นตามความคาดหวังหรือไม่?
คู่มือสำคัญสำหรับ Qmake
คู่มือสำคัญสำหรับ Qmake
หลักการออกแบบ Mobile UX
หลักการออกแบบ Mobile UX
MIDI Tutorial: การสร้างแอปพลิเคชั่นเสียงบนเบราว์เซอร์ที่ควบคุมโดยฮาร์ดแวร์ MIDI
MIDI Tutorial: การสร้างแอปพลิเคชั่นเสียงบนเบราว์เซอร์ที่ควบคุมโดยฮาร์ดแวร์ MIDI
Init.js: คำแนะนำเกี่ยวกับสาเหตุและวิธีการใช้ JavaScript แบบ Full-Stack
Init.js: คำแนะนำเกี่ยวกับสาเหตุและวิธีการใช้ JavaScript แบบ Full-Stack
 
Splash of EarlGrey - UI การทดสอบแอพ ApeeScape Talent
Splash of EarlGrey - UI การทดสอบแอพ ApeeScape Talent
จาก Node.js ไปจนถึงการจ่ายภาษีอิสระของคุณ: บทสัมภาษณ์กับ Developer ที่ประสบความสำเร็จ
จาก Node.js ไปจนถึงการจ่ายภาษีอิสระของคุณ: บทสัมภาษณ์กับ Developer ที่ประสบความสำเร็จ
ขายธุรกิจของคุณ? หยุดทิ้งเงินไว้บนโต๊ะ
ขายธุรกิจของคุณ? หยุดทิ้งเงินไว้บนโต๊ะ
บทช่วยสอนเกี่ยวกับส่วนขยายแอป iOS 8
บทช่วยสอนเกี่ยวกับส่วนขยายแอป iOS 8
ผู้จัดการการเติบโต
ผู้จัดการการเติบโต
โพสต์ยอดนิยม
  • บริษัทเอกชนด้านการลงทุนอสังหาริมทรัพย์
  • ปัญหาด้านความปลอดภัยในอินเทอร์เน็ตของสรรพสิ่ง
  • กวดวิชาแมชชีนเลิร์นนิงสำหรับผู้เริ่มต้น
  • สร้างราสเบอร์รี่ pi ตั้งแต่เริ่มต้น
  • วิธีหาความยืดหยุ่นของราคาของอุปสงค์
  • ทำความเข้าใจรหัส c ++
หมวดหมู่
  • การจัดการโครงการ
  • การเพิ่มขึ้นของระยะไกล
  • การบริหารโครงการ
  • เครื่องมือและบทช่วยสอน
  • © 2022 | สงวนลิขสิทธิ์

    portaldacalheta.pt