본문 바로가기
Opengles 3.0 with Android

10.1 Scene Graph (Transformation Graph)

by SimonLee 2025. 1. 30.

Scene Graph 사용 예제

Scene 을 생성하고

BaseSphere를 Root Object로 생성 후 하위 오브젝트를 등록한다.

계층 구조.

BaseSphere (root Object)

- Cube[0]

  -- Sphere

bool GraphicsInit()
{
    scene1 = new Scene("MeshScene", graphicsEngine);
    scene1->addLight(new Light(Material(MaterialWhite),glm::vec4(0.0, 0.0, 10.0, 1.0)));

    BaseSphere =  new ObjLoader	( scene1, NULL, SPHERE, None , "SphereRoot");
    ObjLoader* BaseSphere2 =  new ObjLoader	( scene1, NULL, SPHERE, None , "SphereRoot2");

    BaseSphere->SetMaterial(Material(MaterialGold));
    BaseSphere->ScaleLocal(1.5,1.5,1.5);

    int j = 0;
    for(int i=-1; i<2; i+=2){
        Cube[j] =  new ObjLoader	( scene1, BaseSphere, CUBE, None, "Cube" );

        Cube[j]->SetMaterial(Material(MaterialCopper));
        Cube[j]->Translate(10.0 * i, 0.0, 0.0);
        for(int i=-1; i<2; i+=2){
            Sphere =  new ObjLoader	( scene1, Cube[j], SPHERE, None, "Spehere");

            Sphere->SetMaterial(Material(MaterialSilver));
            Sphere->Translate(0.0, -5.0 * i, 0.0);
        }
        j++;
    }
    scene1->addModel( BaseSphere);
    scene1->addModel( BaseSphere2);
}

bool GraphicsRender()
{
    BaseSphere->Rotate(1.0, 0.0, 1.0, 0.0);
    Cube[0]->Rotate(-1.0, 1.0, 0.0, 0.0);
    Cube[1]->Rotate( 1.0, 1.0, 0.0, 0.0);
	graphicsEngine->render();

    return true;
}

 

클래스 구조

 

모델 클래스에서 Renderer, Resize, Update Virtual로 선언됨.

root object인 BaseSphere를 로테이션 시키면 하위 오브젝트가 다 영향을 받도록 설계됨.

class Object
{
public:
    Object(std::string name = "", Object* parentObj = NULL);
    virtual ~Object();
    void SetName(std::string mdlName){ name = mdlName;}
    std::string GetName(){return name;
    void SetParent(Object* parent = 0);
    void RemoveParent();
    void SetChild(Object* child = 0);
    void RemoveFromParentChildList();
    Object*  GetParent();
    std::vector<Object*>* GetChildren();

public:
    Object* parent;
    std::vector<Object*> childList;
};

class Model : public Object //Parminder Obj Rem
{
public:
    Model(Scene*	SceneHandler, Model* model, ModelType type,std::string objectName = "");
    virtual void InitModel();
	virtual void Update( float t ) {}
    virtual void Render() = 0;
	virtual void Resize(int, int) {}
	virtual ModelType GetModelType() { return modelType; }
	virtual bool useProgram(char* program);
	virtual void setStates(){}
	virtual void releaseStates(){}
	...
};

class ObjLoader : public Model
{
public:
    ObjLoader( Scene* parent, Model* model, MeshType mesh, ModelType type);
    virtual ~ObjLoader();
    void InitModel();
    void Render();
    void ApplyLight();
    void ApplyMaterial();
private:
    void LoadMesh();
    ...
    Mesh* objMeshModel;
    .....
    // Material variables
    char MaterialAmbient;
    // Light variables
    char LightAmbient;
};

 

부모 오브젝트에 자식 오브젝트 등록하기

 

루트 오브젝트를 생성하고

ObjLoader 생성자를 호출할때 첫번째 인자는 scene을 넣어주고,

부모 오브젝트를 두번째 인자로 넣어준다.

 

두번째 인자는 Object클래스의 부모 오브젝트로 등록해준다.

부모 오브젝트로 등록을 하게 되면 부모 오브젝트 childList에 자식이 등록이 된다.

아래 코드 함수는 일련의 과정을 보여준다.

 

ObjLoader::ObjLoader( Scene* parent, Model* model, MeshType mesh, ModelType type, std::string name) : Model(parent, model, type, name)
{
    if (!parent)
        return;

    glEnable	( GL_DEPTH_TEST );
    ModelNumber = (int)mesh;
    ModelNumber %= sizeof(ModelNames)/(sizeof(char)*STRING_LENGTH);
    transformation[0][0] = transformation[1][1] = transformation[2][2] = transformation[3][3] = 1.0;
    LoadMesh();
}

Model::Model(Scene*	handler, Model* model, ModelType type, std::string objectName) : Object(objectName, model) //Parminder Obj Rem
{
    SceneHandler        = handler;
    ProgramManagerObj	= handler->SceneProgramManager();
    TransformObj		= handler->SceneTransform();
    modelType           = type;
    transformation      = glm::mat4();
    transformationLocal = glm::mat4();
    center              = glm::vec3(0.0, 0.0, 0.0);
    isVisible           = true;
}


Object::Object(std::string objectName, Object* parentObj)
{
    parent  = NULL; // Important: do not remove this check, the SetParent method checks for parent's Null pointer condition.
    name    = objectName;
    SetParent(parentObj);
    return;
}

void Object::SetParent(Object* parentModel)
{
    // If there existing a parent
    if (this->parent != NULL){
        RemoveParent();
    }
    
    parent = parentModel;
    if(parent){
        parent->SetChild(this);
    }
}

void Object::SetChild(Object* child)
{
    for(int i =0; i<childList.size(); i++){
        if(child == childList.at(i)){
            return;
        }
    }
    child->parent = this;
    childList.push_back(child);
}

 

위의 형태로 렌더링이 된다.

 

부모 오브젝트의 자식 등록이 끝나게 되면

root object만 addModel을 해준다.

void Scene::addModel(Model* model)
{
    if(!model){
        return;
    }
    
    // Add root model only
    if(model->GetParent() == NULL) {
        LOGI("Add root model : %s", model->name.c_str());
        models.push_back( model );
        model->setSceneHandler(this);
    }
}

 

setSceneHanler를 호출 하게 되면,

등록된 자식 모델들에게도 동일하게 scene에 접근할수 있도록 핸들러를 설정해준다.

void Model::setSceneHandler(Scene* sceneHandle){
    SceneHandler = sceneHandle;
    //LOGI("dlgmlals3 modelType : %d %p", modelType, this);

    for(int i =0; i<childList.size(); i++){
        dynamic_cast<Model*>(childList.at(i))->setSceneHandler( sceneHandle );
    }
}

 

렌더링

씬 렌더러를 하게되면

현재 씬에 등록된 모든 모델을 렌더링하게 된다..

Renderer::render() --> Scene::render() -> ObjLoader::Render() -> Model::Render()

ObjLoader::Render에서 자신의 오브젝트를 렌더링 끝난 후, 자식 렌더링 호출한다.

bool GraphicsRender()
{
	...
	graphicsEngine->render();
    return true;
}

// 모든 씬 렌더링
void Renderer::render()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    for( int i=0; i<scenes.size();  i++ ){
        scenes.at(i)->render();
    }
}

