본문으로 바로가기

200812_OutLine_PSShader.zip
3.70MB

 

0. FilterShader -> [ActionFilterShader]

  - Action이 포함된 Object를 Render 하기 위해 ActionBuffer(slot : b0) 추가

  - imageSize -> action->size로 변경

  - action의 frame이 적용하여 uv 수정

더보기
/* Filter Shader - Texture Shader Base */
Texture2D map : register(t0);
SamplerState samp : register(s0);
//
cbuffer Info : register(b1)
{
    int selected;
    int value;
    //
    int value1;
    int value2;
}
//
cbuffer ImageSize : register(b2)
{
    float2 imageSize;
}
// 주변 픽셀 8칸
static const float2 edges[8] =
{
    float2(-1, -1), float2(0, -1), float2(1, -1),
    float2(-1, 0), float2(1, 0),
    float2(-1, 1), float2(0, 1), float2(1, 1)
};
///////////////////////////////////////
struct PixelInput
{
    float4 pos : SV_Position;
    float2 uv : UV;
};

float4 Mosaic(float2 uv)
{
    float2 temp;
    // original
    if(value == 100)
        return map.Sample(samp, uv);
    //
    temp.x = floor(uv.x * value) / value;
    temp.y = floor(uv.y * value) / value;

    return map.Sample(samp, temp);
}

float4 Blur(float2 uv)
{
    float4 result = 0;
    /* solv_1 : 상하 좌우 픽셀 더해서 나누기
    float divX = 1.0f / imageSize.x;
    float divY = 1.0f / imageSize.y;
    //
    result += map.Sample(samp, float2(uv.x + divX, uv.y));
    result += map.Sample(samp, float2(uv.x - divX, uv.y));
    result += map.Sample(samp, float2(uv.x, uv.y + divY));
    result += map.Sample(samp, float2(uv.x, uv.y - divY));
    //
    result /= 4.0f;
    */
    /* solv_2 : 천진반 style
    //float divX = value / imageSize.x;
    //float divY = value / imageSize.y;
    //
    result += map.Sample(samp, float2(uv.x + divX, uv.y));
    result += map.Sample(samp, float2(uv.x - divX, uv.y));
    result += map.Sample(samp, float2(uv.x, uv.y + divY));
    result += map.Sample(samp, float2(uv.x, uv.y - divY));
    //
    // result /= (4.0f * (float) value);
    */
    /* solv_3 : 고성능, 고비용 (Shader 언어의 근본적 한계(반복문))
    [unroll (16)] // 반복 횟수 제한
    for (int i = 0; i < value; ++i)
    {
        float divX = (i + i) / imageSize.x;
        float divY = (i + i) / imageSize.y;
        //
        result += map.Sample(samp, float2(uv.x + divX, uv.y));
        result += map.Sample(samp, float2(uv.x - divX, uv.y));
        result += map.Sample(samp, float2(uv.x, uv.y + divY));
        result += map.Sample(samp, float2(uv.x, uv.y - divY));
    }
    //
    result /= (4.0f * (float) value);
    */
    /* solv_4 : 주변 8 방향 픽셀 혼합 */
    for (int i = 0; i < value; i++)
    {
        float divX = (i + 1) / imageSize.x;
        float divY = (i + 1) / imageSize.y;
        //
        for (int j = 0; j < 8; j++)
        {
            float x = edges[j].x * divX + uv.x;
            float y = edges[j].y * divY + uv.y;
            //
            result += map.Sample(samp, float2(x, y));
        }
    }
    result /= 8 * value;
    //
    return result;
}

// 정규 분포
static const float weights[13] =
{
    0.0561f, 0.1353f, 0.2730f, 0.4868f, 0.7261f, 0.9231f,
    1.0f,
    0.9231f, 0.7261f, 0.4868f, 0.2730f, 0.1353f, 0.0561f
};

float4 GaussianBlur(float2 uv)
{ // 정규분포 응용
    // 1 pixel uv size
    float divX = 1.0f / imageSize.x;
    float divY = 1.0f / imageSize.y;
    //
    float sum = 0;
    float4 result = 0;
    //
    for (int i = -6; i <= 6; ++i)
    {
        float2 temp = 0;
        //
        temp = uv + float2(divX * i * value, 0);
        result += weights[6 + i] * map.Sample(samp, temp);
        //
        temp = uv + float2(0, divY * i * value);
        result += weights[6 + i] * map.Sample(samp, temp);
        //
        sum += weights[6 + i] * 2.0f;
    }
    result /= sum;
    return result;
}

