Android高级进阶十一 Android OpenGL建立3D空间

最新版本:Android高级进阶十一 Android OpenGL建立3D空间

  这一篇文章继续写在Android上使用OpenGL,前阶段的文章一直是旨在建立一个3D的形状,这一篇文章我们就来建立一个3D的空间模型。

        老规矩先上效果图:

        这次我们使用一个文件来存储我们所有顶点及纹理定点数据,具体使用方式待会儿见分晓。

        代码如下:

        WordActivity.java

Java代码
  1. package org.ourunix.android.world;  
  2.   
  3. import android.app.Activity;  
  4. import android.opengl.GLSurfaceView;  
  5. import android.os.Bundle;  
  6. import android.view.KeyEvent;  
  7.   
  8. public class WordActivity extends Activity {  
  9.     /** Called when the activity is first created. */  
  10.     GLRenderer render;  
  11.       
  12.     @Override  
  13.     public void onCreate(Bundle savedInstanceState) {  
  14.         super.onCreate(savedInstanceState);  
  15.         GLSurfaceView glView = new GLSurfaceView(this);  
  16.         render = new GLRenderer(this);  
  17.         glView.setRenderer(render);  
  18.         setContentView(glView);  
  19.     }  
  20.       
  21.     @Override  
  22.     public boolean onKeyUp(int keyCode, KeyEvent event) {  
  23.         // TODO Auto-generated method stub  
  24.         return render.onKeyUp(keyCode, event);  
  25.     }  
  26.       
  27. }  

        ScData.java

Java代码
  1. package org.ourunix.android.world;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. public class ScData {  
  7.   
  8.     public static class VERTEX{  
  9.         float x, y, z; //三角形的顶点坐标  
  10.         float u, v;//纹理坐标  
  11.           
  12.         public VERTEX(float x, float y , float z, float u, float v){  
  13.             this.x = x;  
  14.             this.y = y;  
  15.             this.z = z;  
  16.             this.u = u;  
  17.             this.v = v;  
  18.         }  
  19.     }  
  20.       
  21.     //三角形结构  
  22.     public static class TRIANGLE{  
  23.         VERTEX[] vertex = new VERTEX[3];  
  24.     }  
  25.       
  26.     //区段结构  
  27.     public static class SECTOR{  
  28.         int numTriangles;  
  29.         List<TRIANGLE> triangle = new ArrayList<TRIANGLE>();  
  30.     }  
  31. }  

        GLRenderer.java

