Ogre 渲染目標解析與多文本合並渲染


實現目標

  因為需求,想找一個在Ogre中好用的文本顯示,經過查找和一些比對.有三種方案

  一利用Overlay的2D顯示來達到效果.

http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MovableTextOverlay

  二重寫Renderable與MovableObject,利用對應字體查找到每個字符元素紋理坐標.

http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MovableText

  三利用BillboardSet在3D空間顯示(公告板技術),這個有點意思的是對於字體的處理是自己用GUI畫成紋理.

http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MOGRE+MovableText+by+Billboards

  我的要求應該是這樣的,文本會比較多,每個文本通常關聯3D空間里一個點,在動畫效果時點會移動要求文本也能移動,方便添加,刪除,修改文本.

  其中第一與第二沒有用到視圖變換,透視變換,直接根據3D位置轉化成對應屏幕xy中的0到1之間,丟失第三維深度信息,這里倒是沒有多大關系.但是這三種都有一個問題,單個顯示沒什么,如果有100個點,就一百個文本,就有一百個Renderable,一百次渲染,在這個需求里,這是完全存在的.

  所以自己動手,豐衣足食(好吧,是因為找到的都不滿足需求),先細清下要達到的目標及思路.

  首先.文本是否保持3維,如果3維顯示,那么需要一直保證文本面對攝像機.如果2維顯示,會簡單一些,只需要去掉對應視圖,透視變換,然后把對應點的位置直接轉化成對應二維的-1到1之間就OK.

  其次,如何把所有文本一次渲染,先說明在Ogre里如何渲染的,如果是一個字母A,那么我們畫一個四邊形,然后對應圖片(這個圖片畫着一個A)放在這個四邊形上就OK了.那么如果有4個點,分別顯示A,B,C,D.我們可以這樣考慮,先把ABCD這四個字母畫到一張圖片上,然后取每個字母在這個圖片上的紋理坐標,這樣我們就能根據一張紋理一下顯示4個文本.

  最后,因為有動畫效果以及添加,刪除,修改文本,考慮每楨更新顯示,本來想着是用Mesh-Entity,但是感覺是在用高射炮打蚊子,花費高效果不好.如果能最簡單方便的達到這個目的,我們需要先看下最基本的渲染元素.

Ogre 渲染基類

  Renderable則負責渲染,主要包含獲取材質,材質技術,得到渲染體RenderOperation,這個類里面比較簡單,就是頂點數據,索引數據這些.

  主要字段:

  mUseIdentityProjection:是否啟用投影矩陣.前面說過,投影矩陣(正交,透視)是把視圖矩陣里的空間轉化成各邊為-1到1的立方體.(ogre中,caram里mFoVy,mFarDist,mNearDist,mAspect可生成二種類型投影矩陣).

  mUseIdentityView:是否啟用視圖變換,就是啟用不視圖變換,如果不啟用,那么渲染物體和抽象機的位置無關.(Ogre中,caram里mPosition,mOrientation生成以視圖矩陣,其實就是一個以mPosition為原點,以lookat方向為Z值的三維坐標軸).

  這二個屬性一般為false,除非在Overlay(多用於UI)中,都設置false,因為這種UI位置與攝像機位置無關,所以mUseIdentityView,至於把mUseIdentityProjection設為false,則是把對應值(0,1)轉化成(-1,1),不然用戶需要先把在頂點在視圖中的位置除viewport的長寬再轉化成(-1,1),會很麻煩.  mCustomParameters:當用戶渲染需要用到着色器,並且需要設置參數時,提供的一種簡單方法.mPolygonModeOverrideable:簡單來說,就是是否受到攝像機針對顯示模式的修改而影響.(顯示點,線,面).

  主要方法:

  getMaterial(純虛函數):當前要渲染的紋理.

  getRenderOperation(純虛函數):渲染中的頂點緩沖區,頂點索引緩沖區,渲染類型(點,線,三角形這些).

  preRender:getRenderOperation后,渲染前,可以得到當前場景管理與渲染系統的對象.

  postRender:和preRender,只是發生在渲染后.

  MovableObject負責與場景管理中的SceneNode交互,是否可見,包圍體,包圍圈.查詢標識,更新對應渲染體Renderable到渲染列表中.

  主要字段:

  mParentNode: 附加上的節點.

  mVisible:是否可見.

  mRenderQueneID:一般設置大於0,少於100,值大的會覆蓋值少的,越大越顯示在上面.

  mQueryFlags: 場景查詢相交測試標示相與,是則查詢.

  mVisibilityFlags: 與對應viewport對應標示相與,是則顯示.

  mWorldAABB:包圍圈

  mCastShadows:是否啟用陰影

  主要方法:

  _updateRenderQueue(純虛函數):把自己要渲染的內容(Renderable)更新到渲染列表.

  _notifyCurrentCamera:在_updateRenderQueue之前,獲取當前攝像機的信息,用於后期要和攝像機有關的渲染處理.

