Android Opengl ES 2.0 纹理贴图

首先,代码是抄网上的,然后再按自己的愿望改改。

http://www.haogongju.net/art/1420642 ,这个是每个面贴同样的纹理,可惜代码拷贝出来有行号,

我是手工一个个去掉行号编译的,还好代码没问题,可以跑。

http://www.devdiv.com/forum.php?mod=viewthread&tid=75356,这个是opengl 1.x 的,可供

参考。


我的代码就是结合这2个例子改出来的,运行结果如下:


以下是代码。

LessonOneActivity.java

package com.example.android3d_one;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

public class LessonOneActivity extends Activity 
{
	/** Hold a reference to our GLSurfaceView */
	private GLSurfaceView mGLSurfaceView;
	LessonOneRenderer mRender;
	
	@Override
	public void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);

		mGLSurfaceView = new GLSurfaceView(this);

		// Check if the system supports OpenGL ES 2.0.
		final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
		final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
		final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;

		if (supportsEs2) 
		{
			// Request an OpenGL ES 2.0 compatible context.
			mGLSurfaceView.setEGLContextClientVersion(2);
			
			mRender = new LessonOneRenderer(this);
			// Set the renderer to our demo renderer, defined below.
			mGLSurfaceView.setRenderer(mRender);
		} 
		else 
		{
			// This is where you could create an OpenGL ES 1.x compatible
			// renderer if you wanted to support both ES 1 and ES 2.
			return;
		}

		setContentView(mGLSurfaceView);
		
		handler.postDelayed(runnable, 1000);
		
		
	}

	Handler handler=new Handler();
	Runnable runnable=new Runnable() {
	    public void run() {
	        // TODO Auto-generated method stub
	        //要做的事情
	    	handler.postDelayed(this, 1000);
	        //Log.v("timer",runTop(TOP));
	    	//setTile();
	    	showFPS(mRender.getFPS());
	    }
	};
	
	private void  showFPS(int fps)
	{
		this.setTitle("fps:"+fps);
	}
	 
	@Override
	protected void onResume() 
	{
		// The activity must call the GL surface view's onResume() on activity onResume().
		super.onResume();
		mGLSurfaceView.onResume();
	}

	@Override
	protected void onPause() 
	{
		// The activity must call the GL surface view's onPause() on activity onPause().
		super.onPause();
		mGLSurfaceView.onPause();
	}	
}

Render.java代码如下:

package com.example.android3d_one;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.Log;


public class LessonOneRenderer implements GLSurfaceView.Renderer 
{ 
	private static final String TAG = "Test5Renderer"; 
	private Context mContext; 
	private static final int BYTES_PER_FLOAT = 4; 
	private final FloatBuffer[] mCubePositions; 
	private final FloatBuffer[] mCubeColors; 
	private final FloatBuffer[] mCubeTextureCoordinates; 
	private float[] mMVPMatrix = new float[16]; 
	private float[] mViewMatrix = new float[16]; 
	private float[] mModelMatrix = new float[16]; 
	private float[] mProjectionMatrix = new float[16]; 
	private int mMVPMatrixHandle; 
	private int mPositionHandle; 
	private int mColorHandle; 
	private int mTextureUniformHandle; 
	private int mTextureCoordinateHandle; 
	private int [] mTextureDataHandle = new int [6]; 
	private int mProgramHandle; 
	private final int POSITION_DATA_SIZE = 3; 
	private final int COLOR_DATA_SIZE = 4; 
	private final int TEXTURE_COORDINATE_DATA_SIZE = 2; 
	
	int fps;
	FPSCounter fpsCounter;
	
