Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 6개 있습니다.)
Graphics: 15. Unity - shader의 World matrix(unity_ObjectToWorld)를 수작업으로 구성
; https://www.sysnet.pe.kr/2/0/11633

Graphics: 17. Unity - World matrix(unity_ObjectToWorld)로부터 TRS(이동/회전/크기) 행렬로 복원하는 방법
; https://www.sysnet.pe.kr/2/0/11637

Graphics: 18. Unity - World matrix(unity_ObjectToWorld)로부터 Position, Rotation, Scale 값을 복원하는 방법
; https://www.sysnet.pe.kr/2/0/11640

Graphics: 22. Unity - shader의 Camera matrix(UNITY_MATRIX_V)를 수작업으로 구성
; https://www.sysnet.pe.kr/2/0/11692

Graphics: 23. Unity - shader의 원근 투영(Perspective projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
; https://www.sysnet.pe.kr/2/0/11695

Graphics: 25. Unity - shader의 직교 투영(Orthographic projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
; https://www.sysnet.pe.kr/2/0/11700




Unity - shader의 원근 투영(Perspective projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성

지난 글에서 월드 행렬과 카메라 행렬을 수작업으로 구성해 봤으니,

Unity - shader의 World matrix(unity_ObjectToWorld)를 수작업으로 구성
; https://www.sysnet.pe.kr/2/0/11633

Unity - shader의 Camera matrix(UNITY_MATRIX_V)를 수작업으로 구성
; https://www.sysnet.pe.kr/2/0/11692

이제 마지막으로 투영 행렬을 구성해 볼 차례입니다. ^^




투영 행렬을 구성하는 요소는 Unity Inspector 창에 보이는 "Field of View", "Clipping Planes"와 게임 화면이 실행되는 윈도우의 "가로/세로" 비율이 됩니다. C# 스크립트에서 이에 대한 값은 각각 다음과 같이 구할 수 있습니다.

Camera camera = Camera.main;

float aspect = camera.aspect; // 또는 (float)camera.pixelWidth / camera.pixelHeight;

float fov = camera.fieldOfView;
float near = camera.nearClipPlane;
float far = camera.farClipPlane;

여기서 Field of View는 다음과 같이 Unity에서 카메라로부터 상하로 퍼져 나가는 시야각을 의미하며 화각 또는 FOV라고 줄여서 말하기도 합니다. (Unity 에디터의 기본값은 60도)

projection_matrix_1.png

near는 기본 값이 0.3인데 월드 좌표계 기준으로 카메라로부터 0.3 만큼 떨어진 "근-평면"의 위치를 의미합니다. Scene을 새로 생성하면 카메라의 기본 위치값이 (0, 1, -10) 좌표이므로 근-평면은 (0, 1, -9.7) 좌표에 위치하게 됩니다.

far는 기본 값이 1000이고 월드 좌표계 기준으로 카메라로부터 1000 만큼 떨어진 "원-평면"의 위치를 의미합니다. 마찬가지로 Scene의 기본 값 상태일 때 원-평면은 (0, 1, 990) 좌표에 위치하게 됩니다. 아래는 유니티 편집 화면에서의 근-평면과 원-평면의 모습을 보여줍니다.

projection_matrix_2.png

FOV와 Near, Far 값은 개발자가 제어할 수 있지만 화면 비율(aspect 값)은 딱히 조정할 수 없습니다. 이건 사용자가 게임을 실행할 때 보통 전체 화면으로 실행하기 때문에 윈도우의 width, height가 고정되거나 "창 모드"로 게임을 실행했을지라도 사용자가 임의로 변경하는 것이기 때문에 개발자 입장에서는 그때마다 aspect 비율을 투영 행렬에 잘 반영하면 됩니다.

이 값들로부터 투영 행렬을 만드는 것은 다음의 책을 보면 잘 나옵니다.

유니티로 배우는 게임 수학  기초 개념부터 모바일까지, 게임 개발에 필요한 수학 원리 설명서 
; http://www.yes24.com/24/goods/30119802

자세하게 수학적인 설명과 곁들여 설명하고 있으므로 그 부분에 대해서는 책을 참고하시고, 이 글에서는 해당 투영 행렬을 Unity shader에서 어떻게 구성할 수 있는지에 대한 내용만 알아보겠습니다.

우선, 책에 있는 프로젝션 변환 행렬 P는 다음과 같이 이뤄진다고 소개하고 있습니다.

${
P = \begin{bmatrix} \frac {2n} {r - l} & 0 & \frac {r + l} {r - l} & 0 \\ 0 & \frac {2n} {t - b} & \frac {t + b} {t - b} & 0 \\ 0 & 0 & \frac {-(f + n)} {f - n} & \frac {-2fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}
}$


하지만, 저 투영 행렬은 OpenGL의 관례를 따라 나타낸 것이고 각각의 플랫폼에 따른 shader 상의 투영 행렬은 다르다고 합니다. 일례로 (DirectX를 사용하는) 윈도우 데스크톱 환경의 경우에는 다음과 같은 투영 행렬이 사용된다고 설명합니다.

${
P = \begin{bmatrix} \frac {2n} {r - l} & 0 & \frac {r + l} {r - l} & 0 \\ 0 & \frac {-2n} {t - b} & -\frac {t + b} {t - b} & 0 \\ 0 & 0 & \frac {-f} {f - n} & \frac {-fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}
}$


수식에 사용된 변수의 의미는 다음과 같습니다.

l == 근-평면의 좌측 끝의 x 좌표
r == 근-평면의 우측 끝의 x 좌표
b == 근-평면의 하단 끝의 y 좌표
t == 근-평면의 상단 끝의 y 좌표
n == 원점으로부터 근-평면까지의 거리
f == 원점으로부터 원-평면까지의 거리

여기서 n과 f는 이미 Unity Inspector 창에서 Camera 객체의 값으로부터 설정된 바로 그 값입니다. 그리고 나머지 left, right, top, bottom의 값은 FOV에 지정된 각을 이용해 탄젠트 삼각함수로 구할 수 있습니다. 가령 top 값은 근-평면 하단 끝의 y 좌표이므로 x축 기준으로 y-z 평면으로 봤을 때,

projection_matrix_3.png

직각 삼각형의 빗변과 밑변의 각도는 Field of View 60도에서 절반인 30도이고 밑변의 길이가 0.3임을 알고 있으므로 top과 bottom은 다음의 공식으로 알아낼 수 있습니다.

top = tan(30°) * near
    = tan(DegreeToRadian(30°)) * near
bottom = -top;

top과 bottom은 쌍을 이루니 당연히 bottom은 -top이 됩니다. 반면 left와 right의 경우에는 FOV를 이용할 수 없습니다. FOV는 시야의 상하각이기 때문인데, 대신 aspect 값이 있으므로 이를 이용해 top을 aspect와 곱해 구할 수 있습니다.

float right = (top * aspect);
float left = -right;

실제로 Unity C# 스크립트로부터 camera.projectionMatrix와 GL.GetGPUProjectionMatrix(camera.projectionMatrix, true)로 구한 행렬의 값은 다음과 같은 식으로 나옵니다. (Unity 기본 Scene 상태를 가정합니다.)

aspect  1.323475        // 사용자의 환경에 따라 변경
tangentFov 0.5773503    // 기본 Scene 상태인 경우 
near 0.3                // 기본 Scene 상태인 경우 
far 1000                // 기본 Scene 상태인 경우 

top 0.1732051
bottom -0.1732051
right   0.2292326
left    -0.2292326

이렇게 해서 소스가 준비되었군요. ^^




이제 Unity C# 스크립트에서 실제 사용하고 있는 행렬을 보겠습니다.

camera.projectionMatrix
1.30871 0.00000  0.00000  0.00000
0.00000 1.73205  0.00000  0.00000
0.00000 0.00000 -1.00060 -0.60018
0.00000 0.00000 -1.00000  0.00000

GL.GetGPUProjectionMatrix(camera.projectionMatrix, true)
1.30871 0.00000 0.00000 0.00000
0.00000 -1.73205 0.00000 0.00000
0.00000 0.00000 0.00030 0.30009
0.00000 0.00000 -1.00000 0.00000

그런데 대충 봐도, (r+l) / (r-l)을 나타내는 projectionMatrix[0,2]의 값이 0인 것을 보면 완전히 똑같은 것은 아닌 것 같습니다. shader에서도 이렇게 쓰고 있는지 다음과 같은 코드로 테스트할 수 있습니다.

v2f vert(appdata v)
{
    float4 pos;

    v2f o;

    float4x4 m = UNITY_MATRIX_P;
    float4x4 projectionMatrix;

    projectionMatrix[0] = float4(m[0].x, 0,      0,      0);
    projectionMatrix[1] = float4(0,      m[1].y, 0,      0);
    projectionMatrix[2] = float4(0,      0,      m[2].z, m[2].w);
    projectionMatrix[3] = float4(0,      0,      -1,     0);

    pos = mul(unity_ObjectToWorld, v.vertex);
    pos = mul(UNITY_MATRIX_V, pos);
    pos = mul(projectionMatrix, pos);

    o.vertex = pos;

    return o;
}

camera.projectionMatrix 및 GL.GetGPUProjectionMatrix의 구조와 동일한 위치의 값만 설정했는데 정상적으로 투영 행렬로 동작하는 것을 확인할 수 있으며 즉, Unity shader에서 사용하는 투영 행렬은 다음의 2개로 정해집니다.

[OpenGL을 따르는 투영 행렬]

${
P = \begin{bmatrix} \frac {2n} {r - l} & 0 & 0 & 0 \\ 0 & \frac {2n} {t - b} & 0 & 0 \\ 0 & 0 & \frac {-(f + n)} {f - n} & \frac {-2fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}
}$

[실행 환경에 맞는 투영 행렬 - 아래는 DirectX를 사용하는 윈도우 환경에서의 투영 행렬]

${
P = \begin{bmatrix} \frac {2n} {r - l} & 0 & 0 & 0 \\ 0 & \frac {-2n} {t - b} & 0 & 0 \\ 0 & 0 & \frac {-f} {f - n} & \frac {-fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}
}$


그럼 끝났군요. ^^ 재료를 이용해 다음과 같이 투영 행렬을 구할 수 있습니다.

Matrix4x4 myProjection1 = new Matrix4x4();
myProjection1[0, 0] = (2 * near) / (right - left);
myProjection1[1, 1] = (2 * near) / (top - bottom);
myProjection1[2, 2] = -(far + near) / (far - near);
myProjection1[2, 3] = -(2 * far * near) / (far - near);
myProjection1[3, 2] = -1;

Matrix4x4 myProjection2 = new Matrix4x4();
myProjection2[0, 0] = (2 * near) / (right - left);
myProjection2[1, 1] = -(2 * near) / (top - bottom);
myProjection2[2, 2] = -far / (far - near);
myProjection2[2, 3] = -(far * near) / (far - near);
myProjection2[3, 2] = -1;

바로 저 2개의 행렬들은 Unity C# 스크립트에서 다음의 코드로 대응합니다.

myProjection1 == camera.worldToCameraMatrix
myProjection2 == GL.GetGPUProjectionMatrix(camera.projectionMatrix, true);

또한 우리가 구한 l, r, b, t, n, f의 값들이 실제로 camera.worldToCameraMatrix.decomposeProjection 값들과 동일합니다.

camera.projectionMatrix.decomposeProjection
		left	-0.2292326
		right	0.2292326
		bottom	-0.1732051
		top	    0.1732051	
		zNear	0.3	
		zFar	1000.134	

GL.GetGPUProjectionMatrix(camera.projectionMatrix, true).decomposeProjection
		left	0.2293702	
		right	-0.2293702	
		bottom	-0.1733091	
		top	    0.1733091	
		zNear	-0.3001801	
		zFar	0.3




다음 단계로 Unity shader에서 사용하는 투영 행렬이 camera.projectionMatrix 인지, GL.GetGPUProjectionMatrix 반환 값인지는 다음과 같은 코드로 쉽게 확인할 수 있습니다.

using UnityEngine;

[ExecuteInEditMode]
public class SetMatrix : MonoBehaviour {

    void Start () {
    }

    void Update () {
        Camera camera = Camera.main;

        Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true);
        Shader.SetGlobalMatrix("_projectionMatrix1", projectionMatrix);

        Shader.SetGlobalMatrix("_projectionMatrix2", camera.projectionMatrix);
    }
}

float4x4 _projectionMatrix;
float4x4 _projectionMatrix2;

v2f vert(appdata v)
{
    float4 pos;

    v2f o;

    pos = mul(unity_ObjectToWorld, v.vertex);
    pos = mul(UNITY_MATRIX_V, pos);

    pos = mul(_projectionMatrix1, pos);
    // pos = mul(_projectionMatrix2, pos);

    o.vertex = pos;

    return o;
}

실제로 실행해 보면 GL.GetGPUProjectionMatrix가 반환한 투영 행렬이 정상적으로 동작하는 것을 볼 수 있습니다. 약간 혼란스러운 점이 있다면, C# 스크립트에서 넘겨줄 때의 행렬 값이 다음과 같은 반면,

GL.GetGPUProjectionMatrix

0.97183  0.00000  0.00000 0.00000
0.00000 -1.73205  0.00000 0.00000
0.00000  0.00000  0.00030 0.30009
0.00000  0.00000 -1.00000 0.00000

"Visual Studio Graphics Analyzer"로 디버깅 환경의 Watch 창에서 GL.GetGPUProjectionMatrix의 값을 보면 다음과 같다는 것입니다.

_projectionMatrix1      float4x4
    _projectionMatrix1[0]   x = 0.971829400, y = 0.000000000, z = 0.000000000, w = 0.000000000
    _projectionMatrix1[1]   x = 0.000000000, y = -1.732051000, z = 0.000000000, w = 0.000000000
    _projectionMatrix1[2]   x = 0.000000000, y = 0.000000000, z = 0.000300050, w = -1.000000000
    _projectionMatrix1[3]   x = 0.000000000, y = 0.000000000, z = 0.300090000, w = 0.000000000

즉, 전치가 되어 있는데 이것은 아마도 Visual Studio의 디버거 창이 자동으로 전치를 해주는 것인지? 아니면 메모리 상의 값을 읽을 때 열/행우선을 잘못 판단한 것인지는 알 수 없으나 C# 스크립트 상에서의 디버거 값이 올바른 형식입니다.

정리해 보면, Unity에서 제공하는 투영 행렬은 2가지가 있습니다.

  • camera.projectionMatrix == OpenGL을 따르는 형식
  • GL.GetGPUProjectionMatrix == Unity 프로그램이 실행되는 환경에 부합하는 투영 행렬(일례로 위에서의 _projectionMatrix1 값은 DirectX 11을 사용하는 윈도우 환경에서 유효한 투영 행렬)

휴~~~ 이것으로 대충 정리가 되었군요. ^^ 행렬의 생성 규칙과 그 값의 decomposeProjection을 알게 되었으니 이제 나머지 단계는 unity shader에서 decomposeProjection에 해당하는 값을 어떻게 구하느냐에 달려 있습니다.




unity shader에서 근-평면/원-평면은 내장 변수를 통해 값을 구할 수 있습니다.

_ProjectionParams float4

x is 1.0 (or ?1.0 if currently rendering with a flipped projection matrix)
y is the camera’s near plane
z is the camera’s far plane
w is 1/FarPlane

float nearPlane = _ProjectionParams.y;
float farPlane = _ProjectionParams.z;

// 기본값인 경우 nearPlane == 0.3, farPlane == 1000

또한 aspect 비율도 _ScreenParams 내장 변수를 통해 구할 수 있습니다.

float width = _ScreenParams.x;
float height = _ScreenParams.y;

float aspect = width / height;

문제는, FOV 값인데 이것은 unity가 내장 변수로 제공하지 않습니다. 대신 기존 UNITY_MATRIX_P로부터 구해올 수는 있습니다. 이에 대한 계산이 재미있는데요. ^^ 이전에 행렬을 구하는 것에서 (0,0)의 값은 다음과 같이 이뤄집니다.

[0, 0] = (2 * near) / (right - left);

그런데 검색해 보면 다음의 소스 코드를 통해,

https://github.com/g-truc/glm/blob/master/glm/ext/matrix_clip_space.inl

template<typename T>
GLM_FUNC_QUALIFIER mat<4, 4, T, defaultp> perspectiveLH_NO(T fovy, T aspect, T zNear, T zFar)
{
    assert(abs(aspect - std::numeric_limits<T>::epsilon()) > static_cast<T>(0));

    T const tanHalfFovy = tan(fovy / static_cast<T>(2));

    mat<4, 4, T, defaultp> Result(static_cast<T>(0));
    Result[0][0] = static_cast<T>(1) / (aspect * tanHalfFovy);
    Result[1][1] = static_cast<T>(1) / (tanHalfFovy);
    Result[2][2] = (zFar + zNear) / (zFar - zNear);
    Result[2][3] = static_cast<T>(1);
    Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear);
    return Result;
}

"1 / (aspect * tanHalfFovy) = UNITY_MATRIX_P[0].x"와 같다는 것을 알 수 있습니다. 따라서 각도는 다음과 같이 구할 수 있습니다.

aspect * tanHalfFovy * UNITY_MATRIX_P[0].x = 1
tanHalfFovy = 1 / (aspect * UNITY_MATRIX_P[0].x)

radian_FOV = arctan(1 / (aspect * UNITY_MATRIX_P[0].x))

자, 그럼 다 끝났군요. ^^ 이제 다음과 같이 UNITY_MATRIX_P를 재조립해 적용할 수 있습니다.

Shader "Unlit/NewUnlitShader"
{
    Properties
    {
    }
    SubShader
    {
        Tags{ "LightMode" = "ForwardBase" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma enable_d3d11_debug_symbols

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            float4x4 _projectionMatrix;

            v2f vert(appdata v)
            {
                float4 pos;

                v2f o;

                float nearPlane = _ProjectionParams.y;
                float farPlane = _ProjectionParams.z;
                
                float width = _ScreenParams.x;
                float height = _ScreenParams.y;
                float aspect = width / height;

                float4x4 m = UNITY_MATRIX_P;

                float halfFov = atan2(1, aspect * m[0].x);

                float top = tan(halfFov) * nearPlane;
                float bottom = -top;

                float right = (top * aspect);
                float left = -right;

                // DirectX를 사용하는 윈도우 환경에서의 투영 행렬
                float p00 = (2 * nearPlane) / (right - left);
                float p11 = -(2 * nearPlane) / (top - bottom);
                float p22 = -farPlane / (farPlane - nearPlane);
                float p23 = -(farPlane * nearPlane) / (farPlane - nearPlane);

                float4x4 projectionMatrix;
                projectionMatrix[0] = float4(p00, 0,   0,   0);
                projectionMatrix[1] = float4(0,   p11, 0,   0);
                projectionMatrix[2] = float4(0,   0,   p22, p23);
                projectionMatrix[3] = float4(0,   0,   -1,  0);

                pos = mul(unity_ObjectToWorld, v.vertex);
                pos = mul(UNITY_MATRIX_V, pos);
                pos = mul(projectionMatrix, pos);
                // pos = mul(UNITY_MATRIX_P, pos);

                o.vertex = pos;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(1, 0, 0, 0);
            }

            ENDCG
        }
    }
}




참고로 https://github.com/g-truc/glm/blob/master/glm/ext/matrix_clip_space.inl 소스 코드를 보면 알 수 있지만 동일한 값을 다음과 같이 다르게 구하고 있습니다.

projectionMatrix[0, 0] = (2 * near) / (right - left);
                       = 1.0 / (aspect * halfFov)

projectionMatrix[1, 1] = -(2 * near) / (top - bottom);
                       = -1.0 / halfFov;

projectionMatrix[2, 2] = -far / (far - near);
projectionMatrix[2, 3] = -(far * near) / (far - near);
projectionMatrix[3, 2] = -1;

2개의 공식이 왜 같은지는 다음과 같이 풀어 보면 쉽게 이해할 수 있습니다.

(2 * near) / (right - left)
 = (2 * near) / (right * 2) // 어차피 left == -right이므로.
 = near / right             // 공통 인수 2 약분
 = near / (top * aspect)    // right = top * aspect이므로.
 = near / (halfFov * near * aspect) // top = halfFov * near
 = 1 / (halfFov * aspect)   // 공통 인수 near 약분

-(2 * near) / (top - bottom)
 = -(2 * near) / (2 * top)  // 어차피 bottom == -top이므로.
 = -near / top              // 공통 인수 2 약분
 = -near / (halfFov * near) // top = halfFov * near
 = -1 / halfFov             // 공통 인수 near 약분

그러니까 결국, left, right, bottom, top을 구할 필요가 없었던 것입니다. 따라서 shader에서의 소스 코드는 다음과 같이 더 간단해집니다.

v2f vert(appdata v)
{
    float4 pos;

    v2f o;

    float nearPlane = _ProjectionParams.y;
    float farPlane = _ProjectionParams.z;

    float width = _ScreenParams.x;
    float height = _ScreenParams.y;
    float aspect = width / height;

    float4x4 m = UNITY_MATRIX_P;

    float halfFov = atan2(1, aspect * m[0].x);

    // DirectX를 사용하는 윈도우 환경에서의 투영 행렬
    float p00 = 1 / (tan(halfFov) * aspect);
    float p11 = -1 / tan(halfFov);
    float p22 = -farPlane / (farPlane - nearPlane);
    float p23 = -(farPlane * nearPlane) / (farPlane - nearPlane);

    float4x4 projectionMatrix;
    projectionMatrix[0] = float4(p00, 0,   0,   0);
    projectionMatrix[1] = float4(0,   p11, 0,   0);
    projectionMatrix[2] = float4(0,   0,   p22, p23);
    projectionMatrix[3] = float4(0,   0,   -1,  0);

    pos = mul(unity_ObjectToWorld, v.vertex);
    pos = mul(UNITY_MATRIX_V, pos);
    pos = mul(projectionMatrix, pos);
    // pos = mul(UNITY_MATRIX_P, pos);

    o.vertex = pos;

    return o;
}

그 외에 여유가 되시면 Unity가 아닌 실제 카메라 영상을 기준으로 한 다음의 설명도 읽어보시고. ^^

카메라 캘리브레이션 (Camera Calibration)
; http://darkpgmr.tistory.com/32?category=460965




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/22/2018]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12467정성태12/29/202010137.NET Framework: 989. HttpContextAccessor를 통해 이해하는 AsyncLocal<T> [1]파일 다운로드1
12466정성태12/29/20208155.NET Framework: 988. C# - 지연 실행이 꼭 필요한 상황이 아니라면 singleton 패턴에서 DCLP보다는 static 초기화를 권장파일 다운로드1
12465정성태12/29/202011260.NET Framework: 987. .NET Profiler - FunctionID와 연관된 ClassID를 구할 수 없는 문제
12464정성태12/29/202010104.NET Framework: 986. pptfont.exe - PPT 파일에 숨겨진 폰트 설정을 일괄 삭제
12463정성태12/29/20209169개발 환경 구성: 520. RDP(mstsc.exe)의 다중 모니터 옵션 /multimon, /span
12462정성태12/27/202010755디버깅 기술: 177. windbg - (ASP.NET 환경에서 유용한) netext 확장
12461정성태12/21/202011572.NET Framework: 985. .NET 코드 리뷰 팁 [3]
12460정성태12/18/20209274기타: 78. 도서 소개 - C#으로 배우는 암호학
12459정성태12/16/20209690Linux: 35. C# - 리눅스 환경에서 클라이언트 소켓의 ephemeral port 재사용파일 다운로드1
12458정성태12/16/20209200오류 유형: 694. C# - Task.Start 메서드 호출 시 "System.InvalidOperationException: 'Start may not be called on a task that has completed.'" 예외 발생 [1]
12457정성태12/15/20208760Windows: 185. C# - Windows 10/2019부터 추가된 SIO_TCP_INFO파일 다운로드1
12456정성태12/15/20209017VS.NET IDE: 156. Visual Studio - "Migrate packages.config to PackageReference"
12455정성태12/15/20208534오류 유형: 693. DLL 로딩 시 0x800704ec - This Program is Blocked by Group Policy
12454정성태12/15/20209113Windows: 184. Windows - AppLocker의 "DLL Rules"를 이용해 임의 경로에 설치한 DLL의 로딩을 막는 방법 [1]
12453정성태12/14/202010089.NET Framework: 984. C# - bool / BOOL / VARIANT_BOOL에 대한 Interop [1]파일 다운로드1
12452정성태12/14/202010238Windows: 183. 설정은 가능하지만 구할 수는 없는 TcpTimedWaitDelay 값
12451정성태12/14/20209448Windows: 182. WMI Namespace를 열거하고, 그 안에 정의된 클래스를 열거하는 방법 [5]
12450정성태12/13/202010154.NET Framework: 983. C# - TIME_WAIT과 ephemeral port 재사용파일 다운로드1
12449정성태12/11/202010568.NET Framework: 982. C# - HttpClient에서의 ephemeral port 재사용 [2]파일 다운로드1
12448정성태12/11/202012156.NET Framework: 981. C# - HttpWebRequest, WebClient와 ephemeral port 재사용파일 다운로드1
12447정성태12/10/202010307.NET Framework: 980. C# - CopyFileEx API 사용 예제 코드파일 다운로드1
12446정성태12/10/202010926.NET Framework: 979. C# - CoCreateInstanceEx 사용 예제 코드파일 다운로드1
12445정성태12/8/20208366오류 유형: 692. C# Marshal.PtrToStructure - The structure must not be a value class.파일 다운로드1
12444정성태12/8/20209172.NET Framework: 978. C# - GUID 타입 전용의 UnmanagedType.LPStruct [1]파일 다운로드1
12443정성태12/8/20208965.NET Framework: 977. C# PInvoke - C++의 매개변수에 대한 마샬링을 tlbexp.exe를 이용해 확인하는 방법
12442정성태12/4/20207939오류 유형: 691. Visual Studio - Build Events에 robocopy를 사용할때 "Invalid Parameter #1" 오류가 발행하는 경우
... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...