【Android App】给三维魔方贴图以及旋转动画讲解和实战(附源码和演示视频 超详细必看)

简介: 【Android App】给三维魔方贴图以及旋转动画讲解和实战(附源码和演示视频 超详细必看)

需要源码请点赞关注收藏后评论区留言私信~~~

一、给三维物体贴图

除了位置坐标、颜色向量和矩阵向量,着色器还支持声明材质坐标和纹理采样器。 其中材质坐标表示物体表面摊平后的二维空间,纹理采样器表示采用第几层的纹理单元。引入纹理指标之后,需要修改顶点着色器的GLSL文件,补充声明纹理坐标的输入参数以及输出参数

接着还要修改片段着色器的GLSL文件,补充声明纹理坐标的输入参数以及纹理采样器的全局变量,并调用texture方法生成片元颜色

然后根据小程序编号在指定的材质坐标上应用纹理,注意末尾调用glGetUniformLocation方法获取采样器的位置序号之后还要调用glUniformli方法传入采样器纹理单元的编号

回到活动页面,在绘制图形之前调用以上的bindImageTexture方法绑定图像纹理

运行测试效果如下

演示视频已上传至个人主页 有需要可自行前往观看

Android魔方贴图旋转动画

代码如下

Java类

package com.example.threed;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import com.example.threed.util.GlUtil;
import com.example.threed.util.EsVertexUtil;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class EsTextureActivity extends AppCompatActivity {
    private final static String TAG = "EsTextureActivity";
    private GLSurfaceView glsv_content; // 声明一个图形库表面视图对象
    private List<FloatBuffer> mVertexList = new ArrayList<>(); // 顶点列表
    private ByteBuffer mIndicateBuffer; // 示意连线的字节缓存
    private int mAngle = 60; // 旋转角度
    private int mProgramId; // 声明glsl小程序的编号
    private Bitmap mMagicBitmap; // 魔方的位图
    private float[] mProjectionMatrix = new float[16]; // 投影矩阵
    private float[] mModelMatrix = new float[16]; // 模型矩阵
    private float[] mMVPMatrix = new float[16]; // 结果矩阵
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_es_texture);
        initShapeSpinner(); // 初始化形状下拉框
        mVertexList = EsVertexUtil.getCubeVertexs(); // // 获得立方体的顶点列表
        mIndicateBuffer = GlUtil.getByteBuffer(EsVertexUtil.cubeIndicates);
        glsv_content = findViewById(R.id.glsv_content);
        // 声明使用OpenGL ES的版本号为3.0。使用ES30方法之前务必指定版本号
        glsv_content.setEGLContextClientVersion(3);
        // 给OpenGL的表面视图注册三维图形的渲染器
        glsv_content.setRenderer(new TextureRenderer());
        // 设置渲染模式。默认的RENDERMODE_CONTINUOUSLY表示持续刷新,RENDERMODE_WHEN_DIRTY表示只有首次创建和调用requestRender方法时才会刷新
        glsv_content.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
    // 初始化形状下拉框
    private void initShapeSpinner() {
        ArrayAdapter<String> shapeAdapter = new ArrayAdapter<>(this,
                R.layout.item_select, shapeArray);
        Spinner sp_shape = findViewById(R.id.sp_shape);
        sp_shape.setPrompt("请选择三维物体形状");
        sp_shape.setAdapter(shapeAdapter);
        sp_shape.setOnItemSelectedListener(new EsTextureActivity.ShapeSelectedListener());
        sp_shape.setSelection(0);
    }
    private String[] shapeArray = { "静止魔方", "旋转魔方" };
    class ShapeSelectedListener implements AdapterView.OnItemSelectedListener {
        public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
            mMagicBitmap = null;
            if (arg2 == 1) {
                glsv_content.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 设置渲染模式
            } else {
                glsv_content.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 设置渲染模式
                glsv_content.requestRender(); // 主动请求渲染操作
            }
        }
        public void onNothingSelected(AdapterView<?> arg0) {}
    }
    public class TextureRenderer implements GLSurfaceView.Renderer {
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            GLES30.glClearColor(1f, 1f, 1f, 1f); //设置背景颜色
            // 初始化着色器
            mProgramId = GlUtil.initShaderProgram(EsTextureActivity.this, "texture_vertex.glsl", "texture_fragment.glsl");
        }
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            GLES30.glViewport(0, 0, width, height); // 设置输出屏幕大小
            float aspectRatio = width>height ? 1.0f*width/height : 1.0f*height/width;
            Matrix.setIdentityM(mProjectionMatrix, 0); // 初始化投影矩阵
            // 计算矩阵的正交投影
            Matrix.orthoM(mProjectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
        }
        @Override
        public void onDrawFrame(GL10 gl) {
            // 清除屏幕和深度缓存
            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
            // 开启深度测试,这样只绘制靠前的表面,不绘制靠后的表面
            GLES30.glEnable(GLES30.GL_DEPTH_TEST);
            Matrix.setIdentityM(mModelMatrix, 0); // 初始化模型矩阵
            Matrix.rotateM(mModelMatrix, 0, mAngle,1f, 1f, 0.5f); // 旋转模型矩阵
            // 把投影矩阵和模型矩阵相乘,得到最终的变换矩阵
            Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mModelMatrix, 0);
            // 获取顶点着色器的unMatrix位置
            int matrixLoc = GLES30.glGetUniformLocation(mProgramId, "unMatrix");
            Log.d(TAG, "matrixLoc="+matrixLoc);
            // 输入变换矩阵信息
            GLES30.glUniformMatrix4fv(matrixLoc, 1, false, mMVPMatrix, 0);
            mAngle++;
            drawMagic(); // 绘制魔方
        }
    }
    // 绘制魔方
    private void drawMagic() {
        if (mMagicBitmap == null) {
            mMagicBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.magic);
            FloatBuffer textureBuffer = GlUtil.getFloatBuffer(EsVertexUtil.cubeTextCoord);
            // 绑定魔方的图像纹理
            int textureId = GlUtil.bindImageTexture(mProgramId, textureBuffer, mMagicBitmap);
            Log.d(TAG, "textureId="+textureId);
        }
        // 获取顶点着色器的vPosition位置
        int positionLoc = GLES30.glGetAttribLocation(mProgramId, "vPosition");
        Log.d(TAG, "positionLoc="+positionLoc);
        GLES30.glEnableVertexAttribArray(positionLoc); // 启用顶点属性数组
        for (FloatBuffer buffer : mVertexList) {
            // 指定顶点属性数组的信息
            GLES30.glVertexAttribPointer(positionLoc, 3, GLES30.GL_FLOAT, false, 0, buffer);
            // 假设绘制一个立方体,绘制类型为GL_TRIANGLES,那么六个面各由两个三角形组成,
            // 就得向渲染管线传入36个顶点依次绘制,可实际上一个矩形也只有4个顶点,
            // 为了优化绘制的效率,减少数据的传递,于是有了glDrawElements绘制方法。
            GLES30.glDrawElements(GLES30.GL_TRIANGLES, EsVertexUtil.cubeIndicates.length,
                    GLES30.GL_UNSIGNED_BYTE, mIndicateBuffer);
        }
        GLES30.glDisableVertexAttribArray(positionLoc); // 禁用顶点属性数组
    }
    @Override
    protected void onPause() {
        super.onPause();
        glsv_content.onPause(); // 暂停绘制三维图形
        mMagicBitmap = null;
        mAngle = 60;
    }
    @Override
    protected void onResume() {
        super.onResume();
        glsv_content.onResume(); // 恢复绘制三维图形
    }
}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="right"
            android:text="三维物体形状:"
            android:textColor="@color/black"
            android:textSize="17sp" />
        <Spinner
            android:id="@+id/sp_shape"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:spinnerMode="dialog" />
    </LinearLayout>
    <!-- 注意这里要使用控件的全路径android.opengl.GLSurfaceView -->
    <android.opengl.GLSurfaceView
        android:id="@+id/glsv_content"
        android:layout_width="match_parent"
        android:layout_height="400dp" />
