Grafik 3D pada STM32F103

gambar

Sebuah cerita pendek tentang cara mendorong grafik tiga dimensi yang tidak dapat diedit dan menampilkan waktu nyata menggunakan pengontrol yang tidak memiliki kecepatan maupun memori untuk ini.

Kembali pada tahun 2017 (berdasarkan tanggal modifikasi file), saya memutuskan untuk beralih dari pengontrol AVR ke STM32 yang lebih kuat. Secara alami, pengontrol pertama adalah F103 yang dipublikasikan secara luas. Tidak kurang wajar bahwa penggunaan papan debug yang tidak ada rak ditolak karena pembuatannya dari awal sesuai dengan persyaratannya. Anehnya, hampir tidak ada tiang tembok (kecuali bahwa UART1 harus dibawa ke konektor normal, dan tidak disambungkan dengan kabel).

Dibandingkan dengan AVR, karakteristik batu ini cukup baik: jam 72 MHz (dalam praktiknya, Anda dapat melakukan overclock hingga 100 MHz, atau bahkan lebih, tetapi hanya dengan risiko dan risiko Anda sendiri!), 20 kB RAM dan 64 kB flash. Plus, satu ton periferal, ketika menggunakan yang masalah utamanya adalah tidak perlu takut akan kelimpahan ini dan menyadari bahwa Anda tidak perlu menyekop kesepuluh register untuk memulai, cukup untuk mengatur tiga bit pada yang benar. Setidaknya sampai Anda menginginkan sesuatu yang aneh.

Ketika euforia pertama dari kepemilikan kekuasaan seperti itu berlalu, muncul keinginan untuk menyelidiki batas-batasnya. Sebagai contoh yang efektif, saya memilih perhitungan grafik tiga dimensi dengan semua matriks ini, pencahayaan, model poligon dan Z-buffer dengan tampilan 320x240 pada pengontrol ili9341. Dua masalah paling jelas yang harus dipecahkan adalah kecepatan dan volume. Ukuran layar 320x240 pada 16 bit per warna menghasilkan 150 kB per frame. Tetapi total RAM yang kita miliki hanya 20 kB ... Dan 150 kB ini harus ditransfer ke layar setidaknya 10 kali per detik, yaitu, nilai tukar harus minimal 1,5 MB / s atau 12 MB / s, yang sudah terlihat seperti beban signifikan pada inti. Untungnya, dalam pengontrol ini terdapat modul RAP (akses langsung ke memori, alias Direct Memory Access, DMA), yang memungkinkan Anda untuk tidak memuat kernel dengan operasi transfusi dari kosong ke kosong.Artinya, Anda dapat menyiapkan buffer, beri tahu modul "di sini Anda memiliki buffer data, bekerja!", Dan saat ini siapkan data untuk transfer berikutnya. Dan dengan mempertimbangkan kemampuan tampilan untuk menerima data dalam aliran, algoritma berikut muncul: buffer depan disorot, dari mana DMA mentransfer data ke display, buffer belakang ke mana rendering berlangsung, dan buffer-Z yang digunakan untuk memotong kedalaman. Buffer adalah satu baris (atau kolom, apa pun) dari tampilan. Dan alih-alih 150 kB, kita hanya perlu 1920 byte (320 piksel per baris * 3 buffer * 2 byte per titik), yang sangat pas di memori. Retasan kedua didasarkan pada fakta bahwa perhitungan matriks transformasi dan koordinat titik tidak dapat dilakukan untuk setiap baris, jika tidak gambar akan terdistorsi dengan cara yang paling aneh, dan kecepatannya tidak menguntungkan. Sebagai gantinya, perhitungan "eksternal",yaitu, perkalian matriks transformasi dan penerapannya pada simpul dihitung ulang pada setiap frame, dan kemudian dikonversi ke representasi perantara, yang dioptimalkan untuk rendering dalam gambar 320x1.

Untuk alasan hooligan, perpustakaan akan menyerupai OpenGL dari luar. Seperti pada OpenGL asli, rendering dimulai dengan pembentukan matriks transformasi - kliring glLoadIdentity () membuat unit matriks saat ini, kemudian seperangkat transformasi glRotateXY (...), glTranslate (...), yang masing-masing dikalikan dengan matriks saat ini. Karena perhitungan ini akan dilakukan hanya sekali per frame, tidak ada persyaratan khusus untuk kecepatan, Anda dapat melakukannya dengan float sederhana, tanpa penyimpangan dengan angka titik tetap. Matriks itu sendiri adalah array float [4] [4], dipetakan ke array float satu dimensi [16] - pada kenyataannya, metode ini biasanya digunakan untuk array dinamis, tetapi Anda juga bisa mendapatkan sedikit manfaat dari array statis. Peretasan standar lainnya: alih-alih terus menghitung sinus dan cosinus, yang banyak terdapat dalam matriks rotasi,hitung terlebih dahulu dan tulis di tablet. Untuk melakukan ini, bagi lingkaran penuh menjadi 256 bagian, hitung nilai sinus untuk masing-masing dan buang ke dalam sin_table [] array. Nah, siapa pun dari sekolah bisa mendapatkan cosinus dari sinus. Perlu dicatat bahwa fungsi rotasi mengambil sudut bukan pada radian, tetapi dalam fraksi revolusi penuh, setelah reduksi ke kisaran [0 ... 255]. Namun, fungsi "jujur" telah diterapkan yang melakukan konversi dari sudut ke lobus di bawah kap.melakukan konversi dari sudut ke lobus di bawah kap.melakukan konversi dari sudut ke lobus di bawah kap.

