Super Kawaii Cute Cat Kaoani 컴퓨터 그래픽스 수업정리) 2_Introduction to NumPy/OpenGL

수업정리/컴퓨터 그래픽스

컴퓨터 그래픽스 수업정리) 2_Introduction to NumPy/OpenGL

치킨고양이짱아 2023. 1. 15. 16:46
728x90
728x90

What is NumPy?

  • scientific computing을 위한 파이썬 모듈
  • C로 되어 있음
  • vector, matrix 계산을 빠르게 할 수 있음
  • 파이썬에 내장되진 않았지만 수치 컴퓨팅을 할때 사실상 표준으로 사용됨(de-facto standard)
  • 벡터와 매트릭스로 이루어진 그래픽스 컴퓨터 그래픽스 어플레이케이션에 굉장히 유용하게 쓰임

NumPy usage

>>> import numpy as np
//모듈 numpy를 np라는 local 이름으로 사용할 수 있게 한다는 뜻

Introducing NumPy Arrays

넘파이에서 어레이는 굉장히 기본적인거!


외워야할 것 체크

  • 넘파이 array 만들 때 constructor는 np.array([~], dtype='~') (dtype은 안넣어줘도 된다.)
  • 새로 어레이가 만들어지고 그게 어디 대입되는게 아니라면 array 출력해줌

    타입이 'float32'일 경우 출력되면서 타입도 명시해줌. 근데 타입이 ''로 묶여있진 않으

    array([[1., 1., 1.], [1., 1., 1.]], dtype = float32) 이런식으로

  • a.dtype → element의 타입. ndtype('int32') 이런식으로 출력된다.
  • a.nbytes → array 총 몇 바이트인지
  • a.ndim → 몇차원인지 (행렬이면 2차원 그냥 어레이면 1차원)
  • a.shape → (row, column) 1차원 어레이일 경우 shape은 (길이)
  • type(a) → 이거 하면 numpy.ndarray
  • 어레이 인덱싱할때 다차원이라도 인덱스 값 [] 한개 안에 다 넣어야함

    ex) a[2::2, ::2]

  • a = np.identity(n) → 이거 하면 n차 identity array를 만들 수 있음. 명시하지 않으면 타입은 float

    타입을 ''으로 안묶어줌...

  • np.ones(shape, dtype = 'flaot64') : 모든 원소가 1인 넘파이 어레이 만들어주는거 np.zeros(shape, dtype = 'float64') (타입 디폴트는 float64)
  • np.linspace(start, end, N) start와 end를 포함하면서 N개로 균등하게 나누어진 어레이를 만들어줌
  • np.arange(start, stop, step, dtype=None)

    start 포함. stop 포함 X 범위에서 step 단위로 나누어진 어레이 만들어줌

  • a.T → a 어레이의 transpose 만들어준다
  • a.reshape(m, n) → 이렇게 하면 a 어레이를 m*n 모양으로 바꾼 새로운 어레이 만들어줌 (자동으로 출력)

  1. 일차원 어레이 생성
    >>> a = np.array([0, 1, 2, 3]);
    >>> a
    array([0, 1, 2, 3])
    
    // 넘파이 어레이 constructor의 파라미터로 리스트를 넣어주는거
  1. 다차원 어레이
    >>> a = np.array([0, 1, 2, 3], [10, 11, 12, 13]])
    >>> a
    array([0, 1, 2, 3], [10, 11, 12, 13])

    (어레이 모양이 이렇다고 생각하면 된다.)

  1. Type 체크
    >>> type(a)
    numpy.ndarray
    //넘파이 n차원 어레이 타입이라는 의미
    //출력할때는 array로 나오면서 타입은 numpy.ndarray래
  1. Element의 Numeric Type 체크
    >>> a.dtype
    dtype('int32')
    //출력되는 형태가 굉장히 특이하다 dtype('int32') 
    //함수 안에 적힌것처럼 나온다. 
  1. 몇차원인지
    >>> a.ndim
    1
    //1차원 어레이일 경우
    
    >>> a.ndim
    2
    //2차원 어레이일 경우(행과 열이 있으면 2차원)
  1. 간단한 어레이 연산
    >>> a = np.array([1, 2, 3, 4])
    >>> b = np.array([2, 3, 4, 5])
    >>> a + b
    array([3, 5, 7, 9])
    
    >>> a * b 
    array([2, 6, 12, 20])
    //행렬처럼 곱해져서 나온다.
    
    >>> a ** b
    array([1, 8, 81, 1024])
    //a 원소의 b 원소 승
    
    >>> 0.1 * a
    array([0.1, 0.2, 0.3, 0.4])
    //아예 새로운 어레이가 만들어지니까 이렇게 바로 출력되는데
    //밑에 두개는 기존 a가 변화하는거니까 자동으로 출력안되고 a라고 쳐야 출력된다.
    
    >>> a += 1
    >>> a
    array([2, 3, 4, 5])
    
    >>> a *= 2
    >>> a
    array([2, 4, 6, 8])
    //apply functions to array
    >>> x = 0.1 * a
    >>> x
    array([0.2, 0.4, 0.6, 0.8])
    
    >>> y = np.sin(x)
    >>> y
    array([0.19866933, 0.38941834, ~~~~])
    
    //위에 2개 처럼 새로운 어레이를 기존 어레이를 사용해서 만들 수 있다. 
    //np.sin(np.narray 타입 변수)
  1. 어레이 인덱싱

    1) 어레이가 1차원일 때

    a는 array([2, 4, 6, 8])

    >>> a[0]
    2
    //이렇게 접근하는 것이 가능하고
    
    >>> a[0] = 10
    >>> a
    array([10, 4, 6, 8])   //접근해서 값을 변경하는 것도 가능하다.
    
    >>> a[0] = 10.6. //이렇게 하더라도 10이 들어간다.

    😀 파이썬은 여러 타입의 변수가 한 어레이에 들어갈 수 있지만 (element들의 사이즈와 type이 다양할 수 있다.)

    넘파이는 동일한 타입의 변수만 어레이에 들어갈 수 있다. (모든 element가 동일한 타입과 사이즈를 가진다.)

    2) 어레이가 2차원일 때

    a는 array([[0, 1, 2, 3], [10, 11, 12, 13]])

    >>> a[1, 3]
    13
    
    >>> a[1, 3] = -1
    >>> a
    array([[0, 1, 2, 3], [10, 11, 12, -1]])
  1. 어레이 shape = (row, columns)
    >>> a. shpae
    (2, 4)
    //a는 array([[0, 1, 2, 3], [10, 11, 12, 13]])
  1. element 개수
    >>> a.size
    8
    //a는 array([[0, 1, 2, 3], [10, 11, 12, 13]])
  1. Slicing
    • Python list와 동일하게 진행한다.
    • var[lower:upper:step]

      : lower 포함, upper 포함안함

      함수같은 느낌엔데 ()가 아니라 []임!

    • 파이썬과 마찬가지로 negative index를 제공한다.

    1) 어레이가 1차원일 때

    >>> a = np.array([10, 11, 12, 13, 14])
    
    >>> a[1:3]
    array([11, 12])
    //a 자체를 변경한게 아니라 새로운 어레이를 만든거니까 바로 출력
    
    >>> a[1:-2]
    array([11, 12])
    // 14가 인덱스 -1 13가 인덱스 -2인데 -2는 포함안하니까 그 앞인 12까지
    
    >>> a[-4: 3]
    array([11, 12])
    
    >>> a[:3]
    array([10, 11, 12])
    //첫번째 원소 생략되어 있으면 0부터 시작하는걸로 생각하면 된다.
    
    >>> a[-2:]
    array([13, 14])
    //두번째 원소 생략되어 있으면 끝까지 포함되는걸로 생각하면 된다.
    
    >>> a[::2]
    array[10, 12, 14]
    //하나 건너 하나씩 들고온다는 뜻
    //인덱스 0 원소 무조건 포함
    
    >>> a[:]
    array[10, 11, 12, 13, 14]
    //전체 다 들고온다는 뜻

    2) 어레이가 다차원일 때

    >>> a = np.array([[i+10*j for i in range(6)] for j in range(6)])
    // 파이썬 for 문 이상해...

    → 어쨌든 이런 모양

    >>> a[0, 3:5]
    array([3, 4])
    //0번째 행에서 [3:5]로 slicing한 부분
    
    >>> a[4:, 4:]
    array([[44, 45], [54, 55]])
    
    >>> a[:, 2]
    array([2, 12, 22, 32, 42, 52])
    //모든 행에서 index 2인 원소들 모으는거
    
    >>> a[2::2, ::2]
    array([[20, 22, 24], [40, 42, 44]])
  1. Float Point 타입 어레이 만들기

    1) 명시하지 않고 만들기

    >>> a = np.array([0, 1.0, 2, 3])
    //생성자에 어레이 넣어줄때 이렇게 원소 하나라도 float면 모든 element가
    //float type으로 들어간다.
    
    >>> a.dtype
    dtype('float64')
    >>> a.nbytes
    32
    // 8*4

    2) 명시하고 만들기

    >>> a = np.array([0, 1., 2, 3], dtype = 'float32')
    //이런식으로 타입을 지정하는게 가능
    
    >>> a.dtype
    dtype('float32')
    >>> a .nbytes
    16
  1. IDENTITY

    대각선은 1이고 나머지는 0인 n*n 행렬 만들기

    >>> a = np.identity(4)
    >>> a
    arrary([[1., 0., 0., 0.], 
    			  [0., 1., 0., 0.],
    	      [0., 0., 1., 0.],
    				[0., 0., 0., 1.]])
    >>> a.dtype
    dtype('float64')
    //타입은 지정해주지 않을 경우 float64
    //a에 대입해주는거니까 만든걸 자동으로 띄워주진 않음
    
    
    >>> np.identity(4, dtype = int)
    array([[1, 0, 0, 0],
    			 [0, 1, 0, 0],
    			 [0, 0, 0, 0],
    			 [0, 0, 0, 1]])
    //진짜 웃긴게 여기선 dtype = int 처럼 ''안에 안넣어줌
    //어디에 대입하는게 아니니까 자동으로 출력된다.
  1. ONES, ZEROS (원소가 모두 0 또는 1인 어레이 만들어주기)
    np.ones(shape, dtype = 'flaot64')
    np.zeros(shape, dtype = 'float64')
    //dtype 안적어줘도 되는데 디폴트는 float64
    
    
    >>> np.ones((2, 3), dtype = float32')
    array([[1., 1., 1.], [1., 1., 1.]].)
    
    
    >>> np.zeros(3)
    array([0., 0., 0.])
    //shpae가 (3)이라는건 그냥 길이 3인 1차원 어레이 말하는거. 그리고 타입 명시 안했으면
    //element type은 디폴트로 float64
  1. LINSPACE
    np.linspace(start, end, N)
    //start와 end를 포함하면서 N개로 균등하게 나누어진 어레이를 만들어줌
    
    
    >>> np.linspace(0, 1, 5)
    array([0., 0.25, 0.5, 0.75, 1.0])

15. ARANGE

np.arange(start, stop, step, dtype=None)

//arange 함수는 반열린구간 [start, stop) 에서 
//step 의 크기만큼 일정하게 떨어져 있는 숫자들을 array 형태로 반환해 주는 함수다.
//stop 매개변수의 값은 반드시 전달되어야 하지만 start 는 step 은 꼭 전달되지 않아도 된다. 
//start 값이 전달되지 않았다면 0 을 기본값으로 가지며, 
//step 값이 전달되지 않았다면 1 값을 기본값으로 갖게 된다.
//dtype이 명시되지 않은 경우 다른 매개변수로부터 추론


>>> np.arange(4)
array([0, 1, 3])
>>> np.arange(1.5, 2.1, 0.3)
array([1.5, 1.8, 2.1])

// flaot type에서 위와 같이 1.8+0.3 == stop일 경우
// stop 값이 포함될수도 있음. 근데 안될수도 있음 (일관성이 X)

16. TRANSPOSE

a.T
//이렇게 하면 a의 transpose 된 새로운 어레이 만들어준다

>>> a = np.array[[0, 1, 2], [3, 4, 5])
>>> a.shpae
(2, 3)

>>> a.T
array([[0, 3], [1, 4], [2, 5]])
>>> a.T.shape
(3, 2)

17. RESHAPE

a.reshape(m, n)
//이렇게 하면 a 어레이를 m*n 모양으로 바꿔준 새로운 어레이를 만들어준다.
//그래서 자동으로 출력해줌

>>> a = np.array([[0, 1, 2], [3, 4, 5]])
>>> a.reshape(3, 2)
array([[0, 1],[2, 3], [4, 5])

//transpose한 모양으로 만드는게 아니라 다차원이 일렬로 배열되어 있을때 다시 끊는 느낌
//원소 개수는 원래 k개 였으면 새로운 shpae의 원소 개수도 k개여야함

>>> a.reshape(4, 2) 
-> Error
//a의 원소개수는 6개인데 만들려고 하는 거의 원소 개수는 4*2 = 8이니까

Quiz #1

Vector & Matrix with NumPy

  • Vector는 1차원 넘파이 어레이를 의미
>>> v = np.arange(3)
>>> v
array([0, 1, 2])
  • Matrix는 2차원 넘파이 어레이를 의미
>>> M = np.arange(9).reshape(3, 3)
>>> M
array([[0, 1, 2], 
			 [3, 4, 5],
			 [6, 7, 8]])
  • 넘파이에선 vector와 matrix의 여러 연산을 지원함
    1. '*' 연산: 그냥 각 요소들끼리 곱하는거 (그다지 쓸일이 없음)
    >>> v * v
    array([0, 1, 4]
    >>> M * M
    array([[0, 1, 4],
    			 [9, 16, 25],
    			 [36, 49, 64]])

    2. '@'연산: dot product(행렬곱, 벡터곱) 을 의미한다.

    >>> v @ v
    5
    // 0*0 + 1*1 + 2*2
    
    >>> M @ M
    array([[15, 18, 21],
    			 [42, 54, 66],
    			 [69, 90, 111]])
    >>> M @ V
    array([5, 14, 23])
    //여기서 벡터는 세로 모양일수도 있고 가로 모양일수도 있음 
    //그러니까 연산이 가능한거임

What is OpenGL

  • Open Graphics Library의 약자
  • 사실 라이브러리는 아니고 API임. 라이브러리는 컴파일된 코드를 말하는데 OpenGL은 그게 아님
  • API는 specification이다.

    interface와 기대되는 행동을 설명해준다.(어떤 함수는 파라미터 뭐고 무슨 일을 하고~)

OpenGL의 특징

  • Cross platform

    OpenGL은 윈도우, OS X, 리눅스, iOS, 안드로이드 등 어떤 OS에서도 돌아간다.

  • Language independent

    OpenGL은 많은 언어에 binding되어 있음 (C, Python, Java, Javascript...)

    수업시간에는 OpenGL의 파이썬 바인딩을 사용할거임 - PyOpenGL

  • 버전이 1점대인 Legacy OpenGL과 버전이 2점대인 Modern OpenGL이 있는데 우리가 배울건

    Legacy OpenGL임. Legacy OpenGL은 shader가 없고 배우기 쉬움.

    Modern OpenGL은 요즘 많이 쓰이고 programmable shader 들을 사용한다.

OpenGL로 무엇을 할수 있나?

  • Just drawing!

    무언가 그리는 것만 할수 있음. 선을 그려라, 다각형을 그려라 이런것들..

    window를 제공하거나 마우스 키보드 이벤트 핸들링을 제공하지 않는다.

    (No functions for creating windows & OpenGL contexts, handling event)

    → 그래서 OpenGL을 사용하기 위해 추가적인 utility libraries를 사용할거임

    가장 많이 쓰는건 OpenGL 배우는 거에 비해 너무 기능이 많음

    GLUT는 학습의 목적에 잘 맞으나 너무 구식임

    그래서 우리가 쓰는건 GLFW


[Practice] First OpenGL Program

아직 안배운게 많으니까 대충 읽고 넘겨라

import numpy
import glfw
from OpenGL.GL import *

#import X : X.attribute, X.method() 를 통해 X의 attribute와 method에 접근할 수 있음
#from X import * 그냥 attribute, method()만 적어서 X의 attribute와 method에 접근할 수 있음

def render():
		pass

def main():
		#Initalize the library
		if not glfw.init():
				return
		#초기화가 성공하면 true return, 실패하면 false
		
		window = glfw.create_window(640, 480, "Hello World, None, None)

		if not window:
				glfw.terminate()
				return

		# Make the window's context current
		glfw.make_context_current(window)

		#Loop until the user closes the window (유저가 창 끌때까지 루프가 돌아감)
		while not glfw.window_should_close(window):
				#Poll events (이 함수에서 무슨 이벤트인지 검사하고 무슨일 할지 콜백함수로 호출)
				glfw.poll_events()
				
				#Render here, e.g. using pyOpenGL (화면에 그린다. 렌더링)
				render()

				# Swap front and back buffers 
				#(back에서 위와 같은 일이 일어나면 front(우리가 보는 화면)에 반영한다.)
				glfw.swap_buffers(window)

		glfw.terminate()


if __name__ == "__main__":
		main()

[Practice] Draw a Triangle

def render():
		#검은 화면 띄우기
		glClear(GL_COLOR_BUFFER_BIT)
		#이건 나중에 배운다.
		glLoadIdentity()

		#여기서 뭐 그릴지 말해준다.
		glBegin(GL_TRIANGLES)

		#이제 점을 지정해줌
		glVertex2f(0.0, 1.0)
		glVertex2f(-1.0, -1.0)
		glVertex2f(1.0, -1.0)
		glEnd()

#OpenGL이 하는건 이정도..? 
		

Vertax들을 명시하는 순서가 반시계 방향일때 → 화면에 앞면을 보여줌

Vertax들을 명시하는 순서가 시계 방향일때 → 화면에 뒷면을 보여줌.

(Vertax들을 명시하는 순서에 따라 OpenGL이 받아들이는건 다르다.)

Vertex

  • OpenGL에서 기하학은 vertices로 명시된다.
  • 무언가를 그리기 위해서 vertax들이 glBegin(primitive_type) 과 glEnd() 사이에 명시되어야한다.
  • glVertex() 로 vertax를 명시하는데 괄호 안에는 좌표 값이 들어가야함

Coordinate System

  • 아무설정도 안할 경우 기본적으로 (-1, -1) to (1, 1) 범위의 2D square에 그림을 그릴 수 있다.
  • 이걸 "Normalized Device Coordinate" (NDC)라고 부른다.
  • 가운데가 원점

Primitive Type

  • glBegin(primitive_type)으로 사용
  • primitive_type은 vertax들이 어떻게 connected 될지 나타내준다.
  • 시계 방향으로 그리면 화면에 앞면 보여주고 반시계 방향으로 그리면 뒷면 보여준다.

Vertax Attribute

OpenGL에서 vertax들은 4가지 특성을 가진다.

  • Vertax coordinate: 좌표 위치를 의미 glVertex*()로 명시된다.
  • Vertax color: 색깔. glColor*()로 명시된다.

    OpenGL은 RGB color model을 사용한다.

    (1, 0, 0) = 빨강 (0, 1, 0) = 초록 (0, 0, 1) = 파랑 → 이거!

    내부 색깔은 보간(interpolated)된다. (자연스럽게 그라데이션 된다는 의미)

  • Normal vector: 법선벡터. glNormal*()로 명시된다.
  • Texture coordinate: 텍스쳐. glTexCoord*()로 명시된다.

1) 무지개 삼각형 만들기

def render():
		glClear(GL_COLOR_BUFFER_BIT)
		glLoadIdentity()
		glBegin(GL_TRIANGLES)
		//삼각형 모양으로 선 이을거임

		glColor3f(1.0, 0.0, 0.0)
		glVertex2f(0.0, 1.0)

		glColor3f(0.0, 1.0, 0.0)
		glVertex2f(-1.0, -1.0)

		glColor3f(0.0, 0.0, 1.0)
		glVertex2f(1.0, -1.0)

		glEnd() 

# 여기서 삼각형 안에 자연스럽게 그라데이션 되면서 채워진다. 

2) 빨간색 삼각형 만들기

  • 첫번째 점 색깔만 빨간색으로 지정해주고 뒤에 점들은 색깔 지정안해주면 된다.
  • 왜? 첫번째 점 색깔 지정해주는 순간 current color는 red가 되는거임. 그래서 뒤에 점을 색깔 명시 없이 만들 경우

    current color로 색깔이 지정된다. (OpenGL은 state machine이기 때문)

def render()
		glClear(GL_COLOR_BUFFER_BIT)
		glLoadIdentity()
		glBegin(GL_TRIANGLES)

		glColor3f(1.0, 0.0, 0.0)
		# 첫번째 점 색깔을 빨간색으로 지정해주는 순간 current color = red
		glVertex2f(0.0, 1.0)

		glVertex2f(0.0, 1.0)
		glVertex2f(-1.0, -1.0)
		glVertex2f(1.0, -1.0)

		glEnd()

OpenGL은 state Machine이다

  • 만약 우리가 state(또는 mode)에 대해 값을 set하면, 이게 우리가 바꾸기 전까지 계속 유지된다.

    (아까 빨간색 삼각형 만들때 점 하나의 색깔만 지정해주면 되는 이유임)

  • state에는 현재 color, 현재 viewing and projection transformation

    현재 polygon drawing modes, 현재 빛의 position과 특성, 현재 object의 재질 특성 등...

  • OpenGL context는 OpenGL instance의 현재 상태의 모음

    (OoenGL context stores all of the state associated with this instance of OpenGL)

OpenGL Functions

  • 숫자, 타입, 벡터 순서
  • v 붙으면 파라미터로 벡터를 넘기라는거(튜플, 어레이, 넘파이 어레이 넘길 수 있음)
import numpy as mp

def render():
		glClear(GL_COLOR_BUFFER_BUT)
		glLoadIdentity()
		glBegin(GL_TRIANGLES)
		glColor3ub(255, 0, 0)

		# 튜플 넘기는거
		glVertex2fv((0.0, 1.0))

		# 어레이 넘기는거
		glVertex2fv([-1.0, -1.0])
		
		# 넘파이 어레이 constructor에 어레이 넣어서 넘파이 어레이 넘기는거
		glVertex2fv(np.array([1.0, -1.0])
		
		glEnd()

Quiz #3

GLFW Input Handling

glfw.poll_events()

  • 수신된 이벤트를 처리하고 반환해줌.
  • 이벤트 타입에 맞는 user-registered callback function을 call한다.
  • 오른쪽에 나와있는 것들을 사용해서 callback 함수로 등록한다.

    (or just poll the position using 이건 무슨 소린지 모르겠다.)

import glfw
from OpenGL.GL import *

def render():
		pass
# 함수나 클래스의 구현을 미룰 때 pass를 사용한다.

def key_callback(window, key, scannode, action, mods):
		if key == glfw.KEY_A
				if action == glfw.PRESS
						print('press a')
				elif action == glfw.RELEASE:
						print('release a')
				elif action == glfw.REPEAT:
						print('repeat a')
		elif key == glfw.KEY_SPACE and action == glfw.PRESS:
				print('press space: (%d, %d)' %glfw.get_cursor_pos(window))

def cursor_callback(window, xpos, ypos):
		print('mouse cursor moving: (%d, %d)' %(xpos, ypos))
	
def button_callback(window, button, action, mod):
		if button == glfw.MOUSE_BUTTON_LEFT
				if action == glfw.PRESS
						print('press left btn: (%d, %d)' %glfw.get_cursor_pos(window))
				elif action == glfw.RELEASE:
						print('release left btn: (%d %d)' %glfw.get_cursor_pos(window)


def main():
		#Initialize the library
				if not glfw.init()
						return
				#OpenGL context를 나타내줄 window 띄우기
				window = glfw.create_window(640, 480, "Hello World", None, None)


				if not window:
						glfw.terminate
						return

		# 이렇게 콜백함수로 등록한다.		
		glfw.set_key_callback(window, key_callback)
		glfw.set_cursor_pos_callback(window, cursor_callback)
		glf.set_mouse_button_callback(window, button_callback)
		glfw.set_scroll_callback(window, scroll_callback)


		# window의 context를 current로 만들기 
		glfw.make_context_current(window)

		#Loop until the user closes the window
		while not glfw.window_should_close(window)
				# Poll for and process events
				glfw.poll_events()
				
				# Render here, e.g. using pyOpenGL
				render()

				#Swap front and back buffers
				glfw.swap_buffers(window)
		
		glfw.terminate()

if __name__ == "__name__"
		main()

Uploaded by N2T

728x90
728x90