Graphics Programming

[Marco Scabia] 05 Stage3D와 원근 투영 본문

Season 1/플래시

[Marco Scabia] 05 Stage3D와 원근 투영

minseoklee 2012. 1. 26. 01:39

원문
http://www.adobe.com/devnet/flashplayer/articles/perspective-projection.html

도입
이 튜토리얼에서는 원근법을 배웁니다. 원근법은 플래시 프로젝트에서 3D 렌더링을 사용할 때 기초가 되는 주제입니다. 이번에는 Stage3D API를 사용하여 원근이 있는 삼차원 공간을 그리는 방법을 알아보겠습니다. 이 튜토리얼은 Stage3D 작업에 관한 시리즈의 일부로 삼각형 그리기를 설명하는 이전 튜토리얼의 정보를 기초로 합니다. 원근법을 적용하여 3D 장면을 그림으로써 이전의 견본을 더 높은 수준으로 끌어올리게 될 것입니다.


원근 이해하기
현실에서 우리는 사물을 "원근법"에 따라 봅니다.

원근은 여러분에게 멀리 있는 물체가 가까이 있는 물체보다 더 작게 보이는 것을 뜻합니다. 또한 직선 도로의 가운데에 앉아 있으면 도로의 경계들이 수렴하는 두 선으로 보인다는 뜻이기도 합니다.

이런 것이 원근법입니다. 원근법은 3D 프로젝트의 핵심입니다. 원근 없이 3D 공간은 진짜처럼 보이지 않습니다.

이것이 당연하다고 생각하시겠지만, 컴퓨터에서 3D 렌더링을 할 때는 2D 표면에 3D 공간을 모사하려는 것임을 고려해야 합니다.

컴퓨터 화면 너머에 진짜 3D 장면이 있는데 컴퓨터 화면의 "유리"를 통해 그곳을 본다고 상상해보세요. 원근법을 사용하는 목적은 화면 너머에 진짜 3D 공간이 있어서 이 "유리"에 "투영된" 장면을 보는 것처럼 만드는 코드를 작성하는 것입니다. 다만 이 3D 공간은 진짜가 아니며 3D 공간을 수학으로 베껴놓았을 따름입니다.

3D 렌더링으로 3D 장면을 모사하고 2D 표면으로 투영하는 이 과정을 원근 투영이라고 부릅니다.

원하는 것을 머리 속에 그리며 시작합시다. 물체는 보는 사람에게 가까울 수록 크게 보이고 멀 수록 작게 보입니다. 또한 물체가 보는 사람으로부터 바로 뒤로 멀어지고 있다면 화면 중앙으로 수렴합니다.

원근법을 수학으로 표현하기
그림 1을 보며 물체가 3D 장면 속에 있다고 상상해보세요. 이 3D 공간에서 물체의 위치는 xW, yW, zW로 표현되며 시점을 원점 삼아 구한 3D 좌표를 나타냅니다. 화면 너머 3D 장면에서 물체는 바로 여기에 위치해 있습니다.

Figure 1. The perspective projection of a 3D object.
그림 1. 3D 물체의 원근 투영

관찰자가 화면을 볼 때 이 3D 물체는 xP, yP로 표현되는 2D 좌표로 "투영"됩니다. 이 좌표는 화면(투영 평면)의 2D 좌표를 나타냅니다.

이 값들을 수학 공식에 넣기 위해 월드 좌표에 x축이 오른쪽, y축이 위, 양의 z축이 화면 안으로 들어가는 3D 좌표계를 쓰겠습니다. 3D 원점은 관찰자 눈의 위치로 정합니다. 이 화면은 z = zProj에서 z축에 수직인 평면입니다.

투영된 위치 xP와 yP는 월드 위치 xW, yW를 zW로 나눠서 계산할 수 있습니다.

xP = K1 * xW / zW
yP = K2 * yW / zW

K1과 K2는 투영 평면의 종횡비 그리고 관찰자가 얼마나 넓은 각도로 보는 지를 나타내는 "시야(field of view)" 같은 기하학적 요인에서 따라나오는 상수입니다.

