프로그래밍 공부
작성일
2023. 12. 19. 14:49
작성자
WDmil
728x90

Map의 블랜딩을 여러개 중첩하여 적용해보자.

 

원래 있었던 Hight를 수정하는 Editer에 Alpha값을 추가하여 수정하고, 저장 , 불러오기를 할 수 있게 할 것이다.

 

우선 MapEdit용 shader를 추가로 제작한다.

 

#include "../VertexHeader.hlsli"
#include "../PixelHeader.hlsli"

struct VertexInput
{
	float4 pos : POSITION;
	float2 uv : UV;
	float3 normal : NORMAL;
	float4 alpha : ALPHA;
};

struct PixelInput
{
	float4 pos : SV_POSITION;
	float2 uv : UV;
	float3 normal : NORMAL;	
	float3 tangent : TANGENT;
	float3 binormal : BINORMAL;
	float3 worldPos : POSITION0;
	float3 viewPos : POSITION1;
	float4 alpha : ALPHA;
};

PixelInput VS(VertexInput input)
{
	PixelInput output;
	output.pos = mul(input.pos, world);
	output.worldPos = output.pos;
	output.viewPos = invView._41_42_43;
	
	output.pos = mul(output.pos, view);
	output.pos = mul(output.pos, projection);
	
	output.uv = input.uv;
	output.alpha = input.alpha;
	
	output.normal = mul(input.normal, (float3x3) world);		
	output.tangent = float3(1, 0, 0);
	output.binormal = float3(0, 1, 0);
	
	return output;
}

cbuffer BrushBuffer : register(b10)
{
	int type;
	float3 pickingPos;
	
	float range;
	float3 color;
}

float4 BrushColor(float3 pos)
{
	float2 direction = pos.xz - pickingPos.xz;
	
	float dist = length(direction);
	
	if (dist < range)
		return float4(color, 0);

	return float4(0, 0, 0, 0);
}

Texture2D secondDiffuseMap : register(t11);
Texture2D thirdDiffuseMap : register(t12);

float4 PS(PixelInput input) : SV_TARGET
{
	Material material;
	material.normal = GetNormal(input.tangent,
		input.binormal, input.normal, input.uv);
	material.baseColor = diffuseMap.Sample(samp, input.uv);
	material.specularIntensity = specularMap.Sample(samp, input.uv);
	material.worldPos = input.worldPos;
	material.viewDir = normalize(input.worldPos - input.viewPos);
	
	float4 second = secondDiffuseMap.Sample(samp, input.uv);
	float4 third = thirdDiffuseMap.Sample(samp, input.uv);
	
	material.baseColor = lerp(material.baseColor, second, input.alpha.r);
	material.baseColor = lerp(material.baseColor, third, input.alpha.g);
	
	float4 ambient = CalcAmbient(material);
	float4 result = 0;
	
	for (int i = 0; i < lightCount; i++)
	{
		if (!lights[i].isActive)
			continue;
	
		if (lights[i].type == 0)
			result += CalcDirectional(material, lights[i]);
		else if (lights[i].type == 1)
			result += CalcPoint(material, lights[i]);
		else if (lights[i].type == 2)
			result += CalcSpot(material, lights[i]);
	}
	
	float4 brush = BrushColor(input.worldPos);
	
	return ambient + result + mEmissive + brush;
}

 

전에 작성하였던, Terrain과 Light Shader를 합쳐서 만들었다.

 

VertexInput값으로 alpha값을 Flaot4로 받아와서, 한 Pixel에 대해 최대 4개까지의 DiffuseMap을 적용할 수 있도록 하였고,

 

PixelInput에서 필요한 데이터를 뽑아다 사용할 수 있게 alpha값을 추가하였다.

 

Texture2D secondDiffuseMap : register(t11);
Texture2D thirdDiffuseMap : register(t12);

 

위 두개의 Texture2D값을 적용하여, register의 t11과 t12에 필요햔 DiffuseMap을 가져와 사용할 수 있게 하였다.

 

VS에서는 큰 작용을 하지 않는다, 그러나, alpha값을 PixelInput에 전달할 수 있는 함수만 추가한다.

 

PS에서는 현재 받아온 alpha값을 적용하여 데이터를 섞는 함수를 추가하여 작용하낟.

 

material의 baseColor에, 현재 material의 uv값의 선형보간데이터를 섞어서 적용한다.

 

