OpenGL'ye Giriş: Bir 3B Metin Oluşturma Eğitimi
Yayınlanan: 2022-03-11DirectX ve OpenGL gibi araçların mevcudiyeti ile, günümüzde 3B öğeler oluşturan bir masaüstü uygulaması yazmak çok zor değil. Ancak, birçok teknolojide olduğu gibi, bazen geliştiricilerin bu niş içine girmelerini zorlaştıran engeller vardır. Zamanla, DirectX ve OpenGL arasındaki yarış, bu teknolojilerin geliştiriciler için daha erişilebilir hale gelmesine, daha iyi belgelere ve yetenekli bir DirectX veya OpenGL geliştiricisi olma sürecini kolaylaştırmasına neden oldu.
Microsoft tarafından tanıtılan ve sürdürülen DirectX, Windows platformuna özgü bir teknolojidir. Öte yandan, OpenGL, özellikleri Khronos Group tarafından sağlanan 3D grafik arenası için platformlar arası bir API'dir.
OpenGL'ye bu girişte, 3B metin modellerini oluşturmak için çok basit bir uygulamanın nasıl yazılacağını açıklayacağım. Kullanıcı arayüzünü uygulamak için Qt/Qt Creator kullanacağız, bu uygulamayı birden çok platformda derlemeyi ve çalıştırmayı kolaylaştıracağız. Bu makale için oluşturulan prototipin kaynak kodu GitHub'da mevcuttur.
Bu basit uygulamanın amacı, 3 boyutlu modeller oluşturmak, bunları basit bir formatta bir dosyaya kaydetmek ve bunları ekranda açıp görüntülemektir. Oluşturulan sahnedeki 3D model, daha iyi bir derinlik ve boyut hissi vermek için döndürülebilir ve yakınlaştırılabilir olacaktır.
Önkoşullar
Başlamadan önce, bu proje için bazı faydalı araçlarla geliştirme ortamımızı hazırlamamız gerekecek. İhtiyacımız olan ilk şey, www.qt.io adresinden indirilebilen Qt çerçevesi ve ilgili yardımcı programlar. Ayrıca işletim sisteminizin standart paket yöneticisi aracılığıyla da mevcut olabilir; Eğer durum buysa, önce onunla denemek isteyebilirsiniz. Bu makale, Qt çerçevesine biraz aşinalık gerektirir. Ancak, çerçeveye aşina değilseniz, prototip çerçevenin oldukça önemsiz bazı özelliklerine dayandığından, lütfen takip etmekten çekinmeyin.
Microsoft Visual Studio 2013'ü Windows'ta da kullanabilirsiniz. Bu durumda, lütfen Visual Studio için uygun Qt Eklentisini kullandığınızdan emin olun.
Bu noktada, depoyu GitHub'dan klonlamak ve bu makaleyi okurken takip etmek isteyebilirsiniz.
OpenGL'ye Genel Bakış
Tek bir belge parçacığı ile basit bir Qt uygulama projesi oluşturarak başlayacağız. Basit bir widget olduğu için, onu derlemek ve çalıştırmak yararlı bir şey üretmeyecektir. Qt tasarımcısı ile dört öğeli bir “Dosya” menüsü ekleyeceğiz: “Yeni…”, “Aç…”, “Kapat” ve “Çıkış”. Bu menü öğelerini karşılık gelen eylemlere bağlayan kodu depoda bulabilirsiniz.
“Yeni…” üzerine tıklamak, şöyle görünecek bir iletişim kutusu açmalıdır:
Burada, kullanıcı bir metin girebilir, bir yazı tipi seçebilir, elde edilen model yüksekliğini değiştirebilir ve bir 3D model oluşturabilir. “Oluştur”a tıklamak modeli kaydetmeli ve ayrıca kullanıcı sol alt köşeden uygun seçeneği seçerse açmalıdır. Anlayabileceğiniz gibi, buradaki amaç, kullanıcı tarafından girilen bazı metinleri 3B modele dönüştürmek ve ekranda oluşturmaktır.
Proje basit bir yapıya sahip olacak ve bileşenler bir avuç C++ ve başlık dosyasına bölünecek:
createcharmodeldlg.h/cpp
Dosyalar, QDialog'dan türetilmiş nesne içerir. Bu, kullanıcının metin yazmasına, yazı tipini seçmesine ve sonucun bir dosyaya kaydedilip kaydedilmeyeceğini ve/veya 3B olarak görüntülenip görüntülenmeyeceğini seçmesine izin veren iletişim pencere aracını uygular.
gl_widget.h/cpp
QOpenGLWidget türetilmiş nesnenin uygulamasını içerir. Bu widget, 3B sahneyi oluşturmak için kullanılır.
ana pencere.h/cpp
Ana uygulama widget'ının uygulamasını içerir. Bu dosyalar, Qt Creator sihirbazı tarafından oluşturuldukları için değişmeden kaldı.
ana.cpp
Ana uygulama pencere öğesini oluşturan ve ekranda gösteren ana(…) işlevini içerir.
model2d_processing.h/cpp
2D sahne oluşturma işlevselliğini içerir.
model3d.h/cpp
3B model nesnelerini depolayan ve bunlar üzerinde işlemlerin (kaydetme, yükleme vb.) çalışmasına izin veren yapıları içerir.
model_creator.h/cpp
3B sahne modeli nesnesinin oluşturulmasına izin veren sınıfın uygulamasını içerir.
OpenGL Uygulaması
Kısaca, kullanıcı arayüzünü Qt Designer ile uygulamanın bariz ayrıntılarını ve etkileşimli elemanların davranışlarını tanımlayan kodu atlayacağız. Bu prototip uygulamasının kesinlikle daha ilginç yönleri var, bunlar sadece önemli değil, aynı zamanda ele almak istediğimiz 3B model kodlama ve işleme ile ilgili olanlar. Örneğin, bu prototipte metni 3B modele dönüştürmenin ilk adımı, metni 2B monokrom bir görüntüye dönüştürmeyi içerir. Bu görüntü oluşturulduktan sonra, görüntünün hangi pikselinin metni oluşturduğunu ve hangilerinin sadece “boş” alan olduğunu bilmek mümkündür. OpenGL kullanarak temel metin oluşturmanın bazı daha basit yolları vardır, ancak OpenGL ile 3B oluşturmanın bazı önemli ayrıntılarını ele almak için bu yaklaşımı kullanıyoruz.
Bu görüntüyü oluşturmak için, QImage::Format_Mono bayrağıyla bir QImage nesnesini somutlaştırıyoruz. Tek bilmemiz gereken hangi piksellerin metnin bir parçası olduğu ve hangilerinin olmadığı olduğundan, tek renkli bir görüntü gayet iyi çalışmalıdır. Kullanıcı bir metin girdiğinde, bu QImage nesnesini eşzamanlı olarak güncelleriz. Yazı tipi boyutuna ve görüntü genişliğine bağlı olarak, metni kullanıcı tanımlı yüksekliğe sığdırmak için elimizden gelenin en iyisini yapıyoruz.
Ardından, metnin parçası olan tüm pikselleri, bu durumda siyah pikselleri sıralarız. Buradaki her piksel, ayrı kare-ish birimleri olarak kabul edilir. Buna dayanarak, köşelerinin koordinatlarını hesaplayarak bir üçgen listesi oluşturabilir ve bunları 3B model dosyamızda saklayabiliriz.
Artık kendi basit 3B model dosya formatımız olduğuna göre, onu oluşturmaya odaklanmaya başlayabiliriz. OpenGL tabanlı 3B işleme için Qt, QOpenGLWidget adlı bir parçacık sağlar. Bu widget'ı kullanmak için üç işlev geçersiz kılınabilir:
- initializeGl() - başlatma kodunun gittiği yer burasıdır
- paintGl() - bu yöntem, pencere öğesi her yeniden çizildiğinde çağrılır
- resizeGl(int w, int h) - bu yöntem, her yeniden boyutlandırıldığında parçacığın genişliği ve yüksekliği ile çağrılır
BaşlatmaGl yönteminde uygun gölgelendirici yapılandırmasını ayarlayarak widget'ı başlatacağız.
glEnable(GL_DEPTH_TEST); glShadeModel(GL_FLAT); glDisable(GL_CULL_FACE);
İlk satır, programın, diğer piksellerin arkasında ve görüş dışında olanlardan ziyade, yalnızca bize daha yakın olan işlenmiş pikselleri göstermesini sağlar. İkinci satır, düz gölgeleme tekniğini belirtir. Üçüncü satır, programın normallerinin hangi yönü gösterdiğine bakılmaksızın üçgenler oluşturmasını sağlar.
Başlatıldıktan sonra, paintGl her çağrıldığında modeli ekranda gösteririz. paintGl yöntemini geçersiz kılmadan önce tamponu hazırlamalıyız. Bunu yapmak için önce bir arabellek tutamacı oluşturuyoruz. Ardından tanıtıcıyı bağlama noktalarından birine bağlarız, kaynak verileri arabelleğe kopyalarız ve son olarak programa arabelleği çözmesini söyleriz:
// Get the Qt object which allows to operate with buffers QOpenGLFunctions funcs(QOpenGLContext::currentContext()); // Create the buffer handle funcs.glGenBuffers(1, &handle); // Select buffer by its handle (so we'll use this buffer // further) funcs.glBindBuffer(GL_ARRAY_BUFFER, handle); // Copy data into the buffer. Being copied, // source data is not used any more and can be released funcs.glBufferData(GL_ARRAY_BUFFER, size_in_bytes, src_data, GL_STATIC_DRAW); // Tell the program we've finished with the handle funcs.glBindBuffer(GL_ARRAY_BUFFER, 0);
Geçersiz kılan paintGl yönteminde, her kare için üçgenler çizmek için bir dizi köşe noktası ve bir dizi normal veri kullanırız:

