본문으로 바로가기

201021_DX3D_TextureSplatting.zip
7.16MB

 

 

1. Texture Editor

더보기
#pragma once

class TerrainEditor : public Transform
{
private:
	typedef VertexUVNormalTangentAlpha VertexType;
	//
public:
	class BrushBuffer : public ConstBuffer
	{
	public:
		struct Data
		{
			int type;
			Float3 location;
			float range;
			Float3 color;
		}data;
		//
		BrushBuffer() : ConstBuffer(&data, sizeof(Data))
		{
			data.type = 0;
			data.location = Float3(0, 0, 0);
			data.range = 10.0f;
			data.color = Float3(0.0f, 0.5f, 0.0f);
		}
	};
	//
	struct InputDesc
	{
		UINT index;
		Float3 v0, v1, v2;
	};
	//
	struct OutputDesc
	{
		int picked;
		float u, v, distance;
	};
	////////////////////////////////////
	const float MAX_HEIGHT = 30.0f;
	const float MAX_ALPHA = 1.0f;
	////////////////////////////////////
	Material* material;
	Mesh* mesh;
	////////////////////////////////////
	vector<VertexType> vertices;
	vector<UINT> indices;
	////////////////////////////////////
	UINT width, height;
	////////////////////////////////////
	Texture* heightMap;
	//Texture* alphaMap;
	Texture* secondMap;
	Texture* thirdMap;
	Texture* alphaMaps[2];
	////////////////////////////////////
	ComputeShader* computeShader;
	RayBuffer* rayBuffer;
	StructuredBuffer* structuredBuffer;
	InputDesc* input;
	OutputDesc* output;
	//
	UINT size;
	////////////////////////////////////
	BrushBuffer* brushBuffer;
	////////////////////////////////////
	bool isRaise;
	float adjustValue;
	////////////////////////////////////
	bool isPainting;
	float paintValue;
	int selectMap;
	////////////////////////////////////
	char inputHeightFileName[100];
	char inputAlphaFileName[100];
	////////////////////////////////////
public:
	TerrainEditor(UINT width = 100, UINT height = 100);
	~TerrainEditor();
	////////////////////////////////////
	void Update();
	void Render();
	void PostRender();
	////////////////////////////////////
	bool ComputePicking(OUT Vector3* position);
	////////////////////////////////////
	void AdjustY(Vector3 position);
	void PaintBrush(Vector3 position);
	////////////////////////////////////
	void SaveHeight(wstring heightFile);
	void LoadHeight(wstring heightFile);
	void ResetHeight();
	void SaveAlpha(wstring alphaFile, int selectMap);
	void LoadAlpha(wstring alphaFile, int selectMap);
	void ResetAlpha();
	////////////////////////////////////
private:
	void CreateMesh();
	void CreateNormal();
	void CreateTangent();
	void CreateCompute();
};
////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "TerrainEditor.h"

TerrainEditor::TerrainEditor(UINT width, UINT height)
	: width(width), height(height),
	isRaise(true), adjustValue(50.0f),
	heightMap(nullptr),
	inputHeightFileName{}, inputAlphaFileName{},
	structuredBuffer(nullptr), rayBuffer(nullptr), output(nullptr),
	isPainting(true), paintValue(5.0f), selectMap(0),
	secondMap(nullptr), thirdMap(nullptr), alphaMaps{} // alphaMap(nullptr)
{
	material = new Material(L"TerrainSplating");
	material->SetDiffuseMap(L"Textures/Landscape/Dirt2.png");
	//
	//alphaMap = Texture::Add(L"Textures/HeightMaps/AlphaMap.png");
	secondMap = Texture::Add(L"Textures/Landscape/Floor.png");
	thirdMap = Texture::Add(L"Textures/Landscape/Stones.png");
	//
	CreateMesh();
	//
	CreateCompute();
	//
	brushBuffer = new BrushBuffer;
}

TerrainEditor::~TerrainEditor()
{
	delete brushBuffer;
	//
	delete material;
	delete mesh;

	delete rayBuffer;
	delete structuredBuffer;

	delete[] input;
	delete[] output;
}

