DX11 3D Game Programming
2020_1021 Terrain Edit ( Texture Splatting )
HI2
2020. 10. 21. 17:46
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);
}