	public LessonOneRenderer(final Context context) 
	{ 
		mContext = context; 
		final float cubePosition[][] = 
		{ 
			// Front face  
			{-1.0f, 1.0f, 1.0f, 
			-1.0f, -1.0f, 1.0f, 
			1.0f, 1.0f, 1.0f, 
			-1.0f, -1.0f, 1.0f, 
			1.0f, -1.0f, 1.0f, 
			1.0f, 1.0f, 1.0f,
			},
			{
			// Right face 
			1.0f, 1.0f, 1.0f, 
			1.0f, -1.0f, 1.0f,
			1.0f, 1.0f, -1.0f, 
			1.0f, -1.0f, 1.0f, 
			1.0f, -1.0f, -1.0f, 
			1.0f, 1.0f, -1.0f,
			},
			{
			// Back face 
			1.0f, 1.0f, -1.0f, 
			1.0f, -1.0f, -1.0f, 
			-1.0f, 1.0f, -1.0f, 
			1.0f, -1.0f, -1.0f, 
			-1.0f, -1.0f, -1.0f, 
			-1.0f, 1.0f, -1.0f,
			},
			// Left face 
			{
			-1.0f, 1.0f, -1.0f, 
			-1.0f, -1.0f, -1.0f,
			-1.0f, 1.0f, 1.0f, 
			-1.0f, -1.0f, -1.0f,
			-1.0f, -1.0f, 1.0f, 
			-1.0f, 1.0f, 1.0f, 
			},
			// Top face
			{
			-1.0f, 1.0f, -1.0f, 
			-1.0f, 1.0f, 1.0f, 
			1.0f, 1.0f, -1.0f, 
			-1.0f, 1.0f, 1.0f, 
			1.0f, 1.0f, 1.0f, 
			1.0f, 1.0f, -1.0f,
			},
			// Bottom face
			{
			1.0f, -1.0f, -1.0f, 
			1.0f, -1.0f, 1.0f, 
			-1.0f, -1.0f, -1.0f, 
			1.0f, -1.0f, 1.0f, 
			-1.0f, -1.0f, 1.0f, 
			-1.0f, -1.0f, -1.0f,
			}
		}; 
		final float cubeColor[][] = 
		{ 
			// Front face (red) 
			{	
			1.0f, 0.0f, 0.0f, 1.0f,
			1.0f, 0.0f, 0.0f, 1.0f, 
			1.0f, 0.0f, 0.0f, 1.0f,  
			1.0f, 0.0f, 0.0f, 1.0f,
			1.0f, 0.0f, 0.0f, 1.0f, 
			1.0f, 0.0f, 0.0f, 1.0f, 
			},
			// Right face (green) 
			{	
			0.0f, 1.0f, 0.0f, 1.0f,  
			0.0f, 1.0f, 0.0f, 1.0f, 
			0.0f, 1.0f, 0.0f, 1.0f, 
			0.0f, 1.0f, 0.0f, 1.0f, 
			0.0f, 1.0f, 0.0f, 1.0f, 
			0.0f, 1.0f, 0.0f, 1.0f,  
			},
			{	
			// Back face (blue) 
			0.0f, 0.0f, 1.0f, 1.0f, 
			0.0f, 0.0f, 1.0f, 1.0f, 
			0.0f, 0.0f, 1.0f, 1.0f, 
			0.0f, 0.0f, 1.0f, 1.0f, 
			0.0f, 0.0f, 1.0f, 1.0f, 
			0.0f, 0.0f, 1.0f, 1.0f,  
			},
			{	
			// Left face (yellow) 
			1.0f, 1.0f, 0.0f, 1.0f, 
			1.0f, 1.0f, 0.0f, 1.0f, 
			1.0f, 1.0f, 0.0f, 1.0f, 
			1.0f, 1.0f, 0.0f, 1.0f, 
			1.0f, 1.0f, 0.0f, 1.0f, 
			1.0f, 1.0f, 0.0f, 1.0f, 
			},
			{	
			// Top face (cyan) 
			0.0f, 1.0f, 1.0f, 1.0f, 
			0.0f, 1.0f, 1.0f, 1.0f, 
			0.0f, 1.0f, 1.0f, 1.0f, 
			0.0f, 1.0f, 1.0f, 1.0f, 
			0.0f, 1.0f, 1.0f, 1.0f, 
			0.0f, 1.0f, 1.0f, 1.0f, 
			},
			{	
			// Bottom face (magenta) 
			1.0f, 0.0f, 1.0f, 1.0f, 
			1.0f, 0.0f, 1.0f, 1.0f, 
			1.0f, 0.0f, 1.0f, 1.0f, 
			1.0f, 0.0f, 1.0f, 1.0f, 
			1.0f, 0.0f, 1.0f, 1.0f, 
			1.0f, 0.0f, 1.0f, 1.0f 
			}
		};  
		final float cubeTextureCoordinate[][] =  
		{  	// Front face 
			{
			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 1.0f, 
			1.0f, 0.0f,  
			},
			{
			// Right face  
			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 1.0f, 
			1.0f, 0.0f,  
			},
			{
			// Back face  
			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 1.0f, 
			1.0f, 0.0f, 
			},
			{
			// Left face  
			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 1.0f, 
			1.0f, 0.0f,  
			},
			{
			// Top face  
			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 1.0f, 
			1.0f, 0.0f,  
			},
			{
			// Bottom face  
			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 1.0f, 
			1.0f, 0.0f 
			}
			};  
			
			mCubePositions = new FloatBuffer[6];
			for(int i = 0; i <cubePosition.length; i++)
			{
				mCubePositions[i] = ByteBuffer.allocateDirect(cubePosition[i].length * BYTES_PER_FLOAT)
						.order(ByteOrder.nativeOrder()).asFloatBuffer(); 
				mCubePositions[i].put(cubePosition[i]).position(0);
			}
			

			mCubeColors = new FloatBuffer[6];
			for(int i = 0; i < cubeColor.length; i++)
			{
				mCubeColors[i] = ByteBuffer.allocateDirect(cubeColor[i].length * BYTES_PER_FLOAT) 
						.order(ByteOrder.nativeOrder()).asFloatBuffer(); 
					mCubeColors[i].put(cubeColor[i]).position(0); 
			}
			
			mCubeTextureCoordinates = new FloatBuffer[6];
			for(int i = 0; i < cubeTextureCoordinate.length; i++)
			{
				mCubeTextureCoordinates[i] = ByteBuffer.allocateDirect(cubeTextureCoordinate[i].length * BYTES_PER_FLOAT) 
				.order(ByteOrder.nativeOrder()).asFloatBuffer(); 
				mCubeTextureCoordinates[i].put(cubeTextureCoordinate[i]).position(0); 
			}
			
			fpsCounter= new FPSCounter();
			
		}   
		@Override 
		public void onDrawFrame(GL10 gl) 
		{  // TODO Auto-generated method stub 
			GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); 
			long time = SystemClock.uptimeMillis() % 10000L; 
			float angleInDegrees = (360.0f / 10000.0f) * ((int) time); 
			GLES20.glUseProgram(mProgramHandle); 
			mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix"); 
			mTextureUniformHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_Texture");
			mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position"); 
			mColorHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Color"); 
			mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate");  
			GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 
			
