渲染器中的场景节点运动

一般渲染器都有场景节点的概念,用于挂载可渲染可运动的实体。

运动是相对的,这里主要写3种运动空间:

TS_LOCAL:本地空间

TS_PARENT:相对空间

TS_WORLD:世界空间,所有变换的起始空间,这不同于物理任何运动都是相对的

以下是场景节点的头文件:

#ifndef EGG_SCENENODE_H
#define EGG_SCENENODE_H

#include <vector>
#include <glm/glm.hpp>
#include <glm/ext.hpp>
using namespace std;
using namespace glm;

enum TransformSpace{
    TS_LOCAL,TS_PARENT,TS_WORLD
};

class SceneNode{
public:
    void translate(const vec3& value,TransformSpace ts);
    void setPosition(const vec3& value,TransformSpace ts);
    void rotate(float degree,const vec3& axis,TransformSpace ts);

    inline mat4 getRelTransform(){
        mat4 ret=toMat4(mRelOrientation);
        ret[3]=vec4(mRelPosition,1.0);
        return ret;
    }
protected:
    void updateTransform();
protected:
    SceneNode* mParent;
    vector<SceneNode*> mChildren;
    vec3 mRelPosition;
    quat mRelOrientation;
    mat4 mAbsTransform;
};

#endif

为节省内存,和运动相关的信息只存了相对的位置mRelPosition和朝向mRelOrientation,朝向用四元数表示。

为提高效率,需要存一下绝对变换mAbsTransform,因为这货每一帧都要提交到gpu,不能渲染前重复去计算。

这里没有写缩放变换,因为类似且比较简单。

复习一下矩阵变换的意义,先声明,这是非官方非严谨且是个人理解的M*P=Q

最简单的构造变换矩阵的方法是确定新的坐标系的xyz,也就是基,将它们逐列排成一个矩阵(这里用的是列优先矩阵,与glsl一致)

M就是把新坐标系中的P变换为父坐标系的Q

以下是SceneNode的实现:

#include "SceneNode.h"

void SceneNode::translate(const vec3& value,TransformSpace ts){
    vec3 relDist;
    if(ts==TS_LOCAL){
        relDist=mRelOrientation*value;
    }else if(ts==TS_PARENT){
        relDist=value;
    }else if(ts==TS_WORLD){
        relDist=mRelOrientation*(inverse(mat3(mAbsTransform))*value);
    }
    setPosition(relDist,TS_PARENT);
}

void SceneNode::setPosition(const vec3& value,TransformSpace ts){
    if(ts==TS_LOCAL){
        mRelPosition=vec3(getRelTransform()*vec4(value,1.0));
    }else if(ts==TS_PARENT){
        mRelPosition=value;
    }else if(ts==TS_WORLD){
        vec4 localPos=inverse(mAbsTransform)*vec4(value,1.0);
        mRelPosition=vec3(getRelTransform()*localPos);
    }
    updateTransform();
}

void SceneNode::rotate(float degree,const vec3& axis,TransformSpace ts){
    vec3 rotAxis;
    if(ts==TS_LOCAL){
        rotAxis=mRelOrientation*axis;
    }else if(ts==TS_PARENT){
        rotAxis=axis;
    }else if(ts==TS_WORLD){
        rotAxis=mat3(inverse(mAbsTransform))*axis;
        rotAxis=mRelOrientation*axis;
    }
    rotAxis=normalize(rotAxis);
    mRelOrientation=angleAxis(degree,rotAxis)*mRelOrientation;
    mRelOrientation=normalize(mRelOrientation);
    updateTransform();
}

void SceneNode::updateTransform(){
    mAbsTransform=mParent->mAbsTransform*getRelTransform();
    for(int i=0;i<mChildren.size();i++){
        mChildren[i]->updateTransform();
    }
}

可见都是以父节点作为基准,因为SceneNode存的是相对位置与相对朝向,运动时需要改变这两个属性。

当value是本地坐标时需要把local位置转变为相对位置 mRelOrientation*value

当value是世界坐标时需要把世界坐标转化为本地坐标再转化为相对坐标 mRelOrientation*(inverse(mat3(mAbsTransform))*value)

这里只是演示一个可以正确运行的代码,实际代码并不会这么写,因为效率低,优化的可以参考ogre的Node和SceneNode

posted on 2013-01-31 18:03  SoMiSoDo  阅读(397)  评论(0)    收藏  举报

导航