Ketika matriks siap, Anda dapat mulai menggambar primitif. Secara umum, dalam grafik tiga dimensi ada tiga jenis primitif - satu titik, satu garis dan satu segitiga. Tetapi jika kita tertarik pada model poligon, perhatian harus diberikan hanya pada segitiga. "Rendering" -nya terjadi pada fungsi glDrawTriangle () atau glDrawTriangleV (). Kata "rendering" terlampir dalam tanda kutip karena tidak ada rendering yang terjadi pada tahap ini. Kami hanya mengalikan semua titik primitif dengan matriks transformasi, dan kemudian kami mengekstrak dari mereka rumus analitik dari tepi y = ky * x + dengan, yang memungkinkan kami untuk menemukan persimpangan ketiga tepi segitiga dengan garis keluaran saat ini. Kami membuang salah satunya, karena tidak terletak pada interval antara simpul, tetapi pada kelanjutannya.Artinya, untuk menggambar bingkai, Anda hanya perlu melewati semua garis dan untuk setiap cat area antara titik persimpangan. Tetapi jika Anda menerapkan algoritma ini "langsung", masing-masing primitif akan tumpang tindih dengan yang digambar sebelumnya. Kita perlu mempertimbangkan koordinat-Z (kedalaman) sehingga segitiga bersilangan dengan indah. Alih-alih hanya mencetak titik demi titik, kami akan mempertimbangkan koordinat Z-nya dan, dibandingkan dengan koordinat-Z yang tersimpan di buffer kedalaman, baik output (memperbarui buffer-Z) atau mengabaikannya. Dan untuk menghitung koordinat Z dari setiap titik garis minat kepada kami, kami menggunakan rumus garis lurus yang sama z = kz * y + bz dihitung oleh dua titik persimpangan yang sama dengan tepi. Akibatnya, objek segitiga "semi-jadi" struct glTriangle terdiri dari tiga koordinat X dari simpul (tidak ada gunanya menyimpan koordinat Y dan Z, mereka akan dihitung) dan k,b koefisien langsung, yah, warnai ke heap. Di sini, berbeda dengan perhitungan matriks transformasi, kecepatan sangat penting, jadi kami sudah menggunakan angka titik tetap. Selain itu, jika untuk istilah b, akurasi yang sama cukup untuk koordinat (2 byte), maka akurasi faktor k, semakin besar semakin baik, jadi kami mengambil 4 byte. Tapi bukan float, karena bekerja dengan bilangan bulat masih lebih cepat, bahkan dengan ukuran yang sama.

Jadi, dengan memanggil sekelompok glDrawTriangle () kami menyiapkan array segitiga setengah jadi. Dalam implementasi saya, segitiga disimpulkan satu per satu oleh panggilan fungsi eksplisit. Sebenarnya, akan logis untuk memiliki array segitiga dengan alamat simpul, tetapi di sini saya memutuskan untuk tidak menyulitkan. Bagaimanapun, fungsi rendering ditulis oleh robot, dan tidak masalah bagi mereka untuk mengisi array konstan atau menulis tiga ratus panggilan yang identik. Saatnya untuk menerjemahkan produk setengah jadi dari segitiga menjadi gambar yang indah di layar. Untuk melakukan ini, fungsi glSwapBuffers () dipanggil. Seperti dijelaskan di atas, ia melewati garis-garis tampilan, mencari setiap titik persimpangan dengan semua segitiga dan menggambar segmen sesuai dengan penyaringan berdasarkan kedalaman. Setelah membuat setiap baris, Anda harus mengirim baris ini ke layar. Untuk melakukan ini, DMA diluncurkan, yang menunjukkan alamat string dan ukurannya.Sementara itu, DMA berfungsi, Anda dapat beralih ke buffer lain dan merender baris berikutnya. Hal utama adalah jangan lupa untuk menunggu akhir transfer jika Anda tiba-tiba selesai render sebelumnya. Untuk memvisualisasikan rasio kecepatan, saya menambahkan dimasukkannya LED merah setelah akhir rendering dan off setelah selesai menunggu DMA. Ternyata sesuatu seperti PWM, yang mengatur kecerahan tergantung pada latensi. Secara teoritis, alih-alih menunggu "bodoh", interupsi DMA dapat digunakan, tetapi kemudian saya tidak dapat menggunakannya, dan algoritme akan menjadi jauh lebih rumit. Untuk program demo, ini berlebihan.Untuk memvisualisasikan rasio kecepatan, saya menambahkan dimasukkannya LED merah setelah akhir rendering dan off setelah selesai menunggu DMA. Ternyata sesuatu seperti PWM, yang mengatur kecerahan tergantung pada latensi. Secara teoritis, alih-alih menunggu "bodoh", interupsi DMA dapat digunakan, tetapi kemudian saya tidak dapat menggunakannya, dan algoritme akan menjadi jauh lebih rumit. Untuk program demo, ini berlebihan.Untuk memvisualisasikan rasio kecepatan, saya menambahkan dimasukkannya LED merah setelah akhir rendering dan off setelah selesai menunggu DMA. Ternyata sesuatu seperti PWM, yang mengatur kecerahan tergantung pada latensi. Secara teoritis, alih-alih menunggu "bodoh", interupsi DMA dapat digunakan, tetapi kemudian saya tidak dapat menggunakannya, dan algoritme akan menjadi jauh lebih rumit. Untuk program demo, ini berlebihan.