float4 RadialBlur(float2 uv)
{ // 카드라이더 Booster style
    float2 radiusUV = uv - float2(0.5f, 0.5f);
    float r = length(radiusUV);
    radiusUV /= r;
    // saturate : 인자에 있는 값을 0 ~ 1 사이로 고정
    r = saturate((2.0f * r) / value1);
    float2 delta = -radiusUV * (r * r) * (value2 / value);
    //
    float4 result = 0;
    //
    for (int i = 0; i < value; ++i)
    {
        result += map.Sample(samp, uv);
        uv += delta;
    }
    result /= value;
    //
    return result;
}

float4 OutLine(float2 uv)
{
    /* TODO : 외곽선 */
    /* TODO2 : 선 두께 */
    float4 result = map.Sample(samp, uv);
    //
    if(result.a != 1.0f)
        return result;

    /* Check edges alpha if this alpha is 1.0f */
    bool isOutLine = false;
    float4 edgeColor = 0;
    //
    float divX = 1.0f / imageSize.x;
    float divY = 1.0f / imageSize.y;
    //
    for (int j = 0; j < 8; j++)
    {
        float x = edges[j].x * divX + uv.x;
        float y = edges[j].y * divY + uv.y;
        //
        edgeColor = map.Sample(samp, float2(x, y));
        if (edgeColor.a == 0)
        {
            isOutLine = true;
            break;
        }
    }
    if(isOutLine)
        return float4(0, 1, 0, 1);
    //
    return result;
}

float4 PS(PixelInput input) : SV_TARGET
{
    //float2 uv = (startPos / maxSize) + input.uv * (size / maxSize);
    [branch]
    if(selected == 1)
        return Mosaic(input.uv);
    else if(selected == 2)
        return Blur(input.uv);
    else if(selected == 3)
        return GaussianBlur(input.uv);
    else if(selected == 4)
        return RadialBlur(input.uv);
    else if(selected == 5)
        return OutLine(input.uv);
    //

    return map.Sample(samp, input.uv);
}
/////////////////////////////////////////////////////////////////////////////////////////////
/* Action Filter Shader - Filter Shader Base */
Texture2D map : register(t0);
SamplerState samp : register(s0);
//
cbuffer Action : register(b0)
{ // 상수버퍼는 전달할 때 16byte로 맞춰야 한다
    float2 startPos;
    float2 size;
    float2 maxSize;
}
//
cbuffer Info : register(b1)
{
    int selected;
    int value;
    //
    int value1;
    int value2;
}
// 주변 픽셀 8칸
static const float2 edges[8] =
{
    float2(-1, -1), float2(0, -1), float2(1, -1),
    float2(-1, 0), float2(1, 0),
    float2(-1, 1), float2(0, 1), float2(1, 1)
};
///////////////////////////////////////
struct PixelInput
{
    float4 pos : SV_Position;
    float2 uv : UV;
};

float4 Mosaic(float2 uv)
{
    float2 temp;
    temp.x = floor(uv.x * value) / value;
    temp.y = floor(uv.y * value) / value;

    return map.Sample(samp, temp);
}

