Inside Geometry Instancing

來源:互聯網
上載者:User

翻譯:clayman
clayman_joe@yahoo.com.cn
僅供個人學習使用,勿用於任何商業用途,轉載請註明作者^_^

註:這篇文章是《GPU Gems2》中的第三章,本人水平有限,不清楚的地方請大家以原文為準

  在互動式程式中,豐富使用者體驗的重要方法之一就是呈現一個充滿大量各種有趣物體的世界。從數不清的草叢、樹木到普通雜物:所有這些都能提高畫面最終的效果,讓使用者保持“幻想狀態(suspension of disbelief)”。只有使用者相信並且融入了這個世界,才會對這個世界充滿感情——這就是遊戲開發的聖杯(Holy Grail)。

  從渲染的觀點來看,實現這種效果,無非就是渲染大量小物體,一般情況下,這些物體彼此都很類似,只在顏色、位置以及朝向上有細小的差別。舉個例子,比如森林中所有樹的幾何形狀都是很類似的,而在顏色和高度上有很大差別。對使用者來說,由外形各異的樹組成的森林才真實,才會相信它,從而豐富自己的遊戲體驗。

  但是,使用當前的GPU和圖形庫渲染大量由少量多邊形組成的小物體會帶來很大的效能損失。諸如Direct3D和OpenGL之類的圖形API都不是為了每幀渲染只有少數多邊形的物體數千次而設計的。本文將討論如何使用Direct3D把同一幾何體渲染為大量獨特的實體(instances)。是Back & White 2中,使用了這一技術的一個例子:

3.1 為何使用Geometry Instancing (Why Geometry Instancing)
  在Direct3D中,把三角形資料提交給GPU是一個相對很慢的操作。Wloka 2003顯示使用Direct3D在1GHz的CPU上,每秒只能渲染10000到400000批次(batches)。對於現代的CPU,可以預測這個值大概在每秒30000到120000批次之間(對FPS為30frame/sec系統來說大概每幀1000到4000批次)。這太少了!這意味著如果我要渲染一片森林,每批次提交一顆樹的資料,那麼無論每棵樹包含多少多邊形,都將無法渲染4000棵樹以上——因為CPU已經沒有時間來處理其他任務了。這種情況當然是我們不想看到的。在應用程式中,我們希望最小化渲染狀態和紋理的改變,同時,在Direct3D中使用一次方法調用,在同一批次中對同一三角形進行多次渲染。這樣,就能減少CPU提交批次的時間,把CPU資源留給物理、AI等其他系統。

3.2 定義(Definitions)
  我們先來定義一系列與geometry instancing相關的概念。

3.2.1 幾何包(Geometry Packet)
  A geometry packet is a description of a packet of geometry to be instanced, a collection of vertices and indices。一個幾何包可以使用頂點——包括他的位置、紋理座標、法線、切線空間(tangent space)以及用於skinning的骨骼資訊——以及頂點流中的索引資訊來描述。這樣的描述,可以直接映射為一個高效的提交幾何體的方法。

  幾何包是對一個幾何體在模型空間進行的抽象描述, 從而可以獨立於當前的渲染環境。

  下面是對幾何包的一種可能的描述,它不但包含了幾何體的資訊,同時還包含了物體的邊界球體資訊:

struct GeometryPacker
{
Primitive mPrimType;
void* mVertice;
unsigned int mVertexStride;

unsigned short* mIndices;
unsigned int mVertexCount;
unsigned int mIndexCount;

D3DXVECTOR3 mSphereCentre;
float mSphereRadius;
}

3.2.2 實體屬性(Instance Attribute)
  對每個實體來說,典型的屬性包括模型到世界的座標變換矩陣,實體顏色以及由animation player提供的用於對幾何包進行skin的骨骼。

struct InstanceAttributes
{
D3DXMATRIX mModelMatrix;
D3DCOLOR mInstanceColor;
AnimationPlayer* mAnimationPlayer;
unsigned int mLOD;
}

3.2.3 幾何實體(Geometry Instance)
  幾何實體就是一個幾何包與特定屬性的集合。他直接聯絡到一個幾何包以及一個將要用於渲染的實體屬性,包含了將要提交給GPU的關於實體的完整描述。