Java代码
  1. package org.ourunix.android.world;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.io.InputStreamReader;  
  7. import java.nio.FloatBuffer;  
  8. import java.nio.IntBuffer;  
  9.   
  10. import javax.microedition.khronos.egl.EGLConfig;  
  11. import javax.microedition.khronos.opengles.GL10;  
  12.   
  13. import org.ourunix.android.world.ScData.SECTOR;  
  14. import org.ourunix.android.world.ScData.TRIANGLE;  
  15. import org.ourunix.android.world.ScData.VERTEX;  
  16.   
  17. import android.content.Context;  
  18. import android.content.res.AssetManager;  
  19. import android.content.res.Resources;  
  20. import android.graphics.Bitmap;  
  21. import android.graphics.BitmapFactory;  
  22. import android.opengl.GLUtils;  
  23. import android.opengl.GLSurfaceView.Renderer;  
  24. import android.view.KeyEvent;  
  25.   
  26. public class GLRenderer implements Renderer {  
  27.     private Context mContext;  
  28.     SECTOR sector1 = new SECTOR();  
  29.       
  30.     public final static float piover180 = 0.0174532925f;  
  31.     float heading;  
  32.     float xpos;  
  33.     float zpos;  
  34.   
  35.     float   yrot;               // Y Rotation  
  36.     float walkbias = 0;  
  37.     float walkbiasangle = 0;  
  38.     float lookupdown = 0.0f;  
  39.     float z=0.0f;         
  40.       
  41.     private int[] texturesID = new int[3];  
  42.       
  43.     public GLRenderer(Context ctx){  
  44.         mContext = ctx;  
  45.     }  
  46.   
  47.     @Override  
  48.     public void onDrawFrame(GL10 gl) {  
  49.         // TODO Auto-generated method stub  
  50.         gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);    // Clear The Screen And The Depth Buffer  
  51.         gl.glLoadIdentity();                                    // Reset The View  
  52.   
  53.         float xtrans = -xpos;  
  54.         float ztrans = -zpos;  
  55.         float ytrans = -walkbias-0.25f;  
  56.         float sceneroty = 360.0f - yrot;  
  57.   
  58.         FloatBuffer vertexPointer = Utils.BufferUtil.fBuffer(new float[9]);  
  59.         FloatBuffer texCoordPointer = Utils.BufferUtil.fBuffer(new float[6]);  
  60.         gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexPointer);  
  61.         gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoordPointer);  
  62.           
  63.         gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);  
  64.         gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);  
  65.           
  66.         gl.glLoadIdentity();  
  67.   
  68.         gl.glRotatef(lookupdown, 1.0f, 0.0f, 0.0f);  
  69.         gl.glRotatef(sceneroty, 0.0f, 1.0f, 0.0f);  
  70.           
  71.         gl.glTranslatef(xtrans, ytrans, ztrans);  
  72.           
  73.         gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesID[0]);  
  74.           
  75.         for(TRIANGLE triangle : sector1.triangle)  
  76.         {  
  77.             vertexPointer.clear();  
  78.             texCoordPointer.clear();  
  79.             gl.glNormal3f(0.0f, 0.0f, 1.0f);  
  80.             for(int i=0; i<3; i++)  
  81.             {  
  82.                 VERTEX vt = triangle.vertex[i];  
  83.                 vertexPointer.put(vt.x);  
  84.                 vertexPointer.put(vt.y);  
  85.                 vertexPointer.put(vt.z);  
  86.                 texCoordPointer.put(vt.u);  
  87.                 texCoordPointer.put(vt.v);  
  88.             }  
  89.             gl.glDrawArrays(GL10.GL_TRIANGLES, 04);  
  90.         }  
  91.           
  92.         gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);  
  93.         gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);  
  94.           
  95.     }  
  96.   
  97.     @Override  
  98.     public void onSurfaceChanged(GL10 gl, int width, int height) {  
  99.         // TODO Auto-generated method stub  
  100.         float ratio = (float)width/height;  
  101.         gl.glViewport(00, width, height);  
  102.         gl.glMatrixMode(GL10.GL_PROJECTION);  
  103.         gl.glLoadIdentity();  
  104.         gl.glFrustumf(-ratio, ratio, -11110);  
  105.         gl.glMatrixMode(GL10.GL_MODELVIEW);  
  106.         gl.glLoadIdentity();  
  107.     }  
  108.   
  109.     @Override  
  110.     public void onSurfaceCreated(GL10 gl, EGLConfig config) {  
  111.         // TODO Auto-generated method stub  
  112.         //加载纹理  
  113.         gl.glEnable(GL10.GL_TEXTURE_2D);  
  114.         LoadGLTextures(gl);  
  115.           
  116.         //开启混色透明  
  117.         gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);  
  118.           
  119.         gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);  
  120.         gl.glClearColor(0000);  
  121.         gl.glShadeModel(GL10.GL_SMOOTH);  
  122.           
  123.         //开启深度测试  
  124.         gl.glClearDepthf(1.0f);  
  125.         gl.glDepthFunc(GL10.GL_LESS);  
  126.         gl.glEnable(GL10.GL_DEPTH_TEST);  
  127.           
  128.         SetupWorld();  
  129.     }  
  130.   
  131.     private void LoadGLTextures(GL10 gl) {  
  132.         int[] imgArray = {R.drawable.img};  
  133.         // TODO Auto-generated method stub  
  134.         for (int i = 0; i < 3; i++){  
  135.             IntBuffer textureBuffer = IntBuffer.allocate(1);  
  136.             gl.glGenTextures(1, textureBuffer);  
  137.             texturesID[i] = textureBuffer.get();  
  138.             gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesID[i]);  
  139.             Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), imgArray[0]);  
  140.              //生成纹理  
  141.             GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);  
  142.              // 线形滤波    
  143.             gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);    
  144.             gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);  
  145.         }  
  146.     }  
  147.       
  148.     private void SetupWorld() {  
  149.         // TODO Auto-generated method stub  
  150.         GLFile glFile = new GLFile(mContext.getResources());  
  151.         BufferedReader br = new BufferedReader(new InputStreamReader(glFile.getFile("data/world.txt")));  
  152.         TRIANGLE triangle = new TRIANGLE();  
  153.         int vertexIndex = 0;  
  154.           
  155.         try {  
  156.             String line = null;  
  157.             while((line = br.readLine()) != null){  
  158.                 if(line.trim().length() <= 0 || line.startsWith("/")){  
  159.                     continue;  
  160.                 }  
  161.                 String part[] = line.trim().split("\\s+");  
  162.                 float x = Float.valueOf(part[0]);  
  163.                 float y = Float.valueOf(part[1]);  
  164.                 float z = Float.valueOf(part[2]);  
  165.                 float u = Float.valueOf(part[3]);  
  166.                 float v = Float.valueOf(part[4]);  
  167.                 VERTEX vertex = new VERTEX(x, y, z, u, v);  
  168.                 triangle.vertex[vertexIndex] = vertex;  
  169.                   
  170.                 vertexIndex ++;  
  171.                 if(vertexIndex == 3){  
  172.                     vertexIndex = 0;  
  173.                     sector1.triangle.add(triangle);  
  174.                     triangle = new TRIANGLE();  
  175.                 }  
  176.             }  
  177.         } catch (IOException e) {  
  178.             e.printStackTrace();  
  179.         }  
  180.     }  
  181.       
  182.     public boolean onKeyUp(int keyCode, KeyEvent event)  
  183.     {  
  184.         switch ( keyCode )  
  185.         {  
  186.             case KeyEvent.KEYCODE_DPAD_LEFT:  
  187.                 yrot -= 1.5f;  
  188.                 break;  
  189.             case KeyEvent.KEYCODE_DPAD_RIGHT:  
  190.                 yrot += 1.5f;  
  191.                 break;  
  192.             case KeyEvent.KEYCODE_DPAD_UP:  
  193.                 // 沿游戏者所在的X平面移动  
  194.                 xpos -= (float)Math.sin(heading*piover180) * 0.05f;   
  195.                 // 沿游戏者所在的Z平面移动  
  196.                 zpos -= (float)Math.cos(heading*piover180) * 0.05f;           
  197.                 if (walkbiasangle >= 359.0f)// 如果walkbiasangle大于359度  
  198.                 {  
  199.                     walkbiasangle = 0.0f;// 将 walkbiasangle 设为0  
  200.                 }  
  201.                 else                                  
  202.                 {  
  203.                      walkbiasangle+= 10;// 如果 walkbiasangle < 359 ,则增加 10  
  204.                 }  
  205.                 // 使游戏者产生跳跃感  
  206.                 walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;          
  207.                 break;  
  208.             case KeyEvent.KEYCODE_DPAD_DOWN:  
  209.                 // 沿游戏者所在的X平面移动  
  210.                 xpos += (float)Math.sin(heading*piover180) * 0.05f;       
  211.                 // 沿游戏者所在的Z平面移动  
  212.                 zpos += (float)Math.cos(heading*piover180) * 0.05f;   
  213.                 // 如果walkbiasangle小于1度  
  214.                 if (walkbiasangle <= 1.0f)                    
  215.                 {  
  216.                     walkbiasangle = 359.0f;// 使 walkbiasangle 等于 359                      
  217.                 }  
  218.                 else                              
  219.                 {  
  220.                     walkbiasangle-= 10;// 如果 walkbiasangle > 1 减去 10  
  221.                 }  
  222.                 // 使游戏者产生跳跃感  
  223.                 walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;          
  224.                 break;  
  225.         }  
  226.         return false;  
  227.     }  
  228.   
  229.     public  class GLFile  
  230.     {  
  231.         public Resources resources;  
  232.         public GLFile(Resources r)  
  233.         {  
  234.             resources = r;  
  235.         }  
  236.         public InputStream getFile(String name){  
  237.             AssetManager am = resources.getAssets();  
  238.             try {  
  239.                 return am.open(name);  
  240.             } catch (IOException e) {  
  241.                 e.printStackTrace();  
  242.                 return null;  
  243.             }  
  244.         }  
  245.     }  
  246. }  

        Utils.java