float4 Blur(float2 uv)
{
    float4 result = 0;
    /* solv_1 : 상하 좌우 픽셀 더해서 나누기
    float divX = 1.0f / size.x;
    float divY = 1.0f / size.y;
    //
    result += map.Sample(samp, float2(uv.x + divX, uv.y));
    result += map.Sample(samp, float2(uv.x - divX, uv.y));
    result += map.Sample(samp, float2(uv.x, uv.y + divY));
    result += map.Sample(samp, float2(uv.x, uv.y - divY));
    //
    result /= 4.0f;
    */
    /* solv_2 : 천진반 style
    //float divX = value / size.x;
    //float divY = value / size.y;
    //
    result += map.Sample(samp, float2(uv.x + divX, uv.y));
    result += map.Sample(samp, float2(uv.x - divX, uv.y));
    result += map.Sample(samp, float2(uv.x, uv.y + divY));
    result += map.Sample(samp, float2(uv.x, uv.y - divY));
    //
    // result /= (4.0f * (float) value);
    */
    /* solv_3 : 고성능, 고비용 (Shader 언어의 근본적 한계(반복문))
    [unroll (16)] // 반복 횟수 제한
    for (int i = 0; i < value; ++i)
    {
        float divX = (i + i) / size.x;
        float divY = (i + i) / size.y;
        //
        result += map.Sample(samp, float2(uv.x + divX, uv.y));
        result += map.Sample(samp, float2(uv.x - divX, uv.y));
        result += map.Sample(samp, float2(uv.x, uv.y + divY));
        result += map.Sample(samp, float2(uv.x, uv.y - divY));
    }
    //
    result /= (4.0f * (float) value);
    */
    /* solv_4 : 주변 8 방향 픽셀 혼합 */
    for (int i = 0; i < value; i++)
    {
        float divX = (i + 1) / size.x;
        float divY = (i + 1) / size.y;
        //
        for (int j = 0; j < 8; j++)
        {
            float x = edges[j].x * divX + uv.x;
            float y = edges[j].y * divY + uv.y;
            //
            result += map.Sample(samp, float2(x, y));
        }
    }
    result /= 8 * value;
    //
    return result;
}

// 정규 분포
static const float weights[13] =
{
    0.0561f, 0.1353f, 0.2730f, 0.4868f, 0.7261f, 0.9231f,
    1.0f,
    0.9231f, 0.7261f, 0.4868f, 0.2730f, 0.1353f, 0.0561f
};

float4 GaussianBlur(float2 uv)
{ // 정규분포 응용
    // 1 pixel uv size
    float divX = 1.0f / size.x;
    float divY = 1.0f / size.y;
    //
    float sum = 0;
    float4 result = 0;
    //
    for (int i = -6; i <= 6; ++i)
    {
        float2 temp = 0;
        //
        temp = uv + float2(divX * i * value, 0);
        result += weights[6 + i] * map.Sample(samp, temp);
        //
        temp = uv + float2(0, divY * i * value);
        result += weights[6 + i] * map.Sample(samp, temp);
        //
        sum += weights[6 + i] * 2.0f;
    }
    result /= sum;
    return result;
}

float4 RadialBlur(float2 uv)
{ // 카드라이더 Booster style
    float2 radiusUV = uv - float2(0.5f, 0.5f);
    float r = length(radiusUV);
    radiusUV /= r;
    // saturate : 인자에 있는 값을 0 ~ 1 사이로 고정
    r = saturate((2.0f * r) / value1);
    float2 delta = -radiusUV * (r * r) * (value2 / value);
    //
    float4 result = 0;
    //
    for (int i = 0; i < value; ++i)
    {
        result += map.Sample(samp, uv);
        uv += delta;
    }
    result /= value;
    //
    return result;
}

float4 OutLine(float2 uv)
{
    /* TODO : 외곽선 */
    /* TODO2 : 선 두께 */
    float4 result = map.Sample(samp, uv);
    //
    if (result.a != 1.0f)
        return result;

    /* Check edges alpha if this alpha is 1.0f */
    bool isOutLine = false;
    float4 edgeColor = 0;
    //
    float divX = 1.0f / size.x;
    float divY = 1.0f / size.y;
    //
    for (int j = 0; j < 8; j++)
    {
        float x = edges[j].x * divX + uv.x;
        float y = edges[j].y * divY + uv.y;
        //
        edgeColor = map.Sample(samp, float2(x, y));
        if (edgeColor.a == 0)
        {
            isOutLine = true;
            break;
        }
    }
    if (isOutLine)
        return float4(0, 1, 0, 1);
    //
    return result;
}

float4 PS(PixelInput input) : SV_TARGET
{
    float2 uv = (startPos / maxSize) + input.uv * (size / maxSize);
    //
    [branch]
    if (selected == 1)
        return Mosaic(uv);
    else if (selected == 2)
        return Blur(uv);
    else if (selected == 3)
        return GaussianBlur(uv);
    else if (selected == 4)
        return RadialBlur(uv);
    else if (selected == 5)
        return OutLine(uv);
    //

    return map.Sample(samp, uv);
    //return map.Sample(samp, input.uv);
}

 

1. Player

  - Sprite 기반 Render

  - 기존에 적용되어있던 ActionShader -> ActionFilterShader로 교체

  - OutLine을 그리기 위해 멤버로 IntBuffer를 선언, Render Option 변경

더보기
#pragma once

class Knight : public Transform
{
public:
	enum ActionType
	{
		IDLE,
		WALK,
		ATTACK,
		JUMP,
	};
	//
private:
	Sprite* sprite;
	//
	Collider* bodyCollider;
	Collider* attackCollider;
	//
	vector<Action*> actions;
	ActionType curAction;
	//
	float speed;
	float jumpPower;
	float gravity;
	//
	bool isRight;
	bool isAttack;
	bool isJump;
	//
	Vector2 attackOffset;
	//
	IntBuffer* intBuffer;
public:
	Knight();
	~Knight();
	//
	void Init();
	//
	void Update();
	void Render();
	void PostRender();
	//
	void LoadAction(string path, string file, Action::Type type, float speed = 0.1f);
	void SetAction(ActionType type);
	void AttackEnd();
	//
	void Control();
	void Move();
	void Jump();
	void Attack();
	//
	Vector2 GetSize() { return actions[curAction]->GetCurClip().size; }
	Collider* GetBodyCollider() { return bodyCollider; }
	IntBuffer* GetIntBuffer() { return intBuffer; }
};
/////////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "Knight.h"

Knight::Knight()
	:speed(500.0f), jumpPower(0), gravity(980.0f),
	curAction(IDLE), isRight(true), isAttack(false), isJump(false),
	attackOffset(60.0f, -25.0f)
{
	Init();
}

Knight::~Knight()
{
	delete intBuffer;
	//
	delete sprite;
	//
	for (auto action : actions)
		delete action;
	actions.clear();
	//
	delete attackCollider;
	delete bodyCollider;
}

void Knight::Init()
{
	sprite = new Sprite(L"ActionFilterShader");
	//sprite = new Sprite(L"ActionShader");
	pos = { 300.0f, 300.0f };
	//
	string path = "Textures/Knight/";
	LoadAction(path, "KnightIdle.xml", Action::LOOP);
	LoadAction(path, "KnightWalk.xml", Action::LOOP);
	LoadAction(path, "KnightAttack.xml", Action::END);
	LoadAction(path, "KnightJump.xml", Action::END);
	//
	actions[ATTACK]->SetEndEvent(bind(&Knight::AttackEnd, this));
	/* SetCollider */
	Vector2 colSize = { 50.0f, 50.0f };
	attackCollider = new RectCollider(colSize, this);
	attackCollider->isActive = false;
	//
	colSize = { 100.0f, 128.0f };
	bodyCollider = new RectCollider(colSize, this);
	bodyCollider->SetOffset({ 0, -56.0f });
	bodyCollider->isActive = true;
	//
	intBuffer = new IntBuffer;
}

void Knight::Update()
{
	Control();
	//
	Action::Clip curClip = actions[curAction]->GetCurClip();
	sprite->SetAction(curClip);
	//
	actions[curAction]->Update();
	sprite->Update();
	//
	scale = curClip.size;
	/* flip method 2
	scale.x = isRight ? curClip.size.x : -curClip.size.x; // scale로 좌우 flip
	scale.y = curClip.size.y;
	*/
	UpdateWorld();
	//
	attackCollider->Update();
	bodyCollider->Update();
}

void Knight::Render()
{
	intBuffer->SetPSBuffer(1); // To Draw OutLine
	//
	SetWorldBuffer();
	//
	sprite->Render();
	//
	attackCollider->Render();
	bodyCollider->Render();
}

void Knight::PostRender()
{
}