struct GeometryInstance
{
GeometryPacket* mGeometryPacket;
InstanceAttributes mInstanceAttributes;
}

3.2.4 渲染及紋理環境(Render and Texture Context)
  渲染環境指的是當前的GPU渲染狀態(比如alpha blending, testing states, active render target等等)。紋理環境指的則是當前啟用(active) 的紋理。通常使用類來對渲染狀態和紋理狀態進行模組化。

class RenderContext
{
public:
//begin the render context and make its render state active
void Begin(void);
//End the render context and restore previous render states if necessary
void End(void);

private:
//Any description of the current render state and pixel and vertex shaders.
//D3DX Effect framework is particularly useful
ID3Deffect* mEffect;
//Application-specific render states
//….
};

class TextureContext
{
public:
//set current textures to the appropriate texture stages
void Apply(void) const;

private :
Texture mDiffuseMap;
Texture mLightMap;
//……..
}

3.2.5 幾何批次(Geometry Batch)
  幾何批次是一系列幾何實體的集合,以及用來渲染這個集合的渲染狀態和紋理環境。為了簡化類的設計,通常直接映射為一次DrawIndexedPrimitive()方法調用。以下是幾何批次類的一個抽象介面:

class GeometryBatch
{
public:
//remove all instances form the geometry batch
virtual void ClearInstances(void);
//add an instance to the collection and return its ID. Return -1 if it can’t accept more instance.
virtual int AddInstance(GeometryInstance* instance);
//Commit all instances, to be called once before the render loop begins and after every change to the instances collection
virtual unsigned int Commit(void) = 0;
//Update the geometry batch, eventually prepare GPU-specific data ready to be submitted to the driver, fill vertex and
//index buffers as necessary , to be called once per frame
virtual void Update(void) = 0;
//submit the batch to the driver, typically impemented eith a call to DrawIndexedPrimitive
virtual void Render(void) const = 0;

private:
GeometryInstancesCollection mInstances;
}

3.3 實現(Implementation)
  引擎的渲染器只能通過GeometryBatch的抽象介面來使用geometry instancing,這樣能很好隱藏具體的實體化(instancing)實現,同時,提供管理實體、更新資料、以及渲染批次的服務。這樣引擎就能集中於分類(sorting)批次,從而最小化渲染和紋理狀態的改變。同時,GeometryBatch完成具體的實現,並且與Direct3D進行通訊。

下面使用的虛擬碼實現了一個簡單的渲染迴圈:
//Update phase
Foreach GeometryBatch in ActiveBatchesList
GeometryBatch.Update();

//Render phase
Foreach RenderJContext
Begin
RenderContext.BeginRendering();
RenderContext.CommitStates();

Foreach TextureContext
Begin
TextureContext.Apply();
Foreach GeometryBatch in the texture context
GeometryBatch.Render();
End
End

  為了能一次更新所有批次並且進行多次渲染,更新和渲染階段應該分為獨立的兩部分:這種方法在渲染陰影貼圖或者水面的反射以及折射時特別有用。這裡我們將討論4種GeometryBatch的實現,並且通過比較記憶體佔用量、可控性來分析各種技術的效能特性。

這裡是一個大概的摘要:
  *靜態批次(static batching):執行instance geometry最快的方法。每個實體通過一次變換移動到全局座標,附加上屬性值,然後就提交給GPU。靜態批次很簡單,但也是可控性最小的一種。

  *動態批次(Dynamic batching):執行insance geometry最慢的方法。每一幀裡,每個經過變換,附加了屬性的實體都以流的形式傳入GPU。動態批次可以完美的支援skinning,也是可控性最強的。

  *Vertex constants instancing:一種混合的實現方法。每個實體的幾何資訊都被複製多次,並且一次性把他們複製到GPU的緩衝中。通過頂點常量,每一幀都重新設定實體屬性,使用一個vertex shader完成gemetry instancing。

  *Batching with Geometry Instancing API。使用DirectX 9提供的Geometry Instancing API,可以獲得GeForce 6系列顯卡完全的硬體支援,這是一種高效而又具有高度可控性的gemetry instancing方法。與其他幾種方法不同的是它不需要把幾何包複製到Direct3D的頂點流中。

