honglab 그래픽스 1챕터 ~ 2.5 챕터(래스터라이저)까지 듣고 만들어본 프로젝트
https://github.com/cakememakeme/ProjectCpuRender

 CpuRender의 작업물을 SimpleRenderer로 이전 (CpuRenderer 이후 D3D11 Renderer 제작 중)

https://github.com/cakememakeme/SimpleRenderer

 

GitHub - cakememakeme/SimpleRenderer: 이런 저런 렌더링 공부용 렌더러

이런 저런 렌더링 공부용 렌더러. Contribute to cakememakeme/SimpleRenderer development by creating an account on GitHub.

github.com


주요 로직은 CpuRenderPipeline::drawMeshed() 에서 파이프라이닝이 시작된다

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void CpuRenderPipeline::drawMeshes()
{
    if (meshes.empty())
    {
        std::cout << "invalid meshes." << std::endl;
        return;
    }
 
    // 실제 파이프라인은
    // 1. Input assembler
    // 2. Vertex shader
    // 3. Tessellation
    // 4. Geometry shader
    // 5. Rasterization
    // 6. Fragment shader
    // 7. Color blending
    // 기준으로 되어 있다(Vulkan tutorial 기준)
    // 원래대로라면, Vertex buffer/Index buffer에 있는 버텍스를 3개로 묶어서(Input assemble) 
    // 묶인 단위만큼 파이프라인에 태워 보내는게 맞으나
    // 렌더링에 대해서 직관적으로 파악 가능하게끔, 버퍼 단위가 아닌 메시 단위로 나눠서 보낸다
    for (const auto mesh : meshes)
    {
        if (!mesh)
        {
            continue;
        }
 
        copyToBuffer(*mesh);
        
        for (size_t i = 0; i < g_vertexBuffer.size(); ++i)
        {
            VsInput vsInput;
            vsInput.Position = g_vertexBuffer[i];
            vsInput.normal = g_normalBuffer[i];
 
            // vertex shader 단계
            VsOutput vsOutput = CpuShader::CpuVertexShader(vsInput);
 
            g_vertexBuffer[i] = vsOutput.Position;
            g_normalBuffer[i] = vsOutput.normal;
        }
 
        // rasterize 단계
// 3개 씩 묶어서 전달, (Input assemble) 원래 이게 vertex shader보다 먼저 이뤄져야 한다
        for (size_t i = 0; i < g_indexBuffer.size(); i += 3)
        {
            CpuRasterizer::DrawIndexedTriangle(i);
        }
    }
 
    meshes.clear();
}
cs