void Knight::LoadAction(string path, string file, Action::Type type, float speed)
{
	XmlDocument* document = new XmlDocument();
	document->LoadFile((path + file).c_str());
	//
	XmlElement* atlas = document->FirstChildElement();
	//
	string fileName = atlas->Attribute("imagePath");
	fileName = path + fileName;
	//
	wstring imagePath{};
	imagePath.assign(fileName.begin(), fileName.end()); // string -> wstring
	//
	Texture* texture = Texture::Add(imagePath);
	//
	vector<Action::Clip> clips{};
	XmlElement* sprite = atlas->FirstChildElement();
	//
	while (sprite != nullptr)
	{
		float x{}, y{}, w{}, h{};
		x = sprite->FloatAttribute("x");
		y = sprite->FloatAttribute("y");
		w = sprite->FloatAttribute("w");
		h = sprite->FloatAttribute("h");
		clips.emplace_back(x, y, w, h, texture);
		//
		sprite = sprite->NextSiblingElement();
	}
	//
	actions.emplace_back(new Action(clips, type, speed));
	//
	delete document;
}

void Knight::SetAction(ActionType type)
{
	if (curAction != type)
	{
		curAction = type;
		actions[curAction]->Play();
	}
}

void Knight::AttackEnd()
{
	SetAction(IDLE);
	attackCollider->isActive = false;
	isAttack = false;
}

void Knight::Control()
{
	Move();
	Jump();
	Attack();
}

void Knight::Move()
{
	if (isAttack)
		return;

	if (KEY_PRESS(VK_RIGHT))
	{
		if(!isJump)
			SetAction(WALK);
		pos.x += speed * DELTA;
		if (!isRight)
		{
			rot.y = 0;
			isRight = true;
		}
	}
	else if (KEY_PRESS(VK_LEFT))
	{
		if(!isJump)
			SetAction(WALK);
		pos.x -= speed * DELTA;
		if (isRight)
		{
			rot.y = PI;
			isRight = false;
		}
	}
	//
	if (KEY_UP(VK_RIGHT) || KEY_UP(VK_LEFT))
		SetAction(IDLE);
}

void Knight::Jump()
{
	if (isAttack)
		return;

	if (KEY_DOWN(VK_LCONTROL) && !isJump)
	{
		jumpPower = 300.0f;
		SetAction(JUMP);
		isJump = true;
	}

	jumpPower -= gravity * DELTA;
	pos.y += jumpPower * DELTA;

	if (pos.y < 200.0f)
	{
		pos.y = 200.0f;
		jumpPower = 0;
		//
		if (isJump)
		{
			SetAction(IDLE);
			isJump = false;
		}
	}
}

void Knight::Attack()
{
	if (isAttack)
		return;

	if (KEY_DOWN(VK_SPACE))
	{
		SetAction(ATTACK);
		attackCollider->isActive = true;
		isAttack = true;
		//
		Vector2 attackPos{};
		//
		if (isRight)
			attackPos = attackOffset;
		else
			attackPos = { -attackOffset.x, attackOffset.y };
		//
		attackCollider->SetOffset(attackPos);
		//
		//EFFECT->Play("fire", pos + attackPos, Float4(1, 0, 0, 1));
		EFFECT->Play("fire", pos + attackPos);
		CAMERA->ShakeStart(30.0f, 0.5f, 100.0f);
	}
}

 

2. BackGround

  - Quad 기반 Render

  - 기존에 적용되어있던 ActionShader -> FilterShader로 교체

  - Mosaic, Blur 등 효과 연출을 위해 IntBuffer, floatBuffer 선언, Render Option 변경

더보기
#pragma once

#include "Object/BasicObject/Sprite.h"
#include "Framework/Math/Transform.h"

class BackGround
{
private:
	Quad* quad;
	IntBuffer* intBuffer;
	FloatBuffer* floatBuffer;
	//
	bool isMosaicEffect;
	bool isRevMosaicEffect;
	float mosaicEffectCounter;
public:
	BackGround();
	~BackGround();
	//
	void Update();
	void Render();
	void PostRender();
	void Init();
	//
	Vector2 GetSize() { return quad->GetSize(); }
	void MosaicEffect();
	void RevMosaicEffect();
};
/////////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "BackGround.h"

BackGround::BackGround()
{
	Init();
}

BackGround::~BackGround()
{
	delete intBuffer;
	delete floatBuffer;
	delete quad;
}

void BackGround::Update()
{
	quad->Update();
	//
	MosaicEffect();
	RevMosaicEffect();
}