3.3.1 靜態批次(Static Batching)
  對靜態批次來說,我們希望對所有實體進行一次變換之後,複製到一塊靜態頂點緩衝中。靜態批次最大的優點就是高效,同時幾乎市場上所有的GPU都能支援這個特性。

  為了實現靜態批次,先建立一個用來填充經過變化後的幾何體的頂點緩衝對象(當然也包括索引緩衝)。需要保證這個這個緩衝足夠大,足以儲存我們希望處理的所有實體。由於我們只對緩衝進行一次填充,並且不再做修改,因此,可以使用Direct3D中的D3DUSAGE_WRITEONLY標誌,提示驅動程式把緩衝放到速度最快的可用顯存中:

HRESULT res;
res = lpDevice -> CreateVertexBuffer( MAX_STATIC_BUFFER_SIZE, D3DUSAGE_WRITE, 0, D3DPOOL_MANAGED, &mStaticVertexStream, 0 );
ENGINE_ASSERT(SUCCEEDED(res));

  根據應用程式的類型或者引擎的記憶體管理方式,可以選擇使用D3DPOOL_MANAGED或D3DPOOL_DEFAULT標誌來建立緩衝。

  接下來實現Commit()方法。它將把需要渲染的經過座標變換的幾何體資料填充到頂點和索引緩衝中。以下是Commit方法的虛擬碼實現:

Foreach GeometryInstance in Instances
Begin
transform geometry in mGeometryPack to world space with instance mModelMatrix
Apply other instnce attributes(like instace color)
Copy transformed geometry to the Vertex Buffer
Copy indices ( with the right offset) to the Index Buffer
Advance current pointer to the Vertex Buffer
Advance currect pointer to the Index Buffer
End

  好了,接下來就只剩使用DrawIndexedPrimitive()方法,提交這些準備好的資料了。Update()方法和Render()方法的實現都很簡單,這裡不具體討論。

  靜態批次是渲染大量實體最快的方法,它可以在一個批次中包含不同類型的幾何包,但也有一些嚴重的限制:

  *大記憶體佔用(Large memory footprint):根據幾何包大小和希望渲染的實體數量,記憶體佔用量可能會變的很大。對於大情境來說,應該預留出幾何體所需的空間。Falling back to AGP memory is possible(註:這裡應該指的是當顯存不夠用時,需要把資料分頁存放到AGP memory中),但這會降低效率,因此,應該盡量避免。

  *不支援多種LOD(No support for different level of detal):由於在提交資料時,所有實體都被一次性複製到頂點緩衝中,因此很難對每種環境都選擇一個有效LOD層次,同時,還會導致對多邊形數量的預算不正確。可以使用一種半靜態方法來解決這個問題,把特定實體的所有LOD層次都放在頂點緩衝中,每一幀選擇不同的索引值,來選擇實體的正確LOD。但這樣會讓實現看起來很笨拙,違反了我們使用這種方法最初的目的:簡單並且高效。

  *No support for skinning

  *不直接支援實體移動(No direct support for moving instances):由於效率的原因,實體的移動應該使用vertex shader邏輯和動態批次來實現。最終的解決方案其實就是vertex constants instancing。

接下來的一種方法將解除這些限制,以犧牲渲染速度換取可控性。

3.3.2 動態批次(DynamicBatching)
  動態批次以降低渲染效率為代價,克服了靜態批次方法的限制。動態批次最大的優點和靜態批次一樣,也能在不支援進階編程管道的GPU上使用。

  首先使用D3DUSAGE_DYNAMIC和D3DPOOL_DEFAULT標誌建立一塊頂點緩衝(同樣也包括相應的索引緩衝)。這些標誌將保證緩衝處於最容易進行記憶體定位的地方,以滿足我們動態更新的要求

HRESULT res;
res = lpDevice->CreateVertexBuffer(MAX_DYNAMIC_BUFFER_SIZE, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 0 , D3DPOOL_DEFAULT, &mDynamicVertexStream, 0)

這裡,選擇正確的MAX_DYNAMIC_BUFFER_SIZE值是很重要的。有兩種策略來選擇這個值:

  *選擇一個可以容納每一幀裡所有可能實體的足夠大值。

  *選擇一個足夠大的值,以保證可以容納一定量的實體。

  第一種策略在一定程度上保證了更新和渲染批次的獨立。更新批次意味著對動態緩衝中的所有資料進行資料流化(streaming);而渲染則只是使用DrawIndexedPrimitive()方法提交幾何資料。當這種方法將會佔用大量的圖形記憶體(顯存或者AGP memory),同時,在最差的情況下,這種方法將變的不可靠,因為我們無法保證緩衝在整個應用程式生命期中都足夠大。

  第二種策略則需要在幾何體資訊資料流化和渲染之間進行交錯:當動態緩衝被填滿時,提交幾何體進行渲染,同時丟棄緩衝中的資料,準備好填充更多將被資料流化的實體。為了最佳化效能,使用正確的標誌是很重要的,換句話說就是,在每一批實體開始時都使用D3DLOCK_DISCARD標誌鎖定(locking)動態緩衝,此外,對每個將要資料流化的新實體都使用D3DLOCK_WRITEONLY標誌。這個方法的缺點是每次當批次需要進行渲染時,都需要重新鎖定緩衝,以資料流化幾何體資訊,比如實現陰影影射時。

  應該根據應用程式的類型和具體要求來選擇不同方法。這裡,由於簡單和清楚的原因,我們選擇了第一種方法,但是也添加了一點點複雜度:動態批次天生支援skinning,我們順便對他進行了實現。

  Update方法與之前在3.3.1討論的Commit()方法很類似,但它需要在每一幀都執行。這裡是虛擬碼的實現;

Foreach GeometryInstance in Instances
Begin
Transform geometry in mGeometryPacket to world space with instance mModelMatrix
if instance nedds skinning, request a set of bones from mAnimationPlayer and skin geometry
Apply other instance attributes(like instance color)
Copy transformd geometry to the Vertex Buffer
Copy indices (with the right offset) to the Index Buffer
Advance current pointer to the Vertex Buffer
Advance current pointer to the Index Buffer
End

  這種情況下,Render()方法只是簡單的調用DrawIndexedPrimitive()方法而已。

此教程著作權歸我所有,僅供個人學習使用,請勿轉載,勿用於任何商業用途。商業應用請同我聯絡。
由於本人水平有限,難免出錯,不清楚的地方請大家以原著為準。也歡迎大家和我多多交流。
其中部分圖片來自網路,盡量保證了和原書中插圖一致。
特別感謝mtt重現了文章中的流程圖^_^
翻譯:clayman
Blog:http://blog.csdn.net/soilwork
clayman_joe@yahoo.com.cn

3.3.3 Vertex Constants Instancing
在vertex constants instancing方法中,我們利用頂點常量來儲存實體屬性。就渲染效能而言,頂點常量批次是非常快的,同時支援實體位置的移動,但這些特點都是以犧牲可控性為代價的。
以下是這種方法主要的限制:
*根據常理數值的大小,每批次的實體數量是受限制的;通常對一次方法調用來說,批次中不會超過50到100個實體。但是,這足以滿足減少CPU調用繪圖函數的負載。
*不支援skinning;頂點常量全部用於儲存實體屬性了
*需要支援vertex shaders的硬體
首先,需要準備一塊靜態頂點緩衝(同樣包括索引緩衝)來儲存同一幾何包的多個副本,每個副本都以模型座標空間儲存,並且對應批次中的一個實體。