// 씬에 있는 모델 렌더링
void Scene::render()
{
    setUpProjection();
    for( int i=0; i<models.size();  i++ ){
        currentModel = models.at(i);
        if(!currentModel){
            continue;
        }
        currentModel->Render();
    }
}

void ObjLoader::Render()
{
    glUseProgram(program->ProgramID);
   	......
        
    // Draw Geometry
    glDrawArrays(GL_TRIANGLES, 0, IndexCount );
    glBindVertexArray(0);
    TransformObj->TransformPopMatrix(); // Local Level
    
    Model::Render(); // 자식 렌더링
    TransformObj->TransformPopMatrix();  // Parent Child Level
}

void Model::Render()
{
    for(int i =0; i<childList.size(); i++){
        dynamic_cast<Model*>(childList.at(i))->Render();
    }
}

 

 

렌더링 순서는 부모 오브젝트 렌더링이 끝난 후, 자식 렌더링이 수행된다.!!

 

다형성으로 인하여 CurrentModel이 Model type임에도, ObjLoader::Render()가 먼저 호출되는 것을 체크하자.

이때 가장 중요한 부분이 ObjLoader::Render()를 분석해본다.

 

void ObjLoader::Render()
{
    // Self 매트릭스 저장
    TransformObj->TransformPushMatrix();
    
    // 부모로 부터 Transform 매트릭스를 설정한다.
    ApplyModelsParentsTransformation();
    
    ///////////////////////////////////////////////
    // Self Transform 매트릭스 저장.
    TransformObj->TransformPushMatrix();
    
    // 부모로 부터 Local Transform 매트릭스 가져와서 self Transform에 설정한다. 
    ApplyModelsLocalTransformation();

    // Self 렌더링.....
    
    // Self 렌더링이 끝나면 self Transform 매트릭스를 복원한다.
    TransformObj->TransformPopMatrix();
    ///////////////////////////////////////////////
    
    // 자식 렌더링 시작
    Model::Render();
    
    // 자식 렌더링이 끝나면 self 매트릭스를 복원한다.
    TransformObj->TransformPopMatrix();  // Parent Child Level
}

 

void Model::ApplyModelsParentsTransformation()
{
    *TransformObj->TransformGetModelMatrix() = *TransformObj->TransformGetModelMatrix()*transformation;
}

void Model::ApplyModelsLocalTransformation()
{
    *TransformObj->TransformGetModelMatrix() = *TransformObj->TransformGetModelMatrix()*transformationLocal;    
}

glm::mat4* Transform::TransformGetModelMatrix( void )
{
	return &TransformMemData.model_matrix[ TransformMemData.modelMatrixIndex ];
}

 

코드를 분석해보면

TransformPushMatrix는 스택형태로 구현이 되어있다.

LocalTransformation은 self 렌더링에만 적용되고, 

ParentsTransformation은 self와 자식 둘 다 적용되는 것을 알 수 있다.