وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

OpenGL پیشرفته در پایتون با PyGame و PyOpenGL

0 19
زمان لازم برای مطالعه: 8 دقیقه


معرفی

به دنبال مقاله قبلی، درک OpenGL از طریق پایتون، جایی که ما پایه و اساس یادگیری بیشتر را تنظیم کرده ایم، می توانیم به آن بپردازیم. OpenGL استفاده کردن PyGame و PyOpenGL.

PyOpenGL کتابخانه استاندارد شده ای است که به عنوان پلی بین پایتون و APIهای OpenGL استفاده می شود و PyGame یک کتابخانه استاندارد شده است که برای ساخت بازی در پایتون استفاده می شود. این کتابخانه‌های گرافیکی و صوتی داخلی را ارائه می‌دهد و ما از آن برای ارائه آسان‌تر نتیجه در پایان مقاله استفاده خواهیم کرد.

همانطور که در مقاله قبلی ذکر شد، OpenGL بسیار قدیمی است، بنابراین آموزش های زیادی را به صورت آنلاین پیدا نخواهید کرد روی چگونه می توان از آن به درستی استفاده کرد و آن را درک کرد، زیرا همه سگ های برتر در حال حاضر در فن آوری های جدید تا زانو عمیق هستند.

در این مقاله به چند موضوع اساسی که باید بدانید می پردازیم:

راه اندازی یک پروژه با استفاده از PyGame

ابتدا باید PyGame و PyOpenGL را نصب کنیم اگر قبلاً نصب نکرده اید:

$ python3 -m pip install -U pygame --user
$ python3 -m pip install PyOpenGL PyOpenGL_accelerate

توجه داشته باشید: می توانید نصب دقیق تری را در مقاله قبلی OpenGL بیابید.

اگر در نصب مشکل دارید، PyGame “شروع شدن” بخش ممکن است مکان خوبی برای بازدید باشد.

از آنجایی که خالی کردن 3 کتاب با ارزش تئوری گرافیک فایده ای ندارد روی شما، ما از کتابخانه PyGame برای شروع کار استفاده خواهیم کرد. این اساسا فقط کوتاه خواهد شد process از مقداردهی اولیه پروژه گرفته تا مدل سازی و متحرک سازی واقعی.

برای شروع، ما نیاز داریم import همه چیز لازم از OpenGL و PyGame:

import pygame as pg
from pygame.locals import *

from OpenGL.GL import *
from OpenGL.GLU import *

بعد، به مقدار اولیه می رسیم:

pg.init()
windowSize = (1920,1080)
pg.display.set_mode(display, DOUBLEBUF|OPENGL)

در حالی که مقداردهی اولیه تنها سه خط کد است، هر کدام حداقل مستحق یک توضیح ساده هستند:

  • pg.init(): راه اندازی همه ماژول های PyGame – این تابع یک موهبت الهی است
  • windowSize = (1920, 1080): تعیین اندازه پنجره ثابت
  • pg.display.set_mode(display, DOUBLEBUF|OPENGL): در اینجا، ما مشخص می کنیم که از OpenGL با استفاده می کنیم بافر دوگانه

بافر دوگانه به این معنی است که در هر زمان دو تصویر وجود دارد – یکی که می‌توانیم ببینیم و دیگری که می‌توانیم آن‌طور که مناسب می‌دانیم تغییر شکل دهیم. ما می‌توانیم تغییرات واقعی ناشی از تبدیل‌ها را زمانی که دو بافر ایجاد می‌کنند، ببینیم swap.

از آنجایی که پورت نمایش خود را تنظیم کرده‌ایم، در مرحله بعد باید مشخص کنیم که چه چیزی را می‌بینیم، یا بهتر است بگوییم که «دوربین» در کجا قرار می‌گیرد و چقدر دور و عرض می‌تواند ببیند.

این به عنوان شناخته شده است سرخوردگی – که فقط یک هرم قطع شده است که به صورت بصری دید دوربین را نشان می دهد (آنچه که می تواند و نمی تواند ببیند).