CpuRasterizer.cpp 의 DrawIndexedTriangle() 함수

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
void DrawIndexedTriangle(const size_t startIndex)
{
    const size_t i0 = g_indexBuffer[startIndex];
    const size_t i1 = g_indexBuffer[startIndex + 1];
    const size_t i2 = g_indexBuffer[startIndex + 2];
 
    const Vector3& rootV0_clip = worldToClip(g_vertexBuffer[i0]);
    const Vector3& rootV1_clip = worldToClip(g_vertexBuffer[i1]);
    const Vector3& rootV2_clip = worldToClip(g_vertexBuffer[i2]);
 
    const Vector2& rootV0_screen = clipToScreen(rootV0_clip);
    const Vector2& rootV1_screen = clipToScreen(rootV1_clip);
    const Vector2& rootV2_screen = clipToScreen(rootV2_clip);
 
    // 삼각형 전체 넓이의 두 배, 음수일 수도 있음
    const float area = edgeFunction(rootV0_screen, rootV1_screen, rootV2_screen);
 
    // 뒷면일 경우
    if (g_cullBackface && area < 0.0f)
    {
        return;
    }
 
    // clipping
    // 캐시에는 쥐약이겠지만 개발 편의를 위해
    std::list<struct Triangle> triangles;
    triangles.push_back({ rootV0_clip, rootV1_clip, rootV2_clip });
    clipTriangle(triangles);
 
    /*const auto& c0 = g_colorBuffer[i0];
    const auto& c1 = g_colorBuffer[i1];
    const auto& c2 = g_colorBuffer[i2];*/
 
    const Vector2& uv0 = g_uvBuffer[i0];
    const Vector2& uv1 = g_uvBuffer[i1];
    const Vector2& uv2 = g_uvBuffer[i2];
 
    // draw internal
    for (const auto& triangle : triangles)
    {
        const Vector2& v0_screen = clipToScreen(triangle.v0);
        const Vector2& v1_screen = clipToScreen(triangle.v1);
        const Vector2& v2_screen = clipToScreen(triangle.v2);
 
        const Vector2& leftTopPos = Vector2::Min(Vector2::Min(v0_screen, v1_screen), v2_screen);
        const Vector2& rightBotPos = Vector2::Max(Vector2::Max(v0_screen, v1_screen), v2_screen);
 
        const auto xMin = size_t(std::clamp(std::floor(leftTopPos.x), 0.0f, float(g_width - 1)));
        const auto yMin = size_t(std::clamp(std::floor(leftTopPos.y), 0.0f, float(g_height - 1)));
        const auto xMax = size_t(std::clamp(std::ceil(rightBotPos.x), 0.0f, float(g_width - 1)));
        const auto yMax = size_t(std::clamp(std::ceil(rightBotPos.y), 0.0f, float(g_height - 1)));
 
        // Primitive 보간 후 Pixel(Fragment) Shader로 넘긴다
        for (size_t y = yMin; y <= yMax; y++)
        {
            for (size_t x = xMin; x <= xMax; x++)
            {
                const Vector2& point = Vector2(float(x), float(y));
 
                // 위에서 계산한 삼각형 전체 넓이 area를 재사용
                float w0 = edgeFunction(v1_screen, v2_screen, point) / area;
                float w1 = edgeFunction(v2_screen, v0_screen, point) / area;
                float w2 = edgeFunction(v0_screen, v1_screen, point) / area;
 
                // backface culling
                if (w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f)
                {
                    // Perspective-Correct Interpolation
                    // OpenGL 구현
                    // https://stackoverflow.com/questions/24441631/how-exactly-does-opengl-do-perspectively-correct-linear-interpolation
 
                    const float z0 = g_vertexBuffer[i0].z + g_distEyeToScreen;
                    const float z1 = g_vertexBuffer[i1].z + g_distEyeToScreen;
                    const float z2 = g_vertexBuffer[i2].z + g_distEyeToScreen;
 
                    const Vector3& p0 = g_vertexBuffer[i0];
                    const Vector3& p1 = g_vertexBuffer[i1];
                    const Vector3& p2 = g_vertexBuffer[i2];
 
                    // 뒷면일 경우에도 쉐이딩이 가능하도록 normal을 반대로
                    /*const Vector3& n0 = area < 0.0f ? -g_normalBuffer[i0] : g_normalBuffer[i0];
                    const Vector3& n1 = area < 0.0f ? -g_normalBuffer[i1] : g_normalBuffer[i1];
                    const Vector3& n2 = area < 0.0f ? -g_normalBuffer[i2] : g_normalBuffer[i2];*/
                    const Vector3& n0 = g_normalBuffer[i0];
                    const Vector3& n1 = g_normalBuffer[i1];
                    const Vector3& n2 = g_normalBuffer[i2];
 
                    if (g_bUsePerspectiveProjection)
                    {
                        w0 /= z0;
                        w1 /= z1;
                        w2 /= z2;
 
                        const float wSum = w0 + w1 + w2;
 
                        w0 /= wSum;
                        w1 /= wSum;
                        w2 /= wSum;
                    }
 
                    const float depth = w0 * z0 + w1 * z1 + w2 * z2;
                    const Vector2& uv = w0 * uv0 + w1 * uv1 + w2 * uv2;
 
                    if (depth < g_depthBuffer[x + g_width * y])
                    {
                        g_depthBuffer[x + g_width * y] = depth;
 
                        PsInput psInput;
                        psInput.Position = w0 * p0 + w1 * p1 + w2 * p2;
                        psInput.normal = w0 * n0 + w1 * n1 + w2 * n2;
                        psInput.uv = uv;
 
                        std::vector<Vector4>& buffer = g_displayBuffer;
                        buffer[x + g_width * y] = CpuShader::CpuPixelShader(psInput);
                    }
                }
            }
        }
    }
}
cs

 

