블로그 이미지
래머
오늘도 열심히 개발하는 개발자입니다.

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

2014. 5. 2. 01:27 알고리즘

버전 1.2, 2003년 2월

목차 [숨기기]

업데이트 정보

버전 1.2

Quat에서 Axis으로 변환하는 공식을 조금 수정했습니다. 스케일을 구하는데 제곱근을 안했거든여! 지적해 준 Shi씨에게 감사드립니다.

버전 1.0 에서 1.1로

4원수의 놈(norm)은 q.q의 제곱근을 취해서 구해야 합니다. 이 사실을 많은 독자분들이 지적해주셨습니다. 그래서 제가 복소수의 유클리드(Euclid) 요소의 정의를 확인한 후에 다음과 같은 놈 속성이 앞의 크기의 정의와 모순된다는 사실을 알았습니다.

	|| u+v || <= || u || + || v ||

샘플코드도 이것을 반영했습니다.

처음에

저는 쿼터니언이라는 용어가 마치 비밀스러운 어둠의 힘을 가지고 있는 암흑물체에 관한 양자론 용어처럼 느껴집니다. 여러분도 역시 이 어둠의 힘에 매료되었다면 이 글이 도움이 될 것입니다(그렇게 되기를 바랍니다). 이 글은 쿼터니언을 이용해서 회전을 하는 방법을 보여주어 쿼터니언을 좀더 쉽게 이해하는데 도움을 줄 것입니다. 만약 여러분이 글에서 잘못된 내용을 발견하시면 robin@cyberversion.com로 메일을 보내주세요. 또, 이 기사를 여러분의 사이트에 실으려고 하신다면 저에게 메일을 보내주세요. 저는 저의 글이 어디에 퍼져있는지 알고 싶답니다.

왜 쿼터니언을 사용할까?

이 질문에 대답하기 위해서 우선 방향을 나타내는 일반적인 방법에 대해 논의해 보도록 합시다.

오일러 표현

이것이 현재까지 알려진 방법중 방향을 나타내는 가장 간단한 방법입니다. 오일러 표현은 각각의 축마다 축주위의 회전량을 가지고 있습니다. 따라서, 다음과 같은 0도에서 360도(혹은 0~2π)의 범위에서 변하는 3개의 값을 가집니다.

	x, y, z     <-- 전역 좌표축 주위의 회전 각도

이 값은 각각 롤, 피치, 요(혹은 피치, 롤, 요, 등 등)를 표현합니다. 방향은 3개의 각도로부터 생성되는 3개의 회전 행렬을 지정한 순서대로 곱해서 구할수 있습니다.

Note: 회전은 전역 좌표계의 좌표축을 기준으로 회전합니다. 즉 최초의 회전이 그 후의 2번째 3번째의 회전축에 영향을 주지 않습니다. 이 때문에 짐벌락(gimbal lock) 문제가 발생하게 됩니다. 짐벌락에 대해서는 조금 후에 자세히 설명하겠습니다.

회전축과 각도(Axis Angle)에 의한 표현

이 방법은 짐벌락을 피할수 있으므로 오일러각 방법보다 좀더 낫습니다. 회전축과 각도에 의한 표현은 임의의 회전축을 나타내는 단위 벡터와 단위 벡터 주위의 회전을 나타내는(0~360) 값으로 구성됩니다.

	x, y, z     <-- 임의의 축을 나타내는 단위 벡터
angle <-- 바로 윗줄에서 정의한 축 주위로 회전 각도

왜 이 방법이 나쁠까요?

짐벌락

오일러 표현에 있어서 회전은 전역 좌표계에서 일어나기 때문에 한 축의 회전이 다른 축의 회전과 겹치는(override) 문제가 발생합니다. 따라서 회전을 할 수 있는 축 하나를 잃게 됩니다. 이것을 짐벌락이라고 합니다.

예를 들어, X축과 평행한 어떤 벡터를 Y축주위로 회전해서 그 벡터가 Z축과 평행하게 되었다고 하면 이 때, Z축주위로 아무리 회전시켜도, 벡터의 방향은 전혀 변하지 않게 됩니다.

나중에 여러분에게 짐벌락의 예와 쿼터니언을 사용해서 짐벌락을 해결하는 방법을 보여드리겠습니다.

보간 문제

회전축 표현이 짐벌락 문제로 부터 자유롭긴 하지만 두개의 회전을 보간하는 경우, 또 다른 문제가 발생합니다. 보간계산을 마친 회전들이 부드럽지 못하여 회전 애니메이션이 삑사리가 날 수도 있습니다. 오일러 표현도 마찬가지로 이 문제를 가지고 있습니다.

자. 쿼터니언으로 빠져 봅시다 !

시작하기에 앞서, 몇가지 가정을 세우겠습니다. 저는 수학적인 이해가 중요시되는 글에서 수학적인 내용들을 대충 생략해버리는 글들에 진절머리가 납니다. 이런 글들은 독자들을 혼란에 빠뜨리기 쉽상입니다.

좌표계 - 이 글은 OpenGL과 같은 오른손 좌표계를 사용합니다. 만약 여러분이 Direct3D 같은 왼손 좌표계를 사용하고 계시다면 행렬들을 전치(transpose)하셔야 합니다. 이미 Direct3D의 샘플들은 쿼터니언 라이브리러를 가지고 있다는 사실에 유념해주시기 바랍니다. 그럼에도 불구하고 저는 여러분이 그 라이브러리를 사용하시기 전에 그 내부가 어떻게 구성되는지를 한번 짚고 넘어갔으면 하는 바램입니다.

회전순서 - 오일러 표현에서 회전 순서는 X->Y->Z의 순입니다. 행렬 형태로 아래와 같이 표현합니다.

	RotX * RotY * RotZ      <-- 매우 중요

행렬 - 행렬은 OpenGL 처럼 열우선 방식으로 합니다.

	   예 [ 0  4  8  12
1 5 9 13
2 6 10 14
3 7 11 15 ]


벡터와 점 - 변환을 위해 벡터와 점은 4x1의 행렬로 표현합니다. 다음과 같은 모습입니다.

	   회전 행렬 * [ vx
vy
vz
1 ] <-- 4x1 벡터

저는 특히 Direct3D보다 OpenGL을 선호하지는 않습니다. 그저 제가 OpenGL을 먼저 배웠고, 쿼터니언도 OpenGL을 통해 익혔을 뿐입니다.

Note: 만약 X->Y->Z 순서가 아닌 다른 회전 순서를 지정하면 몇몇 쿼터니언의 함수들을 다시 구현해야 합니다. 특히 오일러 표현을 다루는 함수들이 그렇습니다.

쿼터니언이란 무엇인가?

복소수는 i라는 기호를 사용하여 정의하는 허수(가상의 수)입니다. i는 i * i = -1 라는 성질을 가지고 있습니다.

쿼터니언은 복소수의 확장입니다. i만 사용하는 것이 아니라 제곱근이 ―1 이 되는 3개의 허수를 가집니다. 이 3개의 수는 보통 i, j, k로 표기합니다. 다시 말해 이것은 다음과 같은 성질을 가집니다.

	j * j = -1
k * k = -1

따라서 쿼터니언을 아래와 같이 표현할 수 있습니다.

	q = w + xi + yj + zk

여기서 w는 실수, x, y, z는 복소수입니다.

흔히 사용하는 또 다른 표현은 아래처럼 벡터형태로 표현할수 있습니다.

	q = [ w, v ]

여기서 v = (x, y, z)는 "벡터"라고 말하며 w는 "스칼라"입니다.

v를 벡터라고 부르지만 이것은 일반적인 3차원 벡터가 아닌 4 차원 공간상에 벡터를 표현한 것으로 직관적으로 시각화할 수 없습니다.

항등 쿼터니언

벡터와 다르게 2개의 항등 쿼터니언이 있습니다.

곱 항등 쿼터니언은 아래 처럼 표현합니다.

	q = [1, (0, 0, 0)]

그래서 이 곱 항등 쿼터니언과 곱해진 어떤 쿼터니언도 변하지 않습니다.

가산 항등 쿼터니언은 (저희는 사용하지 않습니다)은 아래처럼 표현합니다.

	q = [0, (0, 0, 0)]

방향 표현으로 쿼터니언 사용하기

우선 저는 쿼터니언이 벡터가 아니라는 사실을 말씀드리고 싶습니다. 따라서 여기서 벡터 수학을 사용하지 말아주십시요.

이 부분은 매우 수학적인 내용을 다룰 것입니다. 참을성을 가지고 제 설명을 읽어주세요.

우선 쿼터니언의 크기를 정의합니다.

	|| q || = Norm(q) = sqrt(w2 + x2 + y2 + z2)

단위 쿼터니언은 아래와 같은 속성을 가지고 있습니다.

	w2 + x2 + y2 + z2 = 1

그 때문에 쿼터니언의 정규화는 아래와 같이 구합니다

	q = q / || q || = q / sqrt(w2 + x2 + y2 + z2)

이 단위 쿼터니언은 특별합니다. 왜냐하면 단위 쿼터니언은 3D 공간에서 방향을 표현할 수 있기 때문입니다. 따라서 앞에서 논의된 두가지 방법 대신에 단위 쿼터니언을 통해 방향을 표현할 수 있습니다. 단위 쿼터니언으로 방향을 표현하려면 단위 쿼터니언을 다른 표현(예: 행렬)으로 변환하거나 반대로 변환하는 방법이 필요합니다. 이것에 대해서는 곧 설명드리겠습니다.

단위 쿼터니언 시각화 하기

단위 쿼터니언은 (x, y, z) 요소를 임의의 축, w요소를 회전 각도로 하는 4차원 공간상의 회전으로서 시각화할 수 있습니다. 모든 단위 쿼터니언들은 4D 공간에서 단위 길이를 가지는 구를 형성하게 됩니다. 다시한번 이게 무슨 소리지 하고 여러분은 직관적으로 이해가 안되실것입니다. 하지만 제가 정말 말하고 싶은 것은 쿼터니언의 스칼라 요소(w)의 부호를 반전시키는 것만으로 180도 회전한 쿼터니언을 얻을 수 있다는 것입니다.

Note: 단위 쿼터니언만을 방향표현에 사용할 수 있습니다. 이 뒤에 논할 모든 내용은 모두 단위 쿼터니언을 사용한다고 가정합니다.

쿼터니언으로부터의 변환

효과적으로 쿼터니언을 사용하려면 결국 쿼터니언을 다른 표현으로 변환해야 할 필요가 있을 것입니다. 키 눌림을 쿼터니언으로 해석할 수는 없지 않습니까? 할수 있으신 분 계신가요? 글쎄여. 아직까지는 없는 듯 하죠?

쿼터니언에서 행렬로의 변환

OpenGL와 DirectX는 행렬로 회전을 표현하기 때문에 쿼터니언->행렬 변환이 아마 가장 중요한 변환 함수일 것입니다. 왜냐하면 동차 행렬이 3D의 기본 표현이기 떄문입니다.

쿼터니언 회전과 동등한 회전 행렬은 다음과 같습니다.

	행렬 =  [ w2 + x2 - y2 - z2	2xy - 2wz		2xz + 2wy
2xy + 2wz w2 - x2 + y2 - z2 2yz - 2wx
2xz - 2wy 2yz + 2wx w2 - x2 - y2 + z2 ]

w2 + x2 + y2 + z2 = 1이 되는 단위 쿼터니언의 속성을 사용하면 위 식을 다음과 같이 간단하게 만들 수 있습니다.

	행렬 = [ 1 - 2y2 - 2z2	2xy - 2wz	2xz + 2wy
2xy + 2wz 1 - 2x2 - 2z2 2yz - 2wx
2xz - 2wy 2yz + 2wx 1 - 2x2 - 2y2 ]

쿼터니언으로부터 회전축과 각도에 의한 표현으로 변환

한 쿼터니언을 삼차원 공간에서의 임의축 주위의 회전축과 각도에 의한 표현으로 변환하는 방법은 다음과 같습니다.

	회전축이		(ax, ay, az)이고
각도가 theta (라디안)이면
각도는 angle= 2 * acos(w)가 됩니다.

그 때
ax= x / scale
ay= y / scale
az= z / scale 이 됩니다.

scale은 scale = sqrt (x2 + y2 + z2)입니다..