Hasil dari prosedur di atas adalah gambar berputar dari tiga bidang berpotongan warna yang berbeda, dan dengan kecepatan yang cukup baik: kecerahan LED merah cukup tinggi, yang menunjukkan margin besar dalam kinerja kernel.

Nah, jika intinya idle, Anda perlu memuatnya. Dan kami akan memuatnya dengan model yang lebih baik. Namun, jangan lupa bahwa memorinya masih sangat terbatas, sehingga pengontrol tidak akan menarik terlalu banyak poligon secara fisik. Perhitungan paling sederhana menunjukkan bahwa setelah mengurangi memori pada buffer garis dan sejenisnya, ada tempat untuk 378 segitiga. Seperti yang telah ditunjukkan oleh praktik, model-model dari game Gothic yang lama namun menarik sangat cocok untuk ukuran ini. Sebenarnya, model ular dan lalat darah ditarik keluar dari sana (dan sudah pada saat menulis artikel ini dan glocoor, memamerkan KDPV), setelah itu controller kehabisan memori flash. Tetapi model permainan tidak dimaksudkan untuk digunakan oleh mikrokontroler.

Katakanlah mereka mengandung animasi, tekstur dan sejenisnya, yang tidak berguna bagi kita, dan tidak muat dalam memori. Untungnya, blender memungkinkan tidak hanya untuk menyimpannya ke * .obj, yang lebih cocok untuk penguraian, tetapi juga untuk mengurangi jumlah poligon jika perlu. Selanjutnya, dengan bantuan program yang ditulis sendiri sederhana obj2arr * .obj, file-file tersebut diurutkan menjadi koordinat, dari mana file * .h kemudian dibentuk untuk dimasukkan langsung dalam firmware.

Tetapi untuk sekarang, modelnya terlihat seperti bercak keriting polos. Pada model uji, ini tidak mengganggu kami, karena semua wajah dicat dengan warna mereka sendiri, tetapi jangan meresepkan warna yang sama untuk setiap poligon model. Tidak, Anda bisa, tentu saja, mengecat lalat dengan warna acak, tetapi itu akan terlihat sangat tiba-tiba, saya memeriksanya. Terutama ketika warna-warna juga berubah pada setiap bingkai ... Sebaliknya, menerapkan setetes sihir vektor dan menambahkan pencahayaan.

Perhitungan pencahayaan dalam versi primitifnya terdiri dalam menghitung produk skalar dari normal dan arah ke sumber cahaya, diikuti dengan mengalikannya dengan warna "asli" pada wajah.
Kami sekarang memiliki tiga model - dua dari permainan dan satu tes, dari mana kami memulai. Untuk beralih, kita akan menggunakan salah satu dari dua tombol yang disolder di papan tulis. Pada saat yang sama, Anda dapat menambahkan kontrol atas prosesor. Kami sudah memiliki satu kontrol - LED merah yang terkait dengan latensi DMA. Dan yang kedua, hijau, LED, kita akan berkedip dengan setiap pembaruan frame - jadi kita bisa memperkirakan frame rate. Untuk mata telanjang, sekitar 15 fps.


Secara umum, saya puas dengan hasilnya: senang menerapkan sesuatu yang pada dasarnya tidak mungkin diselesaikan secara langsung. Tentu saja, masih banyak untuk mengoptimalkan dan meningkatkan, tetapi ada sedikit gunanya dalam hal ini. Secara obyektif, pengontrol untuk grafik tiga dimensi lemah, dan bahkan bukan soal kecepatan, melainkan RAM. Namun, seperti sampel demoscene lainnya, proyek ini berharga bukan oleh hasilnya, tetapi oleh prosesnya.

Jika seseorang tiba-tiba tertarik, kode sumber tersedia di sini .

All Articles