opengl
OGL보기 및 투영
수색…
소개
모델 행렬, 뷰 행렬, 직교 투영 및 원근 투영
OGL 4.0 GLSL 400에서 카메라 구현
카메라로 사진을 촬영 한 것처럼 장면을보고 싶다면 먼저 몇 가지 사항을 정의해야합니다.
- 장면을 보는 위치, 눈 위치
pos
. - 우리가 현장 (
target
)에서 바라 보는 지점. 또한 우리가 바라는 방향을 정의하는 것이 일반적입니다. 기술적으로 우리 는 시야 가 필요합니다. 하나의 직선 공간은 수학적으로 2 점 또는 점과 벡터에 의해 정의됩니다. 정의의 첫 번째 부분은 눈의 위치이고, 2는 하나 인target
또는 시선 벡터의 라인los
. - 위쪽 방향으로
up
. - 시야 '
fov_y
. 이것은 두 직선 사이의 각도를 의미하며, 눈 위치에서 시작하여 가장 왼쪽 지점과 가장 오른쪽 지점에서 끝나며 동시에 볼 수 있습니다. - 우리가 이미지
vp
투사하는 뷰포트의 크기와 종횡비가 크다. - 가까운면
near
와 멀리 비행기far
. 가까운 평면 은 눈의 위치에서 물체가 보이는 곳까지의 거리입니다. 먼 평면 은 눈 위치에서 장면의 대상을 볼 수있는 평면 까지 의 거리입니다. 가까운 비행기 와 먼 비행기 가 필요한지에 대한 설명은 나중에 따릅니다.
C ++ 및 Python에서이 데이터의 정의는 다음과 같습니다.
C ++
using TVec3 = std::array<float,3>;
struct Camera
{
TVec3 pos {0.0, -8.0, 0.0};
TVec3 target {0.0, 0.0, 0.0};
TVec3 up {0.0, 0.0, 1.0};
float fov_y {90.0};
TSize vp {800, 600};
float near {0.5};
float far {100.0};
};
파이썬
class Camera:
def __init__(self):
self.pos = (0, -8, 0)
self.target = (0, 0, 0)
self.up = (0, 0, 1)
self.fov_y = 90
self.vp = (800, 600)
self.near = 0.5
self.far = 100.0
장면을 그릴 때이 모든 정보를 고려하기 위해 일반적으로 투영 행렬과 뷰 행렬이 사용됩니다. 장면에서 장면의 개별 부분을 배열하기 위해 모델 행렬이 사용됩니다. 그러나 여기에 언급 된 내용은 완전성을 위해서만 사용되며 여기서는 다루지 않습니다.
투영 행렬 : 투영 행렬은 핀홀 카메라에서 뷰포트의 2D 점으로 볼 때 세계의 3D 점에서의 매핑을 설명합니다.
뷰 매트릭스 : 뷰 매트릭스는 장면의 눈 위치와 뷰 방향을 정의합니다.
모형 행렬 : 모형 행렬은 장면에서 객체의 위치와 상대적 크기를 정의합니다.
위의 데이터 구조를 해당 데이터로 채운 후에는 적절한 행렬로 변환해야합니다. OGL 호환 모드에서는 내장 된 유니폼 gl_ModelViewMatrix
, gl_NormalMatrix
및 gl_ModelViewProjectionMatrix
를 설정하는 gluLookAt
및 gluPerspective
함수를 사용 gluPerspective
작업을 수행 할 수 있습니다. OGL 3.1 및 GLSL #version 150에서는 전체 고정 함수 행렬 스택이 사용되지 않으므로 내장 된 유니폼이 제거되었습니다. 우리가 GLSL 버전 330 이상의 OGL 하이 레벨 셰이더를 사용하고 싶다면 매트릭스 유니폼을 정의하고 설정해야합니다 (GLSL compatibility
키워드 사용 제외).
원근감 설정 - 투영 행렬
뷰포트의 점은 점 (-1.0, -1.0, -1.0)
및 (1.0, 1.0, 1.0)
정의 된 원시 AABB (축 정렬 경계 상자)에있을 때 표시됩니다. 이를 정규화 된 장치 좌표 (NDC)라고합니다. 좌표 (-1.0, -1.0, z)
가있는 점은 뷰포트의 왼쪽 아래 모서리에 그려지며 좌표 (1.0, 1.0, z)
가있는 점은 뷰포트의 오른쪽 위 모서리에 그려집니다. Z 좌표는 간격 (-1.0, 1.0)에서 간격 (0.0, 1.0)으로 매핑되고 Z 버퍼에 기록됩니다.
우리가 볼 수있는 것은 모두 4면 피라미드 안에 있습니다. 피라미드의 꼭대기는 눈의 위치 입니다. 피라미드의 4면은 fov_y
뷰 ( fov_y
)와 종횡비 ( vp[0]/vp[1]
)로 정의됩니다. 투영 행렬은 피라미드 내부의 점을 점 (-1.0, -1.0, -1.0)
과 (1.0, 1.0, 1.0)
정의 된 NDC로 매핑해야합니다. 이 시점에서 우리의 피라미드는 무한합니다. 깊이가 없으며 무한한 공간을 유한 한 것에 매핑 할 수 없습니다. 이를 위해 우리는 이제 가까운 평면 과 먼 평면 이 필요합니다. 그들은 꼭대기를 잘라 깊이의 피라미드를 제한함으로써 피라미드를 절두체로 변형시킵니다. 가까운 평면과 먼 평면은 장면에서 볼 수있는 모든 것을 포함하는 방식으로 선택되어야합니다.
절두체 내의 점에서 NDC 로의 매핑은 순수한 수학이며 일반적으로 해결할 수 있습니다. 수식 개발은 종종 웹 전반에 걸쳐 논의되고 반복적으로 발표되었습니다. Stack Overflow 문서에 LaTeX 수식을 삽입 할 수 없으므로 여기서는 생략하고 완성 된 C ++ 및 Python 소스 코드 만 추가합니다. 눈 좌표는 오른손 좌표계에서 정의되지만 NDC는 왼손 좌표계를 사용합니다. 프로젝션 행렬의 뷰 필드에서 계산 fov_y
종횡비 vp[0]/vp[1]
가까운면 near
상기 평면 멀리 far
.
C ++
using TVec4 = std::array< float, 4 >;
using TMat44 = std::array< TVec4, 4 >;
TMat44 Camera::Perspective( void )
{
float fn = far + near;
float f_n = far - near;
float r = (float)vp[0] / vp[1];
float t = 1.0f / tan( ToRad( fov_y ) / 2.0f );
return TMat44{
TVec4{ t / r, 0.0f, 0.0f, 0.0f },
TVec4{ 0.0f, t, 0.0f, 0.0f },
TVec4{ 0.0f, 0.0f, -fn / f_n, -1.0 },
TVec4{ 0.0f, 0.0f, -2.0f * far * near / f_n, 0.0f } };
}
파이썬
def Perspective(self):
fn, = self.far + self.near
f_n = self.far - self.near
r = self.vp[0] / self.vp[1]
t = 1 / math.tan( math.radians( self.fov_y ) / 2 )
return numpy.matrix( [
[ t/r, 0, 0, 0 ],
[ 0, t, 0, 0 ],
[ 0, 0, -fn/f_n, -1 ],
[ 0, 0, -2 * self.far * self.near / f_n, 0 ] ] )
장면의 모습 설정 - 매트릭스보기
뷰포트의 좌표계에서 Y 축은 위쪽 (0, 1, 0)
가리키고 X 축은 오른쪽 (1, 0, 0)
가리 킵니다. 결과적으로 뷰포트 ( (0, 0, -1) = cross( X-axis, Y-axis )
)을 가리키는 Z 축을 얻습니다.
장면에서 X 축은 동쪽을 가리키고 Y 축은 북쪽을 가리키고 Z 축은 위쪽을 가리 킵니다.
뷰포트의 X 축 (1, 0, 0)
장면의 Y 축과 일치하는 (1, 0, 0)
, 뷰포트의 Y 축 (0, 1, 0 )
장면의 Z 축을 일치 (0, 0, 1)
과 뷰포트 (0, 0, 1 )
의 Z 축이 장면 (0, -1, 0)
의 음수 Y 축과 일치합니다.
따라서 장면의 참조 시스템에서 각 점과 각 벡터를 먼저 뷰포트 좌표로 변환해야합니다. 이는 스칼라 벡터의 일부 스왑 및 반전 작업을 통해 수행 할 수 있습니다.
x y z
--------
1 0 0 | x' = x
0 0 1 | y' = z
0 -1 0 | z' = -y
뷰 행렬을 설정하려면 위에 설명 된 것처럼 위치 pos
, 대상 target
및 업 벡터 up
을 뷰포트 좌표계에 매핑해야합니다. 그러면 다음 코드 조각에서와 같이 2 포인트 p
와 t
와 벡터 u
가됩니다. 뷰 행렬의 Z 축은 p - t
의해 계산 된 역의 시선입니다. Y 축은 위쪽 벡터 u
입니다. X 축은 Y 축과 Z 축의 외적에 의해 계산됩니다. 뷰 매트릭스를 직교 정규화하기 위해 교차 곱이 두 번째로 사용되어 Z 축과 X 축에서 Y 축을 계산합니다 (물론 Gram-Schmidt 직교 화도 마찬가지로 작동합니다). 결국, 3 축 모두가 정규화되어야하고 눈 위치 pos
는 뷰 매트릭스의 원점으로 설정되어야합니다.
아래 코드는 장면의 모양을 계산하는 데 필요한 단계를 정확하게 캡슐화하는 행렬을 정의합니다.
- 모델 좌표를 뷰포트 좌표로 변환합니다.
- 시야 방향으로 회전하십시오.
- 눈 위치로의 이동
C ++
template< typename T_VEC >
TVec3 Cross( T_VEC a, T_VEC b )
{
return { a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] };
}
template< typename T_A, typename T_B >
float Dot( T_A a, T_B b )
{
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
}
template< typename T_VEC >
void Normalize( T_VEC & v )
{
float len = sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] ); v[0] /= len; v[1] /= len; v[2] /= len;
}
TMat44 Camera::LookAt( void )
{
TVec3 mz = { pos[0] - target[0], pos[1] - target[1], pos[2] - target[2] };
Normalize( mz );
TVec3 my = { up[0], up[1], up[2] };
TVec3 mx = Cross( my, mz );
Normalize( mx );
my = Cross( mz, mx );
TMat44 v{
TVec4{ mx[0], my[0], mz[0], 0.0f },
TVec4{ mx[1], my[1], mz[1], 0.0f },
TVec4{ mx[2], my[2], mz[2], 0.0f },
TVec4{ Dot(mx, pos), Dot(my, pos), Dot(TVec3{-mz[0], -mz[1], -mz[2]}, pos), 1.0f }
};
return v;
}
파이썬
def LookAt(self):
mz = Normalize( (self.pos[0]-self.target[0], self.pos[1]-self.target[1], self.pos[2]-self.target[2]) ) # inverse line of sight
mx = Normalize( Cross( self.up, mz ) )
my = Normalize( Cross( mz, mx ) )
tx = Dot( mx, self.pos )
ty = Dot( my, self.pos )
tz = Dot( (-mz[0], -mz[1], -mz[2]), self.pos )
return = numpy.matrix( [
[mx[0], my[0], mz[0], 0],
[mx[1], my[1], mz[1], 0],
[mx[2], my[2], mz[2], 0],
[tx, ty, tz, 1] ] )
행렬은 최종적으로 유니폼으로 작성되고 정점 셰이더에서 모델 위치를 변환하는 데 사용됩니다.
정점 셰이더
버텍스 쉐이더에서는 다른 하나의 변환이 수행됩니다.
- 모델 행렬은 객체 (메쉬)를 장면의 해당 위치로 가져옵니다. (이것은 완전성을 위해 열거 된 것이며 현장에 대한 뷰와는 아무런 관련이 없으므로 여기에 문서화되지 않았습니다)
- 뷰 행렬은 장면을 보는 방향을 정의합니다. 뷰 매트릭스를 사용한 변형은 장면의 객체를 회전시켜 뷰포트의 좌표계를 참조하여 원하는 뷰 방향에서 보게합니다.
- 투영 행렬은 객체를 평행 뷰에서 투시 뷰로 변환합니다.
#version 400
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inCol;
out vec3 vertCol;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
void main()
{
vertCol = inCol;
vec4 modelPos = u_modelMat44 * vec4( inPos, 1.0 );
vec4 viewPos = u_viewMat44 * modelPos;
gl_Position = u_projectionMat44 * viewPos;
}
조각 쉐이더
프래그먼트 셰이더는 완전성을 위해서만 여기에 나열되어 있습니다. 그 일은 전에 행해졌 다.
#version 400
in vec3 vertCol;
out vec4 fragColor;
void main()
{
fragColor = vec4( vertCol, 1.0 );
}
셰이더가 컴파일되고 좋아지면 행렬은 균일 변수에 바인딩 될 수 있습니다.
C ++
int shaderProg = ;
Camera camera;
// ...
int prjMatLocation = glGetUniformLocation( shaderProg, "u_projectionMat44" );
int viewMatLocation = glGetUniformLocation( shaderProg, "u_viewMat44" );
glUniformMatrix4fv( prjMatLocation, 1, GL_FALSE, camera.Perspective().data()->data() );
glUniformMatrix4fv( viewMatLocation, 1, GL_FALSE, camera.LookAt().data()->data() );
파이썬
shaderProg =
camera = Camera()
# ...
prjMatLocation = glGetUniformLocation( shaderProg, b"u_projectionMat44" )
viewMatLocation = glGetUniformLocation( shaderProg, b"u_viewMat44" )
glUniformMatrix4fv( prjMatLocation, 1, GL_FALSE, camera.Perspective() )
glUniformMatrix4fv( viewMatLocation, 1, GL_FALSE, camera.LookAt() )
또한, 파이썬 예제의 전체 코드 덤프를 추가했습니다 (C ++ 예제를 추가하는 것은 불행히도 30000자를 초과합니다). 이 예제에서 카메라는 타원의 초점에서 정사면체 주위를 타원으로 움직입니다. 시야 방향은 항상 tetraeder로 안내됩니다.
파이썬
파이썬 스크립트를 실행하려면 NumPy 가 설치되어 있어야합니다.
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy
from time import time
import math
import sys
def Cross( a, b ): return ( a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 )
def Dot( a, b ): return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
def Normalize( v ):
len = math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] )
return (v[0] / len, v[1] / len, v[2] / len)
class Camera:
def __init__(self):
self.pos = (0, -8, 0)
self.target = (0, 0, 0)
self.up = (0, 0, 1)
self.fov_y = 90
self.vp = (800, 600)
self.near = 0.5
self.far = 100.0
def Perspective(self):
fn, f_n = self.far + self.near, self.far - self.near
r, t = self.vp[0] / self.vp[1], 1 / math.tan( math.radians( self.fov_y ) / 2 )
return numpy.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-1], [0,0,-2*self.far*self.near/f_n,0] ] )
def LookAt(self):
mz = Normalize( (self.pos[0]-self.target[0], self.pos[1]-self.target[1], self.pos[2]-self.target[2]) ) # inverse line of sight
mx = Normalize( Cross( self.up, mz ) )
my = Normalize( Cross( mz, mx ) )
tx = Dot( mx, self.pos )
ty = Dot( my, self.pos )
tz = Dot( (-mz[0], -mz[1], -mz[2]), self.pos )
return = numpy.matrix( [ [mx[0], my[0], mz[0], 0], [mx[1], my[1], mz[1], 0], [mx[2], my[2], mz[2], 0], [tx, ty, tz, 1] ] )
# shader program object
class ShaderProgram:
def __init__( self, shaderList, uniformNames ):
shaderObjs = []
for sh_info in shaderList: shaderObjs.append( self.CompileShader(sh_info[0], sh_info[1] ) )
self.LinkProgram( shaderObjs )
self.__unifomLocation = {}
for name in uniformNames:
self.__unifomLocation[name] = glGetUniformLocation( self.__prog, name )
print( "uniform %-30s at loaction %d" % (name, self.__unifomLocation[name]) )
def Use(self):
glUseProgram( self.__prog )
def SetUniformMat44( self, name, mat ):
glUniformMatrix4fv( self.__unifomLocation[name], 1, GL_FALSE, mat )
# read shader program and compile shader
def CompileShader(self, sourceFileName, shaderStage):
with open( sourceFileName, 'r' ) as sourceFile:
sourceCode = sourceFile.read()
nameMap = { GL_VERTEX_SHADER: 'vertex', GL_FRAGMENT_SHADER: 'fragment' }
print( '\n%s shader code:' % nameMap.get( shaderStage, '' ) )
print( sourceCode )
shaderObj = glCreateShader( shaderStage )
glShaderSource( shaderObj, sourceCode )
glCompileShader( shaderObj )
result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
if not (result):
print( glGetShaderInfoLog( shaderObj ) )
sys.exit()
return shaderObj
# linke shader objects to shader program
def LinkProgram(self, shaderObjs):
self.__prog = glCreateProgram()
for shObj in shaderObjs: glAttachShader( self.__prog, shObj )
glLinkProgram( self.__prog )
result = glGetProgramiv( self.__prog, GL_LINK_STATUS )
if not ( result ):
print( 'link error:' )
print( glGetProgramInfoLog( self.__prog ) )
sys.exit()
# vertex array object
class VAObject:
def __init__( self, dataArrays, tetIndices ):
self.__obj = glGenVertexArrays( 1 )
self.__noOfIndices = len( tetIndices )
self.__indexArr = numpy.array( tetIndices, dtype='uint' )
noOfBuffers = len( dataArrays )
buffers = glGenBuffers( noOfBuffers )
glBindVertexArray( self.__obj )
for i_buffer in range( 0, noOfBuffers ):
vertexSize, dataArr = dataArrays[i_buffer]
glBindBuffer( GL_ARRAY_BUFFER, buffers[i_buffer] )
glBufferData( GL_ARRAY_BUFFER, numpy.array( dataArr, dtype='float32' ), GL_STATIC_DRAW )
glEnableVertexAttribArray( i_buffer )
glVertexAttribPointer( i_buffer, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
def Draw(self):
glBindVertexArray( self.__obj )
glDrawElements( GL_TRIANGLES, self.__noOfIndices, GL_UNSIGNED_INT, self.__indexArr )
# glut window
class Window:
def __init__( self, cx, cy ):
self.__vpsize = ( cx, cy )
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH )
glutInitWindowPosition( 0, 0 )
glutInitWindowSize( self.__vpsize[0], self.__vpsize[1] )
self.__id = glutCreateWindow( b'OGL window' )
glutDisplayFunc( self.OnDraw )
glutIdleFunc( self.OnDraw )
def Run( self ):
self.__startTime = time()
glutMainLoop()
# draw event
def OnDraw(self):
self.__vpsize = ( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) )
currentTime = time()
# set up camera
camera = Camera()
camera.vp = self.__vpsize
camera.pos = self.EllipticalPosition( 7, 4, self.CalcAng( currentTime, 10 ) )
# set up attributes and shader program
glEnable( GL_DEPTH_TEST )
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
prog.Use()
prog.SetUniformMat44( b"u_projectionMat44", camera.Perspective() )
prog.SetUniformMat44( b"u_viewMat44", camera.LookAt() )
# draw object
modelMat = numpy.matrix(numpy.identity(4), copy=False, dtype='float32')
prog.SetUniformMat44( b"u_modelMat44", modelMat )
tetVAO.Draw()
glutSwapBuffers()
def Fract( self, val ): return val - math.trunc(val)
def CalcAng( self, currentTime, intervall ): return self.Fract( (currentTime - self.__startTime) / intervall ) * 2.0 * math.pi
def CalcMove( self, currentTime, intervall, range ):
pos = self.Fract( (currentTime - self.__startTime) / intervall ) * 2.0
pos = pos if pos < 1.0 else (2.0-pos)
return range[0] + (range[1] - range[0]) * pos
def EllipticalPosition( self, a, b, angRag ):
a_b = a * a - b * b
ea = 0 if (a_b <= 0) else math.sqrt( a_b )
eb = 0 if (a_b >= 0) else math.sqrt( -a_b )
return ( a * math.sin( angRag ) - ea, b * math.cos( angRag ) - eb, 0 )
# initialize glut
glutInit()
# create window
wnd = Window( 800, 600 )
# define tetrahedron vertex array opject
sin120 = 0.8660254
tetVAO = VAObject(
[ (3, [ 0.0, 0.0, 1.0, 0.0, -sin120, -0.5, sin120 * sin120, 0.5 * sin120, -0.5, -sin120 * sin120, 0.5 * sin120, -0.5 ]),
(3, [ 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, ])
],
[ 0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2 ]
)
# load, compile and link shader
prog = ShaderProgram(
[ ('python/ogl4camera/camera.vert', GL_VERTEX_SHADER),
('python/ogl4camera/camera.frag', GL_FRAGMENT_SHADER)
],
[b"u_projectionMat44", b"u_viewMat44", b"u_modelMat44"] )
# start main loop
wnd.Run()