Renderable與MovableObject組合渲染物體

  Renderable可以直接用於渲染,但是Renderable需要依靠MovableObject置入場景管理,更新渲染列表中.所以大部分需要渲染的元素都是Renderable和MovableObject的派生類.在渲染中,一般是根據MovableObject所附加的SceneNode位置來決定對應Renderable是否添加對應到渲染列表(_updateRenderQueue),然后渲染 渲染列表中的物體時,根據Renderable的getMaterial來設置渲染環境(如果有着色器代碼,則啟用),然后getRenderOperation設置要渲染頂點與索引,開始渲染.

  Ogre中大致有三種組合方式來渲染.下面列舉其中一些比較常見的:

  第一種:只從Renderable繼承,不需要附加到SceneNode上.一般用於OgreUI系統.

  OverlayElement: Overlay組件里所有元素的基類.如panel,textArea,borderPanel.從基類中得到可以渲染的能力.

  BorderRenderable:borderPanel比panel多出來要渲染的部分,這個元素會分二次渲染.

  第二種:從Renderable與MovableObject繼承.

  SimpleRenderable: Ogre中幫我們定義的一個簡單實現,Ogre內部有一些此類的派生實現,大家可以簡單看下.

  BillboardSet:公告板技術的一種實現,繪制多個始終面對攝像機的方形框.用於一些特效如草地啥的,還有Ogre中的Particle粒子效果也是交給BillboardSet處理的.

  BillboardChain:實現線條特效,其子類RibbonTrail實現軌跡特效,如刀光,流星等.

  Frustum:畫對應投影可視體,如正交是一個長方體,透視則是一個立方錐.

  第三種:類A從MovableObject繼承,然后類A中包含類B的列表,而類B從Renderable繼承.

  Entity-SubEntity:Entity繼承MovableObject,可以附加到SceneNode上,而SubEntity繼承Renderable,才是真正用於渲染.比如一個人是一個整體,但是我們需要分開渲染,先渲染頭,手,身體就是這個道理.同時也是內置模型Mesh的包裝類,其中,封裝了如姿態,頂點,骨骼動畫.

  ManualObject-ManualObjectSection:同Entity-SubEntity一樣,讓用戶能方便實現一個物體包含多個組件的模型,並且參考了opengl即時模式的API,不同之后,最后還是以緩沖區模式提交數據.