제가 알고 있는 다른 방법은 scale = sin(acos(w))을 사용하는 것입니다. 저 스스로 수학적으로 동치관계를 증명하려고 하지는 않았지만 두 방법 다 결과는 같을 것이라 생각합니다.

어쨌든, scale이 0이라면 회전이 없다는 뜻입니다. 그리고 여러분이 특별한 조치를 취하지 않는다면 회전축이 무한대가 됩니다. 따라서 scale이 0인 경우에는 언제나 회전각이 0인 임의의 단위벡터를 그 축에 설정하시면 됩니다.

간단한 예

제가 설명하려고 하는 것들에 대해서 뭐가 뭔지 모르겠다 하시는 독자분들을 위해 이제부터 간단한 예를 보여드리겠습니다.

우선 카메라 방향을 오일러 각으로 표현한다고 해봅시다. 그러면 렌더링 루프에서 다음과 같은 식을 이용하여 카메라를 위치시킵니다.

	RotateX * RotateY * RotateZ * Translate

이 때 각각의 요소는 4x4 행렬입니다.

이제 단위 쿼터니언을 사용해서 카메라의 방향을 표현하려면 먼저 쿼터니언을 행렬로 변환해야 합니다. 그러면

	(쿼터니언으로부터 변환한 회전 행렬) Rotate * Translate

같은 것이 생기게 됩니다

OpenGL에 특화한 예는 다음과 같게 될것입니다.

오일러쿼터니언

glRotatef( angleX, 1, 0, 0)
glRotatef( angleY, 0, 1, 0)
glRotatef( angleZ, 0, 0, 1)
// 평행이동

// 오일러를 쿼터니언으로 변환
// 쿼터니언을 회전축과 각도로 변환
glRotate(theta, ax, ay, az)
// 평행이동

위의 표현은 모두 같습니다. 제가 말할려고 하는 것은 방향에 쿼터니언을 사용하는 것은 오일러나 회전축과 각도에 의한 표현과 완전히 같고, 앞서 언급한 변환 함수를 통해 상호교환이 가능하다라는 것입니다.

다만, 위의 쿼터니언에 의한 표현에는 오일러 표현법과 마찬가지로 짐벌락의 위험성이 있습니다.

역자주 – "왜 짐벌락 위험성이 있지"라는 답을 글내용상 알수가 없습니다. 


물론, 아직은 회전을 쿼터니언으로 만드는 방법을 모르시겠지만 그것은 아래 부분에서 설명하겠습니다.

Note: Direct3D나 OpenGL를 사용하시는 독자분들은 API가 행렬 연결을 처리해주기 때문에 직접 행렬을 취급하는 일은 없겠지만, 이것에 대해서 알아둘 필요가 있습니다.

쿼터니언 곱

단위쿼터니언이 3D공간에서의 한 방향을 표현하기 때문에 2개의 단위 쿼터니언간의 곱은 2개의 단위 쿼터니언의 회전을 결합한 회전을 나타내는 단위 쿼터니언이 됩니다. 놀라운 일이지만, 사실입니다.

다음과 같은 2개의 쿼터니안이 있다고 가정해봅시다.

	Q1=(w1, x1, y1, z1);
Q2=(w2, x2, y2, z2);

이 2개의 단위 쿼터니언을 결합한 회전은 아래와 같이 구해집니다

	Q1 * Q2 =( w1.w2 - v1.v2, w1.v2 + w2.v1 + v1*v2)

이 때 ,

	v1 = (x1, y1, z1)
v2 = (x2, y2, z2)

이 됩니다.

여기서 .와 *은 내적과 외적을 나타냅니다.

하지만 위 식을 아래와 같이 최적화할 수 있습니다.

	w = w1w2 - x1x2 - y1y2 - z1z2
x = w1x2 + x1w2 + y1z2 - z1y2
y = w1y2 + y1w2 + z1x2 - x1z2
z = w1z2 + z1w2 + x1y2 - y1x2

물론, 여타 쿼터니언들과 마찬가지로 결과 단위 쿼터니언을 다른 회전 표현으로 변환하는 것도 가능합니다. 이런 점이 바로 쿼터니언의 진정한 미학입니다. 2개의 단위 쿼터니언은 구 상에 위치하기 때문에 4차원 공간에서 이들을 곱하는 방법은 짐벌락의 문제를 해결합니다.

여기서 곱하는 순서가 중요합니다. 쿼터니언의 곱은 교환칙이 성립되지 않습니다. 즉 이것은 아래의 식을 의미합니다.

	q1 * q2 ≠ q2 * q1

Note: 2개의 쿼터니언은 동일한 좌표계 축을 참조해야 합니다. 저는 서로 다른 좌표계에서 2개의 쿼터니언을 합성하는 실수를 범한 적이 있고, 이 때 그 결과 쿼터니언이 특정 각도에서만 실패하는 이유를 알아내기 위해 많은 시간을 고민했습니다.

쿼티니언으로의 변환

이제 다른 표현법을 쿼터니언으로 변환하는 방법을 배워봅시다. 저는 샘플 프로그램에 있는 모든 변환들을 사용하지는 않지만 역운동학 등의 보다 진보된 용도로 쿼터니언 방향을 사용하려고 할 때 이것들이 필요할 것입니다.

회전축과 각도(Angle Axis)로부터 쿼터니언으로 변환

3D공간에서의 임의의 회전축을 도는 회전은 아래처럼 쿼터니언으로 변환됩니다.

회전축이		(ax, ay, az) 이고 (반드시 단위 벡터여야 함)
회전 각도를 theta (라디안)이면

w = cos(theta/2)
x = ax * sin(theta/2)
y = ay * sin(theta/2)
z = az * sin(theta/2)이 됩니다.

우선 회전축이 정규화되어 있어야 합니다. 만약 정규화된 회전축 벡터의 길이가 0이라면 회전이 없는 것을 의미하므로 쿼터니언은 단위(identity) 쿼터니언으로 설정되어야 합니다.

오일러로부터 쿼터니언으로 변환

오일러로부터 쿼터니언으로의 변환은 회전순서가 올바르지 않으면 안 되기 때문에 조금더 힘듭니다. 임의축으로부터 3개의 좌표축을 분해하면 오일러 각을 3개의 독립적인 쿼터니언으로 변환해서 이 3개의 쿼터니언을 곱하면 최종 쿼터니언을 얻을 수 있습니다.

따라서 오일러 각 (a, b, c)으로 세개의 독립적인 쿼터니언을 만들수 있으며

	Qx = [ cos(a/2), (sin(a/2), 0, 0)]
Qy = [ cos(b/2), (0, sin(b/2), 0)]
Qz = [ cos(c/2), (0, 0, sin(c/2))]

최종 쿼터니언은 Qx * Qy * Qz로 구할수 있습니다.

역자주 –이것은 오일러 회전 순서가 x->y->z의 순서인 경우에만 해당됩니다. 다른 순서로 된다면  Qx * Qy * Qz이 다르게 표현됩니다

데모 - 짐벌락 피하기

드디어 "어떻게 쿼터니언이 짐벌락을 피할수 있지?"에 대해서 모두가 기다렸던 궁금증에 대답할 때가 왔습니다.

기본적인 아이디어는

  1. 회전을 표현하는 데 쿼터니언을 사용한다.
  2. 현재 방향에서 새로운 방향으로 변경하기 위해 하나의 임시 쿼터니언을 생성한다.
  3. 임시 쿼터니언과 원래의 쿼터니언을 곱한다. 이렇게 하면 양쪽 모두의 회전을 합성한 새로운 방향이 얻어진다.
  4. 이 결과 쿼터니언을 행렬로 변환해서 평소처럼 행렬 곱을 사용한다.

우선, 저는 샘플 코드에 대해서는 어떠한 책임도 지지 않겠습니다. 이 코드는 난잡스러고 제대로 구성되어 있지도 않습니다. 이것은 쿼터니언 테스트할 때 제 프로그램에서 사용했던 쿼터니언 코드를 간략히 줄여놓은 버전일 뿐입니다. 따라서 코드에 대해서 크게 신경을 쓰지 않았다는 점만 기억해 주셨으면 좋겠습니다(돈받고 파는 코드가 아니니까요 ^^)