آ سرخوردگی با 4 پارامتر کلیدی تعریف می شود:

  1. FOV (میدان دید): زاویه بر حسب درجه
  2. نسبت ابعاد: به عنوان نسبت عرض و ارتفاع تعریف می شود
  3. مختصات z صفحه برش نزدیک: حداقل فاصله قرعه کشی
  4. مختصات z صفحه برش دور: حداکثر فاصله قرعه کشی

بنابراین، بیایید جلوتر برویم و دوربین را با در نظر گرفتن این پارامترها، با استفاده از کد OpenGL C پیاده سازی کنیم:

void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
gluPerspective(60, (display(0)/display(1)), 0.1, 100.0)

برای درک بهتر روش عملکرد یک frustum، در اینجا یک تصویر مرجع آورده شده است:

نمای فرستوم

برای عملکرد بهتر از هواپیماهای دور و نزدیک استفاده می شود. به طور واقع بینانه، رندر کردن هر چیزی خارج از میدان دید ما، هدر دادن عملکرد سخت افزاری است که می تواند برای رندر کردن چیزی که واقعاً می توانیم ببینیم استفاده شود.

بنابراین هر چیزی که بازیکن نمی تواند ببیند به طور ضمنی در حافظه ذخیره می شود، حتی اگر به صورت بصری وجود نداشته باشد. اینجا یک ویدئوی عالی از اینکه چگونه رندرینگ فقط در درون frustum به نظر می رسد.

طراحی اشیاء

پس از این تنظیم، تصور می کنم ما از خودمان همین سوال را می پرسیم:

خوب این همه چیز خوب و شیک است، اما چگونه می توانم یک Super Star Destroyer بسازم؟

خوب… با نقطه. هر مدل در یک شی OpenGL به عنوان مجموعه ای از رئوس و مجموعه ای از روابط آنها (که رئوس به هم متصل هستند) ذخیره می شود. بنابراین از نظر تئوری اگر موقعیت تک نقطه‌ای را که برای ترسیم یک ناوشکن سوپر استار استفاده می‌شود می‌دانستید، می‌توانید به خوبی یکی را ترسیم کنید!

چند راه وجود دارد که بتوانیم اشیاء را در OpenGL مدل سازی کنیم:

  1. ترسیم با استفاده از رئوس، و بسته به روی چگونه OpenGL این رئوس را تفسیر می‌کند، می‌توانیم با آن ترسیم کنیم:
    • نکته ها: مانند نقاط لفظی که به هیچ وجه به هم متصل نیستند
    • خطوط: هر جفت رئوس یک خط متصل می سازد
    • مثلثها: هر سه رأس یک مثلث می سازند
    • چهار ضلعی: هر چهار رأس یک چهار ضلعی می سازند
    • چند ضلعی: متوجه موضوع شدی
    • خیلی بیشتر…
  2. طراحی با استفاده از اشکال و اشیاء ساخته شده که به سختی توسط همکاران OpenGL مدل‌سازی شده‌اند.
  3. وارد کردن اشیاء کاملا مدل شده

بنابراین، برای رسم یک مکعب، ابتدا باید رئوس آن را تعریف کنیم:

cubeVertices = ((1,1,1),(1,1,-1),(1,-1,-1),(1,-1,1),(-1,1,1),(-1,-1,-1),(-1,-1,1),(-1, 1,-1))

کشیدن یک مکعب

سپس، ما باید روش اتصال همه آنها را تعریف کنیم. اگر بخواهیم یک مکعب سیم بسازیم، باید لبه های مکعب را مشخص کنیم:

cubeEdges = ((0,1),(0,3),(0,4),(1,2),(1,7),(2,5),(2,3),(3,6),(4,6),(4,7),(5,6),(5,7))

این بسیار شهودی است – نکته 0 دارای یک لبه با 1، 3، و 4. نکته 1 دارای یک لبه با امتیاز است 3، 5، و 7، و غیره روی.