實現代碼

  回到我們需求,我們采用最合適的方式當是第二種,從Renderable與MovableObject繼承,如果第一種,我們能控制的比較少,第三種我們又只需要一次渲染,用不着.先確定下,我們采用公告板技術,啟用視圖,投影矩陣.

  下面是Axiom代碼,要轉成Ogre,MOgre相應代碼都非常方便. 

    public class ALabel
    {
        public string Label { get; set; }
        public Vector3 Position { get; set; }
    }

    public class ALabelSet : MovableObject, IRenderable
    {
        protected AxisAlignedBox aab = new AxisAlignedBox();
        public ALabelList labelList = new ALabelList();
        protected RenderOperation renderOperation = new RenderOperation();

        private Font _font;
        private string _fontName;
        protected Material material;

        //字體名,設置后加載對應字體的紋理,上面有各個字符在紋理中的坐標以及大小
        public string FontName
        {
            get
            {
                return this._fontName;
            }
            set
            {
                if (this._fontName != value || material == null || this._font == null)
                {
                    this._fontName = value;
                    this._font = (Font)FontManager.Instance[this._fontName];
                    if (this._font == null)
                    {
                        throw new Exception(String.Format("Could not find font '{0}'.", this._fontName));
                    }
                    this._font.Load();
                    if (material != null)
                    {
                        if (material.Name != "BaseWhite")
                        {
                            MaterialManager.Instance.Unload(material);
                        }
                        material = null;
                    }
                    material = this._font.Material.Clone(name + "Material", false, this._font.Material.Group);
                    if (material.IsLoaded == true)
                    {
                        material.Load();
                    }
                    material.DepthCheck = false;
                    //material.CullingMode = CullingMode.None;
                    //material.ManualCullingMode = ManualCullingMode.None;
                    material.Lighting = false;
                }
            }
        }

        private int _spaceWidth;
        private int _characterHeight;

        public int CharacterHeight
        {
            get
            {
                return this._characterHeight;
            }
            set
            {
                this._characterHeight = value;
            }
        }
        public int SpaceWidth
        {
            get
            {
                return this._spaceWidth;
            }
            set
            {
                this._spaceWidth = value;
            }
        }

        public bool buffersCreated;

        protected VertexData vertexData = null;
        protected HardwareVertexBuffer mainBuffer = null;
        private BufferBase lockPtr = null;
        private int ptrOffset = 0;

        //多攝像機,在每個viewport渲染時,需要記錄對應攝像機,在渲染時要計算對應位置
        protected Camera currentCamera;

        private ColorEx _color = ColorEx.White;
        private int iColor = 0;
        public ColorEx Color
        {
            get
            {
                return this._color;
            }
            set
            {
                this._color = value;
            }
        }

        public ALabelSet(string fontName, int charHeight)
        {
            this.FontName = fontName;
            this._characterHeight = charHeight;
            castShadows = false;
        }

        protected override void dispose(bool disposeManagedResources)
        {
            if (!IsDisposed)
            {
                if (disposeManagedResources)
                {
                    if (this.renderOperation != null)
                    {
                        if (!this.renderOperation.IsDisposed)
                        {
                            this.renderOperation.Dispose();
                        }
                        this.renderOperation = null;
                    }
                }
            }

            base.dispose(disposeManagedResources);
        }

        //先檢查是否需要重新申請緩沖區,然后獲取當前緩沖區句柄
        private void BeginRender()
        {
            if (this.labelList.Count == 0)
                return;
            if (this.buffersCreated)
            {
                int count = this.labelList.TextAll.Length;
                if (count * 6 != this.vertexData.vertexCount)
                {
                    this.DestroyBuffers();
                }
            }
            if (!this.buffersCreated)
            {
                this.CreateBuffer();
            }
            this.iColor = Root.Instance.ConvertColor(this._color);
            this.lockPtr = this.mainBuffer.Lock(BufferLocking.Discard);
            this.ptrOffset = 0;
        }
        //根據當前攝像機,得到每個字符的位置.根據字體紋理,得到每個字符紋理坐標.
        private void Rendering(ALabel label)
        {
            if (!this.currentCamera.IsObjectVisible(label.Position))
            {
                return;
            }
            var charlen = label.Label.Length;
            var camera = this.currentCamera;
            var camPos = this.parentNode.FullTransform * camera.DerivedPosition;
            //camera to pos
            var camTo = camPos - label.Position;
            camTo.Normalize();
            //var xAxis = camTo.Cross(Vector3.UnitY);
            //var yAxis = camTo.Cross(xAxis);
            //xAxis = camTo.Cross(yAxis);
            //var camQ = Quaternion.FromAxes(xAxis, yAxis, camTo);
            var labelTo = Vector3.UnitZ;
            if (camTo.z < 0)
                labelTo = Vector3.NegativeUnitZ;
            var dotAngle = camTo.Dot(labelTo);
            var angle = Math.Acos(dotAngle);
            var axis = labelTo.Cross(camTo);
            var camQ = Quaternion.FromAngleAxis(angle, axis);
            //if (camera.Name == "PerspectiveViewportCamera")
            //    System.Diagnostics.Debug.WriteLine("{0}<->{1}<->{2}", dotAngle, angle, camTo.z);
            var left = 0.0f;
            for (var i = 0; i != charlen; i++)
            {
                char cr = label.Label[i];
                var clyph = this._font.Glyphs[cr];
                var width = clyph.aspectRatio;// this._font.GetGlyphAspectRatio(cr);                
                //Real u1, u2, v1, v2;this._font.GetGlyphTexCoords(cr, out u1, out v1, out u2, out v2);
                Real u1 = clyph.uvRect.Top;
                Real u2 = clyph.uvRect.Bottom;
                Real v1 = clyph.uvRect.Left;
                Real v2 = clyph.uvRect.Right;

                var xLeft = camQ * Vector3.UnitX * left * 2.0f * labelTo.z;
                var xRigth = camQ * Vector3.UnitX * (left + width) * 2.0f * labelTo.z;
                var y = camQ * (Vector3.UnitY * this._characterHeight * 2.0f);
                var tl = label.Position + xLeft;
                var bl = label.Position + xLeft - y;
                var tr = label.Position + xRigth;
                var br = label.Position - y + xRigth;
                left += width;
                unsafe
                {
                    var posPtr = this.lockPtr.ToFloatPointer();
                    var colPtr = this.lockPtr.ToIntPointer();
                    var texPtr = posPtr;

                    //first tri
                    //top left
                    posPtr[ptrOffset++] = tl.x;
                    posPtr[ptrOffset++] = tl.y;
                    posPtr[ptrOffset++] = tl.z;// ml.Position.z;
                    colPtr[ptrOffset++] = this.iColor;
                    texPtr[ptrOffset++] = u1;
                    texPtr[ptrOffset++] = v1;
                    //2 bottom left
                    posPtr[ptrOffset++] = bl.x;
                    posPtr[ptrOffset++] = bl.y;
                    posPtr[ptrOffset++] = bl.z;// ml.Position.z;
                    colPtr[ptrOffset++] = this.iColor;
                    texPtr[ptrOffset++] = u1;
                    texPtr[ptrOffset++] = v2;
                    //3 top right
                    posPtr[ptrOffset++] = tr.x;
                    posPtr[ptrOffset++] = tr.y;
                    posPtr[ptrOffset++] = tr.z;// ml.Position.z;
                    colPtr[ptrOffset++] = this.iColor;
                    texPtr[ptrOffset++] = u2;
                    texPtr[ptrOffset++] = v1;

                    //second tri
                    //1 top right
                    posPtr[ptrOffset++] = tr.x;
                    posPtr[ptrOffset++] = tr.y;
                    posPtr[ptrOffset++] = tr.z;// ml.Position.z;
                    colPtr[ptrOffset++] = this.iColor;
                    texPtr[ptrOffset++] = u2;
                    texPtr[ptrOffset++] = v1;
                    //2 bottom left
                    posPtr[ptrOffset++] = bl.x;
                    posPtr[ptrOffset++] = bl.y;
                    posPtr[ptrOffset++] = bl.z;// ml.Position.z;
                    colPtr[ptrOffset++] = this.iColor;
                    texPtr[ptrOffset++] = u1;
                    texPtr[ptrOffset++] = v2;
                    //3 bottom right
                    posPtr[ptrOffset++] = br.x;
                    posPtr[ptrOffset++] = br.y;
                    posPtr[ptrOffset++] = br.z;// ml.Position.z;
                    colPtr[ptrOffset++] = this.iColor;
                    texPtr[ptrOffset++] = u2;
                    texPtr[ptrOffset++] = v2;
                }
            }
        }

        //提交修改后的緩沖區數據
        private void EndRender()
        {
            this.mainBuffer.Unlock();
            this.lockPtr = null;
        }

        private void CreateBuffer()
        {
            this.vertexData = new VertexData();
            this.vertexData.vertexStart = 0;
            this.vertexData.vertexCount = this.labelList.TextAll.Length * 6;

            var decl = this.vertexData.vertexDeclaration;
            var binding = this.vertexData.vertexBufferBinding;

            var offset = 0;
            decl.AddElement(0, offset, VertexElementType.Float3, VertexElementSemantic.Position);
            offset += VertexElement.GetTypeSize(VertexElementType.Float3);
            decl.AddElement(0, offset, VertexElementType.Color, VertexElementSemantic.Diffuse);
            offset += VertexElement.GetTypeSize(VertexElementType.Color);
            decl.AddElement(0, offset, VertexElementType.Float2, VertexElementSemantic.TexCoords, 0);

            this.mainBuffer = HardwareBufferManager.Instance.CreateVertexBuffer(decl.Clone(0), this.vertexData.vertexCount,
                                                                     BufferUsage.DynamicWriteOnlyDiscardable);
            binding.SetBinding(0, this.mainBuffer);
            this.buffersCreated = true;
        }

        private void DestroyBuffers()
        {
            this.vertexData = null;
            this.mainBuffer = null;
            this.buffersCreated = false;
        }

        #region MovableObject
        //不需要場景查詢.
        public override AxisAlignedBox BoundingBox
        {
            get { return (AxisAlignedBox)this.aab.Clone(); }
        }
        //同上
        public override Real BoundingRadius
        {
            get { return 1.0f; }
        }
        //檢查物體是否渲染時,更新當前Renderable到渲染列表中
        public override void UpdateRenderQueue(RenderQueue queue)
        {
            //if (bCameraMove)
            {
                BeginRender();
                foreach (var label in this.labelList)
                {
                    Rendering(label);
                }
                EndRender();
            }
            queue.AddRenderable(this);//, RenderQueue.DEFAULT_PRIORITY, renderQueueID);
        }

        private bool bCameraMove = false;
        private Vector3 prePosition = Vector3.Zero;
        //我們有多個攝像機,而每次渲染需要根據攝像機位置更新緩沖區
        public override void NotifyCurrentCamera(Camera camera)
        {
            var currPos = Root.Instance.SceneManager.GetCamera("PerspectiveViewportCamera").Position;
            bCameraMove = currentCamera == null || currPos != prePosition;
            if (camera.Name == "PerspectiveViewportCamera")
                prePosition = currPos;
            this.currentCamera = camera;
        }
        #endregion

        #region IRenderable
        //不需要陰影
        public bool CastsShadows
        {
            get { return false; }
        }
        //沒有啟用着色器.不需要
        public Vector4 GetCustomParameter(int index)
        {
            return Vector4.Zero;
        }

        public Real GetSquaredViewDepth(Camera camera)
        {
            return parentNode.GetSquaredViewDepth(camera);
        }
        //當前模型矩陣
        public void GetWorldTransforms(Matrix4[] matrices)
        {
            matrices[0] = parentNode.FullTransform;
        }

        public Axiom.Core.Collections.LightList Lights
        {
            get { return QueryLights(); }
        }
        //字體紋理
        public Material Material
        {
            get { return material; }
        }
        //格式化法線
        public bool NormalizeNormals
        {
            get { return false; }
        }
        //一個
        public ushort NumWorldTransforms
        {
            get { return 1; }
        }
        //是否和攝像機同PolygonMode
        public bool PolygonModeOverrideable
        {
            get { return true; }
        }

        public RenderOperation RenderOperation
        {
            get
            {
                if (bCameraMove)
                {
                    this.renderOperation.vertexData = this.vertexData;
                    this.renderOperation.vertexData.vertexStart = 0;
                    this.renderOperation.operationType = OperationType.TriangleList;
                    this.renderOperation.useIndices = false;
                    this.renderOperation.indexData = null;
                    this.renderOperation.vertexData.vertexCount = this.vertexData.vertexCount;
                }
                return this.renderOperation;
            }
        }
        //着色器
        public void SetCustomParameter(int index, Vector4 val)
        {            
        }

        public Technique Technique
        {
            get { return this.material.GetBestTechnique(); }
        }
        //着色器
        public void UpdateCustomGpuParameter(GpuProgramParameters.AutoConstantEntry constant, GpuProgramParameters parameters)
        {            
        }
        //是否啟用投影變換
        public bool UseIdentityProjection
        {
            get { return false; }
        }
        //是否啟用視圖變換
        public bool UseIdentityView
        {
            get { return false; }
        }
        //當前模型在世界空間方位
        public Quaternion WorldOrientation
        {
            get { return parentNode.DerivedOrientation; }
        }
        //當前模型在世界空間頂點
        public Vector3 WorldPosition
        {
            get { return parentNode.DerivedPosition; }
        }
        #endregion
    }

    public class ALabelList : List<ALabel>
    {
        public ALabelList()
        {
        }
        public void Add(string label, Vector3 position)
        {
            ALabel ab = new ALabel();
            ab.Label = label ?? this.Count.ToString();
            ab.Position = position;
            this.Add(ab);
        }

        public string TextAll
        {
            get
            {
                string text = string.Empty;
                foreach (var txt in this)
                {
                    text += txt.Label;
                }
                return text;
            }
        }
    }