必須更新最初的頂點格式,為每個頂點添加一個整數索引值。對每個實體來說,這個值將是一個常量,標誌了特定幾何包屬於哪個實體。這和palette skinning有些類似,每個頂點都包含了一個索引,指向將會影響他的一個或多個骨骼。
更新之後的頂點格式如下:
Stuct InstanceVertex
{
D3DVECTOR3 mPosition;
//other properties……
WORD mInstanceIndex[4];  //Direct3D requires SHORT4
};
在所有實體資料都添加到幾何批次之後,Commit()方法將按照正確的設計,準備好頂點緩衝。
接下來就是為每個需要渲染的實體載入屬性。我們假設屬性只包括描述實體位置和朝向的模型矩陣,以及實體顏色。
對於支援DirectX9系列的GPU來說,最多能使用256個頂點常量:我們使用其中的200個來儲存實體屬性。在我們所舉的例子中,每個實體需要4個常量儲存模型矩陣,1個常量儲存顏色,這樣每個實體需要5個常量,因此每批次最多包含40個實體。
以下是Update()方法。實際的實體將在vertex shader進行處理。
D3DVECTOR4 instancesData[MAX_NUMBER_OF_CONSTANTS];
unsigned int count = 0;
for(unsigned int i=0; i<GetInstancesCount(); ++i)
{
//write model matrix
instancesData[count++] = *(D3DXVECTOR4*) & mInstances[i].mModeMatrix.m11;
instancesData[count++] = *(D3DXVECTOR4*) & mInstances[i].mModelMatrix.m21;
instancesData[count++] = *(D3DXVECTOR4*) & mInstances[i].mModelMatrix.m31;
instancesData[count++] = *(D3DXVECTOR4*) & mInstances[i].mModelMatrix.m41;
//write instance color
instaceData[count++] = ConverColorToVec4(mInstances[i].mColor);
}
lpDevice->SetVertexConstants(INSTANCES_DATA_FIRST_CONSTANT, instancesData, count);
下面是vertex shader:
//vertex input declaration
struct vsInput
{
float4 postion : POSITON;
float3 normal : NORMAL;
//other vertex data
int4 instance_index : BLENDINDICES;
};

