-
ในบทนี้ ฉันจะสอนวิธีใช้วิธีกรองพื้นผิวที่แตกต่างกันสามวิธี
สอนวิธีใช้แป้นพิมพ์เพื่อย้ายวัตถุในฉาก และยังสอนวิธีใช้การจัดแสงแบบง่ายๆ ในฉาก OpenGL
บทเรียนนี้มีเนื้อหามากมาย หากคุณมีคำถามเกี่ยวกับบทเรียนก่อนหน้านี้ ให้กลับไปทบทวนก่อน
ก่อนที่จะเข้าใจโค้ดเบื้องหลัง สิ่งสำคัญคือต้องมีความเข้าใจพื้นฐานเป็นอย่างดี
เรายังคงแก้ไขโค้ดจากบทเรียนแรก
ความแตกต่างจากเมื่อก่อนคือเมื่อใดก็ตามที่มีการเปลี่ยนแปลงครั้งใหญ่ ผมจะเขียนโค้ดทั้งหมด
ก่อนอื่นเราต้องเพิ่มหน่วย SysUtils และหน่วย Glaux
-
การใช้งาน
SysUtils,
โอเพ่นเกิล,
หน้าต่าง,
ข้อความ
Glaux ใน '../../GLAUX/Glaux.pas';
//บรรทัดต่อไปนี้เพิ่มตัวแปรใหม่
//เราเพิ่มตัวแปรบูลีนสามตัว
// ตัวแปรไฟติดตามว่าไฟเปิดอยู่หรือไม่
//ตัวแปร lp และ fp ใช้เพื่อจัดเก็บว่ามีการกดปุ่ม 'L' และ 'F' หรือไม่
//ฉันจะอธิบายความสำคัญของตัวแปรเหล่านี้ในภายหลัง สำหรับตอนนี้ พักเรื่องนั้นไว้ก่อน
แสง : บูลีน; // เปิด/ปิดแหล่งกำเนิดแสง
lp : Boolean; // กดปุ่ม L หรือไม่?
fp : Boolean; // กดปุ่ม F หรือไม่?
// ตอนนี้ตั้งค่าตัวแปร 5 ตัวเพื่อควบคุมขนาดขั้นตอนของมุมการหมุนรอบแกน x และแกน y
//และความเร็วการหมุนรอบแกน x และแกน y
//นอกจากนี้ ตัวแปร z ยังถูกสร้างขึ้นเพื่อควบคุมระยะห่างถึงความลึกของหน้าจอ
xrot : GLfloat; // การหมุน X
yrot : GLfloat; // การหมุน Y
xspeed : GLfloat; // X ความเร็วในการหมุน
yspeed : GLfloat; // Y ความเร็วในการหมุน
z : GLfloat = -5.0 f; // ระยะห่างลึกเข้าไปในหน้าจอ
//จากนั้นตั้งค่าอาร์เรย์ที่ใช้สร้างแหล่งกำเนิดแสง
//เราจะใช้ไฟสองดวงที่แตกต่างกัน
//อันแรกเรียกว่าแสงโดยรอบ แสงโดยรอบมาจากทุกทิศทาง
//วัตถุทั้งหมดในฉากได้รับแสงสว่างจากแสงโดยรอบ
//แหล่งกำเนิดแสงประเภทที่สองเรียกว่าแสงแบบกระจาย
//แสงกระจายถูกสร้างขึ้นโดยแหล่งกำเนิดแสงเฉพาะและสร้างการสะท้อนบนพื้นผิวของวัตถุในฉากของคุณ
// พื้นผิววัตถุใด ๆ ที่ได้รับแสงสว่างโดยตรงด้วยแสงที่กระจายจะสว่างมาก
//บริเวณที่ไม่ค่อยได้รับแสงสว่างจะดูมืดลง
//สิ่งนี้จะสร้างเอฟเฟกต์เงาที่ดีมากบนขอบของลังไม้ที่เราสร้างขึ้น
//กระบวนการสร้างแหล่งกำเนิดแสงเหมือนกับการสร้างสีทุกประการ
//พารามิเตอร์สามตัวแรกคือส่วนประกอบ RGB สามสี และพารามิเตอร์สุดท้ายคือพารามิเตอร์ช่องอัลฟา
//ดังนั้น ในโค้ดต่อไปนี้ เราจะได้แสงสีขาวสว่างเพียงครึ่งเดียว (0.5f)
//หากไม่มีแสงโดยรอบ พื้นที่ที่แสงไม่กระจายจะมืดมาก
LightAmbient: Array[0..3] ของ GLfloat = (0.5, 0.5, 0.5, 1.0); // พารามิเตอร์แสงโดยรอบ (ใหม่)
//บรรทัดถัดไปของโค้ดเราสร้างแสงกระจายที่สว่างที่สุด
//ค่าพารามิเตอร์ทั้งหมดจะถูกนำไปใช้กับค่าสูงสุด 1.0f
//มันจะส่องแสงไปที่หน้าลังไม้ของเราและดูดี
LightDiffuse: Array[0..3] ของ GLfloat = (1.0, 1.0, 1.0, 1.0); // พารามิเตอร์แสงกระจาย (ใหม่)
//ในที่สุดเราก็บันทึกตำแหน่งของแหล่งกำเนิดแสง
//พารามิเตอร์สามตัวแรกเหมือนกับใน glTranslate
//การกระจัดบนแกน XYZ เป็นไปตามลำดับ
//เนื่องจากเราต้องการให้แสงส่องตรงด้านหน้ากล่องไม้ การกระจัดบนแกน XY จึงเป็น 0.0 ทั้งคู่
//ค่าที่สามคือการกระจัดบนแกน Z
//เพื่อให้แน่ใจว่ามีแสงสว่างอยู่หน้ากล่องไม้เสมอ
//ดังนั้นเราจึงย้ายแหล่งกำเนิดแสงออกจากหน้าจอไปยังผู้สังเกตการณ์ (ซึ่งก็คือคุณ)
//เรามักจะเรียกตำแหน่งของหน้าจอ นั่นก็คือ กระจกหน้าจอของมอนิเตอร์ จุด 0.0 ของแกน Z
//ในที่สุดการกระจัดบนแกน Z ก็ถูกกำหนดเป็น 2.0
//ถ้าคุณมองเห็นแหล่งกำเนิดแสง มันก็ลอยอยู่หน้ามอนิเตอร์ของคุณ
//แน่นอนว่าคุณไม่สามารถมองเห็นกล่องไม้ได้หากไม่ได้อยู่ด้านหลังกระจกหน้าจอมอนิเตอร์
//『หมายเหตุผู้แปล: ฉันซาบซึ้งในความอดทนของเนเฮ
//บอกตามตรงว่าบางครั้งฉันก็รำคาญ ทำไมเขาถึงพูดเรื่องไร้สาระเรื่องง่ายๆ แบบนี้ล่ะ?
//แต่ถ้าทุกอย่างกระจ่างแล้วยังจะพลิกหน้าแบบนี้ไม่สิ้นสุดอีกเหรอ? -
//พารามิเตอร์สุดท้ายถือเป็น 1.0f
//สิ่งนี้จะบอก OpenGL ว่าพิกัดที่ระบุที่นี่คือตำแหน่งของแหล่งกำเนิดแสง ฉันจะอธิบายเพิ่มเติมในบทช่วยสอนในอนาคต
LightPosition: Array[0..3] ของ GLfloat = (0.0, 0.0, 2.0, 1.0); // ตำแหน่งแหล่งกำเนิดแสง (ใหม่)
//ตัวแปรตัวกรองจะติดตามประเภทพื้นผิวที่ใช้ในการแสดง
// พื้นผิวแรก (พื้นผิว 0) ถูกสร้างขึ้นโดยใช้วิธีการกรอง gl_nearest (ไม่ราบรื่น)
// พื้นผิวที่สอง (พื้นผิว 1) ใช้วิธี gl_linear (การกรองเชิงเส้น)
//ภาพที่ใกล้กับหน้าจอดูนุ่มนวลขึ้น
//พื้นผิวที่สาม (พื้นผิว 2) ใช้วิธีการกรองแบบ mipmapped
//นี่จะสร้างพื้นผิวที่ดูดีมาก
// ขึ้นอยู่กับประเภทการใช้งานของเรา ค่าของตัวแปรตัวกรองจะเท่ากับ 0, 1 หรือ 2 ตามลำดับ
//มาเริ่มกันที่เนื้อชิ้นแรกกันเลย
//texture จัดสรรพื้นที่เก็บข้อมูลสำหรับพื้นผิวที่แตกต่างกันสามแบบ
//พวกมันอยู่ใน texture[0], texture[1] และ texture[2] ตามลำดับ
ตัวกรอง : GLuint; // ประเภทตัวกรอง
texture : Array[0..2] ของ GLuint; // พื้นที่เก็บข้อมูลสำหรับ 3 textures
PROcedure glGenTextures (n: GLsizei; พื้นผิว Var: GLuint); stdcall ภายนอก
opengl32;
ขั้นตอน glBindTexture (เป้าหมาย: GLenum; texture: GLuint); stdcall ภายนอก
opengl32;
ฟังก์ชั่น gluBuild2DMipmaps (เป้าหมาย: GLenum; ส่วนประกอบ, ความกว้าง, ความสูง: GLint;
รูปแบบ atype: GLenum; ข้อมูล: ตัวชี้): จำนวนเต็ม; ชื่อ glu32 ภายนอก
'gluBuild2DMipmaps';
-
ตอนนี้ให้โหลดบิตแมปและใช้มันเพื่อสร้างพื้นผิวที่แตกต่างกันสามแบบ
บทเรียนนี้ใช้ไลบรารีเสริม gaux เพื่อโหลดบิตแมป
ดังนั้นคุณควรตรวจสอบว่ารวมไลบรารี gaux ไว้เมื่อทำการคอมไพล์หรือไม่
ฉันรู้ว่าทั้ง Delphi และ VC++ มีไลบรารี่ gaux แต่ไม่รับประกันว่าจะมีในภาษาอื่น
"หมายเหตุผู้แปล: gaux เป็นไลบรารีเสริมของ OpenGL ตามลักษณะข้ามแพลตฟอร์มของ OpenGL
รหัสควรเหมือนกันในทุกแพลตฟอร์ม แต่ไลบรารีเสริมไม่ใช่ไลบรารีมาตรฐาน OpenGL อย่างเป็นทางการ
ไม่มีให้บริการในทุกแพลตฟอร์ม แต่มันเกิดขึ้นได้บนแพลตฟอร์ม Win32
ฮ่าฮ่า แน่นอนว่า BCB ก็ไม่มีปัญหาเช่นกัน 』ในที่นี้ฉันจะอธิบายเฉพาะโค้ดที่เพิ่มใหม่เท่านั้น
หากคุณมีคำถามเกี่ยวกับโค้ดบางบรรทัด โปรดดูบทช่วยสอนที่ 6
บทเรียนนั้นจะอธิบายการโหลดและการสร้างพื้นผิวอย่างละเอียด
หลังจากโค้ดส่วนก่อนหน้าและก่อน glResizeWnd ()
เราเพิ่มรหัสต่อไปนี้ ซึ่งเกือบจะเหมือนกับโค้ดที่ใช้ในบทที่ 6 เพื่อโหลดบิตแมป
-
ฟังก์ชั่น LoadBmp (ชื่อไฟล์: pchar): PTAUX_RGBImageRec;
วาร์
BitmapFile : Thandle; // ตัวจัดการไฟล์
เริ่ม
ถ้า Filename = '' จากนั้น // ตรวจสอบให้แน่ใจว่าได้ระบุชื่อไฟล์แล้ว
ผลลัพธ์ := ไม่มี; // ถ้าไม่ระบุ ให้คืนค่า NULL
BitmapFile := FileOpen(ชื่อไฟล์, fmOpenWrite); //ลองเปิดไฟล์
ถ้า BitmapFile > 0 แล้ว // มีไฟล์อยู่หรือไม่?
เริ่ม
FileClose(BitmapFile); //ปิดที่จับ
ผลลัพธ์ := auxDIBImageLoadA(ชื่อไฟล์); //โหลดบิตแมปและส่งคืนตัวชี้
จบ
อื่น
ผลลัพธ์ := Nil; // หากการโหลดล้มเหลว ให้ส่งคืน NiL
จบ;
ฟังก์ชั่น LoadTexture: boolean; // โหลดบิตแมปและแปลงเป็นพื้นผิว
วาร์
สถานะ : บูลีน; // ตัวบ่งชี้สถานะ
TextureImage : Array[0..1] ของ PTAUX_RGBImageRec; // สร้างพื้นที่เก็บข้อมูลพื้นผิว
เริ่ม
สถานะ := เท็จ;
ZeroMemory(@TextureImage, sizeof(TextureImage)); // ตั้งค่าตัวชี้เป็น NULL
TextureImage[0] := LoadBMP('Walls.bmp');
ถ้า TextureImage[0] <> ไม่มีเลย
เริ่ม
สถานะ := TRUE; // ตั้งค่าสถานะเป็น TRUE
glGenTextures(1, texture[0]); // สร้างพื้นผิว
//ในบทที่ 6 เราใช้การแมปพื้นผิวแบบกรองเชิงเส้น
//สิ่งนี้ต้องใช้พลังการประมวลผลค่อนข้างสูงจากเครื่อง แต่ก็ดูดีทีเดียว
//ในบทเรียนนี้ พื้นผิวแรกที่เราจะสร้างจะใช้เมธอด GL_NEAREST
//โดยหลักการแล้ว เมธอดนี้ไม่ได้ทำการกรองจริงๆ
//ใช้พลังการประมวลผลน้อยมากและดูไม่ดี
//ข้อได้เปรียบเพียงอย่างเดียวคือโครงการของเราสามารถทำงานได้ตามปกติทั้งบนเครื่องที่เร็วและช้า
// คุณจะสังเกตเห็นว่าเราใช้ GL_NEAREST สำหรับทั้ง MIN และ MAG
//คุณสามารถผสม GL_NEAREST และ GL_LINEAR ได้
//พื้นผิวจะดูดีขึ้น แต่เราให้ความสำคัญกับความเร็วมากกว่า ดังนั้นเราจึงใช้แผนที่คุณภาพต่ำทั้งหมด
//MIN_FILTER ถูกใช้เมื่อรูปภาพถูกดึงให้เล็กกว่าขนาดดั้งเดิมของพื้นผิว
//MAG_FILTER ถูกใช้เมื่อรูปภาพถูกดึงให้ใหญ่กว่าขนาดดั้งเดิมของพื้นผิว
// สร้างแผนผังตัวกรองที่ใกล้ที่สุด
glBindTexture(GL_TEXTURE_2D, พื้นผิว[0]);
// สร้างพื้นผิว
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, พื้นผิวรูปภาพ[0].sizeX,
พื้นผิวรูปภาพ[0].sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
พื้นผิวรูปภาพ[0].ข้อมูล);
//พื้นผิวถัดไปจะเหมือนกับพื้นผิวในบทที่ 6 การกรองเชิงเส้น ข้อแตกต่างเพียงอย่างเดียวคือคราวนี้มันถูกวางไว้
//พื้นผิว[1]. เพราะนี่คือเนื้อที่สอง ถ้าวางไว้
//texture[0] มันจะเขียนทับพื้นผิว GL_NEAREST ที่สร้างไว้ก่อนหน้านี้
glBindTexture(GL_TEXTURE_2D, texture[1]); //ใช้พื้นผิวทั่วไปที่สร้างจากข้อมูลบิตแมป
// สร้างพื้นผิว
glTexImage2D(GL_TEXTURE_2D, 0, 3, พื้นผิวรูปภาพ[0].sizeX,
พื้นผิวรูปภาพ[0].sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
พื้นผิวรูปภาพ[0].ข้อมูล);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // การกรองเชิงเส้น
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // การกรองเชิงเส้น
//ต่อไปนี้เป็นวิธีใหม่ในการสร้างพื้นผิว แมปแมป!
//『หมายเหตุผู้แปล: ฉันไม่สามารถแปลคำนี้เป็นภาษาจีนได้ แต่มันก็ไม่สำคัญ หลังจากอ่านย่อหน้านี้แล้ว คุณจะรู้ว่าความหมายนั้นสำคัญที่สุด -
//คุณอาจสังเกตได้ว่าเมื่อภาพมีขนาดเล็กลงบนหน้าจอ รายละเอียดต่างๆ จะหายไปมากมาย
//ลายที่ดูดีเมื่อกี้กลายเป็นน่าเกลียดไปแล้ว เมื่อคุณบอกให้ OpenGL สร้างพื้นผิวแบบ mipmapped
//OpenGL จะพยายามสร้างพื้นผิวคุณภาพสูงในขนาดต่างๆ เมื่อคุณวาดพื้นผิวแบบ mipmapped ลงบนหน้าจอ
//OpenGL จะเลือกพื้นผิวที่ดูดีที่สุด (พร้อมรายละเอียดเพิ่มเติม) ที่สร้างขึ้นเพื่อวาดทับ
//แทนที่จะปรับขนาดรูปภาพต้นฉบับ (ซึ่งจะส่งผลให้สูญเสียรายละเอียด)
//ฉันเคยกล่าวไว้ว่ามีหลายวิธีที่จะหลีกเลี่ยงข้อจำกัดที่ OpenGL กำหนดไว้ที่ความกว้างและความสูงของพื้นผิว - 64, 128, 256 ฯลฯ
//วิธีแก้ปัญหาคือ gluBuild2DMipmaps จากสิ่งที่ฉันพบ คุณสามารถใช้บิตแมปที่กำหนดเองเพื่อสร้างพื้นผิวได้
//OpenGL จะปรับขนาดให้เป็นขนาดปกติโดยอัตโนมัติ
//เนื่องจากเป็นพื้นผิวที่สาม เราจึงบันทึกลงในพื้นผิว[2] ด้วยวิธีนี้ พื้นผิวทั้งสามในบทเรียนนี้จึงถูกสร้างขึ้น
//สร้างพื้นผิว MipMapped
glBindTexture(GL_TEXTURE_2D, พื้นผิว[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexพารามิเตอร์(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_NEAREST); // (ใหม่)
//บรรทัดต่อไปนี้สร้างพื้นผิวแบบ mipmapped
//เราใช้สามสี (แดง เขียว น้ำเงิน) เพื่อสร้างพื้นผิว 2 มิติ
//TextureImage[0].sizeX คือความกว้างบิตแมป
//TextureImage[0].sizeY คือความสูงของบิตแมป
//(====ด้วยเหตุผลบางประการ ฟังก์ชันนี้ภายใต้ Delphi ไม่มีพารามิเตอร์ความสูง
//แต่มีตัวช่วยอยู่ด้วย ฉันไม่รู้ว่าเดลฟีจะทำอะไรต่อไป ซึ่งทำให้ฉันรู้สึกหดหู่ใจ...
//ในที่สุดฉันก็เขียน gluBuild2DMipmaps ด้วยตัวเองก่อนหน้านี้
//หากต้องการโหลดฟังก์ชัน gluBuild2DMipmaps ใน glu32.dll =====)
//GL_RGB หมายความว่าเราใช้สี RGB ตามลำดับ
//GL_UNSIGNED_BYTE หมายความว่าหน่วยของข้อมูลพื้นผิวเป็นไบต์
//TextureImage[0].data ชี้ไปที่บิตแมปที่เราใช้ในการสร้างพื้นผิว
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0].sizeX,
พื้นผิวรูปภาพ[0].ขนาด, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data); //(ใหม่) }
จบ;
หากกำหนด (TextureImage[0]) จากนั้น // ไม่ว่าจะมีพื้นผิวอยู่หรือไม่
หากกำหนด (TextureImage[0].data) จากนั้น // ไม่ว่าจะมีรูปภาพพื้นผิวอยู่หรือไม่
TextureImage[0].data := Nil; // ปล่อยหน่วยความจำที่ครอบครองโดยภาพพื้นผิว
TextureImage[0] := Nil; // ปล่อยโครงสร้างภาพ
ผลลัพธ์ := สถานะ; // สถานะการกลับมา
จบ;
//ถึงเวลาโหลดพื้นผิวและเริ่มต้นการตั้งค่า OpenGL
//บรรทัดแรกของฟังก์ชัน GLInit ใช้โค้ดด้านบนเพื่อโหลดพื้นผิว
//หลังจากสร้างพื้นผิวแล้ว เราจะเรียก glEnable(GL_TEXTURE_2D) เพื่อเปิดใช้งานการแมปพื้นผิว 2D
// โหมดเงาถูกตั้งค่าเป็นการแรเงาแบบเรียบ (การแรเงาแบบเรียบ)
//สีพื้นหลังถูกตั้งค่าเป็นสีดำ เราเปิดใช้งานการทดสอบเชิงลึก จากนั้นเราเปิดใช้งานการคำนวณเปอร์สเปคทีฟที่ปรับให้เหมาะสม
ขั้นตอน glInit(); // เริ่มการตั้งค่าทั้งหมดสำหรับ OpenGL ที่นี่
เริ่ม
ถ้า (ไม่ใช่ LoadTexture) จากนั้น // เรียกรูทีนย่อยการโหลดพื้นผิว
exit; // หากโหลดไม่สำเร็จ ให้ออก
glEnable(GL_TEXTURE_2D); // เปิดใช้งานการแมปพื้นผิว
glShadeModel(GL_SMOOTH); // เปิดใช้งานการปรับเงาให้เรียบ
glClearColor(0.0, 0.0, 0.0, 0.0); // พื้นหลังสีดำ
glClearDepth(1.0); //ตั้งค่าบัฟเฟอร์ความลึก
glEnable(GL_DEPTH_TEST); // เปิดใช้งานการทดสอบเชิงลึก
glDepthFunc(GL_LESS); // ประเภทของการทดสอบเชิงลึกเสร็จสิ้น
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // การคำนวณการฉายภาพเปอร์สเปคทีฟที่ปรับให้เหมาะสมที่สุด
//ตอนนี้เริ่มตั้งค่าแหล่งกำเนิดแสง บรรทัดถัดไปด้านล่างจะกำหนดปริมาณแสงโดยรอบที่ปล่อยออกมา
//แหล่งกำเนิดแสง light1 เริ่มเปล่งแสง
//ในตอนต้นของบทเรียนนี้ เราจัดเก็บปริมาณแสงโดยรอบไว้ในอาร์เรย์ LightAmbient
//ตอนนี้เราจะใช้อาร์เรย์นี้ (แสงโดยรอบที่มีความสว่างเพียงครึ่งเดียว)
glLightfv(GL_LIGHT1, GL_AMBIENT, @LightAmbient[0]); // ตั้งค่าแสงโดยรอบ
//ต่อไปเราจะกำหนดปริมาณแสงที่กระจาย มันถูกจัดเก็บไว้ในอาร์เรย์ LightDiffuse (แสงสีขาวความสว่างเต็มที่)
glLightfv(GL_LIGHT1, GL_DIFFUSE, @LightDiffuse[0]); // ตั้งค่าการกระจายแสง
//จากนั้นจึงกำหนดตำแหน่งของแหล่งกำเนิดแสง
//ตำแหน่งถูกเก็บไว้ในอาร์เรย์ LightPosition
//(ตรงกลางด้านหน้าของกล่องไม้ X-0.0, Y-0.0 ขยับ 2 หน่วยไปทางผู้สังเกตในทิศทาง Z <นอกหน้าจอ>)
glLightfv(GL_LIGHT1, GL_POSITION, @LightPosition); // ตำแหน่งแหล่งกำเนิดแสง
//สุดท้าย เราก็เปิดใช้งานแหล่งกำเนิดแสงหมายเลขหนึ่ง เรายังไม่ได้เปิดใช้งาน GL_LIGHTING
//ดังนั้นคุณจึงมองไม่เห็นแสงใดๆ
//ข้อควรจำ: การตั้งค่า ตำแหน่ง หรือแม้แต่การเปิดใช้งานแหล่งกำเนิดแสงจะไม่ทำงาน
//เว้นแต่เราจะเปิดใช้งาน GL_LIGHTING
glEnable(GL_LIGHT1); // เปิดใช้งานแหล่งกำเนิดแสงหมายเลข 1
จบ;
//โค้ดชิ้นถัดไปจะดึงลูกบาศก์ที่มีพื้นผิว ฉันใส่คำอธิบายประกอบโค้ดใหม่เท่านั้น
//หากคุณมีคำถามเกี่ยวกับโค้ดที่ไม่มีคำอธิบายประกอบ ให้กลับไปที่บทที่ 6
ขั้นตอน glDraw(); // การวาดทั้งหมดเริ่มต้นจากที่นี่
เริ่ม
glClear(GL_COLOR_BUFFER_BIT หรือ GL_DEPTH_BUFFER_BIT); // ล้างหน้าจอและบัฟเฟอร์ความลึก
glLoadIdentity(); //รีเซ็ตเมทริกซ์การสังเกตโมเดลปัจจุบัน
//วางโค้ดสามบรรทัดถัดไปและหมุนลูกบาศก์พื้นผิว
//glTranslatef(0.0,0.0,z) ย้ายหน่วยลูกบาศก์ Z ไปตามแกน Z
//glRotatef(xrot,1.0f,0.0f,0.0f) หมุนลูกบาศก์รอบแกน X xrot
//glRotatef(yrot,0.0f,1.0f,0.0f) หมุนลูกบาศก์ yrot รอบแกน Y
glTranslatef(0.0, 0.0, z); // ย้ายเข้า/ออกจากหน้าจอ z หน่วย
glRotatef(xrot, 1.0, 0.0, 0.0); // หมุนรอบแกน X
glRotatef(yrot, 0.0, 1.0, 0.0); // หมุนรอบแกน Y
//บรรทัดถัดไปคล้ายกับที่เราทำในบทที่ 6
//ข้อแตกต่างคือคราวนี้พื้นผิวที่เราผูกไว้คือ texture[filter]
//แทนที่จะเป็น texture[0] ในบทเรียนที่แล้ว
//ทุกครั้งที่เรากดปุ่ม F ค่าของตัวกรองจะเพิ่มขึ้น
//หากค่านี้มากกว่า 2 ตัวกรองตัวแปรจะถูกรีเซ็ตเป็น 0
//เมื่อโปรแกรมเริ่มต้น ค่าของตัวกรองตัวแปรจะถูกตั้งค่าเป็น 0 ด้วย
//การใช้ตัวกรองตัวแปรทำให้เราสามารถเลือกพื้นผิวใดก็ได้จากสามแบบ
glBindTexture(GL_TEXTURE_2D, texture[filter]); //เลือกพื้นผิวที่กำหนดโดยฟิลเตอร์
glBegin(GL_QUADS); // เริ่มวาดรูปสี่เหลี่ยม
//glNormal3f ยังใหม่กับบทเรียนนี้ ปกติ ความหมายคือ ปกติ
//สิ่งที่เรียกว่าเส้นปกติหมายถึงเส้นตรงที่ผ่านจุดบนพื้นผิว (รูปหลายเหลี่ยม) และตั้งฉากกับพื้นผิวนี้ (รูปหลายเหลี่ยม)
//เมื่อใช้แหล่งกำเนิดแสงต้องระบุค่าปกติ ค่าปกติจะบอก OpenGL ถึงการวางแนวของรูปหลายเหลี่ยม และระบุด้านหน้าและด้านหลังของรูปหลายเหลี่ยม
//หากไม่ระบุสภาวะปกติ อาจมีสิ่งแปลก ๆ เกิดขึ้นได้ พื้นผิวที่ไม่ควรส่องสว่างจะถูกส่องสว่าง และด้านหลังของรูปหลายเหลี่ยมก็ส่องสว่างเช่นกัน....
//ยังไงก็ตาม เส้นปกติควรชี้ไปที่ด้านนอกของรูปหลายเหลี่ยม เมื่อดูที่หน้ากล่องจะสังเกตเห็นว่าเส้นปกติอยู่ในทิศทางเดียวกับแกน Z บวก
//ซึ่งหมายความว่าภาวะปกติกำลังชี้ไปยังผู้สังเกต - ตัวคุณเอง นี่คือสิ่งที่เราหวังไว้
//สำหรับด้านหลังกล่องไม้ตามที่เราต้องการคือหันหน้าปกติออกจากคนดู
//ถ้าลูกบาศก์หมุน 180 องศาตามแนวแกน X หรือ Y เส้นปกติที่ด้านหน้ายังคงหันหน้าไปทางผู้สังเกต และเส้นปกติด้านหลังยังคงหันหน้าออกจากผู้สังเกต
// กล่าวอีกนัยหนึ่ง ไม่ว่าจะเป็นพื้นผิวใดก็ตาม ตราบใดที่มันหันหน้าเข้าหาผู้สังเกต สภาพปกติของพื้นผิวนี้จะชี้ไปที่ผู้สังเกต
//เนื่องจากแหล่งกำเนิดแสงอยู่ติดกับผู้สังเกตทันที เมื่อใดก็ตามที่วัตถุปกติหันหน้าเข้าหาผู้สังเกต พื้นผิวนี้ก็จะสว่างขึ้น
//และยิ่งเส้นปกติเข้าใกล้แหล่งกำเนิดแสงมากเท่าไร แสงก็จะยิ่งสว่างมากขึ้นเท่านั้น
//ถ้าคุณวางจุดสังเกตไว้ในลูกบาศก์ คุณจะพบกับความมืดภายในเส้นปกติ
//เพราะว่าจุดปกติออกด้านนอก หากไม่มีแหล่งกำเนิดแสงภายในลูกบาศก์ แน่นอนว่ามันจะเป็นสีดำสนิท
// ด้านหน้า
glNormal3f(0.0, 0.0, 1.0); // ชี้ปกติไปที่ผู้สังเกต
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, -1.0, 1.0); // พื้นผิวและด้านซ้ายล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, -1.0, 1.0); // พื้นผิวและมุมขวาล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, 1.0); // พื้นผิวและมุมขวาบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, 1.0); // พื้นผิวและด้านซ้ายบนของรูปสี่เหลี่ยม
// ภายหลัง
glNormal3f(0.0, 0.0, -1.0); // ปกติหันหน้าออกจากผู้ชม
glTexCoord2f(1.0, 0.0);
glVertex3f(-1.0, -1.0, -1.0); // พื้นผิวและมุมขวาล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 1.0);
glVertex3f(-1.0, 1.0, -1.0); // พื้นผิวและมุมขวาบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 1.0);
glVertex3f(1.0, 1.0, -1.0); //พื้นผิวและด้านซ้ายบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 0.0);
glVertex3f(1.0, -1.0, -1.0); // พื้นผิวและด้านซ้ายล่างของรูปสี่เหลี่ยม
// พื้นผิวด้านบน
glNormal3f(0.0, 1.0, 0.0); // ปกติขึ้นไป
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, -1.0); //พื้นผิวและด้านซ้ายบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, 1.0, 1.0); // พื้นผิวและด้านซ้ายล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, 1.0, 1.0); // พื้นผิวและมุมขวาล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, -1.0); // พื้นผิวและมุมขวาบนของรูปสี่เหลี่ยม
// ด้านล่าง
glNormal3f(0.0, -1.0, 0.0); // คว่ำหน้าปกติ
glTexCoord2f(1.0, 1.0);
glVertex3f(-1.0, -1.0, -1.0); // พื้นผิวและมุมขวาบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 1.0);
glVertex3f(1.0, -1.0, -1.0); //พื้นผิวและด้านซ้ายบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 0.0);
glVertex3f(1.0, -1.0, 1.0); // พื้นผิวและด้านซ้ายล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 0.0);
glVertex3f(-1.0, -1.0, 1.0); // พื้นผิวและมุมขวาล่างของรูปสี่เหลี่ยม
// ขวา
glNormal3f(1.0, 0.0, 0.0); // ปกติทางด้านขวา
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, -1.0, -1.0); // พื้นผิวและมุมขวาล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, -1.0); // พื้นผิวและมุมขวาบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 1.0);
glVertex3f(1.0, 1.0, 1.0); //พื้นผิวและด้านซ้ายบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 0.0);
glVertex3f(1.0, -1.0, 1.0); // พื้นผิวและด้านซ้ายล่างของรูปสี่เหลี่ยม
// ซ้าย
glNormal3f(-1.0, 0.0, 0.0); // ปกติไปทางซ้าย
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, -1.0, -1.0); // พื้นผิวและด้านซ้ายล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 0.0);
glVertex3f(-1.0, -1.0, 1.0); // พื้นผิวและมุมขวาล่างของรูปสี่เหลี่ยม
glTexCoord2f(1.0, 1.0);
glVertex3f(-1.0, 1.0, 1.0); // พื้นผิวและมุมขวาบนของรูปสี่เหลี่ยม
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, -1.0); //พื้นผิวและด้านซ้ายบนของรูปสี่เหลี่ยม
glEnd();
xrot := xrot + xspeed; // xrot เพิ่มหน่วย xspeed
yrot := Yrot + yspeed; // yrot เพิ่มหน่วย yspeed
จบ;
//ตอนนี้ไปที่ฟังก์ชันหลักของ WinMain()
// เราจะเพิ่มโค้ดควบคุมที่นี่เพื่อเปิดและปิดแหล่งกำเนิดแสง หมุนกล่องไม้ สลับวิธีการกรอง และย้ายกล่องไม้ให้เข้ามาใกล้และไกลออกไป
// ใกล้ถึงจุดสิ้นสุดของฟังก์ชัน WinMain() คุณจะเห็นบรรทัดโค้ด SwapBuffers(hDC)
//จากนั้นเพิ่มโค้ดต่อไปนี้หลังบรรทัดนี้
//โค้ดจะตรวจสอบว่ามีการกดปุ่ม L หรือไม่
//หากกดปุ่ม L แล้ว แต่ค่า lp ไม่เป็นเท็จ แสดงว่ายังไม่ได้ปล่อยปุ่ม L และจะไม่มีอะไรเกิดขึ้นในเวลานี้
SwapBuffers(h_DC); // สลับบัฟเฟอร์ (บัฟเฟอร์คู่)
ถ้า (keys[ord('L')] และไม่ใช่ lp) จากนั้น
เริ่ม
//ถ้าค่าของ lp เป็นเท็จ
//หมายความว่ายังไม่ได้กดปุ่ม L หรือไม่ได้ปล่อย lp จะถูกตั้งค่าเป็น TRUE
//เหตุผลในการตรวจสอบทั้งสองเงื่อนไขพร้อมกันคือเพื่อป้องกันไม่ให้กดปุ่ม L
//โค้ดนี้ถูกดำเนินการซ้ำๆ และทำให้ฟอร์มกระพริบอย่างต่อเนื่อง
//หลังจากตั้งค่า lp เป็นจริง คอมพิวเตอร์จะรู้ว่ามีการกดปุ่ม L
//เราสามารถเปิด/ปิดแหล่งกำเนิดแสงได้ตามต้องการ: ไฟแบบแปรผันแบบบูลีนจะควบคุมการเปิด/ปิดแหล่งกำเนิดแสง
lp := true; // lp ถูกตั้งค่าเป็น TRUE
light := ไม่สว่าง; // เปลี่ยนแหล่งกำเนิดแสงเป็น TRUE/FALSE
ถ้าไม่สว่างก็ // หากไม่มีแหล่งกำเนิดแสง
glDisable(GL_LIGHTING) // ปิดการใช้งานแหล่งกำเนิดแสง
อื่นๆ // อื่นๆ
glEnable(GL_LIGHTING); //เปิดใช้งานแหล่งกำเนิดแสง
จบ;
ถ้าไม่ใช่คีย์[ord('L')] จากนั้น // จะปล่อยคีย์ L หรือไม่
lp := FALSE; // ถ้าใช่ ให้ตั้งค่า lp เป็น FALSE
// จากนั้นทำการตรวจสอบที่คล้ายกันสำหรับปุ่ม "F"
// หากกดปุ่ม "F" และไม่ได้กดปุ่ม "F" หรือไม่เคยกดเลย
//ตั้งค่าตัวแปร fp ให้เป็นจริง ซึ่งหมายความว่ากำลังกดปุ่มอยู่
//จากนั้นเพิ่มหนึ่งรายการในตัวแปรตัวกรอง หากตัวแปรตัวกรองมีค่ามากกว่า 2
//(เนื่องจากอาร์เรย์ที่เราใช้ที่นี่คือ texture[3] ไม่มีพื้นผิวที่ใหญ่กว่า 2)
//เรารีเซ็ตตัวแปรตัวกรองเป็น 0
ถ้า (keys[ord('F')] และไม่ใช่ fp) จากนั้น // กดปุ่ม F หรือไม่?
เริ่ม
fp := TRUE; // fp ถูกตั้งค่าเป็น TRUE
inc(filter); // เพิ่มหนึ่งเข้าไปในค่าของ filter
ถ้า filter > 2 แล้ว // มากกว่า 2 หรือเปล่า?
ตัวกรอง := 0; // หากรีเซ็ตเป็น 0
จบ;
หากไม่ใช่คีย์ [ord ('F')] จากนั้น // ปุ่ม F ถูกปล่อยออกมาหรือไม่?
fp := FALSE; // หากตั้งค่า fp เป็น FALSE
//สี่บรรทัดนี้ตรวจสอบว่ามีการกดปุ่ม PageUp หรือไม่ หากเป็นเช่นนั้น ให้ลดค่าของตัวแปร z วิธีนี้การเรียก glTranslatef(0.0f,0.0f,z) ที่รวมอยู่ในฟังก์ชัน DrawGLScene จะย้ายกล่องไม้ให้ห่างจากตัวแสดง
หากคีย์ [VK_PRIOR] จากนั้น //PageUp ถูกกด?
z := z - 0.02; // หากกดให้เลื่อนกล่องไม้ไปทางด้านในของหน้าจอ
//สี่บรรทัดถัดไปตรวจสอบว่ามีการกดปุ่ม PageDown หรือไม่ หากเป็นเช่นนั้น ให้เพิ่มค่าของตัวแปร z ด้วยวิธีนี้ การเรียก glTranslatef(0.0f,0.0f,z) ที่รวมอยู่ในฟังก์ชัน DrawGLScene จะย้ายกล่องไม้เข้าใกล้ผู้สังเกตการณ์มากขึ้น
ถ้าคีย์[VK_NEXT] จากนั้น // กด PageDown หรือไม่?
z := z + 0.02; //ถ้ากด ให้ย้ายกล่องไม้ไปทางผู้สังเกต
//ตอนนี้ตรวจสอบปุ่มลูกศร กดปุ่มทิศทางซ้ายและขวาเพื่อลดหรือเพิ่ม xspeed ตามลำดับ
// กดปุ่มทิศทางขึ้นและลงเพื่อลดหรือเพิ่มความเร็วตามนั้น
//โปรดจำไว้ว่าในบทช่วยสอนในอนาคต หากค่าของ xspeed และ yspeed เพิ่มขึ้น ลูกบาศก์จะหมุนเร็วขึ้น
//หากคุณกดปุ่มทิศทางใดปุ่มหนึ่ง ลูกบาศก์จะหมุนเร็วขึ้นในทิศทางนั้น
ถ้าคีย์[VK_UP] จากนั้น // กดปุ่มทิศทางขึ้นหรือไม่?
xspeed := xspeed - 0.01; // ถ้าใช่ ให้ลด xspeed
หากคีย์ [VK_DOWN] จากนั้น // มีการกดปุ่มทิศทางลงหรือไม่?
xspeed := xspeed + 0.01; // ถ้าใช่ ให้เพิ่ม xspeed
หากคีย์ [VK_RIGHT] จากนั้น // มีการกดปุ่มทิศทางที่ถูกต้องหรือไม่?
yspeed := yspeed + 0.01; // ถ้าใช่ ให้เพิ่ม yspeed
หากคีย์ [VK_LEFT] จากนั้น // กดปุ่มทิศทางซ้ายหรือไม่?
yspeed := yspeed - 0.01; // ถ้าใช่ ให้ลด yspeed
ถ้า (คีย์ [VK_ESCAPE]) จากนั้น // หากกดปุ่ม ESC
เสร็จสิ้น := จริง
เรียกใช้และเห็นผล