본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 51 - Instancing(Mesh)

 

필요한 개념


- Mesh는 그릴 정보(Vertex, Index)만을 남기고 렌더링 가능하도록 세팅하고
MeshRender 클래스를 만들어서 인스턴싱을 세팅해주고, Mesh를 인스턴싱으로 렌더링해준다.

 

- MeshRender에서 세팅해줄 거여서 각 Mesh class들은 Shader를 생성자에서 받지 않아도 된다.(MeshRender를 통해서 각 Mesh 객체들(인스턴싱으로 여러개를 찍어낼수 있도록 함)을 만들 것이다.)

 

 

========================================================================

주 조명 처리

더보기

Shader를 통합하려고 함
공통적인 값들은 /Renders/Context에가 몰아 넣음(조명(light)도 넣음)

실외 환경에서는 주가 되는 태양이나.. 이런것들은 주빛이 1개이다.(주빛이 있다고 가정)
주 빛을 Context에 넣어줌

매 프레임마다 Shader에 밀어주는 Perframe에다가 값을 똑같이 넣어줌

-> 이렇게 하면 장점 : 주 조명을 어디서든 편집이 가능하다.
ex) ImGui::SliderFloat3("Light Direction", Context::Get()->Direction(), -1, +1);

 

 

MeshRender.h


더보기
#pragma once
// 인스턴싱으로 메시를 그릴 최대 개수
#define MAX_MESH_INSTANCE 500

class MeshRender
{
public:
	// 그려질 셰이더, 어떤 메시가 그려질지
	MeshRender(Shader* shader, Mesh* mesh);
	~MeshRender();

	Mesh* GetMesh() { return mesh; }

	void Update();
	void Render();

	void Pass(UINT val) { mesh->Pass(val); }

	// 그릴 개수만큼 추가
	Transform* AddTransform();
	Transform* GetTransform(UINT index) { return transforms[index]; }
	// transforms들을 worlds Matrix에 복사해주고 worlds Matrix들을
	// Gpu에 복사해줌
	void UpdateTransforms();

private:
	// Render가 그릴 메시
	Mesh* mesh;

	// 외부에서 그릴 개수 만큼 push함
	// transforms의 개수가 instancing의 개수(실제로 그려질 개수)
	vector<Transform*> transforms;

	// 그릴려는 것 만큼 복사를 해줌
	// MAX_MESH_INSTANCE : 최대 갯수
	Matrix worlds[MAX_MESH_INSTANCE];

	VertexBuffer* instanceBuffer;
};

 

 

 

 

 

 

 

MeshRender.Cpp


더보기
#include "Framework.h"
#include "MeshRender.h"


MeshRender::MeshRender(Shader* shader, Mesh* mesh)
	: mesh(mesh)
{
	Pass(0);

	mesh->SetShader(shader);

	for (UINT i = 0; i < MAX_MESH_INSTANCE; i++)
		D3DXMatrixIdentity(&worlds[i]);

	// 인자에 CpuWrite를 켜준다. SRT가 변경되면 GPU로 복사가능하게 하기 위함
	// 시시때때로 바뀌기 때문에, 쓰게 가능해줘야 Buffer를 수정가능하다.
	instanceBuffer = new VertexBuffer(worlds, MAX_MESH_INSTANCE, sizeof(Matrix), 1, true);
}

MeshRender::~MeshRender()
{
	for (Transform* transform : transforms)
		SafeDelete(transform);

	SafeDelete(instanceBuffer);
	SafeDelete(mesh);
}

void MeshRender::Update()
{
	mesh->Update();
}

void MeshRender::Render()
{
	// 한번만 세팅해주면 됨
	// 어차피 GPU에 복사된 상태로 렌더링에 들어가니까
	instanceBuffer->Render();

	mesh->Render(transforms.size());
}

Transform* MeshRender::AddTransform()
{
	Transform* transform = new Transform();
	
	transforms.push_back(transform);

	return transform;
}