저는 2개의 실행 가능한 샘플을 준비했습니다. 첫번째 프로그램인 CameraEuler.exe (http://www.gamedev.net/reference/programming/features/qpowers/CameraEuler.zip)는 오일러 각을 사용해서 카메라를 구현한 예입니다.

여러분이 주의깊게 봐야 할 부분은 main.cpp의 Main_Lopp 함수입니다.

여러분이 while 문에서 눈여겨 봐야할 곳은 다음과 같습니다.

  1. X축, Y축, Z축 각각의 회전을, 3개의 각도로 보관 유지 부분
  2. 키를 누르면 관련된 회전량을 조정하는 부분
  3. while 문에서, 3 개의 오일러 각도를 회전 행렬로 변환해서 그것을 최종 변환 행렬에 곱하는 부분

위/아래 화살표 키는 X축의 회전, 왼쪽/오른쪽 화살표 키는 Y축회전, Insert/PageUp 키는 Z축회전을 담당합니다.

이 프로그램은, 짐벌락을 일으킵니다. 여러분이 짐벌락 현상을 보고 싶으면, 요를 90도로 취하고 X축이나 Z축회전을 시도해 보십시오. 그리고 무슨일이 일어나는지 살펴보세요.

지금부터는 쿼터니언으로 짐벌락 문제를 해결한 프로그램을 살펴봅시다. 이 프로그램은 CameraQuat.exe (http://www.gamedev.net/reference/programming/features/qpowers/CameraQuat.zip)이며 앞의 프로그램을 조금 변경한 프로그램입니다.

여러분이 while문에서 눈여겨봐야하는 곳은 다음과 같습니다.

  1. 카메라 방향을 쿼터니언으로 표현한 부분
  2. 키 입력으로부터 3개의 각도를 얻는 부분. 이 3개의 각도는 on/off 스윗치이며, 누적되지 않는 다는 것에 주의하십시오. 저는 while문에 이것들을 리셋했습니다. 물론 이것이 최선의 방법은 아닙니다. 그냥 신속히 처리하기 위해서 그랬을 뿐입니다.
  3. 3개의 각도를 임시 쿼터니언으로 변환하는 부분
  4. 임시 쿼터니언과 카메라 쿼터니언을 곱해서 합성한 방향을 얻는 부분. 곱 순서에 주의.
  5. 이렇게 해 얻은 카메라 회전을 최종 변환 행렬을 위해 회전축과 각도에 의한 표현으로 변환하는 부분.

키가 눌러졌을 때, 저는 키 입력에 대응하는 고유의 축에서의 작은 회전을 나타내는 임시 쿼터니언을 생성했습니다. 그리고 임시 쿼터니언과 카메라 쿼터니언을 곱했습니다. 이 4차원 공간에서의 회전을 합성한 것이 짐벌락을 피하게 해줍니다. 여러분이 직접 이것을 해보고 자신의 눈으로 확인해주세요.

카메라 쿼터니언은 최종 변환 행렬로 합성할 수 있도록 행렬이나 그에 상응하는 형태로 변환되야 합니다. 여러분이 쿼터니언을 사용할 때, 3차원 공간과 4차원 공간은 섞일 수가 없기 때문에 항상 이 작업을 해주셔야 합니다. OpenGL의 경우에 저는 그냥 쿼터니언으로부터 회전축을 변경하기만 했고 나머지는 API에게 위임했습니다.

제가 두번째 프로그램에서 오일러 각을 회전에 사용하지 않았지만 저는 여러분이 첫번째 프로그램에서의 오일러 회전에 대해서 볼 수 있게 하기 위해서 오일러 회전 부분을 남겨 두었습니다. 오일러 각은 하나 이상의 축으로 회전시키면 올바르게 되지 않을 것입니다. 왜냐하면 쿼터니언이 카메라 쿼터니언으로부터 오일러 각을 얻는 대신 키 입력을 통해서 계산하기 때문입니다. 두번째 프로그램은 프로그램을 시작했을 때 그냥 여러분이 요 값을 90도로 회전하려는 경우에 짐벌락이 더 이상 일어나지 않는다는 것을 보기 위해서 만든 참고자료일뿐입니다.

Note: 저는 여러분에게 저의 수학 라이브러리를 사용하라고 추천하고 싶지 않습니다. 여러분이 쿼터니언을 이해하고 나서 스스로 짜 보십시오. 참고로 저는 이 코드를 전부 버리고 다시 만들 예정입니다. 이 코드들은 저에게 있어서 너무나 지저분하고 난잡한 코드입니다.

제가 보여드리지 않은 것

여러분이 이미 눈치채셔겠지만 저는 쿼터니언을 오일러 각으로 변환하는 방법에 대해서는 설명하지 않았습니다. 그 이유는 아직까지도 완벽하게 동작하는 변환을 제가 알아내지 못했기 때문입니다. 제가 알고 있는 유일한 방법은 쿼터니언으로부터 행렬을 얻고, 그 행렬로부터 오일러 각을 추출하는 것입니다. 그러나, 오일러에서 행렬 변환은 다대일 관계(sin과 cos으로 인해)이기 때문에 저는 atan2를 사용해서 그 역을 구하는 방법을 알지 못합니다. 만약 정확하게 행렬로부터 오일러 각을 추출하는 방법을 알고 계시다면 저에게 알려주세요.

제가 보여드리지 않은 다른 내용은 행렬을 쿼터니언으로 변환하는 방법입니다. 여러분이 오일러 표현과 회전축과 각도에 의한 표현을 쿼터니언으로 변환할 때 행렬을 거치지 않고도 직접변환이 가능하므로 굳이 행렬을 쿼터니언으로 변환할 필요가 없기 때문입니다.

과제 - SLERP

여러분이 쿼터니언을 완전히 이해했다고 생각하신다면 다시 한번 생각해 보십시오. 아직도 쿼터니언에 대해서 더 배워야 할 게 남아 있습니다. 제가 전에 회전축과 각도(Axis Angle)에 의한 표현이 왜 나쁠까라고 말했던 것을 기억하시나요? '보간'이라는 단어가 갑자기 떠오르지 않나요?

저는 쿼터니언을 사용한 보간에 대해서 설명한 시간을 갖지 못했습니다. 이 글은 제가 예상한 것보다 시간이 오래 걸렸습니다. 여기서 SLERP(구면 선형 보간)에 대한 기본적인 아이디어를 드리겠습니다. 이것은 기본적으로 2개의 쿼터니언 방향 사이에 일련의 쿼터니언들을 생성합니다. 일련의 쿼터니언들은 처음과 마지막 쿼터니언사이에서 부드러운 모션(움직임)의 결과를 제공합니다. 오일러와 회전축 표현으로는 일관성있게 이러한 부드러운 움직임을 만들 수 없습니다.

마지막으로

저는 이 기사가 쿼터니언 이론 뒤에 숨겨진 알수 없는 미스테리들을 속시원하게 없애주었길 바랍니다. 다시한번 마지막으로 여러분에게 당부하고 싶은 말은 서로 다른 좌표계에서 2개의 쿼터니언을 곱하지 말아주십시오. 이렇게 하시면 여러분은 고통의 시간을 맛보시게 될것입니다.

그러면 새로 찾아낸 쿼터니언의 능력에 여러분이 기뻐하시길 바라면서 저는 이제 그만 여러분과 헤어질까 합니다. 몸조심하시구여. 그리구 다시 만나뵙기를 바라면서...

원문정보

  • 저자: Sobeit Void
  • 원문보기: Quaternion Powers  (http://www.gamedev.net/reference/articles/article1095.asp)

번역문 정보

현재상태

  • 초벌번역시작 (2005. 8. 2)
  • 초벌번역완료 (2005. 8. 7)
  • 재벌번역시작 (2005. 8. 9)
  • 재벌번역완료 (2005. 8. 12)
  • 감수완료 (2005. 8. 12)
http://www.galexandria.com/doc/index.php/%EC%BF%BC%ED%84%B0%EB%8B%88%EC%96%B8%EC%9D%98_%EB%8A%A5%EB%A0%A5#.EC.99.9C_.EC.BF.BC.ED.84.B0.EB.8B.88.EC.96.B8.EC.9D.84_.EC.82.AC.EC.9A.A9.ED.95.A0.EA.B9.8C.3F



출처 : Tong - joonchuri님의 기본통



'알고리즘' 카테고리의 다른 글

A* 알고리즘 구현해보기  (0) 2014.05.04
A* 알고리즘이란?  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:23 C/C++

보통 클래스를 만들면 소멸자를 virtual(가상)함수로 만들게 됩니다.
남들이 다 하닌까.. 따라 하는 사람도 많습니다.
하지만 정확한 이유를 아는 것이 중요합니다.

클래스에서 virtual 함수를 가상함수라고 합니다.
가상함수를 두는 이유는 자신을 상속한 자손 클래스에서 해당 기능을 별도로 구현할때 주로 씁니다.

class TSoccerBall
{
;
virutal void PaintBall();
;
};

위는 축구공을 다루는 클래스라고 가정하고 축구 공을 그리는 부분은 PaintBall() 함수에서 한다고 치십다.
그런데 이 클래스가 그리는 축구공은 2002년형 피파 공인구 입니다. 이를 2006년형 공인구 모양으로 바꾸어
그리고 싶습니다.
물론 TSoccerBall 클래스의 여타 기능은 손댈 필요가 없는 상황입니다.
바로 그런 경우를 대비해서 원 설계자가 필요하면 다시 그리라고 함수를 virtual 로 선언해 놓은 것입니다.

class TSoccerBall2006 : public TSoccerBall
{
;
virutal void PaintBall();
;
virtual ~TSoccerBall2006();
};

이렇게 상속을 받아서 PaintBall() 함수를 새로 만들어 2006형 공으로 그려주면 됩니다.
원래 TSoccerBall  클래스에서는 어떤 상황에서 볼의 모양을 그리도록 내부에서 호출을 하는데
TSoccerBall2006 클래스가 상속받은 후에는 새롭게 만들어진 PaintBall() 함수를 호출하게 바뀝니다.
(물론, 굳이 virtual 로 안해도 이렇게 상속받아 새 공모양을 그리면 되지만, virtual을 쓰게 되면
조상 클래스의 포인트를 가지고도 후손 클래스의 virtual 함수를 부르게 되므로
TSoccerBall 클래스의 객체에서 PaintBall() 을 불러도 2006형 공 모양이 그려지게 됩니다)

이렇게 되면 원래 2002년형 볼을 그리도록 되어 있던 부분은 호출되지 않겠지요.
물론 TSoccerBall::PaintBall(); 로 억지로 호출할 수는 있지만, 새로운 공 모양만을 사용한다면
한번도 호출할 필요가 없어지고 그 코드는 사실 쓸모가 없게 됩니다.
이렇게 되면 이는 필요없는 코드 즉 오버헤드(overhead)가 되는 것입니다.

TSoccerBall2006 클래스는 TSoccerBall의 모든 기능을 이어 받으면서도 공모양만 바꾸었으므로
가상 함수는 정말로 유용한 것이라 아니할 수 없습니다.
그리고 2010년에 또 다른 공모양을 그리라고 새로 만든 PaintBall() 함수도 virtual 로 선언했습니다.

이런 식으로 가상함수를 만드는 것은 오버라이드(override)한다고 합니다.
인자만 다르고 함수 이름이 같은 것을 오버로딩(overloading)이라고 하는 것과 발음은 비슷해도
뜻이 완전히 틀리니 주의 하시기를...

가상함수라는 것은 위에서 잠시 봤듯이 오버헤드를 감수할만큼의 좋은 특징이 있기 때문에
너무나 흔하게 주변에서 접하는 소스에서 virtual 을 볼수 있습니다.

여기까지는 대부분 아시는 사실일 테고요.

다음은 왜 소멸자는 항상 virtual 로 만들까? 하는 문제에 대해 생각해 보겠습니다.
물론 소멸자를 virtual 로 만들지 않는다고 누가 뭐라고하는 사람 없습니다.
어디까지나 플머 마음입니다.

하지만 virtual로 하지 않으면 안되는 이유가 있습니다.
위의 예에서

TSoccerBall2006  *p2006 = new TSoccerBall2006;
TSoccerBall *p = p2006;
와 비슷한 형태로 p 가 자손 객체의 포인트를 가지고 있고
나중에
delete p;
로 객체를 소멸할때
소멸자가 virtual 이 아니면 결코 TSoccerBall2006  의 소멸자는 불려지지 않습니다.
바로 이런 문제 때문에 상속관계에 있고 리소스를 해제해야하는
소멸자는  거의 virtual 로 선언되는 것입니다.

여기서 반드시 알아야 하는 것은 생성과 소멸의 순서입니다.
생성의 과정은 반드시 조상 클래스의 생성자가 먼저 실행되고
소멸의 과정은 virtual 인 경우 자손 클래스의 소멸자가 먼저 실행됩니다.
물론 virtual 이 아니어도 자손의 소멸자가 먼저 실행되지만,
위처럼 조상 클래스 포인트가 자손 객체를 가르키는 경우의 객체 소멸시에도
virtual 인 경우는 소멸자가 안전하게 먼저 불려지게 됩니다.
이 때문에 소멸자는 거의 virtual 을 붙이게 되는 것입니다.

여러분들도 소멸자는 적절하게 상황을 판단해 virtual 을 붙이는게 좋습니다.

또 하나 기억할 것은,
일반 가상함수는 최종적으로 override된 자손의 함수만이 실행되지만
소멸자의 경우는 virtual 일지라도 모든 클래스의 소멸자가 불려진다는 사실을 알아야 합니다.
그러므로, 따로 조상 소멸자의 호출은 필요 없습니다.


너무 간단히 설명해서 초입자 분들은 조금 알아 듣기 힘들지도 모르겠는데
공부하다 보면 무슨 뜻이지 알게 될 것입니다.

그럼..





posted by 래머
2014. 5. 2. 01:22 C/C++


작성자 : 김태선  (jsdkts)

흔히 배열로 할당받은 메모리는 delete[] 로 해제하고, 단일 객체로 할당받은 메모리는 delete 로 해제한다고 알고 있습니다.
이것이 정답이며, 이외의 꽁수를 부리는 것은 문제를 발생시킵니다.
습관적으로 늘 이렇게 짝을 맞춰서 사용했지만, 그래도 정확하게 문제를 아는 것이 중요하다고 생각됩니다.

우선 delete 와 delete[]의 차이를 한마디로 말하라면
delete 는 단일 객체에 대한 소멸자의 호출과 메모리 환원을 하며
delete[] 는 배열객체에 대한 각각의 소멸자의 호출과 메모리 환원을 한다는 것입니다.

그러므로
ClassA  *pp = new ClassA;
delete pp;
는 아무런 문제가 없으나
ClassA  *pp = new ClassA[10];
delete pp;
과 같이 하는 경우는 pp[0]에 대한 소멸자와 메모리 해제만 이루어 집니다.
반드시
ClassA  *pp = new ClassA[10];
delete[] pp;
와 같이 해야 합니다. 그렇지 않으면 메모리 누수가 발생합니다.

혹자는 그냥 delete 만 써도 아무런 문제가 없다라고 말합니다.
이는 프로그램 종료시 할당받았던 전체 메모리가 OS에 의해 해제되기 때문입니다.
즉 메모리에 민감한 프로그램이 아닌 경우이겠지만, 정밀한 프로그램에서는 반드시 짝을 맞춰야 합니다.

실제 눈으로 확인을 해 봅시다.

다음은 테스트코드입니다. C++빌더의 테스트코드이긴 하지만 이해에 어려움은 없을 것입니다.

void    add(void *ptr)
{
    ULONG  p = (ULONG)ptr;
    Form1->Memo1->Lines->Add(String().sprintf("%08X", p));
}

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    class A
    {
    private:
        void *p;
    public:
        A(void *ptr = 0)
       {
            p = ptr;
            add(this);  // 메모리 할당 받은 직후 자신의 메모리 베이스 어드레스를 표시하게 합니다.
        }
        ~A()
        {
            int k = 0;      // for trace
        }
    };

    A  *p = new A[2];

    delete[] p;        //***** 이 라인이 문제입니다.

    A  *p1 = new A;
    delete p1;
}


위 코드는 아무런 문제가 없습니다.

제 컴퓨터에 실행해서 힙 메모리 할당 번지를 보면

00D16D00   // p[0] 꺼
00D16D04   // p[1] 꺼
00D16D0C  // p1 꺼
이렇게 주소가 나옵니다.

여러번 실행해도 같은 주소만 계속 나옵니다. 메모리 할당과 해제가 완전하게 이루어진다는 뜻이죠.

그러면  delete[] p; 문장을 delete p; 로 바꾸고 실행해 보겠습니다.
처음 3개는 위와 같습니다.
00D16D00
00D16D04
00D16D0C
두번째로 함수가 다시 실행할때는 해제되지 못한 메모리로 인해 그 뒷번지에서 할당 받습니다.
00D16D10
00D16D14
00D16D1C
세번째도 마찬가지입니다. 이런식으로 메모리 잠식이 일어납니다.
00D16D20
00D16D24
00D16D2C

프로그램을 종료하고 다시 실행해 봅니다.
역시 위와 똑 같은 메모리 번지가 출력됩니다. 해제되지 못한 메모리가 OS에 되돌려졌다는 것을 알수 있습니다.

그럼..



posted by 래머
2014. 5. 2. 01:19 알고리즘


1. A* 알고리즘이란?

 

A* 알고리즘은 초기노드(시작지점)에서 목표 노드(목표지점)까지의 경로를 찾는 그래프 탐색 알고리즘이다. 다른 그래프 탐색 알고리즘과 다른 점은 목표에 얼마나 근접한 것인지를 평가하는데 휴리스틱 함수를 사용한다는 것이다.

 

 

2. 알고리즘 순서

 

(1) 시작지점을 열린목록(Openlist)에 넣는다.

(2) 열린목록에 있는 노드 중 1개를 빼서 여덟 방향 주변노드를 탐색한다.
( 평가함수 F= G+H 를 계산 & 부모노드를 명시, 장애물과 닫힌목록은 제외한다.)
F= G+H 설명
G : 시작노드N0에서 현재노드N까지의 최단경로의 값. (수직,수평 +1, 대각선일 경우 +1.41)
H : 현재노드N에서 목표노드까지의 (조건없는)최단경로의 값. (휴리스틱요소)
F :  시작노드N0에서 목표노드까지 현재노드N을 통해 갈 수 있는 모든 가능한 경로 중  최단경로의 값이 된다.

(3) 2단계에서 뺀 노드를 닫힌목록(Closelist)에 삽입한다

(4) 2단계에서 탐색한 노드들을 열린 목록(우선순위 queue)에 삽입한다
(우선순위 큐는 가장 작은 값부터 순서대로 자동정렬되는 특성을 가지기 때문에 가장 낮은 F비용을 가진 노드를 찾을 수 있다.)

(5) 열린 목록 중 가장 앞 노드를 빼고 그 노드를 닫힌 목록에 추가한다.

(6) 5단계에서 뺀 노드의 여덟방향 주요 노드를 탐색한다.
(장애물과 닫힌목록제외, 목표노드가 있는지 조사)
(끝내는 경우: 1. 만일 목표노드가 있다면 부모노드를 추적하여 역순으로 스택에 삽입 2. 열린목록이 비어있게 될 경우 목표노드를 찾는데 실패한 것, 길이 없음.)

(7) 열린목록에 존재하지 않는 노드는 열린목록에 추가하고
중복되는 노드는 G값을 서로비교하여 더 작은 값을 열린 목록으로 교체한다.

(8) 5단계부터 반복

[출처] A* 알고리즘|작성자 김도완


'알고리즘' 카테고리의 다른 글

A* 알고리즘 구현해보기  (0) 2014.05.04
쿼터니언  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:18 C/C++



hlsl 함수 비공개 3d관련 자료

2009/07/16 01:30 수정 삭제

작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130064811122

DirectX HLSL함수

놀 것 2008/01/29 03:11
absvalue abs(value a)절대치 (성분마다).
acosacos(x)x 의 각 성분의 역코사인을 돌려준다. 각 성분은,[-1, 1] 의 범위로 한다.
allall(x)x 의 모든 성분이 0 이외의 값인지 아닌지를 테스트한다.
anyany(x)x 의 몇개의 성분이 0 이외의 값인지 아닌지를 테스트한다.
asinasin(x)x 의 각 성분의 역정현을 돌려준다. 각 성분은,[-pi/2, pi/2] 의 범위로 한다.
atanatan(x)x 의 각 성분의 역탄젠트를 돌려준다. 반환값은,[-pi/2, pi/2] 의 범위이다.
atan2atan2(y, x)y/x 의 역탄젠트를 돌려준다. y 와 x 의 부호를 사용해 [-pi, pi] 의 범위에 있는 반환값의 상한을 판단한다. atan2 는, x 가 0 으로 동일하고, y 가 0 으로 동일하지 않은 경우에서도, 원점 이외의 각 점에 대해서 충분히 정의되고 있다.
ceilceil(x)x 이상의 최소의 정수를 돌려준다.
clampclamp(x, min, max)x 를 [min, max] 의 범위에 제한한다.
clipclip(x)x 의 몇개의 성분이 0 보다 작은 경우, 현재의 픽셀을 파기한다. x 의 각 성분이 면으로부터의 거리를 나타내는 경우, 이 함수를 사용해, 클립면을 시뮬레이션 한다.
coscos(x)x 의 코사인을 돌려준다.
coshcosh(x)x 의 쌍곡코사인을 돌려준다.
crosscross(a, b)2 개의 3D 벡터 a 와 b 의 외적을 돌려준다.
D3DCOLORtoUBYTE4D3DCOLORtoUBYTE4(x)4D 벡터 x 의 성분을 교체 및 스케일링 해, 일부 하드웨어에 있는 UBYTE4 지원의 부족을 보정한다.
ddxddx(x)스크린 공간의 x 좌표에 대해, x 의 편미분을 돌려준다.
ddyddy(x)스크린 공간의 y 좌표에 대해, x 의 편미분을 돌려준다.
degreesdegrees(x)x 를 라디안 단위로부터 도수로 변환한다.
determinantdeterminant(m)서방 행렬 m 의 행렬식을 돌려준다.
distancedistance(a, b)2 개의 점 a 와 b 간의 거리를 돌려준다.
dotdot(a, b)2 개의 벡터 a 와 b 의 내적을 돌려준다.
expexp(x)e 를 바닥으로 하는 지수 ex 를 돌려준다.
exp2value exp2(value a)2 를 바닥으로 하는 지수 (성분마다).
faceforwardfaceforward(n, i, ng)-n * sign(dot(i, ng))를 돌려준다.
floorfloor(x)x 이하의 최대의 정수를 돌려준다.
fmodfmod(a, b)a = i * b + f 가 되는 것 같은, a / b 의 부동 소수점수(실수)의 잉여 f 를 돌려준다. 여기서, i 는 정수, f 는 x 와 부호가 같아, 그 절대치는 b 의 절대치보다 작다.
fracfrac(x)f 가 0 보다 크고, 1 보다 작은 값이 되는 것 같은, x 의 소수부 f 를 돌려준다.
frcvalue frc(value a)소수부 (성분마다).
frexpfrexp(x, out exp)x 의 가수와 지수를 돌려준다. frexp 는 가수를 돌려주어, 지수는 출력 인수 exp 에 저장 된다. x 가 0 의 경우, 함수는 가수와 지수의 양쪽 모두에 0 을 돌려준다.
fwidthfwidth(x)abs(ddx(x)) +abs(ddy(x))를 돌려준다.
isfiniteisfinite(x)x 가 유한의 경우는 TRUE 를 돌려준다. 그 이외의 경우는 FALSE 를 돌려준다.
isinfisinf(x)x 가 +INF 나 -INF 의 경우는 TRUE 를 돌려준다. 그 이외의 경우는 FALSE 를 돌려준다.
isnanisnan(x)x 가 NAN 나 QNAN 의 경우는 TRUE 를 돌려준다. 그 이외의 경우는 FALSE 를 돌려준다.
ldexpldexp(x, exp)x * 2exp 를 돌려준다.
lenfloat len(value a)벡터의 길이.
lengthlength(v)벡터 v 의 길이를 돌려준다.
lerplerp(a, b, s)a + s(b - a)를 돌려준다. 이 함수는, s 가 0 의 경우는 a 를 돌려주어, 1 의 경우는 b 를 돌려주도록, a 와 b 의 사이를 선형 보간 한다.
litlit(ndotl, ndoth, m)조명의 벡터 (앰비언트, 디퓨즈, 스펙큐러, 1)를 돌려준다. 앰비언트 = 1; 디퓨즈 = (ndotl < 0) ? 0 : ndotl; 스펙큐러 = (ndotl < 0) || (ndoth < 0) ? 0 : (ndoth * m);
loglog(x)x 의, 바닥이 e 의 자연대수를 돌려준다. x 가 부의 경우, 이 함수는 무한을 돌려준다. x 가 0 의 경우, +INF 를 돌려준다.
log10log10(x)x 의, 바닥이 10 의 자연대수를 돌려준다. x 가 부의 경우, 이 함수는 무한을 돌려준다. x 가 0 의 경우, +INF 를 돌려준다.
log2log2(x)x 의, 바닥이 2 의 자연대수를 돌려준다. x 가 부의 경우, 이 함수는 무한을 돌려준다. x 가 0 의 경우, +INF 를 돌려준다.
maxmax(a, b)a 와 b 의 큰 (분)편을 선택한다.
minmin(a, b)a 와 b 가 작은 (분)편을 선택한다.
modfmodf(x, out ip)값 x 를, 각각이 x 와 같은 부호를 가진 소수부와 정수부로 나눈다. x 의 부호 첨부 소수부가 반환된다. 정수부는 출력 인수 ip 에 저장 된다.
mulmul(a, b)a 와 b 의 사이의 행렬 곱셈을 실행한다. a 가 벡터의 경우, 행 벡터로서 처리한다. b 가 벡터의 경우, 열로서 처리한다. 내부 넓이의 a 열과 b 행은 동일해야 한다. a 행 x b 열의 넓이를 얻을 수 있다.
noisenoise(x)처리되지 않다.
normalizenormalize(v)정규화된 벡터 v / length(v)를 돌려준다. v 의 길이가 0 의 경우, 결과는 무한이 된다.
powpow(x, y)xy 를 돌려준다.
radiansradians(x)x 를 도수로부터 라디안 단위로 변환한다.
reflectreflect(i, n)입사 방향 i, 표면 법선 n 로 했을 경우의, v = i - 2 * dot(i, n) * n 에 의해 구할 수 있는, 반사 벡터 v 를 돌려준다.
refractrefract(i, n, eta)입사 방향 i, 표면 법선 n, 굴절 eta 의 상대 인덱스가 주어졌을 경우의, 굴절 벡터 v 를 돌려준다. i 와 n 의 사이의 입사각이 지정된 eta 보다 너무 크면 (0,0,0)를 돌려준다.
roundround(x)x 를 가장 가까운 정수에 말다.
rsqrtrsqrt(x)1 / sqrt(x)를 돌려준다.
saturatesaturate(x)x 를 [0, 1] 의 범위에 제한한다.
signsign(x)x 의 부호를 요구한다. x 가 0 보다 작은 경우는 -1, 0 으로 동일한 경우는 0, 0 보다 큰 경우는 1 을 돌려준다.
sinsin(x)x 의 정현을 돌려준다.
sincossincos(x, out s, out c)x 의 정현과 코사인을 돌려준다. sin(x)는 출력 인수 s 에 저장 되어 cos(x)는 출력 인수 c 에 저장 된다.
sinhsinh(x)x 의 쌍곡정현을 돌려준다.
smoothstepsmoothstep(min, max, x)x < min 의 경우는 0 을 돌려준다. x > max 의 경우는 1 을 돌려준다. x 가 [min, max] 의 범위내이면, 0 으로 1 의 사이의 매끄러운 에르미트 보간을 돌려준다.
sqrtvalue sqrt(value a)제곱근 (성분마다).
stepstep(a, x)(x >= a) ? 1 : 0 을 돌려준다.
tantan(x) x 의 탄젠트를 돌려준다.
tanhtanh(x)x 의 쌍곡탄젠트를 돌려준다.
tex1Dtex1D(s, t)1D 의 텍스처 참조. s 는 샘플러 또는 sampler1D 개체. t 는 스칼라-.
tex1Dtex1D(s, t, ddx, ddy)미분을 지정한, 1D 의 텍스처 참조. s 는 샘플러 또는 sampler1D 개체. t, ddx, ddy 는 스칼라-.
tex1Dprojtex1Dproj(s, t)1D 의 투영 텍스처 참조. s 는 샘플러 또는 sampler1D 개체. t 는 4D 벡터. t 는, 참조가 실행되기 직전의 성분으로 나눗셈 된다.
tex1Dbiastex1Dbias(s, t)1D 의 바이어스 텍스처 참조. s 는 샘플러 또는 sampler1D 개체. t 는 4D 벡터. 참조를 실행하기 전에, 밉레벨에 t.w 의 바이어스를 걸칠 수 있다.
tex2Dtex2D(s, t)2D 의 텍스처 참조. s 는 샘플러 또는 sampler2D 개체. t 는 2D 텍스처 좌표.
tex2Dtex2D(s, t, ddx, ddy)미분을 지정한, 2D 의 텍스처 참조. s 는 샘플러 또는 sampler2D 개체. t, ddx, ddy 는 2D 벡터.
tex2Dprojtex2Dproj(s, t)2D 의 투영 텍스처 참조. s 는 샘플러 또는 sampler2D 개체. t 는 4D 벡터. t 는, 참조가 실행되기 직전의 성분으로 나눗셈 된다.
tex2Dbiastex2Dbias(s, t)2D 의 바이어스 텍스처 참조. s 는 샘플러 또는 sampler2D 개체. t 는 4D 벡터. 참조를 실행하기 전에, 밉레벨에 t.w 의 바이어스를 걸칠 수 있다.
tex3Dtex3D(s, t)3D 의 볼륨 텍스처 참조. s 는 샘플러 또는 sampler3D 개체. t 는 3D 텍스처 좌표.
tex3Dtex3D(s, t, ddx, ddy)미분을 지정한, 3D 의 볼륨 텍스처 참조. s 는 샘플러 또는 sampler3D 개체. t, ddx, ddy 는 3D 벡터.
tex3Dprojtex3Dproj(s, t)3D 의 투영 볼륨 텍스처 참조. s 는 샘플러 또는 sampler3D 개체. t 는 4D 벡터. t 는, 참조가 실행되기 직전의 성분으로 나눗셈 된다.
tex3Dbiastex3Dbias(s, t)3D 의 바이어스 텍스처 참조. s 는 샘플러 또는 sampler3D 개체. t 는 4D 벡터. 참조를 실행하기 전에, 밉레벨에 t.w 의 바이어스를 걸칠 수 있다.
texCUBEtexCUBE(s, t)3D 의 큐브 텍스처 참조. s 는 샘플러 또는 samplerCUBE 개체. t 는 3D 텍스처 좌표.
texCUBEtexCUBE(s, t, ddx, ddy)미분을 지정한, 3D 의 큐브 텍스처 참조. s 는 샘플러 또는 samplerCUBE 개체. t, ddx, ddy 는 3D 벡터.
texCUBEprojtexCUBEproj(s, t)3D 투영의 큐브 텍스처 참조. s 는 샘플러 또는 samplerCUBE 개체. t 는 4D 벡터. t 는, 참조가 실행되기 직전의 성분으로 나눗셈 된다.
texCUBEbiastexCUBEbias(s, t)3D 의 바이어스 큐브 텍스처 참조. s 는 샘플러 또는 samplerCUBE 개체. t 는 4D 벡터. 참조를 실행하기 전에, 밉레벨에 t.w 의 바이어스를 걸칠 수 있다.
transposetranspose(m)행렬 m 의 전치행렬을 돌려준다. 입력의 넓이가 m 행 x m 열의 경우, 결과는 넓이 m 열 x m 행이 된다.





posted by 래머
2014. 5. 2. 01:17 C/C++



[본문스크랩] [번역] Light Space Perspective Shadow Maps [LisPSM] 비공개 3d관련 자료

2009/12/13 16:19 수정 삭제

작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130075701859

출처 꿈★은 이루어진다 | 주바리
원문 http://blog.naver.com/xtar/70038803883

Light Space Perspective Shadow Maps

Michael Wimmer, Daniel Scherzer and Werner Purgathofer


원문: http://www.cg.tuwien.ac.at/research/vr/lispsm
번역: http://starlike.cafe24.com/moniwiki/wiki.php/LightSpacePerspectiveShadowMaps
개요: LisPSM 기본 이론

1 소개
2 이전의 작업들
3 Light space perspective shadow maps
3.1 동기
3.2 개요
3.3 Focussing the shadow map
3.4 The perspective frustum in light space
3.5 Choosing the free parameter n
3.6 Applying the perspective frustum
4 Analysis and optimal parameter estimation
4.1 What is perspective aliasing
4.2 Logarithmic shadow mapping
4.3 Analysis of light space perspective shadow maps
4.4 General case
4.5 Discussion
5 결과
6 결론과 이후의 작업들
7 참고문헌



 




'C/C++' 카테고리의 다른 글

delete 와 delete[] 의 차이점  (0) 2014.05.02
hlsl 함수  (0) 2014.05.02
액티브x배포관련  (0) 2014.05.02
ActiveX 권한 상승에 대한 일반적인 이야기와 솔루션들 [펌]  (0) 2014.05.02
[본문스크랩] [MSSQL] 함수  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:16 C/C++

이미 여러 블로그 아티클로도 잘 다뤄진 내용이지만 몇 가지 부수적인 내용을 덧붙여서 ActiveX 패키징 및 배포에 관한 내용을 총 정리해보았습니다. 실무에서는 아직도 ActiveX 컨트롤에 관한 유지보수를 틈틈이 수행할 필요가 있다보니 이런 문서를 작성하게 되는 것 같습니다. :-)

1. INF 파일에 대한 이해

INF 파일은 ActiveX 패키지 배포 시 내장되는 설치 정보 파일로 보통 배포되는 CAB 파일의 이름과 동일하게 맞추어서 배포합니다. 예를 들어 MyControl.cab을 배포한다면 MyControl.inf 파일을 내장하고 있어야 하는 것입니다.

그리고 INF 파일의 문법적인 구조에 대한 상세한 설명이 없어서 상당히 답답한 때가 많습니다만 간략하게 두 가지 사례를 짚어서 설명해보도록 하겠습니다.

a. Hooking 없이 설치하는 경우

Hooking이란 설치 도중에 별도의 외부 프로세스를 이용하여 설치 자체를 자동화하거나 설치 전/후 과정을 제어하기 위한 일종의 옵션 기능입니다. ActiveX에 수많은 보안 결함이 존재하지만 디자인적으로 보았을 때에도 충분히 문제가 드러납니다. 대부분 간단한 유형의 ActiveX 컨트롤은 Hooking 기능 없이 배포가 되는 편입니다. 다음의 샘플 INF 코드를 살펴보기로 합니다.

접기

[version]
signature="$CHICAGO$
AdvancedINF=2.0

[Add.Code]
SecureMail.ocx=SecureMail.ocx
libeay32.dll=libeay32.dll
clientcert.pem=clientcert.pem
clientkey.pem=clientkey.pem

[clientkey.pem]
file-win32-x86=thiscab
Destdir=11

[clientcert.pem]
file-win32-x86=thiscab
Destdir=11

[libeay32.dll]
file-win32-x86=thiscab
Destdir=11

[SecureMail.ocx]
file-win32-x86=thiscab
clsid={6BBDF9C9-AF4E-45CE-B543-159C9E19577B}
RegisterServer=yes
FileVersion=2,0,0,2
Destdir=11

접기

흔히 볼 수 있는 형태의 INF 파일입니다. 제일 상단에 있는 것이 INF 파일의 형식에 관한 기본 정보이고 Windows 95 이후로부터는 대체로 Advanced INF 2.0 규격을 따르는 것 같네요. (시그니처에 Windows 95의 코드 네임인 Chicago가 적혀있는 것으로 보았을 때도 그러합니다.)

하단의 [Add.Code] 섹션에서는 이 CAB 파일을 통해서 배포될 파일들을 기술합니다. 편의 상 파일 이름과 엔트리 이름을 일치시켜두는 것도 좋습니다. 그리고 각각의 파일명으로 기술된 섹션이 해당 파일을 어떤 식으로 복사되고 관리되어야 하는지를 서술하는 부분으로 이후에 설치 제거를 할 때 근거 정보가 됩니다.

각 섹션 별로 file-win32-x86 이라는 엔트리 명이 보이는데 파일의 플랫폼과 대상 아키텍처를 지정하는 부분입니다. 옛날 (NT 시절)에는 Alpha나 MIPS 계열 프로세서를 위하여, 요즈음에는 x64 (AMD64) 프로세서를 위하여 이 부분이 달리 할당될 수도 있을 것입니다. 그리고 thiscab 이라는 값은 이 CAB 파일 내에 파일이 있음을 뜻하는 부분입니다.

DestDir 엔트리는 이 파일이 어느 위치로 복사되어야 하는지를 나타내는 부분으로 많은 옵션이 있을것 처럼 보이지만 실제로 알려진 것은 세 종류인듯 합니다. 값을 10으로 지정하면 %windir% 경로 (흔히 C:\Windows)에, 11로 지정하면 %windir%\system32 (Windows 9x 계열 운영체제에서는 %windir%\system)에, 비워두면 OCCACHE 디렉터리 (%windir%\Downloaded Program Files)에 복사됩니다.

그리고 이제 OCX 파일 섹션의 내용을 살펴보기로 합니다. OCX 파일의 내용에서 꼭 확인해야 할 것은 RegisterServer=yes 항목과 Version 항목, 그리고 clsid 항목입니다. Version은 OCX 파일을 빌드할 때 추가되는 Win32 리소스의 버전 테이블 정보와 일치할 필요가 있으며, clsid 항목은 실제로 브라우저에서 인스턴스화되어 실행되어야 하는 COCLASS의 CLSID와 일치해야 합니다.

b. Hooking의 경우

더보기

ActiveX를 이용하여 대리 설치 프로그램을 기동시키기 위하여 위와 같이 작성할 수도 있지만, 컴포넌트별로 Hook을 다르게 지정할 수도 있으니 참고하시기 바랍니다. Hook을 실행하기 위하여 %EXTRACT_DIR% 라는 지역 환경 변수를 지정한 것을 볼 수 있고 프로그램 매개 변수까지 지정이 가능한 것을 보실 수 있습니다.

Hook에서 지정한 프로그램의 경우 주의 사항이 한 가지 있다면, 프로그램의 실행 종료 코드가 0으로 끝날 필요가 있다는 것입니다. 그렇지 않으면 설치가 중간에 실패한 것으로 인지되어 브라우저에는 아무것도 표시되지 않는다고 합니다. 이를 미리 확인해보려면 패키징 하기 전에 Hook을 통해서 구동하려는 프로그램을 똑같이 명령 프롬프트에서 아래와 같이 실행하면 알 수 있습니다.

START /WAIT "실행할 프로그램 및 매개 변수들"
ECHO %ERRORLEVEL%

%ERRORLEVEL% 환경 변수의 값이 0이 아닌 것으로 나타나면 Hook 프로그램으로는 사용할 수 없을 것입니다.

2. PVK 파일과 SPC 인증서 파일을 PFX 파일로 변환하기

SIGNCODE 도구에서 SIGNTOOL 도구로 새롭게 바뀐 이후부터, ActiveX CAB 파일에 서명을 하거나 EXE 파일 위에 서명을 하는 방법이 조금 바뀌었습니다. 바로 PFX 파일을 생성하는 작업이 추가 된 것인데, 기존에 가지고 있는 인증서 파일 형식이 PVK 파일과 SPC 파일 두 가지로 구성된 경우 아래와 같이 변환해야 합니다.

PVK2PFX -PVK "PVK 파일의 경로" -SPC "SPC 파일의 경로" -PFX "PFX 파일을 만들 경로" -PI "기존 인증서의 Password" -F

PVK2PFX 유틸리티는 .NET Framework SDK v2.0 혹은 Windows SDK v6.0 이상을 설치하면 같이 따라오는 유틸리티입니다. 여기서 -PVK 스위치 다음에 PVK 파일의 경로를, -SPC 스위치 다음에 SPC 파일의 경로를 서술해주어야 하고, 그 다음 -PFX 스위치 다음에 새로 만들 PFX 파일의 경로를 서술해야 합니다. 추가적으로 인증서로부터 암호를 해독하고 PFX 파일에 암호를 새로 설정하기 위하여 -PI 스위치 뒤에 인증서 암호를 기술합니다. 기본 동작이 기존 파일이 있을 경우 실패로 처리되는데 편의상 -F 스위치를 지정하여 PFX 파일을 덮어쓰도록 실행합니다.

3. CAB 파일에 서명을 할 때에는 반드시 Spanning Space를 확보할 것

CAB 파일에 서명을 추가하기 위해서는 별도의 Spanning Space를 확보해야 하는데 이를 위해서는 CABARC 유틸리티로 압축해야 합니다. (다른 압축 유틸리티에서는 지원하지 않을 수도 있습니다.)

CABARC -S 6144 N "생성할 CAB 파일의 경로" "파일1" "파일2" ... "파일n"

-S 스위치가 Spanning Space 확보를 지시하는 스위치로 이 뒤에 Magic Constant 값 6144를 지정합니다. 이것이 인증서 크기에 관한 Magic Constant로 문서화된 수치이므로 그대로 사용하면 됩니다. 새로운 파일을 만들어야 하므로 N 스위치를 지정하여 모드를 변경하고, 그 다음에는 CAB 파일이 생성될 경로, 그리고 그 다음부터는 CAB 파일에 포함할 파일들을 하나씩 서술하면 됩니다. 와일드 카드도 지원하므로 편리합니다.

4. 실제로 서명하고 확인하기

SIGNCODE 프로그램보다 기능이 다양하고 정교해진 SIGNTOOL을 이용해서 서명을 하려면 다음의 스위치 설정을 활용합니다.

SIGNTOOL SIGN /F "PFX 파일 경로" /P "PFX 파일 암호" /T "타임스탬프 URL" "대상 파일 1" "대상 파일 2" ... "대상 파일 n"

SIGNTOOL 프로그램을 서명 모드로 실행하기 위하여 SIGN 스위치를 지정하고, /F 스위치에는 2단계에서 만든 PFX 파일, 그리고 /P 스위치에는 2단계에서 지정한 인증서 암호를 입력합니다. /T 스위치 뒤에는 실제로 접속 가능한 타임스탬프 URL을 지정해야 하는데 이는 인증서 발행 업체가 제공하는 고유 URL을 이용하면 됩니다. 마지막으로 대상 파일들을 지정하면 한꺼번에 동일한 인증서로 서명할 수 있습니다.

CAB 파일을 제외한 보통의 EXE, DLL, OCX 등에 직접 서명하는 것은 별도로 빌드할 설정해주어야 할 특별 옵션같은것들은 존재하지 않습니다.

좀 더 자세한 내용을 보시려면 아래 Article들도 참고하시면 좋겠습니다.

http://ditongs.egloos.com/1511212
http://littletrue.egloos.com/3968904




posted by 래머
2014. 5. 2. 01:15 C/C++



ActiveX 권한 상승에 대한 일반적인 이야기와 솔루션들 [펌] 비공개 액티브x

2009/12/27 19:08 수정 삭제

작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130076764234

 ActiveX 권한 상승에 대한 일반적인 이야기와 솔루션들  | ActiveX/COM2009-10-20 오전 11:02:44
남정현 (rkttu2002)   번호: 8380 추천:4  / 읽음:898
BLOCKQUOTE { MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px } P { MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px } v\:* { BEHAVIOR: url(#default#VML) } o\:* { BEHAVIOR: url(#default#VML) } .shape { BEHAVIOR: url(#default#VML) } x\:* { POSITION: relative; VISIBILITY: hidden } TD { WORD-BREAK: break-all } BODY { WORD-WRAP: break-word } UL { MARGIN-TOP: 5pt; MARGIN-BOTTOM: 5pt; MARGIN-LEFT: 30pt } OL { MARGIN-TOP: 5pt; MARGIN-BOTTOM: 5pt; MARGIN-LEFT: 30pt }

인사말

 

안녕하세요. 데브피아 C# 포럼에서 활동 중인 남정현 인사드립니다. 예전에 Visual C++ 포럼에서 자주 곁눈질로 코드를 배우고 연습했던 추억이 새록새록 떠오르네요. 지금은 C#으로 주로 프로그래밍을 하고 있지만 Visual C++을 통하여 프로그래밍을 하거나, 이슈를 고민하는 일도 아직 많이 있다보니 블로그에 Visual C++ 관련 포스트를 가끔 올립니다.

 

이제 어느정도 Windows Vista나 Windows 7에 관한 UAC 문제가 해결되셨을 것으로 알고 있습니다만 Visual C++ 포럼에 인사도 올릴겸 블로그 포스트 내용을 발췌하여 올립니다. 혹시 수정해야 할 부분이나 잘못된 부분이 있으면 많은 지적과 첨삭 부탁드립니다.

 

감사합니다. :-)

 

데브피아 C# 포럼 시삽 남정현 올림

 


 

최근 고객사의 요청으로 ActiveX 컨트롤 하나를 유지보수하고 있습니다. ActiveX 컨트롤에서 탈피하려는 추세가 있지만, 별 다른 대안이 없어서 ActiveX 컨트롤을 유지보수해야 하는 경우도 아직 우리나라에서는 상당히 많은것 같습니다.

 

Internet Explorer 7.0부터는 보호 모드라는 개념이 새로 소개되었습니다. 보호 모드란, 일종의 Sand-box 개념으로 기존과 같이 현재 로그온한 사용자의 권한을 그대로 물려받아 무분별하게 실행되는 것을 방어하는 안전 장치입니다. Windows XP와는 달리 Windows Vista부터는 일반 사용자를 단순히 관리자로 분류하지 않고, UAC를 통하여 작업에 대해 허가/거절 여부를 정할 수 있게 하였습니다.

 

우리가 권한 상승이라고 이야기하는 기능은 사실 권한 상승을 사용자에게 요청하는 것입니다. 이러한 권한 상승 요청을 구현하기 위하여 이제까지 참고할 수 있는 보편적인 리소스는 EXE 파일과 함께 매니페스트 파일을 배포하는 것이 대표적인 것이었습니다. 그리고 ActiveX 컨트롤의 경우 Elevation Moniker를 통하여 권한 할당을 받는 것이 대표적입니다.

 

ActiveX 컨트롤에서 어떻게 권한 상승을 구현할 수 있는지에 대하여 잘 정리한 권용휘 MVP님의 아티클을 참고하시면 어떻게 권한 상승이 이루어지고 관리될 수 있는지에 대한 컨셉을 확인하실 수 있습니다. (http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=7669) 저는 권용휘 MVP님의 아티클 위에 몇 가지 내용을 더 첨언해보고자 합니다.

 

1. Visual C++ 6.0 (SP6)에서의 권한 상승 구현

 

놀랍게도, 그리고 안타깝게도, Visual C++ 6.0 개발 도구를 업그레이드하지 못하는 이슈는 도처에 널려있습니다. 기존에 개발되어있던 소프트웨어나 라이브러리가 특정 문자 세트에 완벽하게 맞추어져있지만, 업그레이드할 수 없을만한 이슈 (개발 업체의 부도 - 또는 - 계약 해지 / 담당자의 연락 두절과 같은)로 인하여, 올해부터는 더 이상 일체의 기술 지원을 받을 수 없는 (서비스 팩 다운로드도 MS 공식 홈페이지에서는 더이상 받으실 수 없습니다.) 그런 개발 플랫폼위에서 고군분투해야 하는 상황은 생각보다 자주 있습니다.

 

사실 권한 상승을 제대로 프로그래밍하려면 Windows Vista나 Windows 7 SDK가 필요합니다. 하지만 이들 SDK의 코드를 가져다 사용하려면 개발 도구를 Visual C++ 2005나 2008로 업그레이드할 필요가 있습니다. 하지만 개발 도구나 Windows SDK를 설치하지 않고 간단히 적용할 수 있다고 소개된 방법은 다행히 Visual C++ 6.0에서 온전하게 동작합니다.

 

typedef struct tagBIND_OPTS3 : tagBIND_OPTS2 {
 HWND hwnd;
} BIND_OPTS3, *LPBIND_OPTS3;


위의 코드를 자주 사용하는 헤더에 선언해두면 권한 상승을 위하여 호출하는 CoGetObject에 정확히 바인딩할 수 있습니다. 그리고, 아래의 두 함수를 이용하여, 권한 상승을 지원하는 운영 체제 (Windows Vista 이상의 운영 체제)를 식별하고 실제로 객체를 생성할 수 있습니다.

 

HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, OUT void ** ppv)
{
 BIND_OPTS3 bo;
 OLECHAR wszCLSID[50];
 OLECHAR wszMonikerName[300];
 const int CHARS_IN_GUID = 39;

 StringFromGUID2(rclsid, wszCLSID, CHARS_IN_GUID);
 swprintf(wszMonikerName, L"Elevation:Administrator!new:%s", wszCLSID);
 wprintf(L"%s\r\n", wszCLSID);
 wprintf(L"%s\r\n", wszMonikerName);

 memset(&bo, 0, sizeof(bo));

 bo.cbStruct = sizeof(bo);
 bo.hwnd = hwnd;
 bo.dwClassContext = CLSCTX_LOCAL_SERVER;

 return CoGetObject((LPCWSTR)wszMonikerName, &bo, riid, ppv);
}

 

BOOL IsUACRequiredOperatingSystem(void)
{
 BOOL bIsUACRequired = FALSE;
 OSVERSIONINFO sInfo;
 sInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

 if (GetVersionEx(&sInfo))
 {
  if( sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
   sInfo.dwMajorVersion >= 6 &&
   sInfo.dwMinorVersion >= 0) { // Windows VISTA or Higher
   bIsUACRequired = TRUE;
  }
 }

 return bIsUACRequired;
}

 

Visual C++ 6.0 기반의 프로젝트들은 거의 대부분 ANSI나 Multibyte Character Set 기반의 문자 세트를 기준으로 프로그램이 구성되어있습니다. 확장성을 위하여 Transition이 가능한 데이터 타입 (LPTSTR, LPCTSTR, TCHAR 등)은 거의 고려하지 않았을 확률이 높습니다. 하지만 우리가 호출해야 할 API들은 유니코드 문자열을 필요로 하기 때문에 직접 유니코드를 사용하도록 기존 코드를 수정하였습니다. 그리고 더불어서, StringCchPrintf 함수를 대신하여 sprintf 계열의 함수를 직접 이용하였습니다.

 

2. 실제 ActiveX 컨트롤에 권한 상승을 적용하는 또 다른 방법

 

권용휘 MVP님의 아티클에서 설명하는 방법은, 특정 메서드나 프로퍼티의 호출 과정에서 필요로하는 권한 상승을, 권한 상승을 외부에서 요구하고 결과를 받기 위한 프록시 멤버와, 실제 처리하는 멤버로 이원화하여 구현하는 방식입니다. 대부분의 경우 이 방법으로 해결할 수 있다고 생각합니다. 하지만 간혹, ActiveX 컨트롤의 동작이나 상태, 속성을 정의하는 작업 전체가 시스템에 종속적으로 구성된 경우도 있을 수 있습니다. 이 경우에는, 격리모드에서 실행되는 ActiveX 컨트롤과는 별도로 새로운 인스턴스를 노출시켜야 할 필요가 있습니다.

 

이에 대한 솔루션은 인터넷 검색중에 발견한 어떤 블로그의 아티클에서 찾을 수 있었습니다. (http://hbesthee.tistory.com/620) 이 아티클은 델파이 기반의 솔루션을 제시하고 있었으며 저는 이것을 Visual C++ 기반의 코드로 옮겼습니다.

STDMETHODIMP CtrusEBANK::trusElevate(VARIANT *ret)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())

 if (IsUACRequiredOperatingSystem())
 {
  ItrusEBANK *pElevatedObj = NULL;
  HRESULT hr = CoCreateInstanceAsAdmin(NULL,
   CLSID_trusEBANK, IID_ItrusEBANK,
   (void **)&pElevatedObj);

  if (SUCCEEDED(hr))
  {
   ret->vt = VT_DISPATCH;
   ret->pdispVal = pElevatedObj;
   ret->pdispVal->AddRef();
   return S_OK;
  }
  else
  {
   ret->vt = VT_NULL;
   return hr;
  }
 }
 else
 {
  ret->vt = VT_BOOL;
  ret->boolVal = VARIANT_FALSE;
 }

 return S_OK;
}

 

