honglab 그래픽스 1챕터 ~ 2.5 챕터(래스터라이저)까지 듣고 만들어본 프로젝트https://github.com/cakememakeme/ProjectCpuRender
CpuRender의 작업물을 SimpleRenderer로 이전 (CpuRenderer 이후 D3D11 Renderer 제작 중)
https://github.com/cakememakeme/SimpleRenderer
주요 로직은 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 |
클리핑이 된다
'개인 작업 > Dev' 카테고리의 다른 글
언리얼 엔진 프로젝트, Maze RPG (2) | 2021.05.06 |
---|---|
3D 게임 프로그래밍 컴퓨터 그래픽스 정리 (0) | 2020.12.20 |