void MeshRender::UpdateTransforms()
{
	// memcpy가 조금 더 빠르다.
	for (UINT i = 0; i < transforms.size(); i++)
		memcpy(worlds[i], transforms[i]->World(), sizeof(Matrix));

	// D3D11_SUBRESOURCE_DATA를 이용해서 GPU에 값 복사해줌
	D3D11_MAPPED_SUBRESOURCE subResource;
	D3D::GetDC()->Map(instanceBuffer->Buffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	{
		// 어차피 개수가 안되면 안그려지는 거니까(최대값 만큼 복사)
		// 최대 한번에 복사가 빠름(100개 그려질거 어차피 최대는 500이어도 그려질 부분만 그려짐)
		memcpy(subResource.pData, worlds, sizeof(Matrix) * MAX_MESH_INSTANCE);
	}
	D3D::GetDC()->Unmap(instanceBuffer->Buffer(), 0);
}

 

 

 

MeshDemo.h


더보기
#pragma once
#include "Systems/IExecute.h"

class MeshDemo : public IExecute
{
public:
	virtual void Initialize() override;
	virtual void Ready() override {};
	virtual void Destroy() override {};
	virtual void Update() override;
	virtual void PreRender() override {};
	virtual void Render() override;
	virtual void PostRender() override {};
	virtual void ResizeScreen() override {};

private:
	Shader* shader;

	// 먼저 Material 세팅하고 MeshRender가 그릴 예정임
	Material* gridMaterial;
	MeshRender* grid;

	Material* cubeMaterial;
	MeshRender* cube;

};

 

 

MeshDemo.cpp


더보기
#include "stdafx.h"
#include "MeshDemo.h"
#include "Converter.h"

void MeshDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
	Context::Get()->GetCamera()->Position(1, 36, -85);

	shader = new Shader(L"51_InstancingMesh.fx");

	// 그리드도 똑같은 셰이더로 그려진다.
	gridMaterial = new Material(shader);
	gridMaterial->DiffuseMap("White.png");
	
	// 셰이더, 그릴려는 메시
	grid = new MeshRender(shader, new MeshGrid());
	// transform 추가
	grid->AddTransform()->Scale(10, 1, 2);
	// 하나라도 데이터가 바뀌면 Update 해줘야함
	// 반드시 한번 콜 필요
	grid->UpdateTransforms();


	cubeMaterial = new Material(shader);
	cubeMaterial->DiffuseMap(L"Box.png");

	cube = new MeshRender(shader, new MeshCube());
	for (float x = -50; x <= 50; x += 2.5f)
	{
		// 하나씩 추가
		Transform* transform = cube->AddTransform();

		transform->Scale(0.25f, 0.25f, 0.25f);
		transform->Position(Vector3(x, 0.25f * 0.5f, 0.0f));
		transform->RotationDegree(0, Math::Random(-180.0f, 180.0f), 0);
	}
	// 데이터 gpu 밀어줌
	cube->UpdateTransforms();

}

void MeshDemo::Update()
{
	grid->Update();
	cube->Update();
}


void MeshDemo::Render()
{
	// 텍스쳐 데이터 밀어주고
	gridMaterial->Render();
	// 그 정보(메터리얼)를 가지고 그림
	grid->Render();

	cubeMaterial->Render();
	cube->Render();
}

 

 

 

51_InstancingMesh.fx


더보기
#include "00_Global.fx"
#include "00_Light.fx"
#include "00_Render.fx"


/*
00_Render.fx 파일에 포함되어 있다.
struct VertexMesh
{
    // 0번 슬롯
    float4 Position : Position;
    float2 Uv : Uv;
    float3 Normal : Normal;
    
    // 1번 슬롯
    // Inst가 붙은 애들을 만든 Shader 클래스에서 1번으로 취급(이후에 설명)
    // world 정보가 들어가 있다.
    matrix Transform : InstTransform;
};

// VertexShader의 부분은 총 3가지로 통합해서 사용한다.
////////////////////////////////////////////////////////////////////////////

// 이 매크로로 정점 셰이더를 정의할 수 있다. 자주사용하고 변하지 않는 것들
// original position 사용하는 일들이 있다.(ex) 큐브맵)
#define VS_GENERATE \
output.oPosition = input.Position.xyz;\
\
output.Position = WorldPosition(input.Position);\
output.Position = ViewProjection(output.Position);\
\
output.Normal = WorldNormal(input.Normal);\
\
output.Uv = input.Uv;


////////////////////////////////////////////////////////////////////////////

// inout : 입력도 되고 출력도 되도록 파라미터를 사용
// 아무것도 안붙은 애들은 입력용이다.
void SetMeshWorld(inout matrix world, VertexMesh input)
{
    world = input.Transform;
}


// 메쉬 뿐만 아니라, 모델, 애니메이션도 이 친구로 output 시킴
// 모든 정점 셰이더가 이 방법으로 리턴됨
MeshOutput VS_Mesh(VertexMesh input)
{
    MeshOutput output;
    
    SetMeshWorld(World, input);
    VS_GENERATE
    
    
    
    return output;
}
*/

float4 PS(MeshOutput input) : SV_Target
{
    float3 normal = normalize(input.Normal);
    float3 light = -GlobalLight.Direction;
    
    return DiffuseMap.Sample(LinearSampler, input.Uv) * dot(light, normal);
}


