오랫동안 개발일지를 안올렸지만
일지 쓰기가 귀찮아서이지 개발하지 않은것은 아니다.
그래서 미뤄놨던 일지를 쓰려고 한다
개발했던 내용과 디버그 작업 등 지금까지 했던 걸 정리하려고 한다.
빌드
개발하다가 중간에 또 빌드 한번 터졌었는데
왜 터졌고 어떻게 고쳤는지는 기억이 잘..
duplicate symbol 'Extrude::OnPointerUp(float, float, float, float)' in: /var/folders/qr/tv7l9znj76v6chz562wdfwp00000gn/T/main-3318cb.o /var/folders/qr/tv7l9znj76v6chz562wdfwp00000gn/T/main-4abb74.o duplicate symbol 'ModifyVertex::OnPointerDown(float, float, float, float)' in: /var/folders/qr/tv7l9znj76v6chz562wdfwp00000gn/T/main-3318cb.o /var/folders/qr/tv7l9znj76v6chz562wdfwp00000gn/T/main-4abb74.o duplicate symbol 'Pen::OnPointerDown(float, float, float, float)' in: /var/folders/qr/tv7l9znj76v6chz562wdfwp00000gn/T/main-3318cb.o /var/folders/qr/tv7l9znj76v6chz562wdfwp00000gn/T/main-4abb74.o...
대충 이런 로그가 한뭉탱이로 뜨면서 실행파일이 생기지 않았다.
헤더파일하고 cpp파일하고 분리하려다가 이런 일이 생겼다.
사실 지금까지는 헤더파일과 cpp파일을 분리하지 않고 헤더파일에 모든 내용을 적었었다.
왜 구분을 안했냐면, 헤더파일과 cpp파일을 분리하면 뭔진 몰라도 뭔가 문제가 생겨서 지금까지 안했다.
(지금 이 상황처럼..)
그러다가 전방선언을 쓸 일이 생겨서 헤더파일과 cpp파일을 분리하려다 이 사태가..
그래서 어떻게 고쳤냐고 한다면
사실 기억은 잘 안나는데
이렇게 명시하고 나니깐 빌드가 됐던것 같다.
레이어 개발
여타 그래픽 툴처럼 레이어가 있고
레이어에 위치, 메시, 쉐이더.. 등의 정보를 담을 생각이다.
class Layer{
public:
std::string name;
std::vector<Layer*> children;
eLayerType layerType=eLayerType::EMPTY;
void SetPosition(glm::vec3 pos);
void SetRotation(glm::vec3 rot);
void SetScale(glm::vec3 scale);
glm::vec3 GetPosition() const {return mPosition;}
glm::vec3 GetRotation() const {return mRotation;}
glm::vec3 GetScale() const {return mScale;}
void SetVisible(bool v);
bool GetVisible() const {return mVisible;}
private:
glm::vec3 mPosition = glm::vec3(0.0f);
glm::vec3 mRotation = glm::vec3(0.0f);
glm::vec3 mScale = glm::vec3(1.0f);
bool mVisible = true;
};
유니티의 게임 오브젝트하고도 비슷한 개념이다.
차이가 있다면 게임 오브젝트는 컴포넌트를 붙여서 커스텀 가능한데
타이니렌더에서는 ShapeLayer, TextLayer..등으로 레이어 종류를 정해놓을 것이다.
예를들어 메시를 가지고 렌더링하는 레이어는 ShapeLayer,
텍스트를 가지고 렌더링하는 레이어는 TextLayer,
곡선 및 직선같은 라인을 가지고 렌더링하는 레이어는
빛을 가지고있는 레이어는 LightLayer... 이런식으로
각 레이어는 Layer를 상속해서 만들 예정이고
지금은 ShapeLayer만 구현해놨음
class ShapeLayer : public Layer
{
public:
ShapeLayer():mShader("Shader/vertexShader.glsl", "Shader/fragmentShader.glsl") {};
ShapeLayer(Mesh* m, std::string name);
void SetPosition(glm::vec3 pos);
void SetRotation(glm::vec3 rot);
void Draw();
Mesh* mesh;
private:
Shader mShader;
};
이전에 이런 메시들을 관리해주는 Collection을 만들었는데
Collection에서 mesh가 아니라 Layer를 관리하도록 바꾸었고
리스트에서 트리형태로 바꾸었다.
class Collection{
public:
static Collection* GetInstance();
Layer* GetSelectedLayer() const {return mSelectedLayer;}
Layer* GetRootLayer() const {return mRootLayer;}
//std::vector<Mesh*> GetModel() const{return mMeshes;}
void AddLayer(Layer* layer,eLayerType layerType);
void AddLayer(Layer* layer);
void SelectLayer(Layer* selected);
void Rendering(Layer* layer);
void SetCollectionCanvas(CollectionCanvas* col);
private:
static Collection* instance;
CollectionCanvas* mCollectionCanvas=nullptr;
Layer* mRootLayer = new Layer();
Layer* mSelectedLayer=mRootLayer;
};
레이어 GUI 개발
기획하면서 레이어, 인스펙터와 같은 개념이 생겨서
UI디자인을 조금 수정했다.
이것도 확정은 아니고
개발하면서 더 다듬어갈 예정이다.
어쨌든 위에서 개발한 레이어는 데이터만 가지고 관리하는 내용이고..
실제로 GUI를 통해 유저에게 데이터를 보여주고 상호작용할 GUI가 필요하다.
우선 가진 Layer의 정보를 보여주는 LayerUI와
LayerUI들을 관리하는 CollectionCanvas가 있다.
LayerUI.h
class LayerUI: public Widget{
public:
LayerUI(Layer* layer,float layerSizeX,float layerSizeY,glm::vec3 layerPos);
void Draw() override;
Layer* GetLayer() const {return mLayer;}
std::vector<LayerUI*> children;
void SelectLayerUI();
void UnSelectLayerUI();
void SetTexture(const char *texPath,eImageType imageType);
void ScrollLayerUI(float xoffset, float yoffset);
private:
Shader* mLayerUIShader;
bool bSelected=false;
bool bDragged=false;
Layer* mLayer;
unsigned int mVBO;
unsigned int mVAO;
unsigned int mEBO;
const char *mTexPath="resource/Layer.jpg";
unsigned int mTexture;
int mWidth, mHeight, mMinimaps;
float mVertexArray[20];
unsigned int mIndices[6]={
0,1,2,
2,1,3
};
};
CollectionCanvas.h
class CollectionCanvas : public IPressed, public IPressedDown, public IPressedUp, public IScrolled
{
public:
CollectionCanvas(InspectorCanvas* inspector);
void AddLayerUI(Layer *layer);
void Rendering();
void RenderingLayer(LayerUI *layer, int depth = 0, int count = 0);
void OnPointer(float xpos, float ypos, float xdelta, float ydelta) override;
void OnPointerUp(float xpos, float ypos, float xdelta, float ydelta) override;
void OnPointerDown(float xpos, float ypos, float xdelta, float ydelta) override;
void OnScroll(float xoffset, float yoffset) override;
void ScrollLayer(LayerUI *layer, float xoffset, float yoffset, int depth = 0, int count = 0);
LayerUI *GetRootLayerUI() { return mRootLayerUI; }
private:
float mLayerOffsetY = 0.12439024390243902f; // 전부 NDC
float mLayerSizeX = 0.3111111111111111f;
float mLayerSizeY = 0.08351219512195122f;
float mDepthOfssetX = 0.2f;
float mScrollDiscanceY = 0; // NDC
int mCount = 0;
LayerUI *mRootLayerUI;
LayerUI *mPrevSelectedLayerUI = nullptr;
Shader *mMaskShader;
unsigned int mMaskVBO;
unsigned int mMaskVAO;
unsigned int mMaskEBO;
unsigned int mMaskIndices[6] = {
0, 1, 2,
2, 1, 3};
InspectorCanvas* mInspector;
int countNodes(LayerUI *layer);
int findLayer(float yPos, LayerUI *layer);
LayerUI *findLayer(LayerUI *layerUI);
void drawMask();
};
음..
Pen툴등의 기능으로 메시를 만들게되면
해당 메시를 가진 ShapeLayer 객체를 만들어
Collection의 AddLayer()를 호출해서 Collection에 추가한다.
그리고 Collection의 AddLayer()에서 Layer를 Collection에 추가함과 동시에
CollectionCanvas의 AddLayerUI도 호출해서 LayerUI도 추가한다!
허허...
SelectLayer
LayerUI만 만들고 끝! 이아니라
만들어진 UI는 상호작용할 수 있어야 한다.
그래서 UI에서 해당 LayerUI를 클릭하면 선택되는 기능을 만들었다.
void CollectionCanvas::OnPointerDown(float xpos, float ypos, float xdelta, float ydelta)
{
float ndcX = (2 * xpos / SCR_WIDTH) - 1;
float ndcY = 1 - (2 * ypos / SCR_HEIGHT);
if (ndcX >= -1 + mLayerSizeX || ndcY >= 1 - mLayerOffsetY)
return;
mCount = 0;
int selectedLayerIndex = findLayer(ndcY, mRootLayerUI);
if (selectedLayerIndex == -1)
return;
if (mPrevSelectedLayerUI != nullptr)
{
mPrevSelectedLayerUI->UnSelectLayerUI();
}
mCount = selectedLayerIndex;
LayerUI *selectedLayerUI = findLayer(mRootLayerUI);
selectedLayerUI->SelectLayerUI();
mInspector->SetInspector(selectedLayerUI->GetLayer());
Collection::GetInstance()->SelectLayer(selectedLayerUI->GetLayer());
mPrevSelectedLayerUI = selectedLayerUI;
}
이것이 그 내용인데...
입력을 받고 해당 위치에 레이어가 있는지 없는지 계산하고
(Y값으로 판별한다)
없으면 리턴, 있으면 이전에 선택된 레이어를 선택해제하고
선택되도록 만드는 내용이다.
또한 선택된 레이어는 색으로 선택되었다는것을 보여준다.
내부적으로도 해당 레이어가 선택된다.
스크롤 기능
레이어가 많으면 레이어 창을 뚫고 나올테니
스크롤 기능도 만들어야한다.
class IScrolled{
public:
virtual ~IScrolled() {};
virtual void OnScroll(float xoffset, float yoffset)=0;
};
여타 했던것과 비슷하게 IScrolled라는 인터페이스를 만들고
필요한곳에 등록해서 입력을 받을 수 있게 했다.
void CollectionCanvas::OnScroll(float xoffset, float yoffset)
{
float scrollDistanceTest = (mScrollDiscanceY + -yoffset * SCROLL_SPEED) * SCR_HEIGHT;
float layerLength = (LAYER_SIZE_Y / 2) * SCR_HEIGHT * (countNodes(mRootLayerUI) + 1);
if (layerLength < SCR_HEIGHT - mLayerOffsetY * SCR_HEIGHT || abs(scrollDistanceTest) >= layerLength - SCR_HEIGHT || scrollDistanceTest <= 0)
return;
ScrollLayer(mRootLayerUI, xoffset, -yoffset);
mScrollDiscanceY += -yoffset * SCROLL_SPEED;
}
void CollectionCanvas::ScrollLayer(LayerUI *layer, float xoffset, float yoffset, int depth, int count)
{
layer->ScrollLayerUI(xoffset, yoffset);
if (layer->children.size() == 0)
return;
int c = 0;
for (LayerUI *child : mRootLayerUI->children)
{
ScrollLayer(child, xoffset, yoffset, depth + 1, c);
c++;
}
}
스크롤입력을 받으면
우선 스크롤을 할 만큼 LayerUI가 많은지 검사하고
충분히 많다면 LayerUI별 ScrollLayer를 호출해서
각 레이어를 옮겨준다.
???
짤에서 보다싶이 스크롤은 잘 되지만
레이어가 뚫고 나가는 것을 볼 수 있다.
그렇기에 마스킹도 해줘야한다.
Stencil testing
마스킹을 구현하는 방식은 여러가지가 있겠으나..
예를들어 그냥 fragmentShader에서 discard 해버린다던가..
그중에서도 Stencil testing을 활용해서 구현했다.
Stencil Testing이란???
원래 fragmentShader를 거치고 나서
depth testing을 거치고
stencil testing을 거쳐 이 픽셀이 출력될지 말지 (혹은 변형하든지) 결정한다.
https://learnopengl.com/Advanced-OpenGL/Stencil-testing
void CollectionCanvas::Rendering()
{
glEnable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
drawMask();
glEnable(GL_DEPTH_TEST);
glStencilMask(0x00);
glStencilFunc(GL_EQUAL, 1, 0xFF);
RenderingLayer(mRootLayerUI);
glStencilFunc(GL_ALWAYS, 0, 0xFF); // Restore default stencil function
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
}
void CollectionCanvas::drawMask()
{
mMaskShader->use();
glBindVertexArray(mMaskVAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
???
glStencilMask(0xFF)를 통해
drawMask 부분의 버퍼에 0xFF라는 값을 채우고..
glStencilFunc을 통해 0xFF라는 값과 같아야지만 통과하도록 작성했다.
RenderingLayer() 아래 부분은 그냥 다시 초기화해주는 부분이다.
이러한 과정을 거치면..
와! 튀어나오지 않아요!
stb_image 라이브러리
타이니렌더에서 텍스쳐를 가져올때 stb_image 라는 라이브러리를 쓰는데
이 라이브러리에서 문제가 생겨서 또 빌드가 안됐다.
#define STB_IMAGE_IMPLEMENTATION
라는 전처리문이 문제가 되었는데(아마도)
#include <stb_image.h>
로 stb_image 라이브러리를 불러오는 곳마다 전처리문을 썼더니 이게 꼬여서 문제가 된것 같다.
아마 #define STB_IMAGE_IMPLEMENTATION 이 구문을 main.cpp 같은곳 한 곳에만 선언해야 하는 듯 하다.
(이유는 잘 모르겠음)
이것가지고도 엄청 삽질했다.
레이어 관련해서 엄청 시간을 많이 쓴것 같은데
아직도 할게 많이남았다.
(이름 변경, visible 버튼, 부모자식 관계 변경, 레이어 다중선택, 레이어 삭제 등등...)
이쯤되니 그냥 미감을 포기하더라도
imgui같은 라이브러리를 쓸걸..하는 생각이 들었다...
언제까지 레이어 개발만 할 수는 없어서
일단은 여기까지하고 다음 내용을 진행하려고 한다.
'컴퓨터 그래픽스 > OpenGL' 카테고리의 다른 글
#10 Tinylender 개발일지 : 곡선으로 메시만들기 (1) | 2024.09.05 |
---|---|
#9 Tinylender 개발일지 : TextBox 개발 및 인스펙터 (3) | 2024.07.28 |
#7 tinylender 개발일지 : extrude 기능 개발 (5) | 2024.05.31 |
#6 tinylender 개발일지 : 펜툴 제작 (0) | 2024.05.31 |
#5 tinylender 개발일지 : 상태 머신 적용 (0) | 2024.05.12 |