STDMETHODIMP CtrusEBANK::trusNeedElevate(VARIANT *ret)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())

 ret->vt = VT_BOOL;

 if (::IsUACRequiredOperatingSystem())
  ret->boolVal = VARIANT_TRUE;
 else
  ret->boolVal = VARIANT_FALSE;

 return S_OK;
}

 

위의 코드에서는 trusNeedElevate를 이용하여 권한 상승이 지원되는 운영 체제인지 파악하는 부분과, trusElevate를 이용하여 권한 상승이 적용될 개체를 반환하는 방법 두 가지를 보여주고 있습니다. 그리고 아래의 코드는 자바스크립트에서의 실제 사용 예시입니다.

 

var obj = new ActiveXObject("EBANK.trusEBANK");

if (obj.trusNeedElevate())
{
 var result = obj.trusElevate();
 if (result) {
  obj = result;
 }
}

// 여기서부터 obj 객체 사용

 

이와 같은 방법을 통하여, 권한 상승이 온전하게 ActiveX 컨트롤에 적용될 수 있도록 하여, 원활한 동작 환경을 구현할 수 있을 것입니다.

 

3. Internet Explorer Host Window를 Elevation하는 방법 [업데이트]

 

그리고 최근에 저는 여러가지 방법을 적용해보던 중에 가장 현실적인 타협안 하나를 발견하였습니다. 바로, Internet Explorer Host Window 자체를 Runtime 도중에 Elevation 처리하는 방법으로, 기존의 응용프로그램 기반 매니페스트와 유사하게 작동합니다. 그리고 이 방법은, Host Window를 Elevation하는 데에 사용할 수 있는것 뿐만 아니라, 대리 실행해야 하는 응용프로그램의 Manifest 보유 여부에 관계없이 Elevation에도 사용될 수 있습니다.

 

