Professional Documents
Culture Documents
OpenGL Draft
OpenGL Draft
HỒ CHÍ MINH
TRƯỜNG ĐẠI HỌC BÁCH KHOA
KHOA ĐIỆN – ĐIỆN TỬ
BỘ MÔN ĐIỆN TỬ
---------------o0o---------------
BÁO CÁO
THỰC TẬP TỐT NGHIỆP
Đề Tài: Tìm Hiểu Lập Trình Python Và PyOpenGl
LỜI CẢM ƠN
Chúng em xin cảm ơn chân thành tới các thầy cô Khoa Điện- Điện Tử Trường ĐH
Bách Khoa- Đại Học Quốc Gia TP.HCM, đặc biệt là thầy Hồ Trung Mỹ đã tận tình hướng
dẫn, hỗ trợ chúng em trong suốt thời gian thực tập và trong quá trình làm báo cáo thực tập
tốt nghiệp. Trong thời gian thực tập chúng em đã được truyền đạt những kinh nghiệm quý
báu để giúp chúng em trong công việc sau này, chúng em cũng được học thêm những kiến
thức cũng như kinh nghiệm thực tế và có điều kiện phát huy những kiến thức chuyên môn đã
được học trong trường do các thầy cô tận tình giảng dạy. Với kiến thức còn hạn chế nên bản
báo cáo TTTN không thể tránh khỏi những thiếu xót rất mong được sự thông cảm của quý
thầy cô.
i
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
MỤC LỤC
LỜI MỞ ĐẦU.......................................................................................................................................3
1.1.2. Ưu điểm..........................................................................................................................4
1.4.1. Mở file...........................................................................................................................10
ii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
3.4 Tạo một khối cầu mặt lưới từ hai mặt phẳng cắt...................................................................26
3.5 Xây dựng mô hình Trái Đất quay xung quanh Mặt Trời.......................................................26
6. PHỤ LỤC....................................................................................................................................26
iii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
LỜI MỞ ĐẦU
Thật rõ ràng để nhận thấy rằng nếu chỉ hiển thị thông tin chỉ với các ký hiệu, chữ cái, chữ
số thì sẽ không thể hấp dẫn và dễ hiểu như khi có thêm biểu diễn bằng Kỹ thuật đồ hoạ. Đây
cũng là công cụ không thể thiếu trong các ngành khoa học kỹ thuật, giáo dục, nghệ thuật, giải
trí, quảng cáo…(để diễn đạt máy móc thiết bị, kiến trúc, cấu trúc cơ thể, thông tin thiên văn
địa lý, hình ảnh minh hoạ..). Cùng với sự phát triển của tin học, kỹ thuật đồ họa trên máy vi
tính, ngày càng trở nên tinh xảo. Giao diện các phần mềm ngày nay trở nên thân thiện, đẹp
mắt nhờ các thể hiện đồ họa. Sự hỗ trợ của tin học cho các ngành khác trở nên đắc lực hơn
nhờ khả năng đồ họa vi tính.
Ngôn ngữ lập trình (Programming language) là dạng ngôn ngữ máy tính sử dụng để phát
triển các chương trình phần mềm, tập lệnh hoặc các chuẩn hóa theo một hệ thống các quy tắc
riêng để máy tính thực thi. Hiện nay có rất nhiều ngôn ngữ lập trình đang được sử dụng như
JavaScript, Java, C/C++,Python,…Python có cú pháp khá đơn giản, tốc độ xử lý nhanh, mượt,
không quá kén người sử dụng, nhất là phù hợp với những người học lập trình game và ứng
dụng. Một điều đáng kinh ngạc đối với Python là tốc độ phát triển nhanh nhất hiện nay.
Trong thành công của kỹ thuật đồ họa ngày nay không thể không nói đến sự phát triển
vượt bậc của tốc độ phần cứng lẫn hệ điều hành. Nhưng bản thân kỹ thuật đồ họa thì có bước
tiến nhảy vọt từ những phép tính toán học phức tạp đến những thư viện đồ họa được tạo sẳn.
Các thư viện này cho phép giảm nhẹ thời gian và công sức của người lập trình; Bởi với chúng,
để có được một “tác phẩm”đồ họa không đòi hỏi phải có một kiến thức hùng hậu về đường
cong Bezier, B-spline, về hình học, tạo bóng…, mà chỉ ứng dụng các hàm tạo sẵn.
Một trong những thư viện đó là OpenGL, được xem là tiêu chuẩn thiết kế công nghiệp cho
đồ họa ba chiều. Mục tiêu của bài tiểu luận này làtìm hiểu thư viện đồ họa của OpenGL trong
đồ họa ba chiều, đồng thời cũng cố gắng đưa ra một ứng dụng của OpenGL trong việc minh
họa các giải thuật đồ họa ba chiều một cách đơn giản và dễ tiếp cận.
iv
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Python là một ngôn ngữ lập trình bậc cao cho các mục đích lập trình đa năng, do Guido
van Rossum tạo ra và lần đầu ra mắt vào năm 1991 tại Viện toán-tin ở Hà Lan. Python kế
thừa từ nhiều ngôn ngữ như ABC, Module-3, C, C++, Unix Shell,… Đến nay thì cộng đồng
người sử dụng ngôn ngữ này rất đông, nếu so sánh từ bảng xếp hạng các ngôn ngữ năm 2016
thì Python đứng thứ 3 trong top 10 ngôn ngữ lập trình phổ biến nhất.
Có thể thấy rất nhiều ví dụ từ những trò chơi điện tử đơn giản, cho đến những thuật toán
tìm kiếm phức tạp, Python đã trở thành sự lựa chọn hoàn hảo cho mọi lập trình viên. Đặc biệt
là với sự bùng nổ về công nghệ AI – trí tuệ nhân tạo trong những năm gần đây, cái tên Python
liên tục được nhắc đến như là một công cụ hữu ích trong lĩnh vực công nghệ thông tin.
1.1.2. Ưu điểm
- Python là một ngôn ngữ bậc cao rất dễ dàng sử dụng. Python có một số lượng từ khóa ít
hơn, cấu trúc của Python đơn giản hơn và cú pháp của Python được định nghĩa khá rõ ràng.
Tất cả các điều này là Python thực sự trở thành một ngôn ngữ thân thiện với lập trình viên.
- Phần code của Python được định nghĩa khá rõ ràng và rành mạch.
- Python có một thư viện chuẩn khá rộng lớn. Thư viện này dễ dàng tương thích và tích
hợp với UNIX, Windows, và Macintosh.
- Python là một ngôn ngữ thông dịch. Trình thông dịch thực thi code theo từng dòng, điều
này giúp cho quá trình debug trở nên dễ dàng hơn và đây cũng là yếu tố khá quan trọng giúp
Python thu hút được nhiều người học và trở nên khá phổ biến.
- Python cũng là một ngôn ngữ lập trình hướng đối tượng. Ngoài ra, Python còn hỗ trợ các
phương thức lập trình theo hàm và theo cấu trúc.
v
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
- Ngoài các đặc điểm trên, Python còn khá nhiều đặc điểm khác như hỗ trợ lập trình GUI,
mã nguồn mở, có thể tích hợp với các ngôn ngữ lập trình khác, …
- Tốc độ chậm hơn so với các ngôn ngữ C/C++ hay Java.
- Không phải là ngôn ngữ tốt dành cho nền tảng mobile.
- Python không phải lựa chọn tốt cho các bài toán cần tối ưu bộ nhớ.
- Python có nhiều giới hạn khi làm việc với cơ sở dữ liệu phức tạp.
Ta khai báo một biến bằng cách gán giá trị cụ thể cho nó. Biến sẽ tự động được giải phóng
khi ra khỏi phạm vi của chương trình sử dụng nó. Một biến có thể được gán nhiều loại giá trị
như số, chuỗi, ký tự.
>>>a = 1
>>>a = [1, 2, 3]
Cũng có thể khai báo nhiều giá trị cho nhiều biến trên cùng một dòng hoặc chuyển đổi
giữa hai giá trị rất dễ dàng:
>>> a , b = 45, 54
>>> a, b = b , a
>>> a
54
vi
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
>>> b
45
Kiểu số
Kiểu bộ (tuple): là cấu trúc mảng và một tuple đã được khai báo rồi thì không thay đổi được
giá trị (immutable)
Kiểu danh sách (list): là cấu trúc mảng và các phần tử có index có thứ tự
Kiểu từ điển (dictionary):là một cấu trúc mảng, nhưng các phần tử bao gồm key và value
{"Vietnam":"Hanoi", "Netherlands":"Amsterdam","France":"Paris"}
vii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Chú thích: giúp cho lập trình viên dễ dàng cải tiến mã nguồn, ghi chú các chức năng của
đoạn code thực hiện
b) For…in…else
for <iterating_var> in <sequence>:
<statements(s)>
else:
<statements(s)>
c) While...else
while <expression>:
<statement(s)>
else:
<statements(s)>
viii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Trong lập trình chúng ta sẽ có lúc sử dụng một đoạn code lặp đi lặp lại nhiều lần trong
chương trình. Hàm sẽ giúp chúng ta thực hiện điều này. Chúng ta có thể viết tất cả những gì
chúng ta muốn thực hiện trong hàm sau đó chỉ cần gọi khi sử dụng.
Hàm nếu không trả dữ liệu thì mặc định sẽ trả về giá trị <None>
Một ví dụ về khai báo và gọi hàm tìm phần tử lớn nhất trong mảng:
def max(a):
max = a[0]
for x in a:
if x > max:
max = x
return max
data = [1, 5, 1, 12,3, 4, 6]
print “Data:”, data
print “Maximum:”, max(data)
Kết quả:
ix
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
def <method()>:
<statements>
def <method2()>:
<statements>
2. Các thư viện liên kết động: có phần mở rộng là .dll , .pyd , .so , .sl ,…
Modules trong Python là các tập tin chưa các hàm được định nghĩa sẵn, biến cái mà chúng
ta có thể sử dụng lại, nó cũng có đuôi mở rộng là .py. Python đã cung cấp sẵn một số module
mặc định. Chúng ta có thể sử dụng chúng. Để sử dụng chúng ta cần dùng lệnh import
import <modulename>
Khi gặp câu lệnh trên thì trình biên dịch sẽ tiến hành tìmkiếm file module tương ứng theo
thứ tự thư mục sau:
Giả sử ta tạo một file python mymath.py có nội dung như sau:
x
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Sau đó, tạo một file có tên myexample.py , trong cùng thư mục với file mymath.py vừa
tạo ở trên, có nội dung như sau:
import mymath
num1 = 1
num2 = 2
print 'Tong hai so la: ', mymath.cong(num1, num2)
Sau khi thực hiện sẽ hiển thị lên màn hình là:
Một tập tin chứa thông tin hoặc dữ liệu được lưu trữ trên thiết bị lưu trữ của máy tính.
Python sẽ cung cấp cách để điều khiển các tập tin như âm nhạc, video, và tập tin văn bản.
Chúng ta sẽ tập trung vào hai loại: tập tin văn bản và binary.
1.4.1. Mở file
Trước khi muốn đọc hoặc ghi file, cần có thao tác mở file theo cú pháp:
fh = open(filepath, mode)
Với hàm này sẽ có hai tham số được truyền vào đó là đường dẫn và chế độ mở. Các chế
độ như sau:
xi
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Mặc định là mở file text, nếu muốn mở file nhị phân(binary) thì thêm b , ví dụ: rb , wb ,
ab , rb+ , wb+ , ab+ .
Sau khi file đã mở ở chế độ đọc thì gọi phương thức read([count]) để trả về toàn bộ nội
dung của file. Ví dụ:
f1 = open('test.txt', 'r')
data = f1.read();
Hàm read() có nhận một tham số là số lượng byte muốn đọc. Nếu không truyền vào thì sẽ
đọc hết nội dung của file.
Nếu file được mở ở chế độ có thể ghi thì có thể dùng phương thức write() để ghi một nội
dung vào file. Ví dụ:
f2 = open('access_log','a+')
f2.write('Attack detected')
Cuối chương trình chúng ta sử dụng hàm close() để thực hiện đóng tập tin khi không còn
thao tác với nó nữa.Ví dụ:
f1.close()
f2.close()
xii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
import os
os.rename('test.txt','test_new.txt')
import os
os.remove('test.txt')
OpenGL (Open Graphics Library) là một tiêu chuẩn kỹ thuật đồ họa có mục đích định ra
một giao diện lập trình ứng dụng (API) đồ họa 3 chiều. OpenGL cũng có thể được dùng trong
các ứng dụng đồ họa 2 chiều. Giao diện lập trình này chứa khoảng 150 hàm để vẽ các cảnh
phức tạp từ những hàm đơn giản
Silicon Graphics, nhà sản xuất máy trạm đồ họa tiên tiến thế giới, là đơn vị đi tiên phong
trong việc phát triển OpenGL. Nó được dùng rộng rãi trong các trò chơi điện tử. Ngoài ra
OpenGL còn dùng trong các ứng dụng CAD, thực tế ảo, mô phỏng khoa học, mô phỏng thông
tin, phát triển trò chơi….
OpenGL hỗ trợ các hàm đồ họa làm các việc như sau:
Xây dựng các đối tượng phức tạp từ các thành phần hình học cơ bản (điểm, đoạn, đa
giác, ảnh, bitmap)
Sắp xếp đối tượng trong đồ họa 3D và chọn điểm thuận lợi để quan sát
xiii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Tính toán màu sắc của các đối tượng (màu sắc của đối tượng được quy định bởi điều
kiện chiếu sáng, texture của đối tượng, mô hình được xây dựng hoặc là kết hợp của cả
3 yếu tố đó)
Biến đổi những mô tả toán học của đối tượng và thông tin màu sắc thành các pixel
trên màn hình (quá trình này được gọi là resterization).
OpenGL sử dụng tiền tố gl và tiếp theo đó là những từ được viết hoa ở chữ cái đầu để tạo
nên tên của một lệnh theo hình thức gl{tên hàm}[{số tham số}{loại tham số}]
Tương tự, OpenGL đặt tên các hằng số bắt đầu bằng GL_ và các từ tiếp sau đều được viết
hoa và cách nhau bởi dấu ‘_’
Ví dụ: GL_COLOR_BUFFER_BIT.
Bên cạnh đó, với một số lệnh, để ám chỉ số lượng cũng như kiểu tham số được truyền,
một số hậu tố được sử dụng như trong bảng sau:
xiv
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Ví dụ: glVertex2i(1,3) tương ứng với xác định một điểm (x,y) với x, y nguyên (integer)
Bản thân OpenGL không có sẵn các hàm nhập xuất hay thao tác trên window
OpenGL không có sẵn các hàm cấp cao để xây dựng các mô hình đối tượng, thay vào
đó,người dùng phải tự xây dựng từ các thành phần hình học cơ bản.
Để khắc phục nhược điểm của OpenGL, GLUT được tạo ra với với nhiều hàm hỗ trợ
Quản lý window
Display callback
Nhập xuất (bàn phím, chuột,...)
Vẽ một số đối tượng 3D phức tạp (mặt cầu, khối hộp,...)
Trong OpenGL có 2 loại buffer phổ biến nhất, mỗi lần vẽ, chúng ta nên xóa buffer
glClearColor(0.0, 0.0, 0.0, 0.0); /* xác định màu để xóa color buffer (màu đen) */
Color buffer: buffer chứa màu của các pixel cần được thể hiện
Depth buffer (hay còn gọi là z-buffer): buffer chứa chiều sâu của pixel, được đo bằng
khoảng cách đến mắt. Mục đích chính của buffer này là loại bỏ phần đối tượng nằm sau đối
tượng khác.
Khi vẽ một đối tượng, OpenGL sẽ tự động sử dụng màu đã được xác định trước đó. Do
đó, để vẽ đối tượng với màu sắc theo ý mình, cần phải thiết lập lại màu vẽ như sau:
glColor3f(0.0, 0.0, 0.0); // black
xv
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
OpenGL không có sẵn các hàm để xây dựng các đối tượng hình học phức tạp, người dùng
phải tự xây dựng chúng từ các đối tượng hình học cơ bản mà OpenGL hỗ trợ: điểm, đoạn
thẳng, đa giác.
Khi khai báo một điểm ta dùng hàm glVertexXY với X là số chiều (2, 3, hoặc 4), Y là
kiểu dữ liệu.
Việc xây dựng các đối tượng hình học khác đều có thể được thực hiện như sau
glBegin(mode);
/* xác định tọa độ và màu sắc của các điểm của hình */
glEnd();
xvi
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Một số tính chất của điểm và đoạn cần quan tâm có thể được thiết lập qua các hàm:
Trước khi thực hiện các thao tác trên ModelView, chúng ta cần gọi hàm
glMatrixMode(GL_MODELVIEW);
OpenGL hỗ trợ sẵn các hàm biến đổi affine cơ bản như sau
Tịnh tiến:
xvii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Ví dụ: chúng ta thực hiện phép quay quanh trục z một góc α và tịnh tiến đi một đoạn theo
vector(trx, try, trz), các bước thực hiện sẽ là:
Giống như chụp hình, thiết lập view là thiết lập vị trí cũng như góc, hướng của camera.
GLUT cómột hàm giúp thiết lập view một cách nhanh chóng
trong đó
Trước khi thực hiện các thao tác chiếu, chúng ta gọi 2 hàm
xviii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
Đặc điểm của phép chiếu này là đối tượng càng lùi ra xa thì trông càng nhỏ
Trong phép chiếu này, khoảng cách của vật tới camera không ảnh hưởng tới độ lớn của
vật đó khi hiển thị.
xix
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
trong đó (x,y) là vị trí điểm trái-trên trong cửa sổ vẽ, width, height là chiều rộng và cao
củaviewport. Mặc định (x,y,width,height) = (0,0,winWidth, winHeight) (chiếm toàn bộ cửa
sổ)
OpenGL hỗ trợ 2 chế độ màu: RGBA và Color-Index. Trong chế độ màu RGBA, RGB lần
lượt thể hiện màu Red, Green, Blue. Còn thành phần A (tứcalpha) không thực sự ảnh hưởng
trực tiếp lên màu pixel, người ta có thể dùng thành phần A đểxác định độ trong suốt hay thông
số nào đó cần quan tâm.
xx
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Để thiết lập màu vẽ hiện hành trong chế độ RGBA, chúng ta có thể sử dụng các hàm sau
trong đó, nếu các tham số là số thực thì thành phần màu tương ứng sẽ nằm trong đoạn
[0,1],ngược lại thì sẽ được chuyển đổi như ở bảng sau
Một đoạn thẳng có thể được tô bởi một màu đồng nhất (chế độ flat) hay bởi nhiều màu sắc
khácnhau (chế độ smooth). Để thiết lập chế độ shading phù hợp, chúng ta có thể sử dụng hàm
như sau:
trong đó mode là chế độ mong muốn, nhận 1 trong 2 giá trị GL_SMOOTH hoặc GL_FLAT.
xxi
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Việc cho phép người dùng chọn đối tượng bằng cách click chuột trên cửa sổ là một yêu
cầu thiếtyếu đối với các ứng dụng tương tác. Để thực hiện được những chức năng như vậy,
trongOpenGL có sẵn một chế độ là Selection.Có 2 công đoạn lớn chúng ta cần phải làm
Công đoạn 1 là các thao tác để biến đổi các đối tượng trong không gian về các pixel và
sau đóhiển thị lên màn hình. Công đoạn 2, gần như ngược lại, chương trình xác định xem
pixel màngười dùng tương tác (ví dụ như nhấn chuột trái) thuộc đối tượng nào.
Để chuyển đổi qua lại giữa các công đoạn (hay chế độ), chúng ta dùng hàm
glRenderMode(GL_SELECT)
Việc xác định vùng chọn tương tự như là việc xác định khối nhìn, tức là chúng ta sẽ thao
tác trênphép chiếu.
glMatrixMode (GL_PROJECTION);
glPushMatrix ();
xxii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
glLoadIdentity ();
gluPickMatrix (...);
/* ... */
glPopMatrix();
Trong đó,
là hàm xác định vùng quan tâm trong viewport (ví dụ như xung quanh vùng click chuột) với:
(x, y, width, height) là tham số xác định quan tâm trên viewport
viewport[4] là mảng 4 phần tử chứa 4 tham số của viewport, có thể dùng
hàmglGetIntegerv(GL_VIEWPORT, GLint *viewport) để lấy ra.
OpenGL có một stack giúp thao tác trên tên các đối tượng, với các hàm
Việc sử dụng stack này giúp cho mục đích thứ 3 – xây dựng tên mang tính phân cấp. Mỗi
lầnthực thi glPushName(name) hoặc glLoadName(name) thì chương trình sẽ hiểu là các đối
tượngđược vẽ ở các dòng lệnh sau sẽ có tên là name và chúng là thành phần bộ phận của đối
tượng cótên đặt ở ngay dưới đỉnh stack.
Để có thể truy vấn xem đối tượng nào được chọn, OpenGL xử lý như sau
trước tiên sẽ đánh dấu mọi đối tượng nào có vùng giao với vùng chọn,
sau đó, với mỗi đối tượng có vùng giao, tên của nó và giá trị z nhỏ nhất, z lớn nhất của
xxiii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Chú ý: thủ thục này phải được gọi trước khi chuyển sang chế độ GL_SELECT.
Example1
xxiv
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Example2
xxv
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
Example3
xxvi
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
xxvii
LỜI CẢM ƠN GVHD: Th.S Hồ Trung Mỹ
OpenGL là nơi gần nhất giữa CPU (cái mà chúng ta - những developer - chạy các ứng
dụng trên các ngôn ngữ lập trình) và GPU (chip xử lý đồ họa). Vì vậy OpenGL cần phải được
hỗ trợ bởi các nhà sản xuất card đồ họa (VD: NVidia) và được cài đặt bởi những đối tác phát
triển hệ điều hành (giống như Apple hay Microsoft) và cuối cùng OpenGL cho chúng ta một
API thống nhất để làm việc. Ngày nay, ta có thể thấy logo của OpenGL (hoặc OpenGL ES)
trong rất nhiều trò chơi, ứng dụng 3D, 2D và rất nhiều phần mềm khác (đặc biệt là phần mềm
3D). OpenGL ES được dùng bởi PlayStation, Android, Nintendo 3DS, Nokia, Samsung,
Symbian và tất nhiên cả Apple với MacOS và iOS.
Bằng việc sử dụng thư viện của OpenGL, chúng ta có thể mở rộng thành các đề tài chẳng
hạn như vẽ hình trong không gian ba chiều hay sử dụng thư viện OpenGl để vẽ các đường
cong và mặt cong Benzier, B-SPLINE… Bên cạnh đó OpenGL cũng là công cụ hỗ trợ lập
trình game điện tử làm việc với các nền tảng khác nhau từ PC/Mac, các hệ máy consoles PS3,
PS4, XBox 360, Xbox One, các thiết bị mobile Android,…
6. PHỤ LỤC
drive.google.com/drive/folders/19phBc1JPGOP0tVR4wj9Z6cBbXGz0v1QS?usp=sharing
xxviii