Java代码
  1. package org.ourunix.android.world;  
  2.   
  3. import java.nio.ByteBuffer;  
  4. import java.nio.ByteOrder;  
  5. import java.nio.FloatBuffer;  
  6. import java.nio.IntBuffer;  
  7. import java.nio.ShortBuffer;  
  8.   
  9. public class Utils {  
  10.      public static class BufferUtil {    
  11.             public static IntBuffer intBuffer;  
  12.             public static FloatBuffer floatBuffer;  
  13.             public static ShortBuffer shortBuffer;  
  14.         
  15.             public static IntBuffer iBuffer(int[] a) {    
  16.                 // 先初始化buffer,数组的长度*4,因为一个float占4个字节    
  17.                 ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 4);    
  18.                 // 数组排列用nativeOrder    
  19.                 mbb.order(ByteOrder.nativeOrder());    
  20.                 intBuffer = mbb.asIntBuffer();    
  21.                 intBuffer.put(a);    
  22.                 intBuffer.position(0);    
  23.                 return intBuffer;    
  24.             }    
  25.               
  26.             public static FloatBuffer fBuffer(float[] a) {    
  27.                 // 先初始化buffer,数组的长度*4,因为一个float占4个字节    
  28.                 ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 4);    
  29.                 // 数组排列用nativeOrder    
  30.                 mbb.order(ByteOrder.nativeOrder());    
  31.                 floatBuffer = mbb.asFloatBuffer();    
  32.                 floatBuffer.put(a);    
  33.                 floatBuffer.position(0);    
  34.                 return floatBuffer;    
  35.             }  
  36.               
  37.             public static ShortBuffer sBuffer(short[] a) {    
  38.                 // 先初始化buffer,数组的长度*4,因为一个float占4个字节    
  39.                 ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 2);    
  40.                 // 数组排列用nativeOrder    
  41.                 mbb.order(ByteOrder.nativeOrder());    
  42.                 shortBuffer = mbb.asShortBuffer();    
  43.                 shortBuffer.put(a);    
  44.                 shortBuffer.position(0);    
  45.                 return shortBuffer;    
  46.             }  
  47.         }  
  48. }  

        源码下载:Android高级进阶十一 Android OpenGL建立3D空间源码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的Android OpenGL ES实现3D抛骰子的示例代码: 首先在build.gradle中添加OpenGL ES依赖: ```groovy implementation 'com.android.support:support-v4:28.0.0' implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.google.android.gms:play-services-gcm:16.0.0' implementation 'com.google.android.gms:play-services-ads:17.1.1' implementation 'com.google.android.gms:play-services-auth:16.0.1' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.squareup.okio:okio:1.14.0' implementation 'com.github.bumptech.glide:glide:4.9.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' implementation 'com.squareup.retrofit2:retrofit:2.5.0' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxjava:2.2.8' ``` 接着创建一个继承自GLSurfaceView的自定义视图: ```java public class DiceView extends GLSurfaceView implements Renderer { private final static int DICE_NUM = 3; // 骰子数量 private final static float DICE_SIZE = 0.5f; // 骰子大小 private final static float DICE_SPEED = 0.1f; // 骰子旋转速度 private Dice[] dices = new Dice[DICE_NUM]; // 骰子数组 private float xAngle = 0; // x轴旋转角度 private float yAngle = 0; // y轴旋转角度 private float zAngle = 0; // z轴旋转角度 public DiceView(Context context) { super(context); setRenderer(this); setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { for (int i = 0; i < DICE_NUM; i++) { dices[i] = new Dice(getContext(), DICE_SIZE, DICE_SPEED); } gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glEnable(GL10.GL_TEXTURE_2D); gl.glShadeModel(GL10.GL_SMOOTH); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); float ratio = (float) width / height; gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); } @Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); GLU.gluLookAt(gl, 0, 0, 3, 0, 0, 0, 0, 1, 0); gl.glRotatef(xAngle, 1, 0, 0); gl.glRotatef(yAngle, 0, 1, 0); gl.glRotatef(zAngle, 0, 0, 1); for (int i = 0; i < DICE_NUM; i++) { dices[i].draw(gl); } } public void setAngles(float xAngle, float yAngle, float zAngle) { this.xAngle = xAngle; this.yAngle = yAngle; this.zAngle = zAngle; } } ``` 然后创建一个Dice类来表示骰子: ```java public class Dice { private FloatBuffer vertexBuffer; private FloatBuffer textureBuffer; private ByteBuffer indexBuffer; private int[] textures = new int[1]; private int[] diceValues = {1, 2, 3, 4, 5, 6}; private float diceSize; private float diceSpeed; private float angleX = 0; private float angleY = 0; private float angleZ = 0; private float[] vertices = { -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f }; private float[] textureCoords = { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }; private byte[] indices = { 0, 1, 2, 0, 2, 3, 0, 3, 7, 0, 7, 4, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 3, 2, 6, 3, 6, 7, 4, 7, 6, 4, 6, 5 }; public Dice(Context context, float diceSize, float diceSpeed) { this.diceSize = diceSize; this.diceSpeed = diceSpeed; ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); ByteBuffer tbb = ByteBuffer.allocateDirect(textureCoords.length * 4); tbb.order(ByteOrder.nativeOrder()); textureBuffer = tbb.asFloatBuffer(); textureBuffer.put(textureCoords); textureBuffer.position(0); indexBuffer = ByteBuffer.allocateDirect(indices.length); indexBuffer.put(indices); indexBuffer.position(0); Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.dice); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); bitmap.recycle(); Random random = new Random(); setDiceValue(diceValues[random.nextInt(6)]); } public void draw(GL10 gl) { gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer); gl.glPushMatrix(); gl.glRotatef(angleX, 1, 0, 0); gl.glRotatef(angleY, 0, 1, 0); gl.glRotatef(angleZ, 0, 0, 1); gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, indexBuffer); gl.glPopMatrix(); gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } public void setDiceValue(int value) { angleX = 0; angleY = 0; angleZ = 0; switch (value) { case 1: angleX = 90; break; case 2: angleX = -90; break; case 3: angleY = 90; break; case 4: angleY = -90; break; case 5: angleZ = 90; break; case 6: angleZ = -90; break; } } public void update() { angleX += diceSpeed; angleY += diceSpeed; angleZ += diceSpeed; } } ``` 最后在Activity中使用DiceView即可: ```java public class MainActivity extends AppCompatActivity { private DiceView diceView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diceView = new DiceView(this); setContentView(diceView); } @Override protected void onResume() { super.onResume(); diceView.onResume(); } @Override protected void onPause() { super.onPause(); diceView.onPause(); } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: float dx = x - lastX; float dy = y - lastY; diceView.setAngles(dy * 0.5f, dx * 0.5f, 0); diceView.requestRender(); break; } lastX = x; lastY = y; return true; } } ``` 这样就实现了一个简单的Android OpenGL ES实现3D抛骰子的程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值