클리핑 함수 clipTriangle() / splitTriangle()

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
void clipTriangle(std::list<struct Triangle>& triangles)
{
    using namespace std;
 
    list<struct Triangle>::iterator eraseMark = triangles.end();
    for (auto triangle = triangles.begin(); triangle != triangles.end(); ++triangle)
    {
        if (eraseMark != triangles.end())
        {
            triangles.erase(eraseMark);
            eraseMark = triangles.end();
        }
 
        const struct Triangle& tri = *triangle;
 
        // far plane 제외하고 클리핑을 수행
        const Vector4& nearClippingPlane = Vector4{ 0.0f, 0.0f, g_distEyeToScreen, -g_nearClip };
        const EPlaceFromPlane nearPlane = intersectPlaneAndTriangle(nearClippingPlane, tri);
        if (nearPlane != EPlaceFromPlane::Inside)
        {
            eraseMark = triangle;
            if (nearPlane == EPlaceFromPlane::Middle)
            {
                triangles.splice(triangles.end(), splitTriangle(nearClippingPlane, tri));
            }
            continue;
        }
 
        const Vector4& leftClippingPlane = Vector4{ 1.0f, 0.0f, 0.0f, g_leftClip };
        const EPlaceFromPlane left = intersectPlaneAndTriangle(leftClippingPlane, tri);
        if (left != EPlaceFromPlane::Inside)
        {
            eraseMark = triangle;
            if (left == EPlaceFromPlane::Middle)
            {
                triangles.splice(triangles.end(), splitTriangle(leftClippingPlane, tri));
            }
            continue;
        }
 
        const Vector4& rightClippingPlane = Vector4{ -1.0f, 0.0f, 0.0f, g_rightClip };
        const EPlaceFromPlane right = intersectPlaneAndTriangle(rightClippingPlane, tri);
        if (right != EPlaceFromPlane::Inside)
        {
            eraseMark = triangle;
            if (right == EPlaceFromPlane::Middle)
            {
                triangles.splice(triangles.end(), splitTriangle(rightClippingPlane, tri));
            }
            continue;
        }
        const Vector4& topClippingPlane = Vector4{ 0.0f, -1.0f, 0.0f, g_topClip };
        const EPlaceFromPlane top = intersectPlaneAndTriangle(topClippingPlane, tri);
        if (top != EPlaceFromPlane::Inside)
        {
            eraseMark = triangle;
            if (top == EPlaceFromPlane::Middle)
            {
                triangles.splice(triangles.end(), splitTriangle(topClippingPlane, tri));
            }
            continue;
        }
        const Vector4& bottomClippingPlane = Vector4{ 0.0f, 1.0f, 0.0f, g_bottomClip };
        const EPlaceFromPlane bottom = intersectPlaneAndTriangle(bottomClippingPlane, tri);
        if (bottom != EPlaceFromPlane::Inside)
        {
            eraseMark = triangle;
            if (bottom == EPlaceFromPlane::Middle)
            {
                triangles.splice(triangles.end(), splitTriangle(bottomClippingPlane, tri));
            }
            continue;
        }
    }
 
    if (eraseMark != triangles.end())
    {
        triangles.erase(eraseMark);
        eraseMark = triangles.end();
    }
}
cs

 

splitTriangle()

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
td::list<struct Triangle> splitTriangle(const DirectX::SimpleMath::Vector4& plane, const Triangle& triangle)
{
    // 주의: 버텍스의 시계방향 순서(CW)는 유지되나, 좌하단 -> 좌상단 -> 우상단의 순서는 깨지게 됩니다
    // 한 칸씩 밀립니다
    const struct Triangle& tri = triangle;
    std::array<Vector3, 3> tris = { tri.v0, tri.v1, tri.v2 };
    std::vector<Vector3> splitTri_inside;
    splitTri_inside.reserve(4);
    std::vector<Vector3> splitTri_outside;
    splitTri_outside.reserve(4);
 
    switch (findVertexPlace(intersectPlaneAndVertex(plane, tris[0])))
    {
    case EPlaceFromPlane::Inside:
    {
        splitTri_inside.push_back(tris[0]);
    }
    break;
    case EPlaceFromPlane::Outside:
    {
        splitTri_outside.push_back(tris[0]);
    }
    break;
    case EPlaceFromPlane::Middle:
    {
        splitTri_inside.push_back(tris[0]);
        splitTri_outside.push_back(tris[0]);
    }
    break;
    default:
    {
 
    }
    }
 
    for (size_t i = 1; i <= tris.size(); ++i)
    {
        const size_t currIdx = i % 3;
        const Vector3& prevVert = tris[i - 1];
        const Vector3& currVert = tris[currIdx];
 
        const float dist = intersectPlaneAndVertex(plane, currVert);
        const EPlaceFromPlane currPlace = findVertexPlace(dist);
        if (currPlace == EPlaceFromPlane::Middle)
        {
            splitTri_inside.push_back(currVert);
            splitTri_outside.push_back(currVert);
        }
        else
        {
            Vector3 intersectPoint = Vector3::Zero;
            if (intersectPlaneAndLine(intersectPoint, plane, prevVert, currVert))
            {
                if (currPlace == EPlaceFromPlane::Inside)
                {
                    splitTri_outside.push_back(intersectPoint);
                    splitTri_inside.push_back(intersectPoint);
                    if (currIdx != 0)
                    {
                        splitTri_inside.push_back(currVert);
                    }
                }
                if (currPlace == EPlaceFromPlane::Outside)
                {
                    splitTri_inside.push_back(intersectPoint);
                    splitTri_outside.push_back(intersectPoint);
                    if (currIdx != 0)
                    {
                        splitTri_outside.push_back(currVert);
                    }
                }
            }
            else
            {
                if (currPlace == EPlaceFromPlane::Inside)
                {
                    splitTri_inside.push_back(currVert);
                }
                if (currPlace == EPlaceFromPlane::Outside)
                {
                    splitTri_outside.push_back(currVert);
                }
            }
        }
    }
 
    /*
    if(splitTri_inside.size() < 3)
    {
        const EPlaceFromPlane top = intersectPlaneAndTriangle(plane, tri);
        int bp = 0;
    }
    if (splitTri_inside.size() > 4 || splitTri_outside.size() > 5)
    {
        const EPlaceFromPlane top = intersectPlaneAndTriangle(plane, tri);
        int bp = 0;
    }
    std::cout << splitTri_inside.size() << ' ' << splitTri_outside.size() << '\n';
    */
    std::list<struct Triangle> insideTris;
    if (splitTri_inside.size() == splitTri_outside.size())
    {
        Triangle insideTri;
        insideTri.v0 = splitTri_inside[0];
        insideTri.v1 = splitTri_inside[1];
        insideTri.v2 = splitTri_inside[2];
        insideTris.push_back(insideTri);
 
        /*Triangle outsideTri;
        outsideTri.v0 = splitTri_outside[0];
        outsideTri.v1 = splitTri_outside[1];
        outsideTri.v2 = splitTri_outside[2];*/
    }
    else if (splitTri_inside.size() > splitTri_outside.size())
    {
        Triangle insideTri0;
        insideTri0.v0 = splitTri_inside[0];
        insideTri0.v1 = splitTri_inside[1];
        insideTri0.v2 = splitTri_inside[2];
        insideTris.push_back(insideTri0);
 
        Triangle insideTri1;
        insideTri1.v0 = splitTri_inside[0];
        insideTri1.v1 = splitTri_inside[2];
        insideTri1.v2 = splitTri_inside[3];
        insideTris.push_back(insideTri1);
 
        /*Triangle outsideTri;
        outsideTri.v0 = splitTri_outside[0];
        outsideTri.v1 = splitTri_outside[1];
        outsideTri.v2 = splitTri_outside[2];*/
    }
    else
    {
        /*Triangle outsideTri0;
        outsideTri0.v0 = splitTri_outside[0];
        outsideTri0.v1 = splitTri_outside[1];
        outsideTri0.v2 = splitTri_outside[2];
 
        Triangle outsideTri1;
        outsideTri1.v0 = splitTri_outside[0];
        outsideTri1.v1 = splitTri_outside[2];
        outsideTri1.v2 = splitTri_outside[3];*/
 
        Triangle insideTri;
        insideTri.v0 = splitTri_inside[0];
        insideTri.v1 = splitTri_inside[1];
        insideTri.v2 = splitTri_inside[2];
        insideTris.push_back(insideTri);
    }
 
    return insideTris;
}
cs



 

 

클리핑이 된다

 