이 변환이 원근법을 모사하는 것을 다음과 같이 확인할 수 있습니다. 화면의 가장자리 근처에 있는 점은 눈으로부터의 거리(zW)가 증가할 수록 중심점으로 쏠립니다. 중심점(0,0)에 가까운 점은 눈으로부터의 거리에 영향을 덜 받으며 중심점 근처에 머무릅니다.

이러한 z에 의한 나눗셈을 흔히 "perspective divide"라고 합니다.

이제 3D 장면 내의 물체가 일련의 정점으로 정의되는 것에 대해 생각해봅시다. 기하 구조의 모든 정점에 이러한 변환을 적용하여 여러분은 물체가 시점에서 멀 수록 작아짐을 확인할 수 있습니다.

다음 절에서는 이 원근 투영 공식을 액션스크립트 내에서 사용할 것입니다

 원근법 수학을 코드로 옮기기
perspective divide를 수학적 표현에서 코드로 옮길 때는 행렬을 사용합니다.

처음에 시작할 때는 원근 투영과 원근 행렬을 다루는 과정이 약간 혼란스럽습니다. 행렬 변환은 일차 변환입니다. 변환된 벡터의 성분들은 입력 벡터들의 일차 결합입니다. 일차 변환은 전치, 회전, 확대/축소, 기울임만을 지원하며, perspective divide처럼 한 성분이 다른 성분을 나누는 연산은 허용하지 않습니다.

이제 삼차원 좌표가 대개 (x, y, z, w) 꼴의 사차원 벡터로 표현됨을 기억하세요. w는 대부분 1입니다. 행렬 기반 perspective divide 문제의 해결책은 네 번째 좌표 w를 기발하게 써먹는 것인데, 바로 zW 좌표를 변환된 벡터의 w 좌표에 저장하는 것입니다.

변환된 벡터의 다른 성분들은 xP, yP에 zW를 미리 곱한 값을 가집니다.

다음 변환을 사용할 것입니다.

xW -> xP' = xP * zW = K1 * xW
yW -> yP' = yP * zW = K2 * yW