ALabelSet

  代碼其實還沒有完成,不過大部分已經實現,對照前面Renderable與MovableObject的實現,說下代碼中要注意的是.

  代碼中,所有方法都加入了注釋.主要重載Renderable與MovableObject相關屬性,實現其方法.包含是否啟用投影變換,視圖變換,陰影,紋理等.

  注意二個地方,一是當我們設置字體時,從字體中我們能得到一張紋理,里面包含所有字符,通過提供的方法,對得到對應字符在紋理中的坐標.二是當我們渲染時,label要一直面對攝像機方向,而我們提供的是XY方向的位置,如何保證XY變換成垂直於攝像機到Label的方向的平面,

  我們得到字符的長寬,在水平面的長度,也就是XY方向,水平面的法線是Z軸,如果當我們攝像機與Label方向垂直XY正平面時,那時我們的字符位置就不需要轉化.我們已知字符轉化前法向量是z軸,而方向矢量(攝像機-label位置)是變化后的法向量.如果我們知道如何從Z軸轉化成label位置到攝像機的方向矢量,那么我們把頂點也進行這個轉化就OK了.(這里法向量旋轉等於頂點的旋轉,如果元素發生大小的變化,這個邏輯不再正確,大家具體可以找相關旋轉變化的書).

  前面有說過,二個向量的點乘可以得到二個向量的角度A,二個向量的叉乘可以得到垂直於這個向量的方向向量V,簡單來說,在方向向量V上,旋轉角度A就能得到達到前面目錄.我們知道,四元數的定義就是由着方向軸旋轉角度,由此,得到前面的V與A,我們能很容易就得到對應四元數,不過需要注意的,二個向量的角度是計算的0-180度,而我們這明顯是360度,比如旋轉了270度和90度都是計算為90度的,所以在這里,我們需要做點改變,當攝像機跑到label后面去時,我們把對應的Z軸變為負Z軸.

  這里我們還可以換個角度來想,Z軸變換后成為(攝像機-label位置)矢量,這個矢量也是新坐標軸的Z軸,知道Z軸,如何求XY軸,想起視圖矩陣是如何得到的沒?很簡單,Z corss Y(0,1,0)得到X,然后Z cross X得到Y,新的三軸我們得到了,或者后變換后的坐標軸我們得到了,根據3軸得到對應矩陣也是一樣的效果.

  下面是效果圖,可以看出,有個視圖還是有問題,大家可以猜下是那導致的.

image

  中間,我想做個嘗試,現在是每楨都更新,這是沒必要的,只有啟用動畫這里才需要如此,我開始想的是,如果數據沒變化,就不更新,可惜的是,我這里是多視圖,多攝像機,然后頂點位置計算又和攝像機有關,而當前緩沖區數據只有一個,這樣必需每楨更新,除非每個攝像機聲明一個緩沖區才行,這樣一想感覺又不划算.

  最后,如上面問題,我們可以關閉視圖變換與投影變換,如Overlay一樣,那么我們如何修改了,記的前面說過,關閉視圖變換與投影變換后,我們設定的x,y值要在-1到1之間,否則是看不到的.所以如果大家記的把Node上的點變換成對應的-1到1就完成了,比上面公告板技術要簡單一點,順便可以參考Overlay里的計算.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM