그래프를 입력받고 BFS를 통해 모든 노드에서 탐색을 시작하여 모든 노드를 순회하며 하나의 컴퓨터가 몇개의 컴퓨터와 신뢰적인 관계인지 횟수를 더해주면 된다. 그리고 최대 관계 수를 구한 다음 그에 맞는 노드를 출력해주면 된다.
정답코드
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
void BFS(int node);
vector<vector<int>> computers;
vector<bool> visited;
vector<int> answers;
int main()
{
int n, m;
cin >> n >> m;
computers.resize(n + 1);
answers.resize(n + 1);
//그래프 입력
for (int i = 0; i < m; i++)
{
int s, e;
cin >> s >> e;
computers[s].push_back(e);
}
visited.resize(n + 1);
//BFS
for (int i = 1; i <= n; i++)
{
fill(visited.begin(), visited.end(), false);
BFS(i);
}
//최대값 찾기
int max_val = 0;
for (int i = 1; i <= n; i++)
{
max_val = max(max_val, answers[i]);
}
//검사
for (int i = 1; i <= n; i++)
{
if (answers[i] == max_val)
{
cout << i << " ";
}
}
return 0;
}
void BFS(int node)
{
queue<int> q;
q.push(node);
visited[node] = true;
while (!q.empty())
{
int cur = q.front();
q.pop();
for (int i : computers[cur])
{
if (visited[i] == false)
{
visited[i] = true;
//몇개의 컴퓨터랑 관계되어 있는지
answers[i]++;
q.push(i);
}
}
}
}
여기서 CPU에서 GPU에 정보를 넘겨주기 위해 BuildBuffersAndViews() 함수를 통해 위에서 정의한 SRV와 UAV 버퍼의 옵션을 정해주고 초기화하여 만들어준다.
void VecAddDemo::BuildBuffersAndViews()
{
std::vector<Data> dataA(_numElements);
std::vector<Data> dataB(_numElements);
for (int i = 0; i < _numElements; ++i)
{
dataA[i].v1 = XMFLOAT3(i, i, i);
dataA[i].v2 = XMFLOAT2(i, 0);
dataB[i].v1 = XMFLOAT3(-i, i, 0.0f);
dataB[i].v2 = XMFLOAT2(0, -i);
}
// Create a buffer to be bound as a shader input (D3D11_BIND_SHADER_RESOURCE).
D3D11_BUFFER_DESC inputDesc;
inputDesc.Usage = D3D11_USAGE_DEFAULT;
inputDesc.ByteWidth = sizeof(Data) * _numElements;
inputDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
inputDesc.CPUAccessFlags = 0;
inputDesc.StructureByteStride = sizeof(Data);
inputDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
D3D11_SUBRESOURCE_DATA vinitDataA;
vinitDataA.pSysMem = &dataA[0];
ComPtr<ID3D11Buffer> bufferA;
HR(_device->CreateBuffer(&inputDesc, &vinitDataA, bufferA.GetAddressOf()));
D3D11_SUBRESOURCE_DATA vinitDataB;
vinitDataB.pSysMem = &dataB[0];
ComPtr<ID3D11Buffer> bufferB;
HR(_device->CreateBuffer(&inputDesc, &vinitDataB, bufferB.GetAddressOf()));
// Create a read-write buffer the compute shader can write to (D3D11_BIND_UNORDERED_ACCESS).
D3D11_BUFFER_DESC outputDesc;
outputDesc.Usage = D3D11_USAGE_DEFAULT;
outputDesc.ByteWidth = sizeof(Data) * _numElements;
outputDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
outputDesc.CPUAccessFlags = 0;
outputDesc.StructureByteStride = sizeof(Data);
outputDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
HR(_device->CreateBuffer(&outputDesc, 0, _outputBuffer.GetAddressOf()));
// Create a system memory version of the buffer to read the results back from.
outputDesc.Usage = D3D11_USAGE_STAGING;
outputDesc.BindFlags = 0;
outputDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
HR(_device->CreateBuffer(&outputDesc, 0, _outputDebugBuffer.GetAddressOf()));
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
srvDesc.BufferEx.FirstElement = 0;
srvDesc.BufferEx.Flags = 0;
srvDesc.BufferEx.NumElements = _numElements;
_device->CreateShaderResourceView(bufferA.Get(), &srvDesc, _inputASRV.GetAddressOf());
_device->CreateShaderResourceView(bufferB.Get(), &srvDesc, _inputBSRV.GetAddressOf());
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
uavDesc.Format = DXGI_FORMAT_UNKNOWN;
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.Flags = 0;
uavDesc.Buffer.NumElements = _numElements;
_device->CreateUnorderedAccessView(_outputBuffer.Get(), &uavDesc, _outputUAV.GetAddressOf());
}
그리고 실행할 때 값을 바인딩해주고 계산작업에 넘겨주는 것은 DoComputeWork함수에서 이루어진다.
void VecAddDemo::DoComputeWork()
{
D3DX11_TECHNIQUE_DESC techDesc;
//바인딩
Effects::VecAddFX->SetInputA(_inputASRV);
Effects::VecAddFX->SetInputB(_inputBSRV);
Effects::VecAddFX->SetOutput(_outputUAV);
Effects::VecAddFX->VecAddTech->GetDesc(&techDesc);
for (UINT p = 0; p < techDesc.Passes; ++p)
{
ID3DX11EffectPass* pass = Effects::VecAddFX->VecAddTech->GetPassByIndex(p);
pass->Apply(0, _deviceContext.Get());
//스레드 그룹지정
_deviceContext->Dispatch(1, 1, 1);
}
// Unbind the input textures from the CS for good housekeeping.
ID3D11ShaderResourceView* nullSRV[1] = { 0 };
_deviceContext->CSSetShaderResources(0, 1, nullSRV);
// Unbind output from compute shader (we are going to use this output as an input in the next pass,
// and a resource cannot be both an output and input at the same time.
ID3D11UnorderedAccessView* nullUAV[1] = { 0 };
_deviceContext->CSSetUnorderedAccessViews(0, 1, nullUAV, 0);
// Disable compute shader.
_deviceContext->CSSetShader(0, 0, 0);
std::ofstream fout("results.txt");
// Copy the output buffer to system memory.
_deviceContext->CopyResource(_outputDebugBuffer.Get(), _outputBuffer.Get());
// Map the data for reading.
D3D11_MAPPED_SUBRESOURCE mappedData;
_deviceContext->Map(_outputDebugBuffer.Get(), 0, D3D11_MAP_READ, 0, &mappedData);
Data* dataView = reinterpret_cast<Data*>(mappedData.pData);
for (int i = 0; i < _numElements; ++i)
{
fout << "(" << dataView[i].v1.x << ", " << dataView[i].v1.y << ", " << dataView[i].v1.z <<
", " << dataView[i].v2.x << ", " << dataView[i].v2.y << ")" << std::endl;
}
_deviceContext->Unmap(_outputDebugBuffer.Get(), 0);
fout.close();
}
받아오는 결과버퍼를 묘사하는 UAV를 통해 값을 가져와서 최종결과값은 outputDebugBuffer에 넣어주고 있는 모습이다.
이렇게 CPU가 아닌 GPU를 통해 연산작업을 해주는 것을 어떻게 사용할 수 있는지는 다음 예제코드를 보고 분석해보자.
우선 예제코드를 실행해보면 Blur효과가 들어가있는 것을 볼 수 가 있다.
예제코드에 gaussian blur 가 적용되어 있는데 이때 모든 픽셀의 값에 Blur 연산을 적용해야하는데 이 부분을 GPU에 넘겨줘서 해주게 된다. 그리고 지금 OM단계에서 렌더타켓이 아니라 텍스처에 렌더링을 해주고 있는 것을 볼 수 있다. 이렇게 해주면 텍스처를 수정하는 것으로 실제 보여지는 화면을 수정해줄 수 있다.
그리고 쉐이더 연산을 보면 가져온 텍스처를 수직또는 수평으로 나눠서 연산을 해주고 있다. 이때 GroupMemoryBarrierWithGroupSync(); 함수를 통해 이 코드의 위에까지의 작업이 모든 스레드에서 끝날 때까지 기다리게 한다.
//입력- 텍스처
Texture2D gInput;
RWTexture2D<float4> gOutput;
#define N 256
#define CacheSize (N + 2*gBlurRadius)
//공유하는 버퍼
groupshared float4 gCache[CacheSize];
[numthreads(N, 1, 1)]
void HorzBlurCS(int3 groupThreadID : SV_GroupThreadID,
int3 dispatchThreadID : SV_DispatchThreadID)
{
//
// Fill local thread storage to reduce bandwidth. To blur
// N pixels, we will need to load N + 2*BlurRadius pixels
// due to the blur radius.
//
//텍스처를 공유하는 버퍼에 캐싱하여서 사용
if (groupThreadID.x < gBlurRadius)
{
// Clamp out of bound samples that occur at image borders.
int x = max(dispatchThreadID.x - gBlurRadius, 0);
gCache[groupThreadID.x] = gInput[int2(x, dispatchThreadID.y)];
}
if (groupThreadID.x >= N - gBlurRadius)
{
// Clamp out of bound samples that occur at image borders.
int x = min(dispatchThreadID.x + gBlurRadius, gInput.Length.x - 1);
gCache[groupThreadID.x + 2 * gBlurRadius] = gInput[int2(x, dispatchThreadID.y)];
}
// Clamp out of bound samples that occur at image borders.
gCache[groupThreadID.x + gBlurRadius] = gInput[min(dispatchThreadID.xy, gInput.Length.xy - 1)];
// Wait for all threads to finish.
GroupMemoryBarrierWithGroupSync();
//
// Now blur each pixel.
//
float4 blurColor = float4(0, 0, 0, 0);
[unroll]
for (int i = -gBlurRadius; i <= gBlurRadius; ++i)
{
int k = groupThreadID.x + gBlurRadius + i;
blurColor += gWeights[i + gBlurRadius] * gCache[k];
}
gOutput[dispatchThreadID.xy] = blurColor;
}
[numthreads(1, N, 1)]
void VertBlurCS(int3 groupThreadID : SV_GroupThreadID,
int3 dispatchThreadID : SV_DispatchThreadID)
{
//
// Fill local thread storage to reduce bandwidth. To blur
// N pixels, we will need to load N + 2*BlurRadius pixels
// due to the blur radius.
//
// This thread group runs N threads. To get the extra 2*BlurRadius pixels,
// have 2*BlurRadius threads sample an extra pixel.
if (groupThreadID.y < gBlurRadius)
{
// Clamp out of bound samples that occur at image borders.
int y = max(dispatchThreadID.y - gBlurRadius, 0);
gCache[groupThreadID.y] = gInput[int2(dispatchThreadID.x, y)];
}
if (groupThreadID.y >= N - gBlurRadius)
{
// Clamp out of bound samples that occur at image borders.
int y = min(dispatchThreadID.y + gBlurRadius, gInput.Length.y - 1);
gCache[groupThreadID.y + 2 * gBlurRadius] = gInput[int2(dispatchThreadID.x, y)];
}
// Clamp out of bound samples that occur at image borders.
gCache[groupThreadID.y + gBlurRadius] = gInput[min(dispatchThreadID.xy, gInput.Length.xy - 1)];
// 위의 1차 작업이 끝날때 까지 기다리기
GroupMemoryBarrierWithGroupSync();
//
// Now blur each pixel.
//
float4 blurColor = float4(0, 0, 0, 0);
[unroll]
for (int i = -gBlurRadius; i <= gBlurRadius; ++i)
{
int k = groupThreadID.y + gBlurRadius + i;
blurColor += gWeights[i + gBlurRadius] * gCache[k];
}
gOutput[dispatchThreadID.xy] = blurColor;
}
우선 스텐실이라고 했을 때 Depth Stencil을 먼저 떠올리게 된다. Depth Stencil에서 Depth는 깊이를 표현하는 것으로 물체의 깊이에 따라서 그려줄지 말지를 결정하는 것이고 Stencil은 픽셀 단이로 정보를 저장하며 렌더링 할 영역을 제어할 수 있는데 말 그대로 그려줄 수 있는 영역을 정해주게 된다. 특정 조건에 따라 픽셀의 렌더링을 허용하거나 차단할 수 있으며, 복잡한 이미지 효과나 마스킹, 포털 등의 특수 효과 구현에 사용한다.
이 두 정보는 텍스처를 통해 같이 전달해주게 되는데 텍스처desc에서 옵션 값중에 Format으로 값의 크기를 정해주는데 보통 다음과 같이 설정해준다.
desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
이렇게 해주면 깊이표현에 24비트 사용하고 스텐실에 8비트를 사용한다는 것이다. 24비트면 3바이트이다. float가 4바이트를 사용하는 것을 생각해보면 오차가 생길 수 있지만 깊이는 0~1까지 사용할 것이고 이 안에서 우리가 float를 활용하여 사용하기 때문에 24비트만 사용하는 것을 볼 수 있다.
스텐실은 0~ 255의 값을 사용할 수 있다.
이렇게 세팅해준 값을 우리가 활용해서 Depth Stencil로 한번에 제어해주면 되는 것이다.
DX에서는 ClearDepthStencilView를 통해 값을 Clear해주고 값을 세팅해주면 된다.
if (gFogEnabled)
{
float fogLerp = saturate((distToEye - gFogStart) / gFogRange);
// Blend the fog color and the lit color.
litColor = lerp(litColor, gFogColor, fogLerp);
}
식을 보면 카메라에서 물체까지의 거리를 판별하고 안개의 시작점통해 연산을 해주는 것으로 안개가 어디서 부터 시작하고 최소 범위와 최대 범위를 통해 연산해주면 0~1사이의 값, 비율로 나오게 될 것이다. 이를 통해 litColor 즉 최종적인 색상에 안개의 색상을 섞에서 반환해주면 된다.
우선 LightDemo를 실행시켜보면 실시간으로 Spot, Direct, Point Light가 적용돼서 화면이 보이는 것을 볼 수 있다.
또한 전에 배웠던 Diffuse Ambient Specular Light도 모두 적용되어 있는 모습을 볼 수 있다.
이렇게 다양한 조명을 적용하는 것도 좋지만 조명이 많아질 수록 연산량이 늘어나기 때문에 필요한 조명만 필요한 영역에 사용하는 것이 좋다.
우선 LightDemo 코드를 보면 헤더파일에 LightHelper가 추가되어있는데 이는 각 조명에 대한 구조체 정의가 있는 부분이다.
우선 DirectionalLight는 일직선으로 가는 조명으로 Ambient Diffuse Specular 3가지 특성을 가지고 있다.
struct DirectionalLight
{
DirectionalLight() { ZeroMemory(this, sizeof(this)); }
XMFLOAT4 Ambient;
XMFLOAT4 Diffuse;
XMFLOAT4 Specular;
XMFLOAT3 Direction;
float Pad; // Pad the last float so we can set an array of lights if we wanted.
};
PointLight는 어떤 Point로부터 모든 방향으로 동일한 세기의 빛을 뿜는 광원을 말한. 이 조명에는 Ambient,Diffuse,Specular 특성이 있고 Point의 위치와 영향을 줄 거리, 높이 등으로 이루어져있다.
struct PointLight
{
PointLight() { ZeroMemory(this, sizeof(this)); }
XMFLOAT4 Ambient;
XMFLOAT4 Diffuse;
XMFLOAT4 Specular;
// Packed into 4D vector: (Position, Range)
XMFLOAT3 Position;
float Range;
// Packed into 4D vector: (A0, A1, A2, Pad)
XMFLOAT3 Att;
float Pad; // Pad the last float so we can set an array of lights if we wanted.
};
SpotLight는 손전등과 무대 조명과 같이 원뿔 모양으로 한곳을 비추는 조명으로 구조체 내부는 Point Light와 같다.
struct SpotLight
{
SpotLight() { ZeroMemory(this, sizeof(this)); }
XMFLOAT4 Ambient;
XMFLOAT4 Diffuse;
XMFLOAT4 Specular;
// Packed into 4D vector: (Position, Range)
XMFLOAT3 Position;
float Range;
// Packed into 4D vector: (Direction, Spot)
XMFLOAT3 Direction;
float Spot;
// Packed into 4D vector: (Att, Pad)
XMFLOAT3 Att;
float Pad; // Pad the last float so we can set an array of lights if we wanted.
};
여기서 조명마다 있는 pad는 16바이트 정렬을 해주기 위한 값이다.
이렇게 정의해준 조명 중에 어떤 조명을 사용할 지 골라준 다음에 쉐이더에서 빛에 따른 색상 연산을 해주어야한다. 쉐이더 코드를 살펴보자면 우선 위의 조명 구조체와 구조를 맞춰준 구조체를 쉐이더쪽에서도 만들어줘야한다.