참고로, 이 방법은 http://www.softblog.com/2008-02/vista-tools/ 에서 소개한 VistaTools.cxx 파일의 코드를 일부 발췌한 것임을 밝혀둡니다.

 

typedef struct _TOKEN_ELEVATION {
    DWORD TokenIsElevated;
} TOKEN_ELEVATION, *PTOKEN_ELEVATION;

 

위의 구조체는 Elevation 상태를 점검하기 위하여 필요한 구조체로 이미 Elevation 처리가 되어있는 호스트 위에서 실행되는 경우 Elevation을 다시 실행하지 않도록 하기 위하여 필요합니다. 이 구조체는 Visual C++ 6.0 기반에서 프로그램을 업데이트할 때 수동으로 지정해야 합니다.

 

#ifndef CSIDL_PROGRAM_FILES
  #define CSIDL_PROGRAM_FILES 0x0026
#endif // CSIDL_PROGRAM_FILES

 

위의 Special Folder Constant는 %PROGRAMFILES% 폴더의 경로를 가져오기 위하여 필요합니다. 마찬가지로 Visual C++ 6.0 기반에서 프로그램을 업데이트할 때 수동으로 지정해야 합니다.

 

#include <shellapi.h>
#include <shlobj.h>

 

마지막으로 위의 두 헤더 파일을 참조하도록 선언하면 일단 준비는 끝납니다.

 

BOOL IsVistaOrHigher(void)
{
 OSVERSIONINFO versionInfo;
 versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

 if (GetVersionEx(&versionInfo) &&
  versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
  versionInfo.dwMajorVersion >= 6)
  return TRUE;
 else
  return FALSE;
}

 

HRESULT IsElevated(BOOL *pElevated)
{
 HRESULT hResult = E_FAIL;
 HANDLE hToken = NULL;

 if (!IsVistaOrHigher())
  return hResult;

 if (!OpenProcessToken(
  GetCurrentProcess(),
  TOKEN_QUERY,
  &hToken))
  return hResult;

 TOKEN_ELEVATION te = { 0 };
 DWORD dwReturnLength = 0;
 const int TokenElevation = 20;

 if (GetTokenInformation(
  hToken,
  (TOKEN_INFORMATION_CLASS)TokenElevation,
  &te,
  sizeof(te),
  &dwReturnLength))
 {
  hResult = te.TokenIsElevated ? S_OK : S_FALSE;

  if (pElevated)
   *pElevated = (te.TokenIsElevated != 0);
 }

 CloseHandle(hToken);
 return hResult;
}

 

BOOL ShellExecWithVerb(HWND hWnd, LPCTSTR lpVerb, LPCTSTR lpPath, LPCTSTR lpParameters, LPCTSTR lpDirectory)
{
 SHELLEXECUTEINFO executeInfo;
 memset(&executeInfo, 0, sizeof(executeInfo));

 executeInfo.cbSize = sizeof(SHELLEXECUTEINFO);
 executeInfo.fMask = 0;
 executeInfo.hwnd = hWnd;
 executeInfo.lpVerb = lpVerb;
 executeInfo.lpFile = lpPath;
 executeInfo.lpParameters = lpParameters;
 executeInfo.lpDirectory = lpDirectory;
 executeInfo.nShow = SW_NORMAL;

 return ShellExecuteEx(&executeInfo);
}

 