</LinearLayout>

创作不易 觉得有帮助请点赞关注收藏~~~

相关文章
|
4天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
24 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
6天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
23 10
|
18天前
|
消息中间件 Java
【实战揭秘】如何运用Java发布-订阅模式,打造高效响应式天气预报App?
【8月更文挑战第30天】发布-订阅模式是一种消息通信模型,发送者将消息发布到公共队列,接收者自行订阅并处理。此模式降低了对象间的耦合度,使系统更灵活、可扩展。例如,在天气预报应用中,`WeatherEventPublisher` 类作为发布者收集天气数据并通知订阅者(如 `TemperatureDisplay` 和 `HumidityDisplay`),实现组件间的解耦和动态更新。这种方式适用于事件驱动的应用,提高了系统的扩展性和可维护性。
33 2
|
20天前
|
API Android开发
Android P 性能优化:创建APP进程白名单,杀死白名单之外的进程
本文介绍了在Android P系统中通过创建应用进程白名单并杀死白名单之外的进程来优化性能的方法,包括设置权限、获取运行中的APP列表、配置白名单以及在应用启动时杀死非白名单进程的代码实现。
40 1
|
24天前
|
IDE Java 开发工具
探索安卓开发之旅:打造你的第一款App
【8月更文挑战第24天】在这篇文章中,我们将一起踏上激动人心的安卓开发之旅。不论你是编程新手还是希望扩展技能的老手,本文将为你提供一份详尽指南,帮助你理解安卓开发的基础知识并实现你的第一个应用程序。从搭建开发环境到编写“Hello World”,每一步都将用浅显易懂的语言进行解释。那么,让我们开始吧!
|
1月前
|
开发工具 Android开发
|
1月前
|
Android开发
将AAB(Android App Bundle)转换为APK
将AAB(Android App Bundle)转换为APK
33 1
|
1月前
|
开发工具 Android开发
上架Google Play报错:For new apps, Android App Bundles must be signed with an RSA key.
上架Google Play报错:For new apps, Android App Bundles must be signed with an RSA key.
80 1
|
1月前
|
Android开发
Cannot create android app from an archive...containing both DEX and Java-bytecode content
Cannot create android app from an archive...containing both DEX and Java-bytecode content
17 2
|
1月前
|
XML Android开发 UED
"掌握安卓开发新境界:深度解析AndroidManifest.xml中的Intent-filter配置,让你的App轻松响应scheme_url,开启无限交互可能!"
【8月更文挑战第2天】在安卓开发中,scheme_url 通过在`AndroidManifest.xml`中配置`Intent-filter`,使应用能响应特定URL启动或执行操作。基本配置下,应用可通过定义特定URL模式的`Intent-filter`响应相应链接。
79 12