void TerrainEditor::Update()
{
	if (ImGui::GetIO().WantCaptureMouse == false)
	{
		if (KEY_PRESS(VK_LBUTTON))
		{
			if (isPainting == true)
				PaintBrush(brushBuffer->data.location);
			else
				AdjustY(brushBuffer->data.location);
		}
		if (KEY_UP(VK_LBUTTON))
		{
			CreateNormal();
			CreateTangent();
			mesh->UpdateVertex(vertices.data(), vertices.size());
		}
	}
	//
	UpdateWorld();
}

void TerrainEditor::Render()
{
	mesh->IASet();
	worldBuffer->SetVSBuffer(0);
	brushBuffer->SetPSBuffer(10);
	//
	//if(alphaMap != nullptr)
	//	alphaMap->PSSet(10);
	//
	if(secondMap != nullptr)
		secondMap->PSSet(11);
	//
	if(thirdMap != nullptr)
		thirdMap->PSSet(12);
	//
	material->Set();
	//
	DC->DrawIndexed((UINT)indices.size(), 0, 0);
}

void TerrainEditor::PostRender()
{
	/* Debug */
	//if(ImGui::IsWindowFocused())
	//
	Vector3 temp{};
	ComputePicking(&temp);
	brushBuffer->data.location = temp;
	//
	ImGui::Spacing();
	ImGui::Text("[Terrain Editor]");
	ImGui::SliderInt("Type",
		&brushBuffer->data.type, 0, 1);
	ImGui::SliderFloat("Range",
		&brushBuffer->data.range, 1.0f, 50.0f);
	ImGui::ColorEdit3("Color",
		(float*)&brushBuffer->data.color);
	ImGui::Checkbox("Raise", &isRaise);
	//
	ImGui::Checkbox("Painting", &isPainting);
	ImGui::InputInt("SelectMap", &selectMap);
	// Save & Load HeightMap
	ImGui::InputText("HeightFileName", inputHeightFileName, 100);
	wstring heightFile = ToWString(inputHeightFileName);
	if (ImGui::Button("SaveHeight", { 128.0f, 32.0f }))
		SaveHeight(heightFile);
	if (ImGui::Button("LoadHeight", { 128.0f, 32.0f }))
	{
		LoadHeight(heightFile);
		//
		CreateNormal();
		CreateTangent();
		mesh->UpdateVertex(vertices.data(), vertices.size());
	}
	if (ImGui::Button("ResetHeight", { 128.0f, 32.0f }))
		ResetHeight();
	// Save & Load AlphaMap
	ImGui::InputText("AlphaFileName", inputAlphaFileName, 100);
	wstring alphaFile = ToWString(inputAlphaFileName);
	if (ImGui::Button("SaveAlpha", { 128.0f, 32.0f }))
		SaveAlpha(alphaFile, selectMap);
	if (ImGui::Button("LoadAlpha", { 128.0f, 32.0f }))
	{
		LoadAlpha(alphaFile, selectMap);
		//
		mesh->UpdateVertex(vertices.data(), vertices.size());
	}
	if (ImGui::Button("ResetAlpha", { 128.0f, 32.0f }))
		ResetAlpha();
}

bool TerrainEditor::ComputePicking(OUT Vector3* position)
{
	Ray ray = CAMERA->ScreenPointToRay(MOUSEPOS);

	rayBuffer->data.position = ray.position;
	rayBuffer->data.direction = ray.direction;
	rayBuffer->data.size = size;
	//
	computeShader->Set();
	rayBuffer->SetCSBuffer(0);
	//
	DC->CSSetShaderResources(0, 1, &structuredBuffer->GetSRV());
	DC->CSSetUnorderedAccessViews(0, 1, &structuredBuffer->GetUAV(), nullptr);

	UINT x = ceil((float)size / 1024.0f);

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

	structuredBuffer->Copy(output, sizeof(OutputDesc) * size);

	float minDistance = FLT_MAX;
	int minIndex = -1;

	for (UINT i = 0; i < size; i++)
	{
		OutputDesc temp = output[i];
		if (temp.picked)
		{
			if (minDistance > temp.distance)
			{
				minDistance = temp.distance;
				minIndex = i;
			}
		}
	}

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

	return false;
}

void TerrainEditor::AdjustY(Vector3 position)
{
	switch (brushBuffer->data.type)
	{
	case 0:
	{
		for (VertexType& vertex : vertices)
		{
			Vector3 p1 = Vector3(vertex.position.x, 0, vertex.position.z);
			Vector3 p2 = Vector3(position.x, 0, position.z);
			//
			float distance = (p2 - p1).Length();
			//
			float cosValue = cos(XM_PIDIV2 * distance / brushBuffer->data.range);
			float temp = adjustValue * max(0, cosValue);
			//
			if (distance <= brushBuffer->data.range)
			{
				if (isRaise)
					vertex.position.y += temp * DELTA;
				else
					vertex.position.y -= temp * DELTA;
				//
				if (vertex.position.y < 0)
					vertex.position.y = 0;
				else if (vertex.position.y > MAX_HEIGHT)
					vertex.position.y = MAX_HEIGHT;
			}
		}
	}
	break;
	case 1:
	{
		for (VertexType& vertex : vertices)
		{
			Vector3 p1 = Vector3(vertex.position.x, 0, vertex.position.z);
			Vector3 p2 = Vector3(position.x, 0, position.z);
			//
			float distX = abs(p2.x - p1.x);
			float distZ = abs(p2.z - p1.z);
			//
			if (distX <= brushBuffer->data.range
				&& distZ <= brushBuffer->data.range)
			{
				if (isRaise)
					vertex.position.y += adjustValue * DELTA;
				else
					vertex.position.y -= adjustValue * DELTA;
			}
		}
	}
	break;
	default:
		break;
	}
	//
	mesh->UpdateVertex(vertices.data(), vertices.size());
}

void TerrainEditor::PaintBrush(Vector3 position)
{
	switch (brushBuffer->data.type)
	{
	case 0:
	{
		for (VertexType& vertex : vertices)
		{
			Vector3 p1 = Vector3(vertex.position.x, 0, vertex.position.z);
			Vector3 p2 = Vector3(position.x, 0, position.z);
			//
			float distance = (p2 - p1).Length();
			//
			float cosValue = cos(XM_PIDIV2 * distance / brushBuffer->data.range);
			float temp = paintValue * max(0, cosValue);
			//
			if (distance <= brushBuffer->data.range)
			{
				if (isRaise)
					vertex.alpha[selectMap] += temp * DELTA;
				else
					vertex.alpha[selectMap] -= temp * DELTA;
				//
				vertex.alpha[selectMap] = Saturate(vertex.alpha[selectMap]);
			}
		}
	}
	break;
	case 1:
	{
		for (VertexType& vertex : vertices)
		{
			Vector3 p1 = Vector3(vertex.position.x, 0, vertex.position.z);
			Vector3 p2 = Vector3(position.x, 0, position.z);
			//
			float distX = abs(p2.x - p1.x);
			float distZ = abs(p2.z - p1.z);
			//
			if (distX <= brushBuffer->data.range
				&& distZ <= brushBuffer->data.range)
			{
				if (isRaise)
					vertex.alpha[selectMap] += paintValue * DELTA;
				else
					vertex.alpha[selectMap] -= paintValue * DELTA;
				//
				vertex.alpha[selectMap] = Saturate(vertex.alpha[selectMap]);
			}
		}
	}
	break;
	}
	//
	mesh->UpdateVertex(vertices.data(), vertices.size());
}

void TerrainEditor::SaveHeight(wstring heightFile)
{
	UINT size = width * height * 4;
	/* Create Height Map File */
	{
		uint8_t* pixels = new uint8_t[size];
		//
		for (UINT i = 0; i < size / 4; ++i)
		{
			float y = vertices[i].position.y;
			uint8_t height = (y * 255.0f) / MAX_HEIGHT;
			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.pixels = pixels;
		image.format = DXGI_FORMAT_R8G8B8A8_UNORM;
		image.rowPitch = image.width * 4;
		image.slicePitch = size;
		//
		wstring fileName =
			L"Textures/HeightMaps/"
			+ heightFile
			+ L".png";
		//
		SaveToWICFile(image, WIC_FLAGS_FORCE_RGB,
			GetWICCodec(WIC_CODEC_PNG), fileName.c_str());
		//
		delete[] pixels;
	}
	/* Create Binary File
	// Create Filesystem
	HANDLE file;
	DWORD write; // unsigned long
	file = CreateFile(L"MapFiles/mapData.map", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
	{
		int count = vertices.size();
		V(WriteFile(file, &count, sizeof(int), &write, nullptr));
		//
		Vector3 data{};
		for (int i = 0; i < count; ++i)
		{
			data = vertices[i].position;
			V(WriteFile(file, &data, sizeof(Vector3), &write, nullptr));
		}
	}
	// Save End
	CloseHandle(file);
	*/
}
void TerrainEditor::LoadHeight(wstring heightFile)
{
	delete mesh;
	//
	wstring fileName =
		L"Textures/HeightMaps/"
		+ heightFile
		+ L".png";
	//
	heightMap = Texture::Load(fileName);
	//
	CreateMesh();
	CreateCompute();

	/* Create Binary File
	// Create File System
	HANDLE file;
	DWORD read; // unsigned long
	file = CreateFile(L"MapFiles/mapData.map", GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
	{
		int count{};
		V(ReadFile(file, &count, sizeof(int), &read, nullptr));
		//
		Vector3 data{};
		for (int i = 0; i < count; ++i)
		{
			V(ReadFile(file, &data, sizeof(Vector3), &read, nullptr));
			vertices[i].position = data;
		}
	}
	// Load End
	CloseHandle(file);
	*/
}

void TerrainEditor::ResetHeight()
{
	for (auto& vertex : vertices)
		vertex.position.y = 0;
	//
	CreateNormal();
	CreateTangent();
	mesh->UpdateVertex(vertices.data(), vertices.size());
}

void TerrainEditor::SaveAlpha(wstring alphaFile, int selectMap)
{
	UINT size = width * height * 4;
	/* Create Alpha Map File */
	{
		uint8_t* pixels = new uint8_t[size];
		//
		for (UINT i = 0; i < size / 4; ++i)
		{
			float a = vertices[i].alpha[selectMap];
			uint8_t alpha = (a * 255.0f) / MAX_ALPHA;
			pixels[(i * 4) + 0] = 0;
			pixels[(i * 4) + 1] = 0;
			pixels[(i * 4) + 2] = 0;
			pixels[(i * 4) + 3] = 255;
			//
			pixels[(i * 4) + selectMap] = alpha;
		}
		//
		Image image;
		image.width = width;
		image.height = height;
		image.pixels = pixels;
		image.format = DXGI_FORMAT_R8G8B8A8_UNORM;
		image.rowPitch = image.width * 4;
		image.slicePitch = size;
		//
		wstring fileName =
			L"Textures/AlphaMaps/"
			+ alphaFile
			+ L"_"
			+ to_wstring(selectMap)
			+ L".png";
		SaveToWICFile(image, WIC_FLAGS_FORCE_RGB,
			GetWICCodec(WIC_CODEC_PNG), fileName.c_str());
		//
		delete[] pixels;
	}
}

void TerrainEditor::LoadAlpha(wstring alphaFile, int selectMap)
{
	wstring fileName =
		L"Textures/AlphaMaps/"
		+ alphaFile
		+ L"_"
		+ to_wstring(selectMap)
		+ L".png";
	//
	alphaMaps[selectMap] = Texture::Load(fileName);
	//
	vector<Float4> pixels = alphaMaps[selectMap]->ReadPixels();
	//
	for (UINT z = 0; z < height; z++)
	{
		for (UINT x = 0; x < width; x++)
		{
			UINT index = width * z + x;
			//
			switch (selectMap)
			{
			case 0:
				vertices[index].alpha[selectMap] = pixels[index].x;
				break;
			case 1:
				vertices[index].alpha[selectMap] = pixels[index].y;
				break;
			default:
				break;
			}
		}
	}
}

void TerrainEditor::ResetAlpha()
{
	for (auto& vertex : vertices)
	{
		vertex.alpha[0] = 0;
		vertex.alpha[1] = 0;
	}
	//
	mesh->UpdateVertex(vertices.data(), vertices.size());
}

void TerrainEditor::CreateMesh()
{
	vertices.clear();
	indices.clear();
	//
	vector<Float4> pixels{};
	if (heightMap != nullptr)
	{
		width = heightMap->Width();
		height = heightMap->Height();
		//Vertices
		pixels = heightMap->ReadPixels();
	}
	//
	for (UINT z = 0; z < height; z++)
	{
		for (UINT x = 0; x < width; x++)
		{
			VertexType vertex;
			vertex.position = Float3((float)x, 0.0f, (float)z);
			vertex.uv = Float2(x / (float)width, 1.0f - z / (float)height);

			UINT index = width * z + x;
			//
			if (index < pixels.size())
				vertex.position.y += pixels[index].x * MAX_HEIGHT;
			//
			vertices.emplace_back(vertex);
		}
	}

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

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

	size = indices.size() / 3;

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

		input[i].v0 = vertices[index0].position;
		input[i].v1 = vertices[index1].position;
		input[i].v2 = vertices[index2].position;

		input[i].index = i;
	}

	CreateNormal();
	CreateTangent();

	mesh = new Mesh(vertices.data(), sizeof(VertexType), (UINT)vertices.size(),
		indices.data(), (UINT)indices.size());
}