vsOutput VertexConstantsInstancingVS( in vsInput input)
{
//get the instance index; the index is premultiplied by 5 to take account of the number of constants used by each instance
int instanceIndex = ((int[4])(input.instance_index))[0];
//access each row of the instance model matrix
float4 m0 = InstanceData[instanceIndex + 0];
float4 m1 = InstanceData[instanceIndex + 1];
float4 m2 = InstanceData[instanceIndex + 2];
float4 m3 = InstanceData[instanceIndex + 3];
//construct the model matrix
float4x4 modelMatrix = {m0, m1, m2, m3}
//get the instance color
float instanceColor = InstanceData[instanceIndex + 4];
//transform input position and normal to world space with the instance model matrix
float4 worldPostion = mul(input.position, modelMatrix);
float3 worldNormal = mul(input.normal, modelMatrix;
//output posion, normal and color
output.position = mul(worldPostion, ViewProjectionMatrix);
output.normal = mul(worldPostion,ViewProjectionMatrix);
output.color = instanceColor;
//output other vertex data
}
Render()方法設定觀察和投影矩陣,並且調用一次DrawIndexedPrimitive()方法提交所有實體。
實際代碼中,可以把模型空間的旋轉部分儲存為一個四元數(quaternion),從而節約2個常量,把最大實體數增加到70左右。之後,在vertex shader中重新構造矩陣,當然,這也增加了編碼的複雜度和執行時間。

3.3.4 Batching with the Geometry Instancing API
最後介紹的一種方法就是在DirectX9中引入的,完全可由Geforce 6系列GPU硬體實現的幾何實體API批次。隨著原來越多的硬體支援幾何實體API,這項技術將變的更加有趣,它只需要佔用非常少的記憶體,另外也不需要太多CPU的幹涉。它唯一的缺點就是只能處理來自同一幾何包的實體。
DirectX9提供了以下函數來訪問幾何實體API:
HRESULT SetStreamSourceFreq( UINT StreamNumber, UINT FrequencyParameter);
StreamNumber是目標資料流的索引,FrequencyParameter表示每個頂點包含的實體數量。
我們首先建立2快頂點緩衝:一塊靜態緩衝,用來儲存將被多次實體化的單一幾何包;一塊動態緩衝,用來儲存實體資料。兩個資料流如所示:

Commit()必須保證所有幾何體都使用了同一幾何包,並且把幾何體的資訊複製到靜態緩[來源:GameRes.com]沖中。
Update()只需簡單的把所有實體屬性複製到動態緩衝中。雖然它和動態批次中的Update()方法很類似,但是卻最小化了CPU的幹涉和圖形匯流排(AGP或者PCI-E)頻寬。此外,我們可以分配一塊足夠大的頂點緩衝,來滿足所有實體屬性的需求,而不必擔心顯存消耗,因為每個實體屬性只會佔用整個幾何包記憶體消耗的一小部分。
Render()方法使用正確流頻率(stream frequency)設定好兩個流,之後調用DrawIndexedPrimitive()方法渲染同一批次中的所有實體,其代碼如下:
unsigned int instancesCount = GetInstancesCount();
//set u stream source frequency for the first stream to render instancesCount instances
//D3DSTREAMSOURCE_INDEXEDDATA tell Direct3D we’ll use indexed geometry for instancing
lpDevice->SetStreamSourceFreq(0, D3DSTREAMSOURCE_INDEXEDDATA | instancesCount);
//set up first stream source with the vertex buffer containing geometry for the geometry packet
lpDevice->setStreamSource(0, mGeometryInstancingVB[0], 0, mGeometryPacketDeck);
//set up stream source frequency for the second stream; each set of instance attributes describes one instance to be rendered
lpDevice->SetstreamSouceFreq(1, D3DSTREAMSOURCE_INDEXEDDATA | 1);
// set up second stream source with the vertex buffer containing all instances’ attributes
pd3dDevice->SetStreamSource(1, mGeometryInstancingVB[0], 0, mInstancesDataVertexDecl);
GPU通過虛擬複製(virtually duplicating)把頂點從第一個流打包到第二個流中。vertex shader的輸入參數包括頂點在模型空間下的位置,以及額外的用來把模型矩陣變換到世界空間下的實體屬性。代碼如下:
// vertex input declaration
struct vsInput
{
//stream 0
float4 position : POSITION;
float3 normal  : NORMAL;
//stream 1
float4 model_matrix0 :  TEXCOORD0;
float4 model_matrix1 :  TEXCOORD1;
float4 model_matrix2 :  TEXCOORD2;
float4 model_matrix3 :  TEXCOORD3;

float4 instance_color :  D3DCOLOR;
};

vsOutput geometryInstancingVS(in vsInput input)
{
//construct the model matrix
float4x4 modelMatrix =
{
input.model_matrix0,
input.model_matrix1,
input.model_matrix2,
input.model_matrix3,
}
//transform inut position and normal to world space with the instance model matrix
float4 worldPosition = mul(input.position, modelMatrix);
float3 worldNormal = mul(input.normal,modelMatrix);
//output positon, normal ,and color
output.positon = mul(worldPostion,ViewProjectionMatrix);
output.normal = mul(worldNormal,ViewProjectionMatrix);
output.color = int.instance_color;
//output other vertex data…..
}
由於最小化了CPU負載和記憶體佔用,這種技術能高效的渲染同一幾何體的大量副本,因此,也是遊戲中理想的解決方案。當然,它的缺點在於需要硬體功能的支援,此外,也不能輕易實現skinning。
如果需要實現skinning,可以嘗試把所有實體的所有骨骼資訊儲存為一張紋理,之後為相應的實體選擇正確的骨骼,這需要用到Shader Model3.0中的頂點紋理訪問功能。如果使用這種技術,那麼訪問頂點紋理帶來的效能消耗是不確定的,應該實現進行測試。

3.4 結論
本文描述了幾何實體的概念,並且描述了4中不同的技術,來達到高效渲染同一幾何體多次的目的。每一種技術都有有點和缺點,沒有哪種單一的方法能完美解決遊戲情境中可能遇到的問題。應該根據應用程式的類型和渲染的物體種類來選擇相應的方法。
一下是一些情境中建議使用的方法:
*對於包含了同一幾何體大量靜態實體的室內情境,由於他們很少移動,靜態批次是最好的選擇。
*包含了大量動畫實體的戶外情境,比如包含了數百戰士的即時戰略遊戲,動態批次也許是最好的選擇。
*包含了大量蔬菜和樹木的戶外情境,通常需要對他們的屬性進行修改(比如實現隨風而動的效果),以及一些粒子系統,幾何批次API也許就是最好的選擇。
通常,同一應用程式會用到兩個以上的方法。這種情況下,使用一個抽象的幾何批次介面隱藏具體實現,能讓引擎更容易進行模組化和管理。這樣,對整個程式來說,幾何實體化的實現工作也能減少很多。

(圖中,靜態建築使用了靜態批次,而樹則使用了幾何實體API)

附上完整的PDF文檔,完整的demo大家可以參考NVIDIA SDK中的樣本Instancing,也可以直接在這裡下載。另外也可參考DirectX SDK中的樣本Instancing

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.