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
}
'DX11 2D Game Programming' 카테고리의 다른 글
DX11_2020_0818_화_Store_Inventory_FileIO(tsv) (0) | 2020.08.18 |
---|---|
DX11_2020_0814_금_Store_FileIO(tsv)_Button (0) | 2020.08.14 |
DX11_2020_0810_월_Top-Down_Shooting (0) | 2020.08.10 |
DX11_2020_0807_금_Camera (0) | 2020.08.07 |
DX11_2020_0806_목_FX (0) | 2020.08.05 |