BOOL ShellExecWithElevation(HWND hWnd, LPCTSTR lpPath, LPCTSTR lpParameters, LPCTSTR lpDirectory)
{
 return ShellExecWithVerb(hWnd, _T("runas"), lpPath, lpParameters, lpDirectory);
}

 

BOOL OpenUrlWithElevation(HWND hWnd, LPCTSTR lpUrl)
{
 _TCHAR lpBuffer[MAX_PATH + 1];

 if (!SHGetSpecialFolderPath(hWnd, lpBuffer, CSIDL_PROGRAM_FILES, 0))
  return FALSE;

 _tcscat(lpBuffer, _T("\\Internet Explorer\\iexplore.exe"));
 return ShellExecWithElevation(hWnd, lpBuffer, lpUrl, _T(""));
}

 

위의 코드의 내용들 중에서 강조표시된 것이 실제 Elevation을 위하여 필요한 코드입니다. 그리고 실제 사용법은 아래와 같습니다.

 

STDMETHODIMP CMyAXControl::RunElevatedWeb(VARIANT *url)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())
 USES_CONVERSION;

 CComBSTR targetUrl;
 url->vt = VT_BSTR;
 targetUrl = CComBSTR(url->bstrVal);

 _TCHAR buffer[MAX_PATH + 1];
 _tcscpy(buffer, (_TCHAR*)OLE2T(targetUrl.m_str));
 BOOL bResult = OpenUrlWithElevation(NULL, buffer);

 if (bResult)
  return S_OK;
 else
  return E_FAIL;
}

 

그리고 위의 메서드가 실제로 실행되어야 할 때와, 그렇지 않을 때를 구분하기 위하여 아래와 같이 상태 확인을 위한 메서드를 배치하는 것도 도움이 됩니다. :-)

 

STDMETHODIMP CMyAXControl::NeedElevate(VARIANT *ret)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())

 ret->vt = VT_I4;
 ret->intVal = 0;

 if (IsVistaOrHigher())
 {
  BOOL bResult = FALSE;

  if (SUCCEEDED(IsElevated(&bResult)))
  {
   if (bResult == TRUE)
    ret->intVal = 4; // 이미 Elevation이 완료됨
   else
    ret->intVal = 3; // Elevation이 필요함
  }
  else
   ret->intVal = 2; // 상태 정보를 조회할 수 없음
 }
 else
  ret->intVal = 1; // UAC가 지원되지 않는 운영체제로 판단함

 return S_OK;
}

 

위의 메서드를 통하여, 반환값이 1 - 또는 - 4로 반환되는 경우에 한정하여 실제로 필요한 코드를 실행하고, 그렇지 않은 경우 Elevation을 수행하도록 유도하는 코드를 웹에서 작성할 수 있을 것입니다.

 

덧) 지적하거나 수정이 필요한 부분이 있으시면 댓글로 남겨주시면 바로 반영하도록 하겠습니다. 감사합니다. :-)

 

출처: http://www.rkttu.com/





posted by 래머
2014. 5. 2. 01:14 C/C++



[본문스크랩] [MSSQL] 함수

2010/02/21 21:20 수정 삭제

작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130081002003

출처 맑밝인 | 맑밝인
원문 http://blog.naver.com/ballkiss/30025302748

MSSQL 에는 여러가지 함수가 있다.

숫자함수, 문자함수, 날짜함수, 시스템함수, 보안함수 등...

그치만 이걸 다 어케 외워.. -_-;;

자주 쓰는 함수만 알아보자..

 

1. 숫자에 대한 산술적 연산 함수

○ CEILING

  - 올림이다. 천장이란 뜻

○ FLOOR

  - 버림이다. 마루라는 뜻

○ RAND

  - 0과 1 사이의 임의의 부동 소수점 수. 랜덤한 숫자를 구할 때 쓴다.

○ ROUND

  - 반올림이다.

 

 

2. 문자에 대한 함수

○ LEFT

  - 왼쪽에서부터 주어진 길이만큼의 잘라낸다.

○ LEN

  - 주어진 문자의 길이

○ LOWER

  - 소문자로 변환한다.

○ LTRIM

  - 왼쪽에 있는 빈칸을 삭제한다.

○ REPLACE

  - 문자열중에서 특정 문자를 지정한 문자로 바꾼다.

○ RIGHT

  - 오른쪽에서부터 주어진 길이만큼 잘라낸다.

○ RTRIM

  - 오른쪽에 있는 빈칸을 삭제한다.

○ SPACE

  - 빈칸을 지정한다.

○ STR

  - 숫자를 문자로 변환한다

○ STUFF

  - 지정한 만큼의 문자열을 지우고 새로 지정한 문자열을 삽입한다.

○ SUBSTRING

  - 지정한 길이만큼 문자열을 잘라준다.

○ UPPER

  - 대문자로 바꾼다.

 

 

3. 날짜에 대한 함수

○ GETDATE

  - 현재 날짜와 시간을 구한다.

  - SELECT GETDATE() --> 2007-12-12  22:21:35.056 이런식으로 나온다.

○ DATEADD

  - datapart 부분에 number 값을 더한다. (datepart, number, date)

○ DATEDIFF

  - 두 날짜 사이의 datepart 값

○ DATENAME

  - date 에 대한 datepart 를 이름으로 돌려준다.

  - SELECT DATENAME(dw, GETDATE()), DATENAME(mm, GETDATE()) --> 화요일, 12

○ DATEPART

  - date 에 대해 지정된 datepart 를 돌려준다.

  - SELECT DATEPART(mm, GETDATE()) --> 12

○ DAY

  - DATEPART(DAY, date) 와 같다.

○ MONTH

  - DATEPART(MONTH, date) 와 같다.

○ YEAR

  - DATEPART(YEAR, date) 와 같다.

 

* DATEPART 사용법

yy - year (1753~9999)

mm - month (1~12)

dd - day (1~31)

dw - weekday 요일 (1~7) 1 : 일요일

hh - hour (0~23)

mi - minute (0~59)

ss - second (0~59)

ms - millisecond (0~999)

 

 

4. 시스템 함수

○ DB_NAME

  - database 이름

○ ISDATE

  - datetime 이나 smalldatetime 자료형인지 아닌지 검사한다. 맞으면 1, 아니면 0

○ ISNULL

  - NULL 이면 지정한 값으로 바꾼다.

 

5. 연산 함수

○ AVG

  - 평균값

○ COUNT

  - 표현식의 개수

○ COUNT(*)

  - 선택된 모든행의 개수

○ MAX

  - 최대값

○ MIN

  - 최소값

○ SUM

  - 합계

 

이거 말고도 참 많다.

하지만 많이 사용하는 것만 살짝 적어봤다.




posted by 래머
2014. 5. 2. 01:12 C/C++



작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130082207567

출처 더 큰 세상을 향하여~ | 요셉처럼
원문 http://blog.naver.com/lkhmymi/100015006977

등급 : 초급

 

ADO oledb를 이용한 간단한 DB조작에 관련된 강좌입니다.

 

아시겠지만 DB조작은 ODBC를 이용하거나 OLEDB 등을 이용해 가능합니다.

 

ODBC는 배포시 ODBC설정등을 해줘야(코드로 대체할 수 있습니다.) 하므로 귀찮기에 전 VB건 ASP건 VC건 모두 OLEDB로 처리하는 편입니다.

 

