컴퓨터 그래픽스/OpenGL

#7 tinylender 개발일지 : extrude 기능 개발

san10 2024. 5. 31. 17:14

펜 툴로 도형을 만들 수 있게 되었으니 실제로 3d로 만들 차례이다.

그래서 그린 도형을 튀어나오게 만드는 extrude 기능을 만들었다.

 

Extrude

class Extrude:public IState, public IPressedDown ,public IPressed, public IPressedUp{
    public:
        void Handle() override;
        void HandleOut() override;
        void OnPointerDown(float xpos, float ypos,float xdelta,float ydelta) override;
        void OnPointer(float xpos, float ypos,float xdelta,float ydelta) override;
        void OnPointerUp(float xpos, float ypos,float xdelta,float ydelta) override;

    private:
        bool bState=false;
        std::vector<Vertex> mCurrentVertices;
        std::vector<Vertex> mNewVertices;
        std::vector<Vertex> mVertices;

        void selectFace();
        void extrudeMesh(float xdelta, float ydelta);
};

 

 

void Extrude::extrudeMesh(float xdelta, float ydelta){

    if(glm::length(mNewVertices[0].Position-mCurrentVertices[0].Position)<=0.0f){
        return;
    }

    Mesh* mesh = Collection::GetInstance()->GetSelectedMesh(); 

    glm::vec3 faceNormal = glm::normalize(glm::cross(
        mCurrentVertices[1].Position-mCurrentVertices[0].Position,
        mCurrentVertices[2].Position-mCurrentVertices[1].Position));


    for(int i=0;i<mNewVertices.size();i++){
        mNewVertices[i].Position.x = (mNewVertices[i].Position.x -(faceNormal.x*((ydelta+xdelta)/2))*0.03f);
        mNewVertices[i].Position.y = (mNewVertices[i].Position.y -(faceNormal.y*((ydelta+xdelta)/2))*0.03f);
        mNewVertices[i].Position.z = (mNewVertices[i].Position.z -(faceNormal.z*((ydelta+xdelta)/2))*0.03f);
    }

    std::vector<Vertex> resultVertices = mCurrentVertices;
    std::vector<Vertex>::iterator it = resultVertices.end();
    resultVertices.insert(it,mNewVertices.begin(),mNewVertices.end());

    unsigned int vertexNum = (mCurrentVertices.size()/3)+2;
    
    mVertices[vertexNum]=mNewVertices[0];
    mVertices[vertexNum+1]=mNewVertices[1];
    mVertices[vertexNum+2]=mNewVertices[2];
    for(int i=1;i<vertexNum-2;i++){
        mVertices[vertexNum+2+i]=mNewVertices[3*i+2];
    }


    for(int i=0;i<vertexNum;i++){
        resultVertices.push_back(mVertices[i]);
        resultVertices.push_back(mVertices[i+vertexNum]);
        resultVertices.push_back(mVertices[(i+1)%vertexNum+vertexNum]);

        resultVertices.push_back(mVertices[i]);
        resultVertices.push_back(mVertices[(i+1)%vertexNum+vertexNum]);
        resultVertices.push_back(mVertices[(i+1)%vertexNum]);
    }

    mesh->vertices = resultVertices;

    mesh->SetMesh();


}

extrudeMesh는 말 그대로 기존의 메시를 튀어나오게 만든다.

기존에 그려져있던 버텍스를 복사해서 새로운 면을 만들고, 옆면을 만들어주는 버텍스들을 채운 다음에 메시를 만든다.

 

결과

 

노말값 채우기

위 짤에서 보다시피, 아직 버텍스에 위치만 있고 노말이 없어서 면 구분이 안된다.

그래서 버텍스에 위치와 노말도 채워넣어야 한다.

 

그런데 펜툴과 extrude 기능은 그냥 만들었는데..

버텍스 노말을 계산하는 것은 쉽지않았다.