신입으로 지원했을 때 제출했던 포트폴리오...ㅎㅎ

 

 

 


https://youtu.be/SK0Vpjo6fM8


RPGMazeGenerator.h

RPGMazeGenerator.cpp

RPGMazeSectionBase.h

RPGMazeSectionBase.cpp

결과는 다음과 같다


더보기
어빌리티를 사용하는 캐릭터들간의 데이터 상호작용

https://mm5-gnap.tistory.com/400

 

플러그인 : Gameplay Ability System

 

mm5-gnap.tistory.com

ActionRPG 프로젝트를 분석하면서 작성한 UML

게임 플레이와 관련된 부분이 포함되어있다

 

UML 웹뷰어 링크

 

MazeRPG의 프로젝트를 개발하면서 작성한 UML

ActionRPG의 UML을 기반으로 추가되거나, 변경된 부분을 수정해가면서 제작했다

수정중

 

 

'개인 작업 > Dev' 카테고리의 다른 글

cpu 렌더  (0) 2023.07.12
3D 게임 프로그래밍 컴퓨터 그래픽스 정리  (0) 2020.12.20

법선 매핑(Normal mapping)과 시차 매핑(Parallax mapping)

mm5-gnap.tistory.com/390

 

노말 매핑, 시차 매핑(parallax mapping)

 

mm5-gnap.tistory.com

 

셰이더 프로그래밍과 그림자 매핑

mm5-gnap.tistory.com/392

 

셰이더 프로그래밍과 그림자 매핑

 

mm5-gnap.tistory.com

 

렌더링 파이프라인과 셰이더

mm5-gnap.tistory.com/393

 

렌더링 파이프라인과 셰이더

 

mm5-gnap.tistory.com

 

텍스처링, 빌보드, 높이맵을 사용한 지형 생성

mm5-gnap.tistory.com/391

 

텍스처링, 빌보드, 높이맵을 사용한 지형 생성

 

mm5-gnap.tistory.com

 

지형 처리 1 - 절두체 컬링과 쿼드트리 컬링

mm5-gnap.tistory.com/322

 

쿼드트리와 절두체 컬링

 

mm5-gnap.tistory.com

 

지형 처리 2 - LOD와 균열방지

mm5-gnap.tistory.com/394

 

LOD와 균열방지

 

mm5-gnap.tistory.com

 

임의의 축 회전과 사원수, 사원수 회전, 구면선형보간

mm5-gnap.tistory.com/338

 

회전과 사원수

 

mm5-gnap.tistory.com

 

캐릭터 애니메이션 1 - 이론(계층 구조, 키프레임 애니메이션, 스키닝 애니메이션)

mm5-gnap.tistory.com/395

 

캐릭터 애니메이션 1 - 이론

(계층 구조, 키프레임 애니메이션, 스키닝 애니메이션)

mm5-gnap.tistory.com

 

캐릭터 애니메이션 2 - 소스(계층 구조, 키프레임 애니메이션, 스키닝 애니메이션)

mm5-gnap.tistory.com/396

 

캐릭터 애니메이션 2 - 소스 (1 / 2)

 (계층 구조, 키프레임 애니메이션, 스키닝 애니메이션) mm5-gnap.tistory.com/397 캐릭터 애니메이션 2 - 소스 (2 / 2) mm5-gnap.tistory.com

mm5-gnap.tistory.com

 

내부지형 처리 1 - BSP 트리 이론 (오브젝트 환경 충돌처리)

mm5-gnap.tistory.com/398

 

내부지형 처리 1 - BSP 트리 이론

오브젝트 환경 충돌처리

mm5-gnap.tistory.com

 

내부지형 처리 2 - BSP 트리 소스 (오브젝트 환경 충돌처리)

mm5-gnap.tistory.com/399

 

내부지형 처리 2 - BSP 트리 소스

오브젝트 환경 충돌처리

mm5-gnap.tistory.com

 

전체 자료 pdf 모음

drive.google.com/file/d/1uIAdjfTncxgkU-7ed4P9dUwh4SHLvmln/view?usp=sharing

'개인 작업 > Dev' 카테고리의 다른 글

cpu 렌더  (0) 2023.07.12
언리얼 엔진 프로젝트, Maze RPG  (2) 2021.05.06

+ Recent posts