그리고, 현재 적용된 brush값은 따로 보정적용을 한다.

이제 필요한 쉐이더 데이터는 모두 적용이 끝났다.

 

TerrainEditer에 필요한 정보를 보내고, 적용할 데이터와 ImGUI를 통해 현재 수정해야할 값,

저장해야할 값, 불러와야할 값 들을 전달해야한다.

 


 

#pragma once

class TerrainEditor : public GameObject
{
private:
    //typedef VertexUVNormal VertexType;

    struct VertexType
    {
        Float3 pos = {};
        Float2 uv = {};
        Float3 normal = {};
        float alpha[4] = {};
    };

    const UINT MAX_SIZE = 256;
    const UINT MAX_HEIGHT = 20.0f;

    enum EditType
    {
        HEIGHT, ALPHA
    };

    class RayBuffer : public ConstBuffer
    {
    private:
        struct Data
        {
            Float3 pos;
            UINT triangleSize;

            Float3 dir;
            float padding;
        };

    public:
        RayBuffer() : ConstBuffer(&data, sizeof(Data))
        {
        }

        Data& Get() { return data; }

    private:
        Data data;
    };

    class BrushBuffer : public ConstBuffer
    {
    private:
        struct Data
        {
            int type = 0;
            Float3 pickingPos = {};

            float range = 5.0f;
            Float3 color = { 0, 1, 0 };
        };

    public:
        BrushBuffer() : ConstBuffer(&data, sizeof(Data))
        {
        }

        Data& Get() { return data; }

    private:
        Data data;
    };

    struct InputDesc
    {
        Float3 v0, v1, v2;
    };

    struct OutputDesc
    {
        int picked;
        float distance;
    };

public:
    TerrainEditor();
    ~TerrainEditor();

    void Update();
    void Render();
    void GUIRender();

    void Picking();
    bool ComputePicking(Vector3& pos);

private:
    void MakeMesh();
    void MakeNormal();
    void MakeComputeData();

    void AdjustHeight();
    void AdjustAlpha();

    void SaveHeightMap();
    void LoadHeightMap();

    void SaveAlphaMap();
    void LoadAlphaMap();

    void UpdateHeight();
    void Resize();
private:
    UINT width, height, triangleSize;
    Vector3 pickingPos;
    float adjustValue = 20.0f;
    string projectPath;
    EditType editType = HEIGHT;
    int selectMap = 0;

    Mesh<VertexType>* mesh;

    Texture* heightMap;
    Texture* secondMap;
    Texture* thirdMap;

    BrushBuffer* brushBuffer;
    //Compute Picking
    RayBuffer* rayBuffer;
    StructuredBuffer* structuredBuffer;

    vector<InputDesc> inputs;
    vector<OutputDesc> outputs;

    ComputeShader* computeShader;
};

 

현재 TerrainEditor에 적용된 함수와 데이터를 살펴보자.

현재 수정할 값이 ALPHA인지 HEIGHT인지 구분할 enum이다.

 

적용할 두번째, 세번째 Map이다.

#include "Framework.h"

TerrainEditor::TerrainEditor() : width(MAX_SIZE), height(MAX_SIZE)
{
    material->SetShader(L"Landscape/TerrainEditor.hlsl");
    material->SetDiffuseMap(L"Textures/Landscape/Dirt2.png");
    
    secondMap = Texture::Add(L"Textures/Landscape/Stones.png");
    thirdMap = Texture::Add(L"Textures/Landscape/Bricks.png");

    mesh = new Mesh<VertexType>();
    MakeMesh();
    MakeNormal();
    MakeComputeData();
    mesh->CreateMesh();

    computeShader = Shader::AddCS(L"Compute/ComputePicking.hlsl");

    structuredBuffer = new StructuredBuffer(
        inputs.data(), sizeof(InputDesc), triangleSize,
        sizeof(OutputDesc), triangleSize);
    rayBuffer = new RayBuffer();
    brushBuffer = new BrushBuffer();

    char path[128];
    GetCurrentDirectoryA(128, path);
    projectPath = path;
}

TerrainEditor::~TerrainEditor()
{
    delete mesh;

    delete structuredBuffer;
    delete rayBuffer;
    delete brushBuffer;
}