그 이유는 버텍스 노말을 어떻게 채워야 할지 잘 몰랐기 때문이다.

 

https://stackoverflow.com/questions/20389335/calculating-per-face-normal-for-a-simple-triangle

페이스 노말은 짤에서 보다시피 CB CA와 같은 두 벡터를 외적하면 얻을 수 있다.

그러면 버텍스 노말은?

나는 인접한 면의 페이스 노말을 모두 더해서 정규화하면 버텍스 노말이 나올 거라고 생각했다.

그래서 그렇게 구현하면?

void Extrude::setVertexNormal(){
    unsigned int vertexNum = (mCurrentVertices.size()/3)+2;

    std::vector<std::vector<unsigned int>> faces;
    std::vector<glm::vec3> facesNormal;

    glm::vec3 faceNormal = glm::cross(
        mVertices[1].Position-mVertices[0].Position,
        mVertices[2].Position-mVertices[1].Position);

    std::vector<unsigned int> upFace;
    for(int i=0;i<vertexNum;i++){
        upFace.push_back(i);
    }
    faces.push_back(upFace);
    facesNormal.push_back(faceNormal);

    std::vector<unsigned int> downFace;
    for(int i=vertexNum;i<vertexNum*2;i++){
        downFace.push_back(i);
    }
    faces.push_back(downFace);
    facesNormal.push_back(glm::vec3(-faceNormal.x,-faceNormal.y,-faceNormal.z));

    
    for(int i=0;i<mNewVertices.size();i++){
        mNewVertices[i].Position.x = (mNewVertices[i].Position.x -(faceNormal.x*0.1f));
        mNewVertices[i].Position.y = (mNewVertices[i].Position.y -(faceNormal.y*0.1f));
        mNewVertices[i].Position.z = (mNewVertices[i].Position.z -(faceNormal.z*0.1f));
    }

    mVertices[vertexNum]=mNewVertices[0];
    mVertices[vertexNum+1]=mNewVertices[1];
    mVertices[vertexNum+2]=mNewVertices[2];
    for(int i=1;i<vertexNum-2;i++){
        mVertices[vertexNum+2+i]=mNewVertices[3*i+2];
    }

    for(int i=0;i<vertexNum;i++){

        std::vector<unsigned int> face;
        face.push_back(i);
        face.push_back(i+vertexNum);
        face.push_back((i+1)%vertexNum+vertexNum);
        face.push_back((i+1)%vertexNum);
        faces.push_back(face);
        facesNormal.push_back(glm::cross(
            mVertices[(i+1)%vertexNum].Position-mVertices[i].Position,
            mVertices[i+vertexNum].Position-mVertices[i].Position
        ));
    }

    for(int index=0;index<mCurrentVertices.size();++index){
        glm::vec3 sum = glm::vec3(0.0f);
        int k;
        if (index % 3 == 0) {
            k=0;
        } else {
            k=index / 3 + index % 3;
        }

        for(int i=0;i<faces.size();i++){
            for(int j=0;j<faces[i].size();j++){
                if(faces[i][j]==k){
                    sum+=facesNormal[i];
                    break;
                }
            }
        }
        mCurrentVertices[index].Normal= glm::normalize(sum);
    }

    for(int index=0;index<mNewVertices.size();++index){
        glm::vec3 sum = glm::vec3(0.0f);
        int k;
        if (index % 3 == 0) {
            k=0;
        } else {
            k=index / 3 + index % 3;
        }

        for(int i=0;i<faces.size();i++){
            for(int j=0;j<faces[i].size();j++){
                if(faces[i][j]==k+vertexNum){
                    sum+=facesNormal[i];
                    break;
                }
            }
        }
        mNewVertices[index].Normal= glm::normalize(sum);
    }
}

코드는.. 복잡한데

어쨌든 해당 버텍스에 인접한 면을 구하고

인접한 면의 페이스 노말을 다 더해서 정규화해서

해당 버텍스의 노말을 구했다.

 

그래서 결과는..