void TerrainEditor::CreateNormal()
{
	for (UINT i = 0; i < indices.size() / 3; i++)
	{
		UINT index0 = indices[i * 3 + 0];
		UINT index1 = indices[i * 3 + 1];
		UINT index2 = indices[i * 3 + 2];

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

		Vector3 A = v1 - v0;
		Vector3 B = v2 - v0;

		Vector3 normal = Vector3::Cross(A, B).Normal();

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

	for (VertexType& vertex : vertices)
		vertex.normal = Vector3(vertex.normal).Normal();
}

void TerrainEditor::CreateTangent()
{
	for (UINT i = 0; i < indices.size() / 3; i++)
	{
		UINT index0 = indices[i * 3 + 0];
		UINT index1 = indices[i * 3 + 1];
		UINT index2 = indices[i * 3 + 2];

		VertexType vertex0 = vertices[index0];
		VertexType vertex1 = vertices[index1];
		VertexType vertex2 = vertices[index2];

		Vector3 p0 = vertex0.position;
		Vector3 p1 = vertex1.position;
		Vector3 p2 = vertex2.position;

		Float2 uv0 = vertex0.uv;
		Float2 uv1 = vertex1.uv;
		Float2 uv2 = vertex2.uv;

		Vector3 e0 = p1 - p0;
		Vector3 e1 = p2 - p0;

		float u0 = uv1.x - uv0.x;
		float u1 = uv2.x - uv0.x;
		float v0 = uv1.y - uv0.y;
		float v1 = uv2.y - uv0.y;

		Vector3 tangent = (v1 * e0 - v0 * e1);

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

	for (VertexType& vertex : vertices)
	{
		Vector3 t = vertex.tangent;
		Vector3 n = vertex.normal;

		Vector3 temp = (t - n * Vector3::Dot(n, t)).Normal();

		vertex.tangent = temp;
	}
}

void TerrainEditor::CreateCompute()
{
	computeShader = Shader::AddCS(L"ComputePicking");
	//
	if (structuredBuffer != nullptr)
		delete structuredBuffer;
	//
	structuredBuffer
		= new StructuredBuffer(input, sizeof(InputDesc), size,
			sizeof(OutputDesc), size);
	//
	if (rayBuffer == nullptr)
		rayBuffer = new RayBuffer;
	//
	if (output != nullptr)
		delete[] output;
	output = new OutputDesc[size];
}

 

2. Terrain Splating Shader

더보기

# Header.hlsli

//VertexShaderBuffer
cbuffer World : register(b0)
{
    matrix world;
}

cbuffer View : register(b1)
{
    matrix view;
    matrix invView;
}

cbuffer Projection : register(b2)
{
    matrix projection;
}

//PixelShaderBuffer
cbuffer Light : register(b0)
{
    float3 lightDirection;
}

cbuffer Material : register(b1)
{
    float3 mDiffuse;
    float padding1;
    
    float3 mSpecular;
    float padding2;
    
    float3 mAmbient;
    float padding3;
    
    float shininess;
    
    int hasDiffuseMap;
    int hasSpecularMap;
    int hasNormalMap;
}

SamplerState samp : register(s0);

Texture2D diffuseMap : register(t0);
Texture2D specularMap : register(t1);
Texture2D normalMap : register(t2);

//VertexLayouts
struct Vertex
{
    float4 pos : Position;    
};

struct VertexUV
{
    float4 pos : Position;
    float2 uv : UV;    
};

struct VertexUVNormal
{
    float4 pos : Position;
    float2 uv : UV;
    float3 normal : Normal;
};

struct VertexUVNormalTangent
{
    float4 pos : Position;
    float2 uv : UV;
    float3 normal : Normal;
    float3 tangent : Tangent;
};
struct VertexUVNormalTangentAlpha
{
    float4 pos : Position;
    float2 uv : UV;
    float3 normal : Normal;
    float3 tangent : Tangent;
    float4 alpha : Alpha;
};

# TerrainSplatting Shader

#include "Header.hlsli"

cbuffer Brush : register(b10)
{
    int type; // 원형, 사각형
    float3 location;
    float range;
    float3 color;
}

struct PixelInput
{
    float4 pos : SV_Position;
    float2 uv : UV;
    float3 normal : Normal;
    float3 tangent : Tangent;
    float3 binormal : Binormal;
    float3 viewDir : ViewDir;
    float3 worldPos : Position;
    float4 alpha : Alpha;
};

float3 BrushColor(float3 pos)
{
    if (type == 0)
    {
        float x = pos.x - location.x;
        float z = pos.z - location.z;
        //
        float distance = sqrt(x * x + z * z);
        //
        if (distance <= range)
            return color;
    }
    else if (type == 1)
    {
        float x = pos.x - location.x;
        float z = pos.z - location.z;
        //
        float distX = abs(x);
        float distZ = abs(z);
        //
        if (distX <= range && distZ <= range)
            return color;
    }
    //
    return float3(0, 0, 0);
}

PixelInput VS(VertexUVNormalTangentAlpha input)
{
    PixelInput output;
    //
    output.pos = mul(input.pos, world);
    //
    float3 camPos = invView._41_42_43;
    output.viewDir = normalize(output.pos.xyz - camPos);
    //
    output.worldPos = output.pos.xyz;
    //
    output.pos = mul(output.pos, view);
    output.pos = mul(output.pos, projection);
    //
    output.normal = mul(input.normal, (float3x3) world);
    output.tangent = mul(input.tangent, (float3x3) world);
    output.binormal = cross(output.normal, output.tangent);
    
    output.uv = input.uv;
    output.alpha = input.alpha;
    
    return output;
}
//
//Texture2D alphaMap : register(t10);
Texture2D secondMap : register(t11);
Texture2D thirdMap : register(t12);
//
float4 PS(PixelInput input) : SV_Target
{
    float4 albedo = float4(1, 1, 1, 1);
    if (hasDiffuseMap)
        albedo = diffuseMap.Sample(samp, input.uv);
    //
    //float4 alpha = alphaMap.Sample(samp, input.uv);
    float4 second = secondMap.Sample(samp, input.uv);
    float4 third = thirdMap.Sample(samp, input.uv);
    //
    albedo = lerp(albedo, second, input.alpha.r);
    albedo = lerp(albedo, third, input.alpha.g);
    //
    float3 light = normalize(lightDirection);
    
    float3 T = normalize(input.tangent);
    float3 B = normalize(input.binormal);
    float3 N = normalize(input.normal);
    
    float3 normal = N;
    
    if (hasNormalMap)
    {
        float4 normalMapping = normalMap.Sample(samp, input.uv);
    
        float3x3 TBN = float3x3(T, B, N);
        normal = normalMapping * 2.0f - 1.0f;
        normal = normalize(mul(normal, TBN));
    }
    
    float3 viewDir = normalize(input.viewDir);
    
    float diffuseIntensity = saturate(dot(normal, -light));
    
    float3 specular = 0;
    if (diffuseIntensity > 0)
    {
        /* phong Shading */
        //float3 reflection = normalize(reflect(light, normal));
        //specular = saturate(dot(reflection, -viewDir));

        /* Blinn - Phong Shading */
        float3 halfWay = normalize(viewDir + light);
        specular = saturate(dot(-halfWay, normal));
        
        float3 specularIntensity = float3(1, 1, 1);
        if (hasSpecularMap)
            specularIntensity = specularMap.Sample(samp, input.uv).rgb;
        
        specular = pow(specular, shininess) * specularIntensity;
    }
    
    float3 diffuse = albedo.rgb * diffuseIntensity * mDiffuse;
    specular *= mSpecular;
    float3 ambient = albedo.rgb * mAmbient;
    //
    float3 brushColor = BrushColor(input.worldPos);
    
    return float4(diffuse + specular + ambient + brushColor,
    1.0f);
}