짧은 경험상 책을 통한 지식(클래스를 이용한 DB조작)은 딱딱하기 그지 없습니다.

 

이는 위에 나열한 언어와 관련된 책 모두 동일했습니다.

(특히 VB로 처음 DB조작이란걸 경험했을때 DAO 컨트롤을 통해 레코드를 이동시키며 공부했을때의 그 막막함이란..ㅡㅡ

 Join이란 녀석 자체도 몰랐기에 테이블 만큼 컨트롤 생성하고 억지로 이것 저것..ㅡㅡ)

 

여튼.. 잡설은 그만두고.. 간단한 사용 예를 들어볼까 합니다.

 

[사전인지사항]

MyAdodb.h, MyAdodb.cpp : DB관련 변수와 초기화 연결등을 담당합니다.

SimpleAdoDlg.h SimpleAdoDlg.cpp : 실질적인 DB조작을 담당합니다.

 

[Step 1 : MyAdodb.h, MyAdodb.cpp]

1.프로젝트 하나를 생성합니다.

 

2.StdAfx.h를 열어 #import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "adoEOF")를

  복사해 넣습니다.

 

3.COM 라이브러리를 초기화해야 하니 CoInitialize(0);를 생성자에 포함시킵니다.

   이와 반대로 소멸자엔 CoUninitialize();를 삽입합니다.  

 

4. 커넥션과 커멘드 그리고 레코드셋 변수를 생성해줍니다.

    _ConnectionPtr m_pConn; // 커넥션은 말 그대로 DB와의 연결을 담당합니다. DB의 Open, Close...

    _RecordsetPtr m_pRs;      // 레코드셋 또한 말 그대로 조회결과인 레코드들을 담당합니다. 이녀석을 통하여 조회된 결과를 Movenext등을 이용해 원하는 결과물을 얻어낼 수 있습니다.

    _CommandPtr m_pComm;// 커멘드는 이 역시 명령과 관련된 녀석입니다. Update를 해라던지...

 

5.DB와 연결을 합니다.

BOOL CMyAdodb::InitDB(CString strUserId, CString strPassWord, CString strDbIp, CString strDbName, CString strPort)

{   

    CString strConn = "";

    strConn.Format("User ID=%s;Password=%s;Data Source=%s;"

                    "Initial Catalog=%s;Network Address=%s,%s;"

                    "Network Library=dbmssocn",

                    strUserId, strPassWord, strDbIp, strDbName, strDbIp, strPort);

 

    m_pConn = new _ConnectionPtr("ADODB.Connection");

    m_pRs = new _RecordsetPtr("ADODB.Recordset");

    m_pRs->CursorType = adOpenDynamic;

 

    m_pComm = NULL;

    m_pConn->Provider = "SQLOLEDB";

    m_pConn->ConnectionString = strConn.GetBuffer(0);  // 접속 스트링 설정.

 

    try

    {

        m_pConn->Open("","","", adConnectUnspecified);

    }catch(...)

    {

        return FALSE;

    }

 

    m_pComm.CreateInstance(__uuidof(Command));

    m_pComm->ActiveConnection = m_pConn;

    m_pComm->CommandType = adCmdText;

 

    return TRUE;

}

 

여기 까지 했다면 일단 사전 준비단계는 끝난거라봐도 무관합니다.

이제부턴 실질적인 연결작업과 조회 수정등을 해 볼 차례입니다.

 

[Step 2 SimpleAdo.h, SimpleAdo.cpp]

1.MyAdodb.h를 SimpleAdoDlg.h에 포함시키고 클래스를 만듭니다.

#include "MyAdodb.h"

CMyAdodb m_MyAdodb;

 

2.SimpleAdoDlg.cpp에서 DB연결 및 조작을 해봅니다.

  먼저 DB연결 문자열을 생성시킵니다.

  쿼리를 날렸을때 이녀석이 어떤 DB에 어떤 정보로 로긴등을 하여 조작이 가능한지에 대한 문자열입니다.

  잘 모르신다면 아래 강좌를 참조하시면 쉽게 설명되어있습니다.

http://www.devpia.com/Forum/BoardView.aspx?no=6840&page=1&Tpage=1&forumname=vc_lec&stype=&ctType=&answer=&KeyR=nameid&KeyC=

MS-SQL을 기준으로

User ID : DB에 로그인할 아이디 (SA정도가 됩니다.)

Password : 로그인할 아이디의 비밀번호

Data Source : MS-SQL이니 특정 아이피가 들어갑니다. 만약 MDB라면 해당 로컬 경로가 들어가있을겁니다.

Initial Catalog : DB의 이름입니다.

Network Address : MS-SQL이니 특정 아이피가 들어갑니다. 만약 MDB라면 해당 로컬 경로가 들어가있을겁니다. 특정 포트로 DB가 설정이 되어있지 않다면 기본포트인 1433이 입력됩니다.

Network Library : 모르겠군요. 책을 보면 나와있을겁니다. 죄송^^

 

Ex)

User ID=USERID;Password=PASSWORD;Data Source=XXX.XXX.XXX.XXX;Initial Catalog=DBNAME;Network Address=XXX.XXX.XXX.XXX,1433;Network Library=dbmssocn

 

위 정보를 토대로 Step1의 5번에서 만든 DB연결 함수를 호출합니다.

    if(m_MyAdodb.InitDB("USERID", "PASSWORD", "XXX.XXX.XXX.XXX", "DBNAME", "1433") == FALSE)

    {

        MessageBox("디비접속에러입니다. 작업을 진행할 수 없습니다.", "SIMPLEDB", MB_ICONSTOP);

        GetParentOwner()->PostMessage(WM_CLOSE);

        return;

    }   

 

 

3.DB와의 연결이 성공하였으니 가장 기본이 되는 조회(Select)를 해보겠습니다.

    // 알맞은 쿼리를 대입하십시요. join이든 union이든.. (당연하지만..ㅡㅡ)

    m_MyAdodb.m_pComm->CommandText = "Select * From Test_Tbl where 어쩌고 저쩌고";

    

    try

    {

        m_MyAdodb.m_pRs = m_MyAdodb.m_pComm->Execute(NULL,NULL,adCmdText);  // 저장된 select문을 수행합니다.

    }

    catch(...)

    {   // 에러일 경우 메세지 박스를 출력하고 조회 작업을 종료해 버립니다.(에러가 날 경우는 거의 없습니다.)

        MessageBox("조회 오류!", "SIMPLEDB", MB_ICONSTOP);

        return;

    }

    

    // 결과가 존재하지 않음

    if(m_MyAdodb.m_pRs->adoEOF)     

    {

        m_MyAdodb.m_pRs->Close();   // 레코드셋을 닫는다.(항상 레코드셋 오픈 후엔 닫아주는 작업을 수행해야 합니다.)

        MessageBox("조건에 맞는 데이터가 없습니다.", "SIMPLEDB", MB_ICONINFORMATION);

        return;

    }   

 

 

    _variant_t Field_1;     // 필드명입니다.(알맞은 필드명을 나열하십시요)

    _variant_t Field_2;     // 필드명입니다.

    

    char szField1[10+1]         = {'\0',};  // 필드명과 1:1대응하게 변수를 선언하십시요

    char szField2[10+1]         = {'\0',};

    

    // 조회된 레코드의 끝까지 루핑을 돌며 데이터를 확인합니다.

    while(!m_MyAdodb.m_pRs->adoEOF)

    {

        Field_1         = m_MyAdodb.m_pRs->Fields->GetItem("field_1")->GetValue();  // 해당 필드를 선언한 variant변수에 저장합니다.

        Field_2         = m_MyAdodb.m_pRs->Fields->GetItem("field_2")->GetValue();

        

        strcpy(szField1,    (char*)((_bstr_t)Field_1));                             // 저장된 variant변수를 조작이 편한 char변수에 복사합니다.

        strcpy(szField2,    (char*)((_bstr_t)Field_2));

 

 

        // 리스트 컨트롤을 추가하셨거나 화면에 출력하실 생각이 있으시다면

        // 루핑을 돌리며 값을 채워넣는 작업을 이부분에서 하시면 됩니다.

 

 

        m_MyAdodb.m_pRs->MoveNext();    // 레코드셋을 다음으로 이동시킵니다.

    }

    m_MyAdodb.m_pRs->Close();           // 레코드셋을 닫아줍니다.

 

그림으로 해보면..


 

4.Update를 해보겠습니다.

    _variant_t LRowCount;   // Update가 적용된 레코드의 로우수

    long lrowcount = 0;     // 적용된 variant형을 레코드의 로우수 저장

 

    char CommandString[400+1]={'\0',};

    strcpy(CommandString, "Update Test_Tbl set Field_1 ='01', Field_2='02' Where User='senosora'");    // 알맞는 update쿼리를 적용하십시요.

 

    m_MyAdodb.m_pComm->CommandText = CommandString;

                

    try

    {

        m_MyAdodb.m_pComm->Execute(&LRowCount,NULL,adCmdText); // Update 실행

    }

    catch(...)

    {

        // 에러일 경우 메세지 박스를 출력하고 update 작업을 종료해 버립니다.(에러가 날 경우는 update문 오류.)

        MessageBox("Update 오류!", "SIMPLEDB", MB_ICONSTOP);

        return;

    }

    

    lrowcount = atol((char*)((_bstr_t)LRowCount));  // variant형을 long형에 저장

    

    if(lrowcount == 0) // 업데이트된게 없다.

    {

        MessageBox("Update 적용된 레코드 없음!", "SIMPLEDB", MB_ICONSTOP);  

        return;

    }

 

 

 

여기까지입니다.

제 지식이 DB에 관해 command객체는 어떠어떠하고 레코드셋은 어떠하며 속도가 어떻다라 논할 실력이 된다면야 좋겠지만

그렇다고 책을 그대로 베껴놓을수도 없고.. 해서 배경지식은 적지 못했습니다.

틀린점이 있다면 바로잡아 주시면 감사하겠습니다.

첨부되는 파일은 실행하면 DB가 없으니..에러가 납니다.

그냥 눈으로 보시면 이해가 빠르지 않을까 싶어 첨부합니다.

테스트 하시려면 DB구성부터 하셔야 합니다.

근데.. 주절 주절 해놓고 보니 어째 더 복잡해보이는군요.ㅡㅡ

 

MYSQL을 사용하시려면 DB연결부분만 MYSQL로 변경해주시면 동일하게 사용이 가능합니다.

 

 

추가 : 대충 이런식으로 알고 있습니다. mysql을 잘 모르는데.. localhost라면 mysql은 pwd를 안넣더군요..

        // MySql

        strcpy(ConStr,"Driver={MySQL};server=");

        strcat(ConStr,DBIP);

 

        if(strcmp(DBIP, "localhost") == 0)

        {       

            strcat(ConStr,";Uid=root;pwd=;Database=yourdbname");

        }

        else

        {       

            strcat(ConStr,";Uid=root;pwd=yourpassword;Database=yourdbname");

        }

    

            pConn = _ConnectionPtr("ADODB.Connection");

            pRS =  _RecordsetPtr("ADODB.Recordset");

 

            pRS->CursorType = adOpenDynamic;

 

            pComm = NULL;

 

            pConn->ConnectionString = ConStr;  // 접속 스트링 설정.

 

            try

            {

                pConn->Open("","","", adConnectUnspecified);

            }catch(_com_error &e)

            {

                _bstr_t bstrSource(e.Source());

                _bstr_t bstrDescription(e.Description());

                

                AfxMessageBox("MySQL DB 접속 에러");

                exit(1);

            }

 

            pComm.CreateInstance(__uuidof(Command));

            pComm->ActiveConnection = pConn;

            pComm->CommandType = adCmdText;

        }

 

출처 : 데브피아 Visual C++

http://www.devpia.com/forum/BoardView.aspx?no=7041&page=1&forumname=vc_lec&stype=&ctType=087VCF





'C/C++' 카테고리의 다른 글

ActiveX 권한 상승에 대한 일반적인 이야기와 솔루션들 [펌]  (0) 2014.05.02
[본문스크랩] [MSSQL] 함수  (0) 2014.05.02
WSAEventSelect 모델  (0) 2014.05.02
WSAEventSelect  (0) 2014.05.02
WSASend  (0) 2014.05.02
posted by 래머