아직 조명은 없기 때문에 노말을 바로 출력했다.

그런데.. 뭔가 딱봐도 이상하다

상식적으로 면 별로 노말값이 같아야 할 것 같은데 그렇지 않다.

 

그래서 실제 3d 모델들은 어떻게 버텍스 노말을 구성하나..

learnopengl 사이트에서 제공하는 기본 큐브를 분석해봤더니..

https://learnopengl.com/code_viewer.php?code=lighting/basic_lighting_vertex_data

 

Code Viewer. Source code: lighting/basic_lighting_vertex_data

 

learnopengl.com

 

지금까지 버텍스의 위치가 같으면 노말값도 똑같을 거라고 생각했는데

버텍스의 위치가 같아도 노말값이 다르다.

자기가 나타내고 있는 면의 노말값을 가지고 있다.

좀 생각해보니깐 당연한 것 같기도 하고..

 

그래서 다시 수정..

void Extrude::extrudeMesh(float xdelta, float ydelta){

    if(glm::length(mNewVertices[0].Position-mCurrentVertices[0].Position)<=0.0f){
        return;
    }

    Mesh* mesh = Collection::GetInstance()->GetSelectedMesh(); 

    glm::vec3 faceNormal = glm::normalize(glm::cross(
        mCurrentVertices[1].Position-mCurrentVertices[0].Position,
        mCurrentVertices[2].Position-mCurrentVertices[1].Position));


    for(int i=0;i<mNewVertices.size();i++){
        mNewVertices[i].Position.x = (mNewVertices[i].Position.x -(faceNormal.x*((ydelta+xdelta)/2))*0.03f);
        mNewVertices[i].Position.y = (mNewVertices[i].Position.y -(faceNormal.y*((ydelta+xdelta)/2))*0.03f);
        mNewVertices[i].Position.z = (mNewVertices[i].Position.z -(faceNormal.z*((ydelta+xdelta)/2))*0.03f);
        mNewVertices[i].Normal=faceNormal;
    }

    std::vector<Vertex> resultVertices = mCurrentVertices;
    std::vector<Vertex>::iterator it = resultVertices.end();
    resultVertices.insert(it,mNewVertices.begin(),mNewVertices.end());

    unsigned int vertexNum = (mCurrentVertices.size()/3)+2;
    
    mVertices[vertexNum]=mNewVertices[0];
    mVertices[vertexNum+1]=mNewVertices[1];
    mVertices[vertexNum+2]=mNewVertices[2];
    for(int i=1;i<vertexNum-2;i++){
        mVertices[vertexNum+2+i]=mNewVertices[3*i+2];
    }


    for(int i=0;i<vertexNum;i++){
        Vertex vert = {glm::vec3(1.0f),mFacesNormal[i+2],glm::vec2(1.0f),glm::vec3(1.0f)};
        vert.Position = mVertices[i].Position;
        resultVertices.push_back(vert);

        vert.Position = mVertices[i+vertexNum].Position;
        resultVertices.push_back(vert);

        vert.Position = mVertices[(i+1)%vertexNum+vertexNum].Position;
        resultVertices.push_back(vert);

        vert.Position = mVertices[i].Position;
        resultVertices.push_back(vert);

        vert.Position = mVertices[(i+1)%vertexNum+vertexNum].Position;
        resultVertices.push_back(vert);

        vert.Position = mVertices[(i+1)%vertexNum].Position;
        resultVertices.push_back(vert);
    }

    mesh->vertices = resultVertices;

    mesh->SetMesh();
}

요약하자면 그냥 이상한 계산하지말고

해당 면의 노말을 가지도록 했다.

 

이렇게하면..

되는거 맞겠지??

 

 

조명

버텍스 노말도(아마)계산했으니

기본적인 조명도 구현했다.

 

쉐이더 코드는 대충 learnopengl에서 긁어왔고

ambient+diffuse+specular 라이트가 구현되어있다.