QOpenGLFunctions funcs(QOpenGLContext::currentContext()); // Vertex data glEnableClientState(GL_VERTEX_ARRAY);// Work with VERTEX buffer funcs.glBindBuffer(GL_ARRAY_BUFFER, m_hVertexes); // Use this one glVertexPointer(3, GL_FLOAT, 0, 0); // Data format funcs.glVertexAttribPointer(m_coordVertex, 3, GL_FLOAT, GL_FALSE, 0, 0); // Provide into shader program // Normal data glEnableClientState(GL_NORMAL_ARRAY);// Work with NORMAL buffer funcs.glBindBuffer(GL_ARRAY_BUFFER, m_hNormals);// Use this one glNormalPointer(GL_FLOAT, 0, 0); // Data format funcs.glEnableVertexAttribArray(m_coordNormal); // Shader attribute funcs.glVertexAttribPointer(m_coordNormal, 3, GL_FLOAT, GL_FALSE, 0, 0); // Provide into shader program // Draw frame glDrawArrays(GL_TRIANGLES, 0, (3 * m_model.GetTriangleCount())); // Rendering finished, buffers are not in use now funcs.glDisableVertexAttribArray(m_coordNormal); funcs.glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY);
Geliştirilmiş performans için prototip uygulamamızda Vertex Buffer Object (VBO) kullandık. Bu, verileri video belleğinde saklamamıza ve doğrudan işleme için kullanmamıza olanak tanır. Buna alternatif bir yöntem, işleme kodundan verileri (köşe koordinatları, normaller ve renkler) sağlamayı içerir:
glBegin(GL_TRIANGLES); // Provide coordinates of triangle #1 glVertex3f( x[0], y[0], z[0]); glVertex3f( x[1], y[1], z[1]); glVertex3f( x[2], y[2], z[2]); // Provide coordinates of other triangles ... glEnd();
Bu daha basit bir çözüm gibi görünebilir; bununla birlikte, verilerin video belleği veri yolundan geçmesini gerektirdiğinden ciddi performans etkileri vardır - nispeten daha yavaş bir işlem. paintGl yöntemini uyguladıktan sonra gölgelendiricilere dikkat etmeliyiz:
m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, QString::fromUtf8( "#version 400\r\n" "\r\n" "layout (location = 0) in vec3 coordVertexes;\r\n" "layout (location = 1) in vec3 coordNormals;\r\n" "flat out float lightIntensity;\r\n" "\r\n" "uniform mat4 matrixVertex;\r\n" "uniform mat4 matrixNormal;\r\n" "\r\n" "void main()\r\n" "{\r\n" " gl_Position = matrixVertex * vec4(coordVertexes, 1.0);\r\n" " lightIntensity = abs((matrixNormal * vec4(coordNormals, 1.0)).z);\r\n" "}")); m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, QString::fromUtf8( "#version 400\r\n" "\r\n" "flat in float lightIntensity;\r\n" "\r\n" "layout (location = 0) out vec4 FragColor;\r\n" "uniform vec3 fragmentColor;\r\n" "\r\n" "void main()\r\n" "{\r\n" " FragColor = vec4(fragmentColor * lightIntensity, 1.0);\r\n" "}")); m_shaderProgram.link(); m_shaderProgram.bind(); m_coordVertex = m_shaderProgram.attributeLocation(QString::fromUtf8("coordVertexes")); m_coordNormal = m_shaderProgram.attributeLocation(QString::fromUtf8("coordNormals")); m_matrixVertex = m_shaderProgram.uniformLocation(QString::fromUtf8("matrixVertex")); m_matrixNormal = m_shaderProgram.uniformLocation(QString::fromUtf8("matrixNormal")); m_colorFragment = m_shaderProgram.uniformLocation(QString::fromUtf8("fragmentColor"));
OpenGL ile gölgelendiriciler, GLSL olarak bilinen bir dil kullanılarak uygulanır. Dil, 3B verileri işlenmeden önce değiştirmeyi kolaylaştırmak için tasarlanmıştır. Burada iki gölgelendiriciye ihtiyacımız olacak: köşe gölgelendirici ve parça gölgelendirici. Köşe gölgelendiricide, döndürme ve yakınlaştırma uygulamak ve rengi hesaplamak için koordinatları dönüşüm matrisiyle dönüştüreceğiz. Parça gölgelendiricide parçaya renk atayacağız. Bu gölgelendirici programları daha sonra derlenmeli ve bağlamla ilişkilendirilmelidir. OpenGL, programın içindeki parametrelere dışarıdan erişilebilmesi veya atanabilmesi için iki ortam arasında köprü kurmanın basit yollarını sağlar:
// Get model transformation matrix QMatrix4x4 matrixVertex; ... // Calculate the matrix here // Set Shader Program object' parameters m_shaderProgram.setUniformValue(m_matrixVertex, matrixVertex);
Köşe gölgelendirici kodunda, dönüşüm matrisini orijinal köşelere uygulayarak yeni köşe konumunu hesaplıyoruz:
gl_Position = matrixVertex * vec4(coordVertexes, 1.0);
Bu dönüşüm matrisini hesaplamak için birkaç ayrı matris hesaplıyoruz: ekran ölçeği, sahneyi çevir, ölçeklendir, döndür ve merkezle. Daha sonra, son dönüşüm matrisini hesaplamak için bu matrislerin çarpımını buluruz. Model merkezini, ekranın merkezi olan orijine (0, 0, 0) çevirerek başlayın. Döndürme, kullanıcının bir işaretleme aygıtı kullanarak sahneyle etkileşimi tarafından belirlenir. Kullanıcı sahneyi tıklayıp döndürmek için sürükleyebilir. Kullanıcı tıkladığında imleç konumunu kaydederiz ve bir hareketten sonra ikinci imleç konumuna sahibiz. Sahne merkeziyle birlikte bu iki koordinatı kullanarak bir üçgen oluşturuyoruz. Bazı basit hesaplamaları takiben dönüş açısını belirleyebiliriz ve bu değişikliği yansıtmak için dönüş matrisimizi güncelleyebiliriz. Ölçeklendirme için, OpenGL parçacığının X ve Y eksenlerinin ölçekleme faktörünü değiştirmek için fare tekerleğine güveniyoruz. Model, sahnenin oluşturulduğu düzlemin arkasında kalması için 0,5 oranında geri çevrilir. Son olarak, doğal en boy oranını korumak için, uzun kenar boyunca model genişlemesinin azalmasını ayarlamamız gerekir (OpenGL sahnesinden farklı olarak, oluşturulduğu pencere öğesi her iki eksende de farklı fiziksel boyutlara sahip olabilir). Tüm bunları birleştirerek, son dönüşüm matrisini aşağıdaki gibi hesaplıyoruz:
void GlWidget::GetMatrixTransform(QMatrix4x4& matrixVertex, const Model3DEx& model) { matrixVertex.setToIdentity(); QMatrix4x4 matrixScaleScreen; double dimMin = static_cast<double>(qMin(width(), height())); float scaleScreenVert = static_cast<float>(dimMin / static_cast<double>(height())); float scaleScreenHorz = static_cast<float>(dimMin / static_cast<double>(width())); matrixScaleScreen.scale(scaleScreenHorz, scaleScreenVert, 1.0f); QMatrix4x4 matrixCenter; float centerX, centerY, centerZ; model.GetCenter(centerX, centerY, centerZ); matrixCenter.translate(-centerX, -centerY, -centerZ); QMatrix4x4 matrixScale; float radius = 1.0; model.GetRadius(radius); float scale = static_cast<float>(m_scaleCoeff / radius); matrixScale.scale(scale, scale, 0.5f / radius); QMatrix4x4 matrixTranslateScene; matrixTranslateScene.translate(0.0f, 0.0f, -0.5f); matrixVertex = matrixScaleScreen * matrixTranslateScene * matrixScale * m_matrixRotate * matrixCenter; }
Çözüm
OpenGL 3D işlemeye yönelik bu girişte, ud'un bir 3D modeli oluşturmak için video kartımızı kullanmasına izin veren teknolojilerden birini araştırdık. Bu, aynı amaç için CPU döngülerini kullanmaktan çok daha verimlidir. Çok basit bir gölgeleme tekniği kullandık ve fareden gelen kullanıcı girdilerinin işlenmesiyle sahneyi etkileşimli hale getirdik. Verileri video belleği ve program arasında ileri geri iletmek için video belleği veri yolunu kullanmaktan kaçındık. Tek bir metin satırını 3B olarak oluşturmamıza rağmen, daha karmaşık sahneler çok benzer şekillerde oluşturulabilir.
Adil olmak gerekirse, bu eğitim 3D modelleme ve işlemenin yüzeyini zar zor çizdi. Bu çok geniş bir konudur ve bu OpenGL öğreticisi, 3B oyunlar veya modelleme yazılımları oluşturabilmek için bilmeniz gereken tek şeyin bu olduğunu iddia edemez. Ancak, bu makalenin amacı size bu alana bir göz atmak ve 3B uygulamalar oluşturmak için OpenGL'yi ne kadar kolay kullanmaya başlayabileceğinizi göstermektir.