void TerrainEditor::Update()
{
    //Picking();
    ComputePicking(pickingPos);
    brushBuffer->Get().pickingPos = pickingPos;

    if (KEY->Press(VK_LBUTTON) && !ImGui::GetIO().WantCaptureMouse)
    {
        switch (editType)
        {
        case TerrainEditor::HEIGHT:
            AdjustHeight();
            break;
        case TerrainEditor::ALPHA:
            AdjustAlpha();
            break;
        default:
            break;
        }        
    }
}

void TerrainEditor::Render()
{
    brushBuffer->SetPS(10);
    secondMap->PSSet(11);
    thirdMap->PSSet(12);

    SetRender();

    mesh->Draw();
}

void TerrainEditor::GUIRender()
{
    ImGui::Text("PickingPos : %f, %f, %f", pickingPos.x, pickingPos.y, pickingPos.z);

    const char* editList[] = { "Height", "Alpha" };
    ImGui::Combo("EditType", (int*)&editType, editList, 2);


    ImGui::DragFloat("BrushRange", &brushBuffer->Get().range);
    ImGui::ColorEdit3("BrushColor", (float*)&brushBuffer->Get().color);
    ImGui::DragFloat("AdjustHeight", &adjustValue);
    ImGui::DragInt("SecondMap", &selectMap);

    SaveHeightMap();
    ImGui::SameLine();
    LoadHeightMap();

    SaveAlphaMap();
    ImGui::SameLine();
    LoadAlphaMap();
}

void TerrainEditor::Picking()
{
    Ray ray = CAM->ScreenPointToRay(mousePos);

    for (UINT z = 0; z < height - 1; z++)
    {
        for (UINT x = 0; x < width - 1; x++)
        {
            UINT index[4];
            index[0] = width * z + x;
            index[1] = width * z + x + 1;
            index[2] = width * (z + 1) + x;
            index[3] = width * (z + 1) + x + 1;

            vector<VertexType>& vertices = mesh->GetVertices();

            Vector3 p[4];
            FOR(4)
                p[i] = vertices[index[i]].pos;

            float distance = 0.0f;

            if (Intersects(ray.pos, ray.dir, p[0], p[1], p[2], distance))
            {
                pickingPos = ray.pos + ray.dir * distance;
                return;
            }
            if (Intersects(ray.pos, ray.dir, p[3], p[1], p[2], distance))
            {
                pickingPos = ray.pos + ray.dir * distance;
                return;
            }
        }
    }
}

bool TerrainEditor::ComputePicking(Vector3& pos)
{
    Ray ray = CAM->ScreenPointToRay(mousePos);

    rayBuffer->Get().pos = ray.pos;
    rayBuffer->Get().dir = ray.dir;
    rayBuffer->Get().triangleSize = triangleSize;

    rayBuffer->SetCS(0);

    DC->CSSetShaderResources(0, 1, &structuredBuffer->GetSRV());
    DC->CSSetUnorderedAccessViews(0, 1, &structuredBuffer->GetUAV(), nullptr);

    computeShader->Set();

    UINT x = ceil((float)triangleSize / 64.0f);

    DC->Dispatch(x, 1, 1);

    structuredBuffer->Copy(outputs.data(), sizeof(OutputDesc) * triangleSize);

    float minDistance = FLT_MAX;
    int minIndex = -1;

    UINT index = 0;
    for (OutputDesc output : outputs)
    {
        if (output.picked)
        {
            if (minDistance > output.distance)
            {
                minDistance = output.distance;
                minIndex = index;
            }
        }
        index++;
    }

    if (minIndex >= 0)
    {
        pos = ray.pos + ray.dir * minDistance;
        return true;
    }

    return false;
}

void TerrainEditor::MakeMesh()
{
    vector<Float4> pixels(width * height, Float4(0, 0, 0, 0));

    if (heightMap)
    {
        width = heightMap->GetSize().x;
        height = heightMap->GetSize().y;
        
        heightMap->ReadPixels(pixels);
    }    

    //Vertices
    vector<VertexType>& vertices = mesh->GetVertices();
    vertices.clear();

    vertices.reserve(width * height);
    for (UINT z = 0; z < height; z++)
    {
        for (UINT x = 0; x < width; x++)
        {
            VertexType vertex;
            vertex.pos = { (float)x, 0.0f, (float)z };
            vertex.uv.x = x / (float)(width - 1);
            vertex.uv.y = z / (float)(height - 1);

            UINT index = width * z + x;
            vertex.pos.y = pixels[index].x * 20.0f;

            vertices.push_back(vertex);
        }
    }

    //Indices
    vector<UINT>& indices = mesh->GetIndices();
    indices.clear();

    indices.reserve((width - 1) * (height - 1) * 6);

    for (UINT z = 0; z < height - 1; z++)
    {
        for (UINT x = 0; x < width - 1; x++)
        {
            indices.push_back(width * z + x);//0
            indices.push_back(width * (z + 1) + x);//1
            indices.push_back(width * z + x + 1);//2

            indices.push_back(width * z + x + 1);//2
            indices.push_back(width * (z + 1) + x);//1
            indices.push_back(width * (z + 1) + x + 1);//3
        }
    }
}