			Matrix.setIdentityM(mModelMatrix, 0); 
			Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, -5.0f); 
			Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 1.0f, 1.0f, 0.0f); 
			for(int i=0; i < mCubePositions.length; i++)
			{
				GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle[i]); 
				GLES20.glUniform1i(mTextureUniformHandle, 0); 
				drawCube(mCubePositions[i], mCubeColors[i], mCubeTextureCoordinates[i]); 
			}
			//drawCube(mCubePositions[1], mCubeColors[1], mCubeTextureCoordinates[1]); 
			fps = fpsCounter.getFPS();
		} 
		private void drawCube(final FloatBuffer cubePositionsBuffer, final FloatBuffer cubeColorsBuffer, final FloatBuffer cubeTextureCoordinatesBuffer) 
		{ 
			cubePositionsBuffer.position(0); 
			GLES20.glVertexAttribPointer(mPositionHandle, POSITION_DATA_SIZE, GLES20.GL_FLOAT, false, 0, cubePositionsBuffer); 
			GLES20.glEnableVertexAttribArray(mPositionHandle); 
			cubeColorsBuffer.position(0); 
			GLES20.glVertexAttribPointer(mColorHandle, COLOR_DATA_SIZE, GLES20.GL_FLOAT, false, 0, cubeColorsBuffer); 
			GLES20.glEnableVertexAttribArray(mColorHandle); 
			cubeTextureCoordinatesBuffer.position(0); 
			GLES20.glVertexAttribPointer(mTextureCoordinateHandle, TEXTURE_COORDINATE_DATA_SIZE, GLES20.GL_FLOAT, false, 0, cubeTextureCoordinatesBuffer); 
			GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);  
			Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0); 
			Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0); 
			GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0); 
			GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6); 
			
		}  
		
		public int getFPS()
		{
			return fps;
		}
		@Override
		public void onSurfaceChanged(GL10 gl, int width, int height) 
		{ 
			// TODO Auto-generated method stub 
			GLES20.glViewport(0, 0, width, height);  
			final float ratio = (float) width / height; 
			final float left = -ratio; 
			final float right = ratio; 
			final float bottom = -1.0f; 
			final float top = 1.0f; 
			final float near = 1.0f; 
			final float far = 10.0f; 
			Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far); 
		}  
		@Override
		public void onSurfaceCreated(GL10 gl, EGLConfig config) 
		{ 
			// TODO Auto-generated method stub 
			GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 
			GLES20.glEnable(GLES20.GL_CULL_FACE); 
			GLES20.glEnable(GLES20.GL_DEPTH_TEST); 
			// Position the eye behind the origin. 
			final float eyeX = 0.0f; 
			final float eyeY = 0.0f; 
			final float eyeZ = -0.5f; 

			// We are looking toward the distance 
			final float lookX = 0.0f; 
			final float lookY = 0.0f; 
			final float lookZ = -5.0f;  
			// Set our up vector. This is where our head would be pointing were we holding the camera. 
			final float upX = 0.0f; 
			final float upY = 1.0f; 
			final float upZ = 0.0f;  
			// Set the view matrix. This matrix can be said to represent the camera position. 
			// NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and 
			// view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose. 
			Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);  
			final String vertexShader = getVertexShader(); 
			final String fragmentShader = getFragmentShader();  
			final int vertexShaderHandle = compileShader(GLES20.GL_VERTEX_SHADER, vertexShader); 
			final int fragmentShaderHandle = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);  
			mProgramHandle = createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, 
					new String[]{"a_Position", "a_Color", "a_TexCoordinate"}); 
			mTextureDataHandle[0] = ToolsUtil.loadTexture(mContext, R.drawable.aa);
			mTextureDataHandle[1] = ToolsUtil.loadTexture(mContext, R.drawable.bb);
			mTextureDataHandle[2] = ToolsUtil.loadTexture(mContext, R.drawable.cc);
			mTextureDataHandle[3] = ToolsUtil.loadTexture(mContext, R.drawable.dd);
			mTextureDataHandle[4] = ToolsUtil.loadTexture(mContext, R.drawable.ee);
			mTextureDataHandle[5] = ToolsUtil.loadTexture(mContext, R.drawable.ff);
		}  
		private String getVertexShader() 
		{  
			final String vertexShader =  
					"uniform mat4 u_MVPMatrix; \n" // A constant representing the combined model/view/projection matrix. 
					+ "attribute vec4 a_Position; \n" // Per-vertex position information we will pass in. 
					+ "attribute vec4 a_Color; \n" // Per-vertex color information we will pass in.  
					+ "attribute vec2 a_TexCoordinate;\n" // Per-vertex texture coordinate information we will pass in.   
					+ "varying vec4 v_Color; \n" // This will be passed into the fragment shader. 
					+ "varying vec2 v_TexCoordinate; \n" // This will be passed into the fragment shader.   
					+ "void main() \n" // The entry point for our vertex shader. 
					+ "{ \n" 
					+ " v_Color = a_Color; \n" // Pass the color through to the fragment shader. 
					// It will be interpolated across the triangle. 
					+ " v_TexCoordinate = a_TexCoordinate;\n"// Pass through the texture coordinate. 
					+ " gl_Position = u_MVPMatrix \n" // gl_Position is a special variable used to store the final position. 
					+ " * a_Position; \n" // Multiply the vertex by the matrix to get the final point in  
					+ "} \n"; // normalized screen coordinates. \n";   
			return vertexShader; 
		}  
		private String getFragmentShader()  
		{  
			final String fragmentShader =  "precision mediump float; \n" 
					// Set the default precision to medium. We don't need as high of a 
					// precision in the fragment shader. 
					+ "uniform sampler2D u_Texture; \n" // The input texture. 
					+ "varying vec4 v_Color; \n" // This is the color from the vertex shader interpolated across the 
					// triangle per fragment.  
					+ "varying vec2 v_TexCoordinate; \n" // Interpolated texture coordinate per fragment.  
					+ "void main() \n" // The entry point for our fragment shader. 
					+ "{ \n" 
					//+ " gl_FragColor = v_Color * texture2D(u_Texture, v_TexCoordinate); \n" // Pass the color directly through the pipeline.   
					+ " gl_FragColor = texture2D(u_Texture, v_TexCoordinate); \n" // Pass the color directly through the pipeline.   
					+ "} \n"; 
			return fragmentShader; 
		}  
		private int compileShader(final int shaderType, final String shaderSource) 
		{ 
			int shaderHandle = GLES20.glCreateShader(shaderType); 
			if (shaderHandle != 0) 
			{ 
				GLES20.glShaderSource(shaderHandle, shaderSource); 
				GLES20.glCompileShader(shaderHandle); 
				final int[] compileStatus = new int[1]; 
				GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0); 

				if (compileStatus[0] == 0) 
				{ 
					Log.e(TAG, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shaderHandle)); 
					GLES20.glDeleteShader(shaderHandle); 
					shaderHandle = 0; 
				} 
			} 

			if (shaderHandle == 0) 
			{ 
				throw new RuntimeException("Error creating shader."); 
			} 
			return shaderHandle; 
		}  
		private int createAndLinkProgram(final int vertexShaderHandle, final int fragmentShaderHandle, final String[] attributes) 
		{
			int programHandle = GLES20.glCreateProgram();  
			if (programHandle != 0) 
			{ 
				GLES20.glAttachShader(programHandle, vertexShaderHandle); 
				GLES20.glAttachShader(programHandle, fragmentShaderHandle);  
				if (attributes != null) 
				{ 
					final int size = attributes.length; 
					for (int i = 0; i < size; i++) 
					{ 
						GLES20.glBindAttribLocation(programHandle, i, attributes[i]); 
					} 
				}  
				GLES20.glLinkProgram(programHandle); 
				final int[] linkStatus = new int[1]; 
				GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);  
				if (linkStatus[0] == 0) 
				{ 
					Log.e(TAG, "Error compiling program: " + GLES20.glGetProgramInfoLog(programHandle)); 
					GLES20.glDeleteProgram(programHandle); 
					programHandle = 0; 
				} 
			}  
			if (programHandle == 0) 
			{ 
				throw new RuntimeException("Error creating program."); 
			}  
			return programHandle; 
		}  
		
		static class ToolsUtil 
		{ 
			public static  int loadTexture(final Context context, final int resourceId) 
			{ 
				final int[] textureHandle = new int[1]; 
				GLES20.glGenTextures(1, textureHandle, 0); 
				if(textureHandle[0] != 0) 
				{ 
					final BitmapFactory.Options options = new BitmapFactory.Options(); 
					options.inScaled = false; 
					final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options); 
					GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]); 
					GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); 
					GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); 
					GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 
					bitmap.recycle(); 
				} 
				
				
				if(textureHandle[0] == 0) 
				{ 
					throw new RuntimeException("failed to load texture"); 
				} 
				return textureHandle[0]; 
			}
		}
		
		class FPSCounter {
			int FPS;
			int lastFPS;
			long tempFPStime;

			public FPSCounter() {
				FPS = 0;
				lastFPS = 0;
				tempFPStime = 0;
			}

			int getFPS() {
				long nowtime = SystemClock.uptimeMillis();
				FPS++;
				if (nowtime - tempFPStime >= 1000) {
					lastFPS = FPS;
					tempFPStime = nowtime;
					FPS = 0;
					//Log.d("FPSCounter","fps="+lastFPS);
				}
				return lastFPS;
			}
		}
}

记得图片资源放在res/drawable目录下。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值