technique11 T0
{
   P_VP(P0, VS_Mesh, PS)
}

 

00_Renderfx


더보기
// 메쉬그리는거, 모델 그리는 거 다 이쪽으로 통합, 애니메이션도

struct VertexMesh
{
    // 0번 슬롯
    float4 Position : Position;
    float2 Uv : Uv;
    float3 Normal : Normal;
    
    // 1번 슬롯
    // Inst가 붙은 애들을 만든 Shader 클래스에서 1번으로 취급(이후에 설명)
    // world 정보가 들어가 있다.
    matrix Transform : InstTransform;
};

// VertexShader의 부분은 총 3가지로 통합해서 사용한다.
////////////////////////////////////////////////////////////////////////////

// 이 매크로로 정점 셰이더를 정의할 수 있다. 자주사용하고 변하지 않는 것들
// original position 사용하는 일들이 있다.(ex) 큐브맵)
#define VS_GENERATE \
output.oPosition = input.Position.xyz;\
\
output.Position = WorldPosition(input.Position);\
output.Position = ViewProjection(output.Position);\
\
output.Normal = WorldNormal(input.Normal);\
\
output.Uv = input.Uv;


////////////////////////////////////////////////////////////////////////////

// inout : 입력도 되고 출력도 되도록 파라미터를 사용
// 아무것도 안붙은 애들은 입력용이다.
void SetMeshWorld(inout matrix world, VertexMesh input)
{
    world = input.Transform;
}


// 메쉬 뿐만 아니라, 모델, 애니메이션도 이 친구로 output 시킴
// 모든 정점 셰이더가 이 방법으로 리턴됨
MeshOutput VS_Mesh(VertexMesh input)
{
    MeshOutput output;
    
    SetMeshWorld(World, input);
    VS_GENERATE
    
    
    
    return output;
}

////////////////////////////////////////////////////////////////////////////
struct VertexModel
{
    float4 Position : Position;
    float2 Uv : Uv;
    float3 Normal : Normal;
    float3 Tangent : Tangent;
    float4 BlendIndices : BlendIndices;
    float4 BlendWeights : BlendWeights;
    
    // Inst라는 키워드가 반드시 앞에 붙어야 한다.
    matrix Transform : InstTransform; // Instancing
    // SV 키워드는 우리가 시스템에 알려주거나, 시스템이 우리에게 데이터를 넘겨줄때
    // SV_InstanceID : 얘가 우리에게 Instance id를 리턴해줌(현재 그리는 애의 인스턴스를 알 수 있다.)
    uint InstanceID : SV_InstanceID;
};

// 지금 인스턴스는 하나만 보내지만, 추후 애니메이션도 여기다 보낼 것이어서
// 애니메이션도 이것을 쓸거여서 Array해줌
Texture2DArray TransformsMap;
#define MAX_MODEL_TRANSFORMS 250

cbuffer CB_Bone
{
    // bone 행렬 자체가 texture로 넘어와서 얘가 필요 없다.
   //matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
    
    uint BoneIndex;
};

void SetModelWorld(inout matrix world, VertexModel input)
{
    // 이전에 world는 이렇게 계산해왔다.
    //World = mul(BoneTransforms[BoneIndex], World);
    
    // 이번에는 texture로 보내와서 texture로 쪼개야 한다.
    float4 m0 = TransformsMap.Load(int4(BoneIndex * 4 + 0, input.InstanceID, 0, 0));
    float4 m1 = TransformsMap.Load(int4(BoneIndex * 4 + 1, input.InstanceID, 0, 0));
    float4 m2 = TransformsMap.Load(int4(BoneIndex * 4 + 2, input.InstanceID, 0, 0));
    float4 m3 = TransformsMap.Load(int4(BoneIndex * 4 + 3, input.InstanceID, 0, 0));
    
    matrix transform = matrix(m0, m1, m2, m3);
    
    // transform : 본의 행렬
    // input.Transform : global에서 그려질 애(인스턴스 world)
    world = mul(transform, input.Transform);
}

// 정점 셰이더를 묶어서 사용할 것이다.
MeshOutput VS_Model(VertexModel input)
{
    MeshOutput output;
    
    SetModelWorld(World, input);
    VS_GENERATE
    
    
    
    return output;
}

////////////////////////////////////////////////////////////////////////////

#define MAX_MODEL_KEYFRAMES 500
#define MAX_MODEL_INSTANCE 500

struct AnimationFrame
{
    int Clip;

    uint CurrFrame;
    uint NextFrame;

    float Time;
    float Running;

    float3 Padding;
};