void TerrainEditor::MakeNormal()
{
    vector<VertexType>& vertices = mesh->GetVertices();
    vector<UINT>& indices = mesh->GetIndices();

    FOR(indices.size() / 3)
    {
        UINT index0 = indices[i * 3 + 0];
        UINT index1 = indices[i * 3 + 1];
        UINT index2 = indices[i * 3 + 2];

        Vector3 v0 = vertices[index0].pos;
        Vector3 v1 = vertices[index1].pos;
        Vector3 v2 = vertices[index2].pos;

        Vector3 e0 = v1 - v0;
        Vector3 e1 = v2 - v0;

        Vector3 normal = Vector3::Cross(e0, e1).GetNormalized();

        vertices[index0].normal += normal;
        vertices[index1].normal += normal;
        vertices[index2].normal += normal;
    }
}

void TerrainEditor::MakeComputeData()
{
    vector<VertexType> vertices = mesh->GetVertices();
    vector<UINT> indices = mesh->GetIndices();

    triangleSize = indices.size() / 3;

    inputs.resize(triangleSize);
    outputs.resize(triangleSize);

    for (UINT i = 0; i < triangleSize; i++)
    {
        UINT index0 = indices[i * 3 + 0];
        UINT index1 = indices[i * 3 + 1];
        UINT index2 = indices[i * 3 + 2];

        inputs[i].v0 = vertices[index0].pos;
        inputs[i].v1 = vertices[index1].pos;
        inputs[i].v2 = vertices[index2].pos;
    }
}

void TerrainEditor::AdjustHeight()
{
    vector<VertexType>& vertices = mesh->GetVertices();

    for (VertexType& vertex : vertices)
    {
        Vector3 pos = Vector3(vertex.pos.x, 0.0f, vertex.pos.z);
        Vector3 pickingPos = brushBuffer->Get().pickingPos;
        pickingPos.y = 0.0f;

        float distance = MATH->Distance(pos, pickingPos);

        if (distance <= brushBuffer->Get().range)
        {
            vertex.pos.y += adjustValue * DELTA;
            vertex.pos.y = MATH->Clamp(0.0f, MAX_HEIGHT, vertex.pos.y);
        }
    }   
    
    UpdateHeight();
}

void TerrainEditor::AdjustAlpha()
{
    vector<VertexType>& vertices = mesh->GetVertices();

    for (VertexType& vertex : vertices)
    {
        Vector3 pos = Vector3(vertex.pos.x, 0.0f, vertex.pos.z);
        Vector3 pickingPos = brushBuffer->Get().pickingPos;
        pickingPos.y = 0.0f;

        float distance = MATH->Distance(pos, pickingPos);

        if (distance <= brushBuffer->Get().range)
        {
            vertex.alpha[selectMap] += adjustValue * DELTA;
            vertex.alpha[selectMap] = MATH->Clamp(0.0f, 1.0f, vertex.alpha[selectMap]);
        }
    }

    UpdateHeight();
}

void TerrainEditor::SaveHeightMap()
{
    if (ImGui::Button("SaveHeight"))
        DIALOG->OpenDialog("SaveHeight", "SaveHeight", ".png", ".");

    if (DIALOG->Display("SaveHeight"))
    {
        if (DIALOG->IsOk())
        {
            string file = DIALOG->GetFilePathName();

            file = file.substr(projectPath.size() + 1, file.size());

            UINT size = width * height * 4;
            uint8_t* pixels = new uint8_t[size];

            vector<VertexType>& vertices = mesh->GetVertices();

            FOR(size / 4)
            {
                float y = vertices[i].pos.y;

                uint8_t height = y / MAX_HEIGHT * 255;

                pixels[i * 4 + 0] = height;
                pixels[i * 4 + 1] = height;
                pixels[i * 4 + 2] = height;
                pixels[i * 4 + 3] = 255;
            }

            Image image;
            image.width = width;
            image.height = height;
            image.format = DXGI_FORMAT_R8G8B8A8_UNORM;
            image.rowPitch = width * 4;
            image.slicePitch = size;
            image.pixels = pixels;

            SaveToWICFile(image, WIC_FLAGS_FORCE_RGB,
                GetWICCodec(WIC_CODEC_PNG), ToWString(file).c_str());

            delete[] pixels;
        }

        DIALOG->Close();
    }
}

void TerrainEditor::LoadHeightMap()
{
    if (ImGui::Button("LoadHeight"))
        DIALOG->OpenDialog("LoadHeight", "LoadHeight", ".png", ".");

    if (DIALOG->Display("LoadHeight"))
    {
        if (DIALOG->IsOk())
        {
            string file = DIALOG->GetFilePathName();

            file = file.substr(projectPath.size() + 1, file.size());

            heightMap = Texture::Add(ToWString(file));

            Resize();
        }

        DIALOG->Close();
    }
}

void TerrainEditor::SaveAlphaMap()
{
    if (ImGui::Button("SaveAlpha"))
        DIALOG->OpenDialog("SaveAlpha", "SaveAlpha", ".png", ".");

    if (DIALOG->Display("SaveAlpha"))
    {
        if (DIALOG->IsOk())
        {
            string file = DIALOG->GetFilePathName();

            file = file.substr(projectPath.size() + 1, file.size());

            UINT size = width * height * 4;
            uint8_t* pixels = new uint8_t[size];

            vector<VertexType>& vertices = mesh->GetVertices();

            FOR(size / 4)
            {
                pixels[i * 4 + 0] = vertices[i].alpha[0] * 255;
                pixels[i * 4 + 1] = vertices[i].alpha[1] * 255;
                pixels[i * 4 + 2] = vertices[i].alpha[2] * 255;
                pixels[i * 4 + 3] = 255;
            }

            Image image;
            image.width = width;
            image.height = height;
            image.format = DXGI_FORMAT_R8G8B8A8_UNORM;
            image.rowPitch = width * 4;
            image.slicePitch = size;
            image.pixels = pixels;

            SaveToWICFile(image, WIC_FLAGS_FORCE_RGB,
                GetWICCodec(WIC_CODEC_PNG), ToWString(file).c_str());

            delete[] pixels;
        }

        DIALOG->Close();
    }
}

void TerrainEditor::LoadAlphaMap()
{
    if (ImGui::Button("LoadAlpha"))
        DIALOG->OpenDialog("LoadAlpha", "LoadAlpha", ".png", ".");

    if (DIALOG->Display("LoadAlpha"))
    {
        if (DIALOG->IsOk())
        {
            string file = DIALOG->GetFilePathName();

            file = file.substr(projectPath.size() + 1, file.size());

            Texture* alphaMap = Texture::Add(ToWString(file));

            vector<Float4> pixels;
            alphaMap->ReadPixels(pixels);

            vector<VertexType>& vertices = mesh->GetVertices();

            FOR(vertices.size())
            {
                vertices[i].alpha[0] = pixels[i].z;
                vertices[i].alpha[1] = pixels[i].y;
                vertices[i].alpha[2] = pixels[i].x;
                vertices[i].alpha[3] = pixels[i].w;
            }

            mesh->UpdateVertices();
        }

        DIALOG->Close();
    }
}

void TerrainEditor::UpdateHeight()
{
    vector<VertexType>& vertices = mesh->GetVertices();
    for (VertexType& vertex : vertices)
        vertex.normal = {};

    MakeNormal();
    MakeComputeData();

    mesh->UpdateVertices();
    structuredBuffer->UpdateInput(inputs.data());
}

void TerrainEditor::Resize()
{
    MakeMesh();
    MakeNormal();
    MakeComputeData();

    mesh->UpdateVertices();
    mesh->UpdateIndices();

    structuredBuffer->UpdateInput(inputs.data());
}

새롭게 진입된 데이터들을 생성해주고, 높이값, 알파값을 수정하는 함수들을 Switch연산자를 사용하여 호출한다.

 

각 함수 수정방식과 저장방식, 로드 방식은 위 코드 방식을 따른다.

 

728x90