write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie
好久好久沒有繼續OpenGL了...中間發生了太多事情,比如Objective C及Cocoa的學習, 粗略看了一些游戲引擎的源代碼,Google離開了,一個公司可以很有骨氣的說走就走,暫時沒有辦法離開的人,該繼續的還是得繼續,現在回過神來,還是留 點時間來學OpenGL吧,不過作為工作需要,我以后可能會常常附帶OpenGL ES的信息,甚至,不是OpenGL ES可用的我就一筆帶過了,畢竟,我的工作現在是在IPhone平臺......
概念
上一節講過了在OpenGL中位圖的顯示,但是,那都是簡單的將圖片貼出來,放在平面上的效果,這一節解釋一下怎么在3D物體上應用位圖,這個過程被稱作 紋理貼圖(Texture Mapping),想想以前的三棱錐吧,最好最好的時候,我們也僅僅是實現了漸變顏色或者是光照,那樣太單調了。既然是金字塔型,我們自然希望它想真的金 字塔一樣,有著巖石的外表,就像經過了歲月的滄桑。
實際中,在制作游戲的時候中,要想某個物體想現實世界的物體,基本上不太可能完全通過程序的頂點色去實現,那樣太復雜了,現實世界應該怎樣描繪,美術比 程序員更加清楚,更重要的是,美術不僅知道怎么描述這個世界,還比程序員更加知道怎么從現實中獲取素材,及處理素材,程序描繪世界最好的辦法應該是很好的 將美術制作的圖片資源恰當的顯示出來,而不是真的編寫程序去描繪每個頂點的顏色,我們是程序員,不是上帝.........
概念上,紋理貼圖的英文更加能夠反映其實質,Texture Mapping,注意Mapping一詞,C++中的map沒有少用吧,這里是映射的意思,紋理貼圖本質就是一個位圖(即紋理)中像素到某個立體圖形的映 射過程,也有紋理映射,材質貼圖等多種翻譯.
2D紋理貼圖
首先,需要說明的是,紋理貼圖的應用實在太廣,內容實在太多,我根本沒有辦法簡簡單單一篇文章就覆蓋全部的知識,甚至是常用的部分都很難,這里只能大概 的講些基礎概念及給出幾個例子了,相對來說,比NEHE的例子還是要多一點。(NEHE每個知識一個例子,而且沒有任何原理講解)
這里先 提供一個簡單的2D對2D的例子.
//OpenGL 初始化開始
void
SceneInit(
int
w,
int
h)
{
GLenum err = glewInit();
if
(err != GLEW_OK)
{
MessageBox(
NULL
, _T(
"Error"
), _T(
"Glew init failed."
), MB_OK);
exit(-
1
);
}
glClearColor (
0.0
,
0.0
,
0.0
,
0.0
);
glShadeModel(GL_FLAT);
glEnable(GL_DEPTH_TEST);
HBITMAP hBmp=(HBITMAP)LoadImageW(
NULL
,
L"tiger.bmp"
, IMAGE_BITMAP,
0
,
0
, LR_CREATEDIBSECTION | LR_LOADFROMFILE );
if
(!hBmp)
{
exit(
3
);
}
GetObject(hBmp,
sizeof
(gBmp), &gBmp);
glPixelStorei(GL_UNPACK_ALIGNMENT,
4
);
//glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(
1
, &gTexName);
glBindTexture(GL_TEXTURE_2D, gTexName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D,
0
, GL_RGBA, gBmp.bmWidth, gBmp.bmHeight,
0
, GL_BGR, GL_UNSIGNED_BYTE, gBmp.bmBits);
}
//這里進行所有的繪圖工作
void
SceneShow(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glBindTexture(GL_TEXTURE_2D, gTexName);
glBegin(GL_QUADS);
glTexCoord2f(
0.0
,
0.0
); glVertex3f(-
1.0
, -
1.0
,
0.0
);
glTexCoord2f(
1.0
,
0.0
); glVertex3f(
1.0
, -
1.0
,
0.0
);
glTexCoord2f(
1.0
,
1.0
); glVertex3f(
1.0
,
1.0
,
0.0
);
glTexCoord2f(
0.0
,
1.0
); glVertex3f(-
1.0
,
1.0
,
0.0
);
glEnd();
SwapBuffers(ghDC);
glDisable(GL_TEXTURE_2D);
}
LoadImage 的使用參看上一節講位圖顯示的內容:《
Win32 OpenGL編程(15) 位圖顯示
》
glGenTextures用于生成一 個材質的ID,就像OpenGL的顯示列表的用法。
glBindTexture 為剛創建的紋理(以紋理ID表示)綁定一個紋理,這里的參數表示是2D的紋理,事實上,紋理可以使1維的,3維的,甚至4維的。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
4句確定了此2D紋理映射的一些參數,參數實在太多,要都解釋,估計抵 的上一本書的一章了。
GL_TEXTURE_WRAP
*的參數用于指定當紋 理的頂點指定超過范圍時的做法,這里的做法就是重復(REPEAT)。類似于設置桌面時,圖小于桌面分辨率的平鋪效果。(哦?MS顯示桌面的方式難道也是 用紋理貼圖?)
GL_TEXTURE_MIN_FILTER,
GL_TEXTURE_MAG_FILTER用于指定縮小方法時的計算的方法,
雖然都叫FILTER, 但是事實上放大時相當于插值。這里用的是最近點的方式,最常用的還有線性方式。一般來說,線程方式會有更大的消耗,更好的質量。《OpenGL SUPERBIBLE》說線性方式在現代的顯卡中的性能弱點可以忽略不計,并且其優點值得使用。從字面上很好理解最近點方式,一般取紋理中心的像素單元放 大縮小,可能導致嚴重鋸齒。線性方式,則是取每幾個臨近像素點進行加權平均,決定新像素點的值,詳細了解。。。看看數字圖像處理的書?印象中大學的數字圖 像處理教程就有比較詳細的解釋。
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
確定紋理的應用方法,說實話,我不太喜歡OpenGL中這種組合參數API,
通過幾個參數組合的方式來決定某個特性,
查資料太費 神,左看右看,半天都不明白自己需要的一個簡單功能為啥弄的這么麻煩。
此句的意思建議去看看
《
OpenGL Reference Manual
》的 解釋,希望你能在10個小時內看明白那5,6個10多行的表格表示的3個參數的組合表示什么意思-_-!對了,忘了說,某些時候,還需要組合多次
glTexEnv*調用來表示一個操作內容-_-!
再看看下面的glTexImage2D函數,紅寶 書用了整整一面多的篇幅,列舉其參數的可能值,不過,有人能看得懂嗎?
想起來某年某月某日,
某人問我:"OpenGL與D3D孰好?"
我 答曰:"或D3D否?"
其責之:"何好之有?"
我嘆之:“OpenGL神設計之,唯神知其所用。"
這是題外話。。。。感嘆一下, 想起了Qt設計者關于API設計哲學的論述,(參看<
設計
qt
風格的c++api【轉】
>) 尤為感嘆。勿怪。
那這句程序到底是啥意思呢?。。。。。。。。。。我說我沒有看懂,你不會怪我吧?基本上,
GL_DECAL在材質沒有Alpha通道時,相當于
GL_REPLACE,(用材質替代原來物體的顏 色)有Alpha通道時,相當于GL_BLEND,(用材質與原有物體混合)
我有點語無倫次了。。。。不 過想想
《
OpenGL Reference Manual
》, 《OpenGL 編程指南》幾萬個字都沒有講清楚,那么我幾十個字沒有講清楚也就不奇怪了。
)《OpenGL 編程指南》如是說:紋理貼圖是個相當大的主題,并且具有相當程序的復雜性.
基本上,就當這 些都是一坨配置吧.
下圖是出來的效果:
這樣的效果像什么?就像是上一節講到的位圖顯示吧?呵 呵,的確,我也有這樣的感嘆,事實上,這才是OpenGL中顯示圖形的最佳方式.....而在OpenGL ES中,前面那些接口甚至都不存在........這里,我通過irrlicht的
draw2DImage
函 數可以獲得印證.以下是Irrlicht中的一個
draw2DImage
函數的具體實現:
void
COpenGLDriver::draw2DImage(
const
video::ITexture* texture,
const
core::rect<s32>& destRect,
const
core::rect<s32>& sourceRect,
const
core::rect<s32>* clipRect,
const
video::SColor*
const
colors,
bool
useAlphaChannelOfTexture)
{
if
(!texture)
return
;
// texcoords need to be flipped horizontally for RTTs
const
bool
isRTT = texture->isRenderTarget();
const
core::dimension2d<u32>& ss = texture->getOriginalSize();
const
f32 invW =
1.f
/
static_cast
<f32>(ss.Width);
const
f32 invH =
1.f
/
static_cast
<f32>(ss.Height);
const
core::rect<f32> tcoords(
sourceRect.UpperLeftCorner.X * invW,
(isRTT?sourceRect.LowerRightCorner.Y:sourceRect.UpperLeftCorner.Y) * invH,
sourceRect.LowerRightCorner.X * invW,
(isRTT?sourceRect.UpperLeftCorner.Y:sourceRect.LowerRightCorner.Y) *invH);
const
video::SColor temp[
4
] =
{
0xFFFFFFFF
,
0xFFFFFFFF
,
0xFFFFFFFF
,
0xFFFFFFFF
};
const
video::SColor*
const
useColor = colors ? colors : temp;
disableTextures(
1
);
setActiveTexture(
0
, texture);
setRenderStates2DMode(useColor[
0
].getAlpha()<
255
|| useColor[
1
].getAlpha()<
255
||
useColor[
2
].getAlpha()<
255
|| useColor[
3
].getAlpha()<
255
,
true
, useAlphaChannelOfTexture);
if
(clipRect)
{
if
(!clipRect->isValid())
return
;
glEnable(GL_SCISSOR_TEST);
const
core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();
glScissor(clipRect->UpperLeftCorner.X, renderTargetSize.Height-clipRect->LowerRightCorner.Y,
clipRect->getWidth(), clipRect->getHeight());
}
glBegin(GL_QUADS);
glColor4ub(useColor[
0
].getRed(), useColor[
0
].getGreen(), useColor[
0
].getBlue(), useColor[
0
].getAlpha());
glTexCoord2f(tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y);
glVertex2f(GLfloat(destRect.UpperLeftCorner.X), GLfloat(destRect.UpperLeftCorner.Y));
glColor4ub(useColor[
3
].getRed(), useColor[
3
].getGreen(), useColor[
3
].getBlue(), useColor[
3
].getAlpha());
glTexCoord2f(tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y);
glVertex2f(GLfloat(destRect.LowerRightCorner.X), GLfloat(destRect.UpperLeftCorner.Y));
glColor4ub(useColor[
2
].getRed(), useColor[
2
].getGreen(), useColor[
2
].getBlue(), useColor[
2
].getAlpha());
glTexCoord2f(tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y);
glVertex2f(GLfloat(destRect.LowerRightCorner.X), GLfloat(destRect.LowerRightCorner.Y));
glColor4ub(useColor[
1
].getRed(), useColor[
1
].getGreen(), useColor[
1
].getBlue(), useColor[
1
].getAlpha());
glTexCoord2f(tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y);
glVertex2f(GLfloat(destRect.UpperLeftCorner.X), GLfloat(destRect.LowerRightCorner.Y));
glEnd();
if
(clipRect)
glDisable(GL_SCISSOR_TEST);
}
除了剪裁測試,還有其對材質進行了 一層封裝,核心內容(從glBegin到glEnd間的部分)就是上述紋理貼圖的內容.講到這里,疑問來了,在OpenGL中,我們到底應該使用哪種方式 來進行位圖的顯示呢?前面講過的方法明顯要更加簡單,紋理貼圖的方法明顯要更繞,但是適用范圍更廣.那么,我想,先看看效率吧,同樣的圖的顯示,我測試一 下此節程序及上節程序的最大幀率.
加入下述FPS測試代碼,并且通過wglSwapIntervalEXT(0);關閉垂直同步(默認是開啟垂直 同步的).(為了相對公平,原glbitmap的程序也改成了雙緩沖,新的程序關閉了深度測試)
// called every frame
int CalculateFPS(DWORD now)
{
static int frameCounted = 0;
static int startTime = GetTickCount();
static int fps = 0;
++frameCounted;
int elapsed = now - startTime;
if (elapsed >= 1500 )
{
fps = ( 1000 * frameCounted ) / elapsed;
startTime = now;
frameCounted = 0;
}
return fps;
}
// calculate the fps and display it in the window captain
void DisplayFPS(DWORD now)
{
static char buffer[100];
int fps = CalculateFPS(now);
sprintf(buffer, "fps=%d", fps);
SetWindowTextA(ghWnd, buffer);
}
測試結果是glBitmap版本大概是950~970之 間.而紋理貼圖版本只有930到950之間,也就是說,glBitmap版本在單純的圖片顯示上,效率還是有優勢的,畢竟,OpenGL提供的特殊接口不 可能比通用實現方式要慢(最壞也要一樣,因為特殊接口起碼還能用通用實現方式實現,不然寫驅動的都吃白飯了)但是,就如前面所說的,紋理貼圖方式適用范圍 更廣,OpenGL ES中,也只能使用此方式,另外,"使用紋理貼圖方式,還能利用上很多3D的特效,因為,只要使用OpenGL寫引擎的,幾乎都用的紋理貼圖的方式." (我同事的原話)
2D紋理,3D圖形
無論如何,OpenGL是為3D而生的,那么光講2D那就相當于沒有講 OpenGL,這里,提供一個2D紋理映射到3D圖形的例子.例子來自于 《Nehe OpenGL Tutorials》(
本教程位置
),因為貼圖位置的設置太繁瑣了,不想自己再寫一個了.例子經過改造,嵌入了 Win32的框架中,然后,顯示的還是上面的老虎.
//OpenGL初始化開始
void
SceneInit(
int
w,
int
h)
{
GLenum err = glewInit();
if
(err != GLEW_OK)
{
MessageBox(
NULL
, _T(
"Error"
), _T(
"Glew init failed."
), MB_OK);
exit(-
1
);
}
wglSwapIntervalEXT(
0
);
glClearColor (
0.0
,
0.0
,
0.0
,
0.0
);
glShadeModel(GL_FLAT);
glViewport(
0
,
0
,WIDTH,HEIGHT);
// Reset The Current Viewport
glMatrixMode(GL_PROJECTION);
// Select The Projection Matrix
glLoadIdentity();
// Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
gluPerspective(
45.0f
,(GLfloat)WIDTH/(GLfloat)HEIGHT,
0.1f
,
100.0f
);
glMatrixMode(GL_MODELVIEW);
// Select The Modelview Matrix
glLoadIdentity();
// Reset The Modelview Matrix
HBITMAP hBmp=(HBITMAP)LoadImageW(
NULL
,
L"tiger.bmp"
, IMAGE_BITMAP,
0
,
0
, LR_CREATEDIBSECTION | LR_LOADFROMFILE );
if
(!hBmp)
{
exit(
3
);
}
GetObject(hBmp,
sizeof
(gBmp), &gBmp);
glPixelStorei(GL_UNPACK_ALIGNMENT,
4
);
glGenTextures(
1
, &gTexName);
glBindTexture(GL_TEXTURE_2D, gTexName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D,
0
, GL_RGBA, gBmp.bmWidth, gBmp.bmHeight,
0
, GL_BGR, GL_UNSIGNED_BYTE, gBmp.bmBits);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glEnable(GL_TEXTURE_2D);
// Enable Texture Mapping ( NEW )
glShadeModel(GL_SMOOTH);
// Enable Smooth Shading
glClearColor(
0.0f
,
0.0f
,
0.0f
,
0.5f
);
// Black Background
glClearDepth(
1.0f
);
// Depth Buffer Setup
glEnable(GL_DEPTH_TEST);
// Enables Depth Testing
glDepthFunc(GL_LEQUAL);
// The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
// Really Nice Perspective Calculations
}
//這里進行所有的繪圖工作
void
SceneShow(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Clear The Screen And The Depth Buffer
glLoadIdentity();
// Reset The View
glTranslatef(
0.0f
,
0.0f
,-
5.0f
);
glRotatef(xrot,
1.0f
,
0.0f
,
0.0f
);
glRotatef(yrot,
0.0f
,
1.0f
,
0.0f
);
glRotatef(zrot,
0.0f
,
0.0f
,
1.0f
);
glBindTexture(GL_TEXTURE_2D, gTexName);
glBegin(GL_QUADS);
// Front Face
glTexCoord2f(
0.0f
,
0.0f
); glVertex3f(-
1.0f
, -
1.0f
,
1.0f
);
glTexCoord2f(
1.0f
,
0.0f
); glVertex3f(
1.0f
, -
1.0f
,
1.0f
);
glTexCoord2f(
1.0f
,
1.0f
); glVertex3f(
1.0f
,
1.0f
,
1.0f
);
glTexCoord2f(
0.0f
,
1.0f
); glVertex3f(-
1.0f
,
1.0f
,
1.0f
);
// Back Face
glTexCoord2f(
1.0f
,
0.0f
); glVertex3f(-
1.0f
, -
1.0f
, -
1.0f
);
glTexCoord2f(
1.0f
,
1.0f
); glVertex3f(-
1.0f
,
1.0f
, -
1.0f
);
glTexCoord2f(
0.0f
,
1.0f
); glVertex3f(
1.0f
,
1.0f
, -
1.0f
);
glTexCoord2f(
0.0f
,
0.0f
); glVertex3f(
1.0f
, -
1.0f
, -
1.0f
);
// Top Face
glTexCoord2f(
0.0f
,
1.0f
); glVertex3f(-
1.0f
,
1.0f
, -
1.0f
);
glTexCoord2f(
0.0f
,
0.0f
); glVertex3f(-
1.0f
,
1.0f
,
1.0f
);
glTexCoord2f(
1.0f
,
0.0f
); glVertex3f(
1.0f
,
1.0f
,
1.0f
);
glTexCoord2f(
1.0f
,
1.0f
); glVertex3f(
1.0f
,
1.0f
, -
1.0f
);
// Bottom Face
glTexCoord2f(
1.0f
,
1.0f
); glVertex3f(-
1.0f
, -
1.0f
, -
1.0f
);
glTexCoord2f(
0.0f
,
1.0f
); glVertex3f(
1.0f
, -
1.0f
, -
1.0f
);
glTexCoord2f(
0.0f
,
0.0f
); glVertex3f(
1.0f
, -
1.0f
,
1.0f
);
glTexCoord2f(
1.0f
,
0.0f
); glVertex3f(-
1.0f
, -
1.0f
,
1.0f
);
// Right face
glTexCoord2f(
1.0f
,
0.0f
); glVertex3f(
1.0f
, -
1.0f
, -
1.0f
);
glTexCoord2f(
1.0f
,
1.0f
); glVertex3f(
1.0f
,
1.0f
, -
1.0f
);
glTexCoord2f(
0.0f
,
1.0f
); glVertex3f(
1.0f
,
1.0f
,
1.0f
);
glTexCoord2f(
0.0f
,
0.0f
); glVertex3f(
1.0f
, -
1.0f
,
1.0f
);
// Left Face
glTexCoord2f(
0.0f
,
0.0f
); glVertex3f(-
1.0f
, -
1.0f
, -
1.0f
);
glTexCoord2f(
1.0f
,
0.0f
); glVertex3f(-
1.0f
, -
1.0f
,
1.0f
);
glTexCoord2f(
1.0f
,
1.0f
); glVertex3f(-
1.0f
,
1.0f
,
1.0f
);
glTexCoord2f(
0.0f
,
1.0f
); glVertex3f(-
1.0f
,
1.0f
, -
1.0f
);
glEnd();
xrot+=
0.3f
;
yrot+=
0.2f
;
zrot+=
0.4f
;
SwapBuffers(ghDC);
}
顯 示效果:
基本的意思還是和上面沒有區別,最最主要的部 分為頂點的設置部分,會發現,將2D紋理映射到2D及映射到3D的區別并不大,2D紋理的位置指定還是按照一個一個面的來.這里,只不過以前2D時是僅有 一個面,現在面多了而已,方法還是一樣.另外,深度測試還是開啟為好,不然會亂作一團.
本文的完整源代碼在代碼庫中的2010-3-25目錄下
參考資料
1. 《 OpenGL Reference Manual 》,OpenGL 參考手冊
2. 《OpenGL 編程指南》(《 OpenGL Programming Guide 》),Dave Shreiner,Mason Woo,Jackie Neider,Tom Davis 著,徐波譯,機械工業出版社
3. 《Nehe OpenGL Tutorials》,Nehe著,在 http://nehe.gamedev.net/ 上可以找到教程及相關的代碼下載,(有PDF版本教程下載)Nehe自己還做了一個面向對象的框架,作為演示程序來說,這樣的框架非常合適。也有 中文版 ,各取所需吧。
4.《OpenGL SUPERBIBLE》Fourth Edition,Comprehensive Tutorial and Reference,Richard S.Wright, Jr.,Benjamin Lipchak, Nicholas Haemel. Addison-Wesley
完整源代碼獲取說明
由于 篇幅限制,本文一般僅貼出代碼的主要關心的部分,代碼帶工程(或者makefile)完整版(如果有的話)都能用Mercurial在Google Code中下載。文章以博文發表的日期分目錄存放,請直接使用Mercurial克隆下庫:
https://blog-sample-code.jtianling.googlecode.com/hg/
Mercurial使用方法見《 分布式的,新一代版本控制系統 Mercurial的介紹及簡要入門 》
要是僅僅想瀏覽全部代碼也 可以直接到google code上去看,在下面的地址:
http://code.google.com/p/jtianling/source/browse?repo=blog-sample-code
原創文章作者保留版權 轉載請注明原作者 并給出鏈接
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