void BackGround::Render()
{
	intBuffer->SetPSBuffer(1); // To Filter Effect - index
	floatBuffer->SetPSBuffer(2); // To Filter Effect - image Size
	//
	quad->Render();
}

void BackGround::PostRender()
{
	ImGui::Text("[BG]");
	ImGui::Text("pos : %f, %f", quad->pos.x, quad->pos.y);
	ImGui::Text("size : %f, %f", quad->GetSize().x, quad->GetSize().y);
	//
	ImGui::SliderInt("Selected",	&intBuffer->data.index[0], 0, 4);
	ImGui::SliderInt("val_1",		&intBuffer->data.index[1], 0, 100);
	ImGui::SliderInt("val_2",		&intBuffer->data.index[2], 0, 30);
	ImGui::SliderInt("val_3",		&intBuffer->data.index[3], 0, 30);
	//
	ImGui::Text("imgSize : %.3f, %.3f",
		floatBuffer->data.value[0], floatBuffer->data.value[1]);
	ImGui::Text("counter : %.3f", mosaicEffectCounter);
}

void BackGround::Init()
{
	wstring texturefile = L"Textures/bg.png";
	wstring pixelShaderfile = L"FilterShader";
	quad = new Quad(texturefile, pixelShaderfile);
	quad->pos = { CENTER_X, CENTER_Y };
	//
	floatBuffer = new FloatBuffer;
	floatBuffer->data.value[0] = quad->GetSize().x;
	floatBuffer->data.value[1] = quad->GetSize().y;
	//
	intBuffer = new IntBuffer;
}

void BackGround::MosaicEffect()
{
	if (KEY_DOWN(VK_F4))
	{
		intBuffer->data.index[0] = 1;
		isMosaicEffect = true;
		return;
	}

	if (isMosaicEffect)
	{
		mosaicEffectCounter += DELTA * 30;
		intBuffer->data.index[1] = (int)mosaicEffectCounter;
		if (intBuffer->data.index[1] >= 100)
		{
			mosaicEffectCounter = 100;
			intBuffer->data.index[1] = 100;
			isMosaicEffect = false;
		}
	}
}

void BackGround::RevMosaicEffect()
{
	if (KEY_DOWN(VK_F5))
	{
		intBuffer->data.index[0] = 1;
		intBuffer->data.index[1] = 100;
		mosaicEffectCounter = 100.0f;
		isRevMosaicEffect = true;
		return;
	}

	if (isRevMosaicEffect)
	{
		mosaicEffectCounter -= (DELTA * 30.0f);
		intBuffer->data.index[1] = (int)mosaicEffectCounter;
		if (intBuffer->data.index[1] <= 0)
		{
			mosaicEffectCounter = 0;
			intBuffer->data.index[1] = 0;
			isRevMosaicEffect = false;
		}
	}
}

 

3. Game Scene

  - Player Body Collider <-> CAMERA->MouseWorldPos 간 충돌 처

  - player->GetIntBuffer()로 intBuffer 접근하여 Render Option을 OutLine으로 변경

더보기
#pragma once

class PixelShaderScene : public Scene
{
private:
	Knight* knight;
	BackGround* bg;
	//
public:
	PixelShaderScene();
	~PixelShaderScene();
	//
	virtual void Update() override;
	virtual void Render() override;
	virtual void PostRender() override;
	//
	void OnMouse();
};
/////////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "PixelShaderScene.h"

PixelShaderScene::PixelShaderScene()
{
	knight = new Knight;
	bg = new BackGround;
}

PixelShaderScene::~PixelShaderScene()
{
	delete bg;
	delete knight;
}

void PixelShaderScene::Update()
{
	OnMouse();
	//
	bg->Update();
	knight->Update();
}

void PixelShaderScene::Render()
{
	bg->Render();
	knight->Render();
}

void PixelShaderScene::PostRender()
{
	knight->PostRender();
	bg->PostRender();
}

void PixelShaderScene::OnMouse()
{
	Vector2 mouseWorldPos = CAMERA->GetMouseWorldPos();
	bool onMouse = knight->GetBodyCollider()->IsCollision(mouseWorldPos);
	if(onMouse)
		knight->GetIntBuffer()->data.index[0] = 5; // OutLine
	else
		knight->GetIntBuffer()->data.index[0] = 0; // Default
}