처음 볼 때는 2D인 화면으로 투영하는 것이기 때문에 그저 xP와 yP의 투영된 좌표만 계산하면 된다고 생각할지도 모르겠습니다. 하지만 렌더링 파이프라인은 깊이(z) 좌표를 요구합니다. 이 좌표는 다른 픽셀을 렌더링할 때 깊이 정렬을 위해 필요합니다. zW = zNear인 zW를 0으로 투영하는 zP(zP')를 계산하는 것이 표준이라 할 만한 절차입니다. near clipping distance는 렌더하려는 가장 가까운 거리입니다. 클리핑에 대해서는 나중에 더 자세히 설명할 것이며 이것이 클리핑에 쓸 변환된 zP'를 계산하는 공식입니다.

zW -> zP' = K3 * (zW - zNear)

이 모든 변환은 일차 행렬 변환으로 수행할 수 있으므로 변환된 벡터들은 월드 벡터들의 일차 결합입니다.
The entire transformation is definitely possible to achieve with a linear matrix transform, as the transformed vectors are a linear combination of the world vector to transform.

다음으로 실제 xP 값과 yP 값은 변환된 x, y, z 성분을 w로 나누어 얻습니다.
Next, the actual transformed xP and yP values are obtained by dividing the transformed x, y, z, components by w, like this:

xP = K1 * xW
yP = K2 * yW
zP = K3 * (zW - zNear) / zW


바로 이것이 다음 내용을 준비하기 위해 필요한 공식입니다.

클립 공간과 정규장치좌표(Normalized Device Coordinates)
Stage3D는 여러분이 정점 셰이더에서 행렬을 사용하여 정점들을 특별한 공간으로 변환할 것이라 가정합니다.

(x, y, z, w) = (xP', yP', zP', zW)

xP', yP', zP', zW는 위에 정의한 바와 같고 상수 K1, K2, K3은 3D 공간 내 모든 보이는 점들의 xP와 yP가 범위 (-1, 1) 내로, zP는 (0, 1) 내로 들어오도록 잡습니다.

이 4차원 공간(xP', yP', zP', zW)을 clip 공간이라 부르는데, 이 공간이 클리핑이 일어나는 영역이기 때문입니다. 나눗셈 후의 좌표(xP, yP, zP) (xP와 yP는 (-1, 1), zP는 (0, 1)) 를 정규장치좌표(NDC)라 합니다.

Stage3D와 GPU는 여러분의 셰이더가 내놓은, 클립 공간을 기준으로 한 데이터를 사용하여 내부에서 perspective divide를 수행합니다.

clipping에 관한 노트
"우리가 그리려는 


Working with clip space and Normalized Device Coordinates

This 4-dimensional space (xP’, yP’, zP’, zW) is called clip space, as it’s the area where clipping usually takes place. The (xP, yP, zP) coordinates, after the divide, with range (-1,1) (for xP and yP), and range (0, 1) (for zP) are called Normalized Device Coordinates (NDC).

Stage3D and the GPU use the data from the output of your Shader in clip space form to carry on internally with the perspective divide.

A note on clipping

Objects that sit on the "closest distance we want to render", at zW = zNear, get a zP equal to 0. While those that are at some distance that is defined as zW = zFar are transformed in NDC space to zP = 1.

zNear and zFar define the clipping planes. Objects falling closer than zNear will be clipped (not drawn), just like objects falling farther away than zFar. Also, objects with xP and yP outside the range (-1, 1) will be clipped.

For simplicity, I’m working with point objects here. An actual extended object can get partially clipped, as parts of it fall into view, while other parts fall outside it.

Using PerspectiveMatrix3D

The correct values for K1, K2 and K3 to use to get the NDC ranges for xP, yP, zP mentioned above are:

K1 = zProj / aspect
K2 = zProjK3 = zFar / (zFar – zNear)

In this example, aspect is the viewport aspect ratio.

It’s easy to verify that these values, for those world points (xW, yW, zW) that fall on the projection plane (i.e. zW = zProj), the NDC ranges xP = (-1, 1), yP = (-1, 1) and zP = (0, 1) correspond to world ranges equal to xW = (-aspect, aspect) and yW = (-1, 1). World points at different distances zW will scale accordingly with the perspective projection equation. Similarly, the zP range of (0, 1) corresponds to the world range zW = (zNear, zFar).

It is usually more convenient to specify these constants in terms of fov (field of view) angle (the angle that defines the eye's degree of wide-angle vision), instead of using the zProj distance. Figure 2 illustrates both fov and zProj defined in a side view of the projection reference system.

Figure 2. A side view of the projection reference system.
Figure 2. A side view of the projection reference system.

Using this strategy for calculating the value of zProj:

zProj = 1 / tg (fov/2)

The scaling constants become:

K1 = 1 / (aspect*tg(fov/2))
K2 = 1 / tg(fov/2)
K3 = zFar / (zFar – zNear)

You can use a simple extension of the Matrix3D class developed by Adobe to help with this process. Download the PerspectiveMatrix3D class; it's close to the official version at the time of this writing.

After downloading the package, review the PerspectiveMatrix3D class. It implements a few simple functions that create the perspective matrix transform needed for this project, with the correct values for the constants K1, K2 and K3:

PerspectiveMatrix3D::perspectiveFieldOfViewLHPerspectiveMatrix3D::perspectiveFieldOfViewRH

Throughout this tutorial, I’ve been using a world coordinate system where x points to the right, y points up, and positive z enters the screen, which is a left handed coordinate system. Therefore I’ll use the LH flavor of the matrix function.

Using PerspectiveMatrix3D, the process of creating a perspective matrix that's suitable for Stage3D is very simple because it just requires you to define a few parameters. For example you could use the code below to set the values of the required variables:

var aspect:Number = 4/3;
var zNear:Number = 0.1;
var zFar:Number = 1000;
var fov:Number = 45*Math.PI/180;
var projectionTransform:PerspectiveMatrix3D = new PerspectiveMatrix3D();
projectionTransform.perspectiveFieldOfViewLH(fov, aspect, zNear, zFar);

As described above, zNear and zFar are the near and far clipping planes respectively; aspect is the aspect ratio and fov is the field of view angle.

Building a perspective projection sample application

In this section you'll use this matrix to create a simple update to the sample application I had provided in my previous Developer Center tutorial titled: Hello Triangle. If you haven't already completed the tutorials in the series prior to this one, please follow along with the instructions in that tutorial to learn how to build the basis of the sample project you'll extend in the steps below.

In this example, instead of working with a triangle, you'll render a rectangle so that it’s easier to notice the effect of perspective.

Begin by appending (pre-multiplying) the projection matrix to the queue of matrix transforms that are used to position and rotate the object. The code below also adds a different spin to the rotations, so that the perspective is visible.

var m:Matrix3D = new Matrix3D();
m.appendRotation(getTimer()/30, Vector3D.Y_AXIS);
m.appendRotation(getTimer()/10, Vector3D.X_AXIS);
m.appendTranslation(0, 0, 2);
m.append(projectionTransform);

Here is the entire code sample used in the perspective projection demo application:

public class PerspectiveProjection extends Sprite{
[Embed( source = "RockSmooth.jpg" )]
protected const TextureBitmap:Class;
protected var context3D:Context3D;
protected var vertexbuffer:VertexBuffer3D;
protected var indexBuffer:IndexBuffer3D;
protected var program:Program3D;
protected var texture:Texture;
protected var projectionTransform:PerspectiveMatrix3D;
public function PerspectiveProjection() {
stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill );
stage.stage3Ds[0].requestContext3D();
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
addEventListener(Event.ENTER_FRAME, onRender);
} protected function initMolehill(e:Event):void {
context3D = stage.stage3Ds[0].context3D;
context3D.configureBackBuffer(800, 600, 1, true);
var vertices:Vector.<Number> = Vector.<Number>([
-0.3,-0.3,0, 0, 0, // x, y, z, u, v
-0.3, 0.3, 0, 0, 1,
0.3, 0.3, 0, 1, 1,
0.3, -0.3, 0, 1, 0]);
// 4 vertices, of 5 Numbers each
vertexbuffer = context3D.createVertexBuffer(4, 5);
// offset 0, 4 vertices
vertexbuffer.uploadFromVector(vertices, 0, 4);
// total of 6 indices. 2 triangles by 3 vertices each
indexBuffer = context3D.createIndexBuffer(6);
// offset 0, count 6
indexBuffer.uploadFromVector (Vector.<uint>([0, 1, 2, 2, 3, 0]), 0, 6);
var bitmap:Bitmap = new TextureBitmap();
texture = context3D.createTexture(bitmap.bitmapData.width, bitmap.bitmapData.height, Context3DTextureFormat.BGRA, false);
texture.uploadFromBitmapData(bitmap.bitmapData);
var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
"m44 op, va0, vc0\n" + // pos to clipspace
"mov v0, va1" // copy uv
);
var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler();
fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
"tex ft1, v0, fs0 <2d,linear,nomip>\n" +
"mov oc, ft1" );
program = context3D.createProgram();
program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode); projectionTransform = new PerspectiveMatrix3D();
var aspect:Number = 4/3;
var zNear:Number = 0.1;
var zFar:Number = 1000;
var fov:Number = 45*Math.PI/180;
projectionTransform.perspectiveFieldOfViewLH(fov, aspect, zNear, zFar); } protected function onRender(e:Event):void {
if ( !context3D ) return;
context3D.clear ( 1, 1, 1, 1 );
// vertex position to attribute register 0
context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // uv coordinates to attribute register 1 context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_2); // assign texture to texture sampler 0 context3D.setTextureAt(0, texture); // assign shader program context3D.setProgram(program); var m:Matrix3D = new Matrix3D(); m.appendRotation(getTimer()/30, Vector3D.Y_AXIS); m.appendRotation(getTimer()/10, Vector3D.X_AXIS); m.appendTranslation(0, 0, 2); m.append(projectionTransform); context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true); context3D.drawTriangles(indexBuffer); context3D.present(); }}

Where to go from here

In this tutorial, you learned about one of the most important topics of 3d rendering: perspective projection. Now that you have a better understanding of what perspective is and how to implement it within Stage3D, proceed to the next tutorial in the series. In the next tutorial in the series, you'll learn how to work with 3D Cameras and see how to implement a point of view that moves around in the 3D scene.

For more information on perspective projection and 3D related math, I recommend a great book by James M. Van Verth and Lars M. Bishop called "Essential Mathematics for Games and Interactive Applications."


ㅁㄴㅇ 


Comments