像素著色器
像素著色器
像素著色器包含由ASCII文本組成的像素著色器指令。算術指令可以用來進行漫反射和/或鏡面反射光照計算。
在Microsoft DirectX 8.0之前,Microsoft Direct3D 使用固定功能流水線把三維幾何體轉換為屏幕上的像素。用戶通過設置流水線的屬性來控制Direct3D進行變換、光照和渲染像素的方式。固定功能頂點格式在編譯的時候定義並決定輸入頂點的格式,一旦定義,用戶在運行的時候就幾乎無法控制流水線的改變。
通過允許對頂點的變換、光照和對每個像素的著色等功能進行編程,著色器把圖形流水線引入了一個新的高度。像素著色器是一些小程序,在對三角形進行光柵化操作時運行。這在渲染像素的方法上給了用戶更高一級的靈活性。
紋理定址指令提供了多種讀取和應用紋理數據的操作。著色器具有這樣的功能,可以給顏色分量設置掩碼以及交換顏色分量。著色器的正文看起來有點像彙編語言,它用Direct3D擴展(D3DX)進行彙編,輸入可以是文本字元串或是文件。彙編器的輸出是一系列操作碼,應用程序可以通過IDirect3DDevice9::CreatePixelShader方法把這些操作碼提供給Direct3D。
本示例用像素著色器對一個四邊形的漫反射色進行高洛德插值。示例顯示了著色器文件的內容以及應用程序中所需的代碼。
第1步:檢查對像素著色器的支持。第2步:聲明頂點數據。第3步:設計像素著色器。第4步:創建像素著色器。第5步:渲染輸出像素。如果讀者已經知道如何構建並運行Direct3D示例,那麼可以從本示例中複製代碼並粘貼到已有的應用程序中。
要檢查對像素著色器的支持,應該使用以下代碼。這個例子檢查1.1版本的像素著色器。
D3DCAPS9 caps;
m_pd3dDevice->GetDeviceCaps(&caps); // 使用m_pd3dDevice前要進行初始化
if( caps.PixelShaderVersion < D3DPS_VERSION(1,1) )
return E_FAIL;
caps結構會返回硬體可用的能力。要用D3DPS_VERSION宏檢查當前硬體支持的所有著色器版本。如果caps返回的版本小於1.1,那麼這個調用會失敗。反之,對所有大於或等於1.1的版本,調用會成功。如果硬體不支持被測試的著色器版本,那麼應用程序將不得不退而使用別的渲染方法(也許可以使用一個較低版本的著色器)。
這個示例使用了一個四邊形,由兩個三角形組成。每個頂點的數據結構包含了位置和漫反射色數據。D3DFVF_CUSTOMVERTEX宏定義了與頂點數據相匹配的數據結構。實際的頂點數據在全局數組g_Vertices中聲明。四個頂點以原點為中心,每個頂點具有不同的漫反射色。
// 聲明頂點數據結構。
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD diffuseColor;
};
// 聲明自定義FVF宏。
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
// 聲明頂點位置和漫反射色數據。
CUSTOMVERTEX g_Vertices[]=
{
// x y z 漫反射色
{ -1.0f, -1.0f, 0.0f, 0xffff0000 }, // 紅 – 左下
{ +1.0f, -1.0f, 0.0f, 0xff00ff00 }, // 綠 – 右下
{ +1.0f, +1.0f, 0.0f, 0xff0000ff }, // 藍 – 右上
{ -1.0f, +1.0f, 0.0f, 0xffffffff }, // 白 – 左上
};
第3步
這個著色器把經過高洛德插值的漫反射色數據複製到輸出像素。著色器文件PixelShader.txt如下所示:
ps_1_1 // 版本指令
mov r0,v0 // 把頂點的漫反射色複製到輸出寄存器。
像素著色器文件的第一條指令聲明了像素著色器的版本,此處為1.1。
第二條指令把顏色寄存器(v0)的內容複製到輸出寄存器(r0)。因為在第1步中聲明的頂點數據已經包含了經過插值的漫反射色,所以顏色寄存器包含了頂點的漫反射色。輸出寄存器決定渲染目標使用的像素顏色(因為本例中沒有更多的處理,如霧,所以輸出寄存器就是最終的像素顏色)。
像素著色器由像素著色器指令創建。本例中,指令被包含在一個單獨的文件中。指令也可以被包含在一個文本字元串中。
LPD3DXBUFFER pCode; // 存放經過彙編的著色器代碼的緩存
LPD3DXBUFFER pErrorMsgs; // 存放錯誤信息的緩存
TCHAR strPixelShaderPath[512];// 用來定位著色器文件
DXUtil_FindMediaFileCb( strPixelShaderPath, sizeof(strPixelShaderPath),
_T("PixelShader.txt") );
這個函數是示例框架使用的一個輔助函數,許多示例都以它為基礎。
LPDIRECT3DPIXELSHADER9 m_pPixelShader;
D3DXAssembleShaderFromFile( strPixelShaderPath, NULL, NULL, 0,
&pCode, &pErrorMsgs, NULL );
m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),
&m_pPixelShader );
著色器創建完成後,指針 m_pPixelShader用來對它進行引用。
除了用像素著色器句柄來設置著色器外,渲染輸出像素的過程和使用固定功能流水線類似。
// 在本例中關閉光照。它不會對最終像素的顏色產生影響。
// 像素顏色完全由經過插值的頂點顏色決定。
m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
m_pd3dDevice->SetStreamSource( 0, m_pQuadVB, sizeof(CUSTOMVERTEX) );
m_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
m_pd3dDevice->SetPixelShader( m_pPixelShader );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
源頂點數據由SetStreamSource設置。本例中, SetFVF使用在聲明頂點數據時定義的FVF碼告訴Direct3D進行固定功能頂點處理。頂點著色器和像素著色器既可以一起使用,也可以分開使用。可以用固定功能流水線代替這兩者。 SetPixelShader設置像素著色器,而 DrawPrimitive則繪製四邊形。
確認對像素著色器的支持
應用程序可以查詢D3DCAPS9的成員以確定硬體對像素著色器所涉及的操作的支持程度。下表列出了與可編程像素處理有關的設備能力。
設備能力 | 描述 |
PixelShader1xMaxValue | 寄存器中可存儲的值的範圍為[-PixelShader1xMaxValue, PixelShader1xMaxValue]。這個值只對版本1.1到1.4有效。 |
MaxSimultaneousTextures | 用於固定功能流水線,紋理取樣器的數量為MaxTextureBlendStages除以MaxSimultaneousTextures。用於像素著色器的紋理取樣器的數量在接下來的表中顯示。 |
PixelShaderVersion | 硬體支持的像素著色器的版本。版本號小於或等於該值的像素著色器被支持。 |
可用於像素著色器的紋理取樣器的數量取決於像素著色器的版本。
像素著色器版本 | 紋理取樣器的數量 |
ps_1_1 - ps_1_3 | 4個紋理取樣器 |
ps_1_4 | 6個紋理取樣器 |
ps_2_0 - ps_3_0 | 16個紋理取樣器 |
Fixed function pixel shader | MaxTextureBlendStages/MaxSimultaneousTextures個紋理取樣器 |
PixelShaderVersion的第一個位元組包含次版本號,第二個位元組包含主版本號。經過彙編的著色器的第一個標記就是像素著色器的版本。每種硬體實現都會設置該版本號,表示它能完全支持的像素著色器的最高版本。
紋理操作的轉換
像素著色器在以下幾個方面擴展並一般化了Microsoft DirectX® 6.0和7.0的多重紋理能力。
加入了一組通用讀/寫寄存器,這樣就允許更為靈活的表達式。用D3DTA_CURRENT的連續級聯需要為每層指定一個單獨的結果寄存器參數。 D3DTOP_MODULATE2X和D3DTOP_MODULATE4X紋理操作被分成了單獨的修飾符,可用於任何指令。這樣就無需單獨的D3DTOP_MODULATE和D3DTOP_MODULATE2X操作了。乘加操作加入了可選的第三個參數,因此程序化的像素著色器可以執行arg1 ×arg2 + arg0的操作。這樣就不再需要D3DTOP_MODULATEALPHA_ADDCOLOR和D3DTOP_MODULATECOLOR_ADDALPHA 紋理操作了。混合操作加入了可選的第三個參數,因此程序化的像素著色器可以用arg0作為arg1和arg2之間的混合比。這樣就不再需要D3DTOP_BLENDDIFFUSEALPHA, D3DTOP_BLENDTEXTUREALPHA, D3DTOP_BLENDFACTORALPHA, D3DTOP_BLENDTEXTUREALPHAPM, 和D3DTOP_BLENDCURRENTALPHA紋理操作了。對紋理定址的修改操作,如D3DTOP_BUMPENVMAP,被從顏色和阿爾法操作中分離出來並作為第三種操作類型,專門用於紋理定址操作。為了有效地支持這種新增的靈活性,API的語法從DWORD對改成了ASCII彙編代碼語法。這樣就暴露了程序化的像素著色器所提供的功能。
注意在使用像素著色器時,鏡面反射加法不專門由一個渲染狀態控制,如果需要,這可能由像素著色器實現。但是,霧混合仍然由固定功能流水線執行。
對紋理的一些考慮
像素著色器完全取代了由Microsoft DirectX® 6.0和7.0的多重紋理API提供的像素混合功能,尤其是那些由D3DTSS_COLOROP, D3DTSS_COLORARG1, D3DTSS_COLORARG2, D3DTSS_ALPHAOP, D3DTSS_ALPHAARG1和D3DTSS_ALPHAARG2紋理層狀態、相關的參數和修飾符定義的操作。如果設置了程序化的像素著色器,那麼這些狀態會被忽略。
如果像素著色器正在運行,那麼以下紋理層狀態仍然會被使用。對這些狀態的使用取決於像素著色器的版本,如下表所示。
紋理層狀態 | 版本 |
D3DTSS_BUMPENVMAT00 | 1_1 - 1_4 |
D3DTSS_BUMPENVMAT01 | 1_1 - 1_4 |
D3DTSS_BUMPENVMAT10 | 1_1 - 1_4 |
D3DTSS_BUMPENVMAT11 | 1_1 - 1_4 |
D3DTSS_BUMPENVLSCALE | 1_1 - 1_3 |
D3DTSS_BUMPENVLOFFSET | 1_1 - 1_3 |
D3DTSS_TEXCOORDINDEX | 1_1 - 1_3. 僅對固定功能頂點處理有效。 |
D3DTSS_TEXTURETRANSFORMFLAGS | 1_1 - 1_3. 僅對固定功能頂點處理有效。 |
如果像素著色器正在運行,那麼下面的取樣器狀態仍然會被使用。
取樣器狀態 | 版本 |
D3DSAMP_ADDRESSU | 所有版本 |
D3DSAMP_ADDRESSV | 所有版本 |
D3DSAMP_ADDRESSW | 所有版本 |
D3DSAMP_BORDERCOLOR | 所有版本 |
D3DSAMP_MAGFILTER | 所有版本 |
D3DSAMP_MINFILTER | 所有版本 |
D3DSAMP_MIPFILTER | 所有版本 |
D3DSAMP_MIPMAPLODBIAS | 所有版本 |
D3DSAMP_MAXMIPLEVEL | 所有版本 |
D3DSAMP_MAXANISOTROPY | 所有版本 |
D3DSAMP_SRGBTEXTURE | 所有版本 |
D3DSAMP_ELEMENTINDEX | 所有版本 |
D3DSAMP_DMAPOFFSET | 僅頂點著色器(位移貼圖) |
因為紋理層狀態不是像素著色器的一部分,在編譯著色器時它們是不可用的,所以驅動程序無法對它們做任何假定。例如,驅動程序不能在那時區分出雙線性過濾和三線性過濾。應用程序可以自由地改變這些狀態而無需重新生成當前綁定的著色器。
紋理取樣和過濾操作由標準紋理層狀態中的放大、縮小、mip過濾、以及環繞定址模式控制。更多信息,請參閱紋理層狀態。因為驅動程序在編譯時也無法得到這些信息,所以著色器必須能夠在這些狀態改變後繼續運行。應用程序負責設置像素著色器所需的正確類型的紋理(二維貼圖,立方體貼圖,立體貼圖等)。如果設置不正確的紋理類型,那麼將會產生不可預料的結果。
其它一些像素操作——如霧混合、模板操作、以及渲染目標混合——在著色器執行以後發生。為了支持本主題描述的新特性,渲染目標混合的語法已經做了更新。
對像素著色器版本1.1到2.0來說,漫反射色和鏡面反射色在給著色器使用之前被截取到範圍0到1之間,因為這是著色器的有效輸入範圍。
輸入到像素著色器的顏色值被認為是經過透視校正的,但並非所有硬體都能保證這一點。定址處理器根據紋理坐標產生的顏色總是以透視校正的方式進行迭代。但是,在迭代過程中,它們也會被截取到範圍0到1之間。
對像素著色器版本1.1到1.4來說,像素著色器產生的輸出是寄存器r0的內容。該寄存器的值會在著色器處理結束后被送往霧處理階段和渲染目標混合器。
對像素著色器版本2.0以上來說,輸出的顏色值為oC0到oC4。
像素著色器示例
本節包含三個像素著色器示例。每個示例都建立在前一個示例的基礎上,並增加一些功能。
應用紋理貼圖 把頂點漫反射色和紋理進行混合 用顏色值把兩張紋理進行混合
本示例把一張紋理貼圖應用於一個四邊形。本例和前例的區別在於:
頂點數據結構和FVF碼包含了紋理坐標。頂點數據包含了u, v數據。因為像素的顏色將從紋理貼圖得到,所以頂點數據不再需要漫反射色。用IDirect3DDevice9::SetTexture把紋理連接到紋理層0。著色器用t0紋理寄存器取代v0漫反射色寄存器。示例代碼如下:
// 定義頂點數據結構。
struct CUSTOMVERTEX
{
FLOAT x, y, z;
FLOAT u1, v1;
};
// 定義相應的FVF碼。
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX|D3DFVF_TEXCOORDSIZE2(0))
// 創建包含位置和紋理坐標的頂點數據。
static CUSTOMVERTEX g_Vertices[]=
{
// x y z u1 v1
{ -1.0f, -1.0f, 0.0f, 0, 1, },
{ 1.0f, -1.0f, 0.0f, 1, 1, },
{ 1.0f, 1.0f, 0.0f, 1, 0, },
{ -1.0f, 1.0f, 0.0f, 0, 0, },
// 為了和Windows從上到下的約定一致,v1被顛倒了。
// 左上角的紋理坐標為(0,0)。
// 右下角的紋理坐標為(1,1)。
};
// 創建紋理。這個文件包含在供下載的DirectX 9.0 SDK的媒體文件中。
TCHAR strTexturePath[512];
DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),
_T("DX5_Logo.bmp") );
LPDIRECT3DTEXTURE9 m_pTexture0;
D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,
&m_pTexture0, D3DFMT_R5G6B5 );
// Create the pixel shader.
TCHAR strShaderPath[512];
DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),
_T("PixelShader2.txt") );
這個函數是示例框架使用的一個輔助函數,許多示例都以它為基礎。
LPD3DXBUFFER pCode; // 用來存放經過彙編的著色器代碼的緩存
LPD3DXBUFFER pErrorMsgs; // 用來存放錯誤信息的緩存
LPDIRECT3DPIXELSHADER9 m_pPixelShader;
D3DXAssembleShaderFromFile( strShaderPath, 0, NULL, &pCode, NULL );
m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),
&m_pPixelShader );
m_pd3dDevice->SetPixelShader( m_pPixelShader );
// 載入紋理並渲染輸出像素。
m_pd3dDevice->SetTexture( 0, m_pTexture0 );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
// 文件"PixelShader2.txt"的內容
// 把紋理貼圖應用於物體的頂點。
ps_1_1 // 文件的第一條必須是版本指令。
tex t0 // 聲明紋理寄存器t0,從紋理層0載入。
mov r0, t0 // 把紋理寄存器數據(t0)複製到輸出寄存器(r0)。
得到的圖像如下面所示。
本例把紋理貼圖中的顏色與頂點的顏色進行混合。本例與前例的區別如下:
頂點的數據結構,FVF碼和頂點數據包含了漫反射色。著色器文件用乘法(mul)指令把紋理的顏色和頂點的漫反射色進行混合。創建紋理和載入紋理的代碼是相同的,放在這裡是為了保持代碼的完整性。
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color1;
FLOAT tu1, tv1;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1|
D3DFVF_TEXCOORDSIZE2(0))
static CUSTOMVERTEX g_Vertices[]=
{
// x y z diffuse u1 v1
{ -1.0f, -1.0f, 0.0f, 0xffff0000, 0, 1, }, // 紅
{ 1.0f, -1.0f, 0.0f, 0xff00ff00, 1, 1, }, // 綠
{ 1.0f, 1.0f, 0.0f, 0xff0000ff, 1, 0, }, // 藍
{ -1.0f, 1.0f, 0.0f, 0xffffffff, 0, 0, }, // 白
// 為了和Windows從上到下的約定一致,v1被顛倒了。
// 左上角的紋理坐標為(0,0)。
// 右下角的紋理坐標為(1,1)。
};
// 創建紋理。這個文件包含在供下載的DirectX 9.0 SDK的媒體文件中。
TCHAR strTexturePath[512];
DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),
_T("DX5_Logo.bmp") );
LPDIRECT3DTEXTURE9 m_pTexture0;
D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,
&m_pTexture0, D3DFMT_R5G6B5 );
// 創建像素著色器。
TCHAR strShaderPath[512];
DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),
_T("PixelShader3.txt") );
LPD3DXBUFFER pCode; // 用來存放經過彙編的著色器代碼的緩存
LPD3DXBUFFER pErrorMsgs; // 用來存放錯誤信息的緩存
LPDIRECT3DPIXELSHADER9 m_pPixelShader;
D3DXAssembleShaderFromFile( strShaderPath, 0, NULL, &pCode, NULL );
m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),
&m_pPixelShader );
m_pd3dDevice->SetPixelShader( m_pPixelShader );
// 載入紋理並渲染輸出像素。
m_pd3dDevice->SetTexture( 0, m_pTexture0 );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
// 文件"PixelShader3.txt"的內容
ps_1_1 // 版本指令
tex t0 // 聲明紋理寄存器t0, 從紋理層0載入
mul r0, v0, t0 // v0*t0, 然後複製到r0
著色器的輸入如下面所示。第一幅圖為頂點顏色,第二幅圖為紋理貼圖。
得到的圖像如下面所示,也就是頂點的顏色和紋理貼圖混合的結果。
紋理進行混合
本例把兩張紋理貼圖進行混合,頂點的顏色用來決定每張紋理貼圖的顏色所佔的比例。本例和前例的區別如下:
因為使用了兩張紋理,所以頂點的數據結構,FVF碼,以及頂點數據包含了第二組紋理坐標。另外IDirect3DDevice9::SetTexture也調用了兩次,並設置兩個紋理層的狀態。著色器文件聲明了兩個紋理寄存器並使用線性插值(lrp)指令把兩張紋理進行混合。漫反射色的值用來決定兩張紋理在輸出的顏色中所佔的比例。下面是示例代碼。
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color;
FLOAT tu1, tv1;
FLOAT tu2, tv2; // 第二組紋理坐標
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3D_FVF_DIFFUSE|D3DFVF_TEX2|
D3DFVF_TEXCOORDSIZE4(0))
static CUSTOMVERTEX g_Vertices[]=
{
// x y z color u1 v1 u2 v2
{ -1.0f, -1.0f, 0.0f, 0xff0000ff, 1.0f, 1.0f, 1.0f, 1.0f },
{ +1.0f, -1.0f, 0.0f, 0xffff0000, 0.0f, 1.0f, 0.0f, 1.0f },
{ +1.0f, +1.0f, 0.0f, 0xffffff00, 0.0f, 0.0f, 0.0f, 0.0f },
{ -1.0f, +1.0f, 0.0f, 0xffffffff, 1.0f, 0.0f, 1.0f, 0.0f },
};
// 創建紋理。這個文件包含在供下載的DirectX 9.0 SDK的媒體文件中。
TCHAR strTexturePath[512];
LPDIRECT3DTEXTURE9 m_pTexture0, m_pTexture1;
DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),
_T("DX5_Logo.bmp") );
D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,
&m_pTexture0, D3DFMT_R5G6B5 );
DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),
_T("snow2.jpg") );
D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,
&m_pTexture0, D3DFMT_R5G6B5 );
// 創建像素著色器。
TCHAR strShaderPath[512];
DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),
_T("PixelShader4.txt") );
LPD3DXBUFFER pCode; // 用來存放經過彙編的著色器代碼的緩存
LPD3DXBUFFER pErrorMsgs; // 用來存放錯誤信息的緩存
LPDIRECT3DPIXELSHADER9 m_pPixelShader;
D3DXAssembleShaderFromFile( strShaderPath, 0, NULL, &pCode, NULL );
m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),
&m_pPixelShader );
m_pd3dDevice->SetPixelShader( m_pPixelShader );
// Load the textures stages.
m_pd3dDevice->SetTexture( 0, m_pTexture0 );
m_pd3dDevice->SetTexture( 1, m_pTexture1 ); // 使用第二層紋理
m_pd3dDevice->SetStreamSource( 0, m_pQuadVB, sizeof(CUSTOMVERTEX) );
m_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
m_pd3dDevice->SetPixelShader( m_pPixelShader );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
// 文件"PixelShader4.txt"的內容
ps_1_1 // 像素著色器版本。
tex t0 // 紋理寄存器t0從第0層載入。
tex t1 // 紋理寄存器t1從第1層載入。
mov r1, t1 // 把紋理t1複製到輸出寄存器r1。
lrp r0, v0, t0, r1 // 用v0中指定的比例在t0和r1間進行線性插值。
得到的圖像如下:
調試
Microsoft Visual Studio 存在一個擴展以支持對某些類型的頂點著色器進行調試。更多信息請參閱著色器調試器。
另一個示常式序MFC Tex Sample是SDK安裝的組成部分。這個MFC程序是學習如何在固定功能流水線中進行多重紋理混合操作的一個不錯的方法。
一些圖形晶元廠商在他們的網站提供著色器調試工具。可以通過查找網際網路或閱讀下面的文章找到這些工具。讀者可以在程序運行的過程中把調試器連上去,並用調試器單步運行著色器。通過設置斷點,讀者可以一次執行一行著色器代碼並觀察寄存器狀態的變化。有關頂點著色器和調試技巧的更多信息。