struct TweenFrame
{
    float TakeTime;
    float TweenTime;
    float RunningTime;
    float Padding;
    
    AnimationFrame Curr;
    AnimationFrame Next;
    
};

// 묶어서 전역변수로 쓰기 위해
cbuffer CB_TweenFrame
{
    TweenFrame TweenFrames[MAX_MODEL_INSTANCE];
};

struct VertexOutput
{
    // SV_Position -> position0번으로 취급됨
    float4 Position : SV_Position;
    float3 Normal : Normal;
    float2 Uv : Uv;
};

// inout : 입력, 출력용으로 사용하겠다.
// in : 입력용으로 사용하겠다. out : 출력용으로 사용하겠다.
// Animation의 world를 계산함
// world는 입력도 하고 출력도 할거라서 inout
void SetTweenWorld(inout matrix world, VertexModel input)
{
    // 각 항목을 for문으로 다루기 위해 배열로 저장
    float indices[4] = { input.BlendIndices.x, input.BlendIndices.y, input.BlendIndices.z, input.BlendIndices.w };
    float weights[4] = { input.BlendWeights.x, input.BlendWeights.y, input.BlendWeights.z, input.BlendWeights.w };

    
    // 0번이 현재 동작 1번이 다음동작(Tweening 때문에)
    // 이후 작업 편하게 하려고 변수 선언
    int clip[2];
    int currFrame[2];
    int nextFrame[2];
    float time[2];
    
    clip[0] = TweenFrames[input.InstanceID].Curr.Clip;
    currFrame[0] = TweenFrames[input.InstanceID].Curr.CurrFrame;
    nextFrame[0] = TweenFrames[input.InstanceID].Curr.NextFrame;
    time[0] = TweenFrames[input.InstanceID].Curr.Time;
    
    clip[1] = TweenFrames[input.InstanceID].Next.Clip;
    currFrame[1] = TweenFrames[input.InstanceID].Next.CurrFrame;
    nextFrame[1] = TweenFrames[input.InstanceID].Next.NextFrame;
    time[1] = TweenFrames[input.InstanceID].Next.Time;

    // 행단위로 써놨었다. 읽어들임
    float4 c0, c1, c2, c3;
    float4 n0, n1, n2, n3;
    
    
    matrix curr = 0, next = 0;
    matrix currAnim = 0;
    matrix nextAnim = 0;
    
    // 최종행렬
    matrix transform = 0;
    
    // 현재 동작 구해줌
    [unroll(4)]
    for (int i = 0; i < 4; i++)
    {
        // 가중치가 4개였다.(스키닝을 하기위한)
        // Sample은 확대/축소가 일어나므로, 데이터의 변형이 일어나서
        // 사용할 수 없다. 그래서 Load을 사용 -> 행열로 원본데이터를 불러옴
        // int4 ? 왜 int인가? -> 마지막 w는 밉맵의 번호이다, 우리는 맵맵을 한장으로 사용하므로 0(우리는 민맵 1로 하나만 사용하니까 0을 줌)
        // 4개 픽셀 했었어서 *4 곱함(x는 그렇다.)
        // + 0은 각 픽셀의 0행을 위해
        // 본 번호(열), 키 프레임(행), clip(면)
        c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[0], clip[0], 0)); // 1행
        c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[0], clip[0], 0)); // 2행
        c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[0], clip[0], 0)); // 3행
        c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[0], clip[0], 0)); // 4행
        curr = matrix(c0, c1, c2, c3);
        
        n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[0], clip[0], 0)); // 1행
        n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[0], clip[0], 0)); // 2행
        n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[0], clip[0], 0)); // 3행
        n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[0], clip[0], 0)); // 4행
        next = matrix(n0, n1, n2, n3);
        
        // 이전 프레임에서 현재 프레임까지의 갑시, desc.time은 0 ~ 1 범위이므로 lerp의 보간 값으로 그대로 대입한다.
        // lerp도 0 ~ 1값으로 처리하기 때문이다.
        // (현재 프레임 Matrix, 다음 프레임 Matrix, 시간값(0 ~ 1))
        currAnim = lerp(curr, next, time[0]);
        
        
        
        // 다음 동작 구해줌
        // flatten : 제어문법(현재는 신경쓰지 말자)
        // 다음이 -1 보다 크다면(clip이 있다는 뜻) 다음 동작을 플레이 시킨다.
        [flatten]
        if (clip[1] > -1)
        {
            c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[1], clip[1], 0)); // 1행
            c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[1], clip[1], 0)); // 2행
            c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[1], clip[1], 0)); // 3행
            c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[1], clip[1], 0)); // 4행
            curr = matrix(c0, c1, c2, c3);
        
            n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[1], clip[1], 0)); // 1행
            n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[1], clip[1], 0)); // 2행
            n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[1], clip[1], 0)); // 3행
            n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[1], clip[1], 0)); // 4행
            next = matrix(n0, n1, n2, n3);
            
            nextAnim = lerp(curr, next, time[1]);
            
            // TweenTime : 0 ~ 1까지 정규화되어 있다.
            currAnim = lerp(currAnim, nextAnim, TweenFrames[input.InstanceID].TweenTime);
        }
        
        
        // 최종행렬에 가중치만 누적시키면 된다.
        // 곱한걸 계속 누적시키는 것(계속 영향을 받을 수 있도록)
        // 초반에 설명한 weights[i] 0.5라면 0.5만큼 curr에 곱해줌(움직여줌)
        // transform : 가중치, 프레임까지 결합된 행렬
        transform += mul(weights[i], currAnim);
    }
    
    // 최종행렬을 world로 변환
    // transform : 애니메이션이 이동할 행렬, input.Transform : 인스턴싱의 world
    world = mul(transform, input.Transform);
}