و اگر بخواهیم یک مکعب جامد بسازیم، باید چهار ضلعی مکعب را تعریف کنیم:

cubeQuads = ((0,3,6,4),(2,5,6,3),(1,2,5,7),(1,0,4,7),(7,4,6,5),(2,3,0,1))

این نیز بصری است – ساختن یک چهار ضلعی روی در سمت بالای مکعب، ما می خواهیم همه چیز را در بین نقاط “رنگ” کنیم 0، 3، 6، و 4.

به خاطر داشته باشید که یک دلیل واقعی وجود دارد که ما رئوس را به عنوان شاخص های آرایه ای که در آن تعریف شده اند برچسب گذاری می کنیم. این نوشتن کدی را که آنها را به هم متصل می کند بسیار آسان می کند.

تابع زیر برای رسم مکعب سیمی استفاده می شود:

def wireCube():
    glBegin(GL_LINES)
    for cubeEdge in cubeEdges:
        for cubeVertex in cubeEdge:
            glVertex3fv(cubeVertices(cubeVertex))
    glEnd()

glBegin() تابعی است که نشان می دهد ما رئوس یک اولیه را در کد زیر تعریف خواهیم کرد. وقتی تعریف اولیه را تمام کردیم، از تابع استفاده می کنیم glEnd().

GL_LINES یک ماکرو است که نشان می دهد ما خطوطی را ترسیم خواهیم کرد.

glVertex3fv() تابعی است که یک راس را در فضا تعریف می کند، چند نسخه از این تابع وجود دارد، بنابراین برای وضوح، اجازه دهید روش ساخت نام ها را بررسی کنیم:

  • glVertex: تابعی که یک راس را تعریف می کند
  • glVertex3: تابعی که یک راس را با استفاده از 3 مختصات تعریف می کند
  • glVertex3f: تابعی که یک راس را با استفاده از 3 مختصات نوع تعریف می کند GLfloat
  • glVertex3fv: تابعی که یک راس را با استفاده از 3 مختصات نوع تعریف می کند GLfloat که در داخل یک بردار (تعدادی) قرار می گیرند (جایگزین آن خواهد بود glVertex3fl که از لیستی از آرگومان ها به جای بردار استفاده می کند)

طبق منطق مشابه، تابع زیر برای رسم یک مکعب جامد استفاده می شود:

def solidCube():
    glBegin(GL_QUADS)
    for cubeQuad in cubeQuads:
        for cubeVertex in cubeQuad:
            glVertex3fv(cubeVertices(cubeVertex))
    glEnd()

انیمیشن تکراری

برای اینکه برنامه ما باشد “قاتل” باید قطعه کد زیر را وارد کنیم:

for event in pg.event.get():
    if event.type == pg.QUIT:
        pg.quit()
        quit()

اساساً فقط یک شنونده است که در رویدادهای PyGame پیمایش می کند و اگر تشخیص دهد که روی دکمه “کشتن پنجره” کلیک کرده ایم، از برنامه خارج می شود.

ما در مقاله‌ای آینده بیشتر رویدادهای PyGame را پوشش خواهیم داد – این مورد فوراً معرفی شد زیرا برای کاربران و خود شما بسیار ناراحت کننده است که هر بار که می‌خواهند برنامه را ترک کنند، مدیر وظیفه را فعال کنید.

در این مثال، ما از آن استفاده خواهیم کرد بافر دوگانه، که فقط به این معنی است که ما از دو بافر استفاده خواهیم کرد (شما می توانید آنها را به عنوان بوم نقاشی برای طراحی در نظر بگیرید) که swap در فواصل ثابت و ایجاد توهم حرکت.

با دانستن این موضوع، کد ما باید الگوی زیر را داشته باشد:

handleEvents()
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
doTransformationsAndDrawing()
pg.display.flip()
pg.time.wait(1)
  • glClear: تابعی که بافرهای مشخص شده (بوم ها) را پاک می کند، در این مورد، بافر رنگ (که حاوی اطلاعات رنگی برای ترسیم اشیاء تولید شده است) و بافر عمق (بافری که روابط جلو یا پشت همه اشیاء تولید شده را ذخیره می کند).
  • pg.display.flip(): تابعی که پنجره را با محتویات بافر فعال به روز می کند
  • pg.time.wait(1): عملکردی که برنامه را برای مدتی متوقف می کند

glClear باید استفاده شود، زیرا اگر از آن استفاده نکنیم، فقط روی بوم نقاشی شده نقاشی می کنیم، که در این مورد، صفحه نمایش ما است و در نهایت به یک آشفتگی می رسیم.

بعد اگر بخواهیم به طور مداوم صفحه ما را به روز کنید، درست مانند یک انیمیشن، ما باید تمام کدهای خود را داخل a قرار دهیم while حلقه ای که در آن ما:

  1. مدیریت رویدادها (در این مورد، فقط ترک کردن)
  2. بافرهای رنگ و عمق را پاک کنید تا بتوان آنها را ترسیم کرد روی از نو
  3. تبدیل و ترسیم اشیاء
  4. صفحه را به روز کنید
  5. GOTO 1.

کد باید چیزی شبیه به این باشد:

while True:
    handleEvents()
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    doTransformationsAndDrawing()
    pg.display.flip()
    pg.time.wait(1)

استفاده از ماتریس های تبدیل

در مقاله قبلی توضیح دادیم که چگونه از نظر تئوری باید تبدیلی بسازیم که نقطه ارجاع داشته باشد.

OpenGL به همین ترتیب کار می کند، همانطور که در کد زیر مشاهده می شود:

glTranslatef(1,1,1)
glRotatef(30,0,0,1)
glTranslatef(-1,-1,-1)

در این مثال، ما یک محور z چرخش در xy-plane با مرکز چرخش بودن (1,1,1) با 30 درجه

اگر این اصطلاحات کمی گیج کننده به نظر می رسند، اجازه دهید کمی تجدید نظر کنیم:

  1. محور z چرخش به این معنی است که ما حول محور z در حال چرخش هستیم

    این فقط به این معنی است که ما یک هواپیمای دوبعدی را با فضای سه بعدی تقریب می کنیم، کل این تبدیل اساساً مانند انجام یک چرخش معمولی حول یک نقطه ارجاع در فضای دو بعدی است.

  2. ما دریافت می کنیم xy-plane با له کردن یک فضای سه بعدی کامل در یک هواپیما که دارای z=0 (ما پارامتر z را از هر نظر حذف می کنیم)
  3. مرکز چرخش یک راس است که یک شی معین را به دور آن می‌چرخانیم (مرکز چرخش پیش‌فرض، راس مبدا است. (0,0,0))

اما یک نکته وجود دارد – OpenGL کد بالا را با به خاطر سپردن و اصلاح مداوم درک می کند یک ماتریس تحول جهانی.

بنابراین وقتی چیزی را در OpenGL می نویسید، چیزی که می گویید این است:



glTranslatef(1,1,1)


همانطور که ممکن است تصور کنید، این یک مشکل بزرگ است، زیرا گاهی اوقات ما می خواهیم از یک تحول استفاده کنیم روی یک شی واحد، نه روی کل کد منبع این یک دلیل بسیار رایج برای اشکالات در OpenGL سطح پایین است.

برای مبارزه با این ویژگی مشکل ساز OpenGL، به ما معرفی شده است هل دادن و ظاهر شدن ماتریس های تبدیل – glPushMatrix() و glPopMatrix():


glPushMatrix()
glTranslatef(1,0,0)
generateObject() 
glPopMatrix()
generateSecondObject() 

اینها به صورت ساده کار می کنند Last-in-First-Out اصل (LIFO) هنگامی که می خواهیم ترجمه ای را به یک ماتریس انجام دهیم، ابتدا آن را کپی می کنیم و سپس فشار دادن آی تی روی بالای پشته ماتریس های تبدیل.

به عبارت دیگر، آن است جدا می کند تمام تبدیل‌هایی که در این بلوک انجام می‌دهیم با ایجاد یک ماتریس محلی که می‌توانیم پس از اتمام آن را حذف کنیم.

هنگامی که شی ترجمه شد، ما ترکیدن ماتریس تبدیل از پشته، باقی ماتریس ها را دست نخورده باقی می گذارد.

اجرای تبدیل چندگانه

در OpenGL، همانطور که قبلا ذکر شد، تبدیل ها به ماتریس تبدیل فعال اضافه می شوند روی بالای پشته ماتریس های تبدیل.

این بدان معنی است که تبدیل ها به ترتیب معکوس اجرا می شوند. مثلا:


glTranslatef(-1,0,0)
glRotatef(30,0,0,1)
drawObject1()



glRotatef(30,0,0,1)
glTranslatef(-1,0,0)
drawObject2()

در این مثال ابتدا Object1 می چرخد، سپس ترجمه می شود و Object2 ابتدا ترجمه می شود و سپس می چرخد. دو مفهوم آخر در مثال پیاده سازی مورد استفاده قرار نخواهند گرفت، اما به طور عملی در مقاله بعدی این مجموعه استفاده خواهند شد.

مثال پیاده سازی

کد زیر یک مکعب جامد رسم می کند روی صفحه نمایش را به طور مداوم 1 درجه به دور صفحه می چرخاند (1,1,1) بردار و می توان آن را به راحتی برای کشیدن یک مکعب سیم با تعویض آن تغییر داد cubeQuads با cubeEdges:

import pygame as pg
from pygame.locals import *

from OpenGL.GL import *
from OpenGL.GLU import *

cubeVertices = ((1,1,1),(1,1,-1),(1,-1,-1),(1,-1,1),(-1,1,1),(-1,-1,-1),(-1,-1,1),(-1,1,-1))
cubeEdges = ((0,1),(0,3),(0,4),(1,2),(1,7),(2,5),(2,3),(3,6),(4,6),(4,7),(5,6),(5,7))
cubeQuads = ((0,3,6,4),(2,5,6,3),(1,2,5,7),(1,0,4,7),(7,4,6,5),(2,3,0,1))

def wireCube():
    glBegin(GL_LINES)
    for cubeEdge in cubeEdges:
        for cubeVertex in cubeEdge:
            glVertex3fv(cubeVertices(cubeVertex))
    glEnd()

def solidCube():
    glBegin(GL_QUADS)
    for cubeQuad in cubeQuads:
        for cubeVertex in cubeQuad:
            glVertex3fv(cubeVertices(cubeVertex))
    glEnd()

def main():
    pg.init()
    display = (1680, 1050)
    pg.display.set_mode(display, DOUBLEBUF|OPENGL)

    gluPerspective(45, (display(0)/display(1)), 0.1, 50.0)

    glTranslatef(0.0, 0.0, -5)

    while True:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                pg.quit()
                quit()

        glRotatef(1, 1, 1, 1)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        solidCube()
        
        pg.display.flip()
        pg.time.wait(10)

if __name__ == "__main__":
    main()

با اجرای این قطعه کد، یک پنجره PyGame ظاهر می شود که انیمیشن مکعب را ارائه می دهد:

انیمیشن مکعب pygame

نتیجه

وجود دارد مقدار زیادی برای کسب اطلاعات بیشتر در مورد OpenGL – نورپردازی، بافت ها، مدل سازی سطح پیشرفته، انیمیشن مدولار ترکیبی، و موارد دیگر.

اما نگران نباشید، همه اینها در مقالات بعدی آموزش عمومی درباره OpenGL از ابتدا توضیح داده خواهد شد.

و نگران نباشید، در مقاله بعدی، ما در واقع چیزی نیمه مناسب را ترسیم خواهیم کرد.

(برچسب‌ها به ترجمه)# python



منتشر شده در 1403-01-19 15:52:04

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید