ảnh chụp bởi sskennel.
Phiên Hỏi & Đáp hôm nay đến với chúng tôi theo lịch sự của SuperUser - một phân khu của Stack Exchange, một nhóm các trang web Hỏi & Đáp dành cho cộng đồng.
Câu hỏi
Người đọc SuperUser Sathya đặt ra câu hỏi:
Ở đây bạn có thể thấy một ảnh chụp màn hình của một chương trình C ++ nhỏ gọi là Triangle.exe với một hình tam giác xoay dựa trên API OpenGL.
Tôi chỉ tò mò và muốn biết toàn bộ quá trình từ nhấp đúp vào Triangle.exe trong Windows XP cho đến khi tôi có thể thấy tam giác xoay trên màn hình. Điều gì xảy ra, làm thế nào để CPU (mà lần đầu tiên xử lý.exe) và GPU (mà cuối cùng kết quả đầu ra tam giác trên màn hình) tương tác?
Tôi đoán tham gia vào việc hiển thị tam giác xoay này chủ yếu là phần cứng / phần mềm sau trong số những người khác:
Phần cứng
- HDD
- Bộ nhớ hệ thống (RAM)
- CPU
- Bộ nhớ video
- GPU
- Màn hình LCD
Phần mềm
- Hệ điều hành
- API DirectX / OpenGL
- Trình điều khiển Nvidia
Bất cứ ai có thể giải thích quá trình, có thể với một số loại biểu đồ dòng chảy để minh họa?
Nó không phải là một lời giải thích phức tạp bao gồm tất cả các bước (đoán rằng sẽ vượt quá phạm vi), nhưng một lời giải thích một chàng trai CNTT trung gian có thể làm theo.
Tôi khá chắc chắn rất nhiều người thậm chí sẽ tự gọi mình là chuyên gia CNTT không thể mô tả chính xác quy trình này.
Câu trả lời
Hình ảnh của JasonC, có sẵn làm hình nền ở đây.
Anh ấy viết:
Tôi quyết định viết một chút về khía cạnh lập trình và cách các thành phần nói chuyện với nhau. Có lẽ nó sẽ làm sáng tỏ một số khu vực nhất định.
Sự trình bày
Phải làm gì để thậm chí có một hình ảnh duy nhất mà bạn đăng trong câu hỏi của mình, được vẽ trên màn hình?
Có nhiều cách để vẽ một hình tam giác trên màn hình. Để đơn giản, giả sử không có bộ đệm đỉnh được sử dụng. (A -bộ đệm đỉnhlà một khu vực bộ nhớ nơi bạn lưu trữ tọa độ.) Giả sử chương trình chỉ đơn giản nói với đường ống xử lý đồ họa về mọi đỉnh đơn (một đỉnh chỉ là một tọa độ trong không gian) trong một hàng.
Nhưngtrước khi chúng ta có thể vẽ bất cứ thứ gì, trước tiên chúng ta phải chạy một số giàn giáo. Chúng ta sẽ thấy tại sao một lát sau:
// Clear The Screen And The Depth Buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Reset The Current Modelview Matrix glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // Drawing Using Triangles glBegin(GL_TRIANGLES); // Red glColor3f(1.0f,0.0f,0.0f); // Top Of Triangle (Front) glVertex3f( 0.0f, 1.0f, 0.0f); // Green glColor3f(0.0f,1.0f,0.0f); // Left Of Triangle (Front) glVertex3f(-1.0f,-1.0f, 1.0f); // Blue glColor3f(0.0f,0.0f,1.0f); // Right Of Triangle (Front) glVertex3f( 1.0f,-1.0f, 1.0f); // Done Drawing glEnd();
Vậy điều đó đã làm gì?
Khi bạn viết chương trình muốn sử dụng cạc đồ họa, bạn thường sẽ chọn một loại giao diện nào đó cho trình điều khiển. Một số giao diện nổi tiếng cho trình điều khiển là:
- OpenGL
- Direct3D
- CUDA
Đối với ví dụ này, chúng tôi sẽ gắn bó với OpenGL. Bây giờ, của bạn giao diện cho người lái xe là những gì cung cấp cho bạn tất cả các công cụ bạn cần để làm cho chương trình của bạn nói chuyện vào cạc đồ họa (hoặc trình điều khiển, sau đó cuộc đàm phán vào thẻ).
Giao diện này chắc chắn sẽ cung cấp cho bạn công cụ. Những công cụ này có hình dạng của một API mà bạn có thể gọi từ chương trình của bạn.
API đó là những gì chúng ta thấy đang được sử dụng trong ví dụ trên. Chúng ta hãy xem xét kỹ hơn.
Giàn giáo
Trước khi bạn thực sự có thể thực hiện bất kỳ bản vẽ thực tế nào, bạn sẽ phải thực hiện thiết lập. Bạn phải xác định chế độ xem của mình (khu vực sẽ thực sự được hiển thị), phối cảnh của bạn ( Máy ảnh vào thế giới của bạn), những gì chống răng cưa bạn sẽ được sử dụng (để mịn ra viền của tam giác của bạn) …
Nhưng chúng tôi sẽ không xem xét bất kỳ điều gì trong số đó. Chúng tôi sẽ chỉ xem xét nội dung bạn sẽ phải làm mọi khung hình. Như:
Xóa màn hình
Các đường ống đồ họa sẽ không xóa màn hình cho bạn mỗi khung hình. Bạn sẽ phải nói điều đó. Tại sao? Đây là lý do tại sao:
Nếu bạn không xóa màn hình, bạn sẽ chỉ đơn giản là vẽ lên mỗi khung hình. Đó là lý do chúng tôi gọi
glClear
với
GL_COLOR_BUFFER_BIT
bộ. Các bit khác (
GL_DEPTH_BUFFER_BIT
) yêu cầu OpenGL xóa chiều sâuđệm. Bộ đệm này được sử dụng để xác định pixel nào ở phía trước (hoặc phía sau) các pixel khác.
Chuyển đổi
Chuyển đổi là phần mà chúng ta lấy tất cả các tọa độ đầu vào (các đỉnh của tam giác của chúng ta) và áp dụng ma trận ModelView của chúng ta. Đây là ma trận giải thích cách chúng tôi mô hình (các đỉnh) được xoay, thu nhỏ và dịch (di chuyển).
Tiếp theo, chúng tôi áp dụng ma trận Chiếu của chúng tôi. Điều này di chuyển tất cả các tọa độ để chúng đối mặt với máy ảnh của chúng tôi một cách chính xác.
Bây giờ chúng ta chuyển đổi một lần nữa, với ma trận Viewport của chúng ta. Chúng tôi làm điều này để mở rộng quy mô mô hình với kích thước màn hình của chúng tôi. Bây giờ chúng ta có một tập hợp các đỉnh đã sẵn sàng để được hiển thị!
Chúng tôi sẽ trở lại để chuyển đổi một chút sau đó.
Vẽ
Để vẽ hình tam giác, chúng tôi chỉ có thể yêu cầu OpenGL bắt đầu một hình mới danh sách hình tam giác bằng cách gọi
glBegin
với
GL_TRIANGLES
không thay đổi. Ngoài ra còn có các hình thức khác mà bạn có thể vẽ. Giống như một dải tam giác hoặc một chiếc quạt hình tam giác.Đây là những tối ưu hóa chủ yếu, vì chúng đòi hỏi ít giao tiếp giữa CPU và GPU để vẽ cùng một lượng tam giác.
Sau đó, chúng ta có thể cung cấp một danh sách các bộ 3 đỉnh nên tạo thành mỗi tam giác. Mỗi tam giác sử dụng 3 tọa độ (như chúng ta đang ở trong không gian 3D). Ngoài ra, tôi cũng cung cấp màu cho mỗi đỉnh, bằng cách gọi
glColor3f
trước gọi điện thoại
glVertex3f
Độ bóng giữa 3 đỉnh (3 góc của tam giác) được tính bằng OpenGL tự động. Nó sẽ nội suy màu sắc trên toàn bộ khuôn mặt của đa giác.
Sự tương tác
Bây giờ, khi bạn nhấp vào cửa sổ. Ứng dụng này chỉ phải nắm bắt thông báo cửa sổ báo hiệu nhấp chuột. Sau đó, bạn có thể chạy bất kỳ hành động nào trong chương trình bạn muốn.
Điều này nhận được nhiều khó khăn hơn khi bạn muốn bắt đầu tương tác với cảnh 3D của mình.
Trước tiên, bạn phải biết rõ pixel nào người dùng đã nhấp vào cửa sổ. Sau đó, lấy của bạn quan điểmvào tài khoản, bạn có thể tính toán hướng của một tia, từ điểm nhấp chuột vào cảnh của bạn. Sau đó, bạn có thể tính toán nếu có bất kỳ đối tượng nào trong cảnh của bạn giao cắt với tia đó. Bây giờ bạn biết nếu người dùng đã nhấp vào một đối tượng.
Vì vậy, làm thế nào để bạn làm cho nó xoay?
Chuyển đổi
Tôi biết hai loại biến đổi thường được áp dụng:
- Chuyển đổi dựa trên ma trận
- Chuyển đổi dựa trên xương
Sự khác biệt là xương ảnh hưởng đến đơn đỉnh. Ma trận luôn ảnh hưởng đến tất cả các đỉnh được vẽ theo cùng một cách. Hãy xem một ví dụ.
Thí dụ
Trước đó, chúng tôi đã tải ma trận nhận dạng trước khi vẽ hình tam giác của chúng ta. Ma trận nhận diện là ma trận đơn giản cung cấp không biến đổi ở tất cả. Vì vậy, bất cứ điều gì tôi vẽ, chỉ bị ảnh hưởng bởi quan điểm của tôi. Vì vậy, tam giác sẽ không được xoay.
Nếu tôi muốn xoay nó ngay bây giờ, tôi có thể tự làm toán (trên CPU) và chỉ cần gọi
glVertex3f
vớikhác tọa độ (được xoay). Hoặc tôi có thể cho phép GPU thực hiện tất cả công việc, bằng cách gọi
glRotatef
trước khi vẽ:
// Rotate The Triangle On The Y axis glRotatef(amount,0.0f,1.0f,0.0f);
amount
là, tất nhiên, chỉ là một giá trị cố định. Nếu bạn muốn animate, bạn sẽ phải theo dõi
amount
và tăng nó lên mỗi khung hình.
Vì vậy, chờ đợi, những gì đã xảy ra với tất cả các cuộc nói chuyện ma trận trước đó?
Trong ví dụ đơn giản này, chúng ta không phải quan tâm đến ma trận. Chúng tôi chỉ cần gọi
glRotatef
và nó sẽ chăm sóc mọi thứ cho chúng ta.
glRotate
tạo ra một vòng quay
angle
độ xung quanh vector x y z. Ma trận hiện tại (seeglMatrixMode) được nhân với ma trận xoay với sản phẩm thay thế ma trận hiện tại, vì ifglMultMatrix được gọi với ma trận sau làm đối số của nó:
x 2 1 - c + cx y 1 - c - z sx z 1 - c + y 0 s y x 1 - c + z sy 2 1 - c + z 1 - c - x s 0 x z 1 - c - y sy z 1 - c + x sz 2 1 - c + c 0 0 0 0 1
Vâng, cảm ơn vì điều đó!
Phần kết luận
Điều hiển nhiên là, có rất nhiều cuộc trò chuyện đến OpenGL. Nhưng nó không nói chúng tôi bất cứ điều gì. Giao tiếp ở đâu?
Điều duy nhất mà OpenGL đang nói với chúng ta trong ví dụ này là khi nào nó hoàn thành. Mọi hoạt động sẽ mất một khoảng thời gian nhất định. Một số hoạt động mất rất nhiều thời gian, những hoạt động khác cực kỳ nhanh chóng.
Gửi một đỉnh với GPU sẽ rất nhanh, tôi thậm chí sẽ không biết cách thể hiện nó. Gửi hàng ngàn đỉnh từ CPU đến GPU, mỗi khung hình duy nhất, rất có thể, không có vấn đề gì cả.
Xóa màn hình có thể mất một phần nghìn giây hoặc tệ hơn (ghi nhớ, bạn thường chỉ có khoảng 16 mili giây thời gian để vẽ mỗi khung hình), tùy thuộc vào tầm nhìn của bạn là bao nhiêu. Để xóa nó, OpenGL phải vẽ mọi pixel đơn lẻ theo màu mà bạn muốn xóa, có thể là hàng triệu pixel.
Ngoài ra, chúng tôi có thể chỉ hỏi OpenGL về khả năng của bộ điều hợp đồ họa (độ phân giải tối đa, tối đa chống răng cưa, độ sâu màu tối đa,…).
Nhưng chúng ta cũng có thể điền vào một kết cấu với các điểm ảnh mà mỗi màu có một màu cụ thể. Mỗi pixel do đó giữ một giá trị và kết cấu là một "tệp" khổng lồ chứa đầy dữ liệu. Chúng ta có thể tải nó vào card đồ họa (bằng cách tạo bộ đệm kết cấu), sau đó tải một shader, nói rằng shader sử dụng texture của chúng ta làm đầu vào và chạy một số tính toán cực kỳ nặng nề trên “file” của chúng ta.
Sau đó chúng ta có thể "kết xuất" kết quả tính toán của chúng ta (dưới dạng các màu mới) thành một kết cấu mới.
Đó là cách bạn có thể làm cho GPU hoạt động cho bạn theo những cách khác. Tôi cho rằng CUDA thực hiện tương tự như khía cạnh đó, nhưng tôi chưa bao giờ có cơ hội làm việc với nó.
Chúng tôi thực sự chỉ chạm nhẹ vào toàn bộ chủ đề. Lập trình đồ họa 3D là địa ngục của một con thú.
Có cái gì để thêm vào lời giải thích? Âm thanh trong các ý kiến. Bạn muốn đọc thêm câu trả lời từ những người dùng Stack Exchange có hiểu biết công nghệ khác? Xem toàn bộ chuỗi thảo luận tại đây.