struct BlendFrame
{
    uint Mode;
    float Alpha;
    float2 Padding;

    AnimationFrame Clip[3];
};

cbuffer CB_BlendFrame
{
    // s는 인스턴싱 때문에 미리 붙여놓음
    BlendFrame BlendFrames[MAX_MODEL_INSTANCE];
};

void SetBlendWorld(inout matrix world, VertexModel input)
{
    // 각 항목을 for문으로 다루기 위해 배열로 저장
    float indices[4] = { input.BlendIndices.x, input.BlendIndices.y, input.BlendIndices.z, input.BlendIndices.w };
    float weights[4] = { input.BlendWeights.x, input.BlendWeights.y, input.BlendWeights.z, input.BlendWeights.w };


    // 행단위로 써놨었다. 읽어들임
    float4 c0, c1, c2, c3;
    float4 n0, n1, n2, n3;
    
    
    matrix curr = 0, next = 0;
    matrix currAnim[3];
    matrix anim = 0;
    
    // 최종행렬
    matrix transform = 0;
    
    
    BlendFrame frame = BlendFrames[input.InstanceID];
    
    
    // 현재 동작 구해줌
    [unroll(4)]
    for (int i = 0; i < 4; i++)
    {
        [unroll(3)]
        for (int k = 0; k < 3; k++)
        {
            c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 1행
            c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 2행
            c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 3행
            c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 4행
            curr = matrix(c0, c1, c2, c3);
        
            n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 1행
            n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 2행
            n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 3행
            n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 4행
            next = matrix(n0, n1, n2, n3);
     
            currAnim[k] = lerp(curr, next, frame.Clip[k].Time);
        }
        
        // 0, 1, 2 구간이 떨어짐
        int clipA = (int) frame.Alpha;
        int clipB = clipA + 1;
        
        float alpha = frame.Alpha;
        
        if (alpha >= 1.0f)
        {
            // 1 ~ 2구간을 가야하니까 -1을 시킴
            // lerp를 하기 위해 0 ~ 1구간을 만들어야 해서 -1을 함
            alpha = frame.Alpha - 1.0f;
            
            // 2를 넘었다면 플레이 구간이 1 ~ 2이다.
            if (frame.Alpha >= 2.0f)
            {
                clipA = 1;
                clipB = 2;
            }
        }
        
        anim = lerp(currAnim[clipA], currAnim[clipB], alpha);
       
        transform += mul(weights[i], anim);
    }
    world = mul(transform, input.Transform);
}

MeshOutput VS_Animation(VertexModel input)
{
    MeshOutput output;
    
    if (BlendFrames[input.InstanceID].Mode == 0)
        SetTweenWorld(World, input);
    else
        SetBlendWorld(World, input);
    
    VS_GENERATE
    
    return output;
}

 

 

00_Light.fx


더보기
// 조명, material 색상에 관한걸 모아놈


// Light 정보
struct LightDesc
{
    float4 Ambient;
    float4 Specular;
    float3 Direction;
    float Padding;
    float3 Position;
};

cbuffer CB_Light
{
    LightDesc GlobalLight;
};


// Material 정보
Texture2D DiffuseMap;
Texture2D SpecularMap;
Texture2D NormalMap;
TextureCube SkyCubeMap;

struct MaterialDesc
{
    float4 Ambient;
    float4 Diffuse;
    float4 Specular;
    float4 Emissive;
};

cbuffer CB_Material
{
    MaterialDesc Material;
};

 

 

 

 

 

 

 

 

 

 

결과