2016-07-29 15:44:07 huachao1001 阅读数 23639

转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001】

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

前面两篇文章我们介绍了OpenGL相关的基本知识,现在我们已经会绘制基本的图案了,但是还远远不能满足我们的需求。我们要做的是显示任意的模型,这也是本文所要做的事情。在阅读本文之前,请先确保你已经看过我前面两篇文章:

虽然标题是说显示任意3D文件,但是本文主要是以STL格式文件为例。其他的格式本质上都是一样的,只是解析部分的代码不同而已。接下来我们开始学习~

1 STL文件

它是标准的3D文件格式,一般3D打印机都是支持打印STL文件,关于STL文件的格式、以及相关介绍请参考百度百科:【stl格式】。当然了,我在代码的注释中也会进行相关解释。

1.1 解析准备

首先,在解析STL文件格式之前,我们需要进行构思。我们无非就是把STL文件中的三角形的顶点信息提取出来。因此我们的主要目标就是把所有点信息读取出来。

但是,3D模型的坐标位置很随机,大小也随机。而不同的模型所处的位置不同,为了能够让模型处于手机显示中心,我们必须对模型进行移动、放缩处理。使得任意大小、任意位置的模型都能在我们的GLSurfaceView中以“相同”的大小显示。

因此,我们不仅仅要读取顶点信息,而且还要获取模型的边界信息。我们想象成一个立方体,这个立方体刚好包裹住模型。即我们要读取x、y、z三个方向上的最大值最小值。

1.2 开始解析

首先,我们定义一个Model类,用于表示一个模型对象:

package com.hc.opengl;

import java.nio.FloatBuffer;

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class Model {
    //三角面个数
    private int facetCount;
    //顶点坐标数组
    private float[] verts;
    //每个顶点对应的法向量数组
    private float[] vnorms;
    //每个三角面的属性信息
    private short[] remarks;

    //顶点数组转换而来的Buffer
    private FloatBuffer vertBuffer;

    //每个顶点对应的法向量转换而来的Buffer
    private FloatBuffer vnormBuffer;
    //以下分别保存所有点在x,y,z方向上的最大值、最小值
    float maxX;
    float minX;
    float maxY;
    float minY;
    float maxZ;
    float minZ;

    //返回模型的中心点
     //注意,下载的源码中,此函数修改修正如下
    public Point getCentrePoint() {

        float cx = minX + (maxX - minX) / 2;
        float cy = minY + (maxY - minY) / 2;
        float cz = minZ + (maxZ - minZ) / 2;
        return new Point(cx, cy, cz);
    }

    //包裹模型的最大半径
    public float getR() {
        float dx = (maxX - minX);
        float dy = (maxY - minY);
        float dz = (maxZ - minZ);
        float max = dx;
        if (dy > max)
            max = dy;
        if (dz > max)
            max = dz;
        return max;
    }

    //设置顶点数组的同时,设置对应的Buffer
    public void setVerts(float[] verts) {
        this.verts = verts;
        vertBuffer = Util.floatToBuffer(verts);
    }

    //设置顶点数组法向量的同时,设置对应的Buffer
    public void setVnorms(float[] vnorms) {
        this.vnorms = vnorms;
        vnormBuffer = Util.floatToBuffer(vnorms);
    }

   //···
   //其他属性对应的setter、getter函数
   //···

}

接下来就是将stl文件转换成Model对象,我们定义一个STLReader类:

package com.hc.opengl;

import android.content.Context;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class STLReader {
    private StlLoadListener stlLoadListener;

    public Model parserBinStlInSDCard(String path)
                             throws IOException {

        File file = new File(path);
        FileInputStream fis = new FileInputStream(file);
        return parserBinStl(fis);
    }

    public Model parserBinStlInAssets(Context context, String fileName) 
                            throws IOException {

        InputStream is = context.getAssets().open(fileName);
        return parserBinStl(is);
    }
    //解析二进制的Stl文件
    public Model parserBinStl(InputStream in) throws IOException {
        if (stlLoadListener != null)
            stlLoadListener.onstart();
        Model model = new Model();
        //前面80字节是文件头,用于存贮文件名;
        in.skip(80);

        //紧接着用 4 个字节的整数来描述模型的三角面片个数
        byte[] bytes = new byte[4];
        in.read(bytes);// 读取三角面片个数
        int facetCount = Util.byte4ToInt(bytes, 0);
        model.setFacetCount(facetCount);
        if (facetCount == 0) {
            in.close();
            return model;
        }

        // 每个三角面片占用固定的50个字节
        byte[] facetBytes = new byte[50 * facetCount];
        // 将所有的三角面片读取到字节数组
        in.read(facetBytes);
        //数据读取完毕后,可以把输入流关闭
        in.close();


        parseModel(model, facetBytes);


        if (stlLoadListener != null)
            stlLoadListener.onFinished();
        return model;
    }

    /**
     * 解析模型数据,包括顶点数据、法向量数据、所占空间范围等
     */
    private void parseModel(Model model, byte[] facetBytes) {
        int facetCount = model.getFacetCount();
        /**
         *  每个三角面片占用固定的50个字节,50字节当中:
         *  三角片的法向量:(1个向量相当于一个点)*(3维/点)*(4字节浮点数/维)=12字节
         *  三角片的三个点坐标:(3个点)*(3维/点)*(4字节浮点数/维)=36字节
         *  最后2个字节用来描述三角面片的属性信息
         * **/
        // 保存所有顶点坐标信息,一个三角形3个顶点,一个顶点3个坐标轴
        float[] verts = new float[facetCount * 3 * 3];
        // 保存所有三角面对应的法向量位置,
        // 一个三角面对应一个法向量,一个法向量有3个点
        // 而绘制模型时,是针对需要每个顶点对应的法向量,因此存储长度需要*3
        // 又同一个三角面的三个顶点的法向量是相同的,
        // 因此后面写入法向量数据的时候,只需连续写入3个相同的法向量即可
        float[] vnorms = new float[facetCount * 3 * 3];
        //保存所有三角面的属性信息
        short[] remarks = new short[facetCount];

        int stlOffset = 0;
        try {
            for (int i = 0; i < facetCount; i++) {
                if (stlLoadListener != null) {
                    stlLoadListener.onLoading(i, facetCount);
                }
                for (int j = 0; j < 4; j++) {
                    float x = Util.byte4ToFloat(facetBytes, stlOffset);
                    float y = Util.byte4ToFloat(facetBytes, stlOffset + 4);
                    float z = Util.byte4ToFloat(facetBytes, stlOffset + 8);
                    stlOffset += 12;

                    if (j == 0) {//法向量 
                        vnorms[i * 9] = x;
                        vnorms[i * 9 + 1] = y;
                        vnorms[i * 9 + 2] = z;
                        vnorms[i * 9 + 3] = x;
                        vnorms[i * 9 + 4] = y;
                        vnorms[i * 9 + 5] = z;
                        vnorms[i * 9 + 6] = x;
                        vnorms[i * 9 + 7] = y;
                        vnorms[i * 9 + 8] = z;
                    } else {//三个顶点
                        verts[i * 9 + (j - 1) * 3] = x;
                        verts[i * 9 + (j - 1) * 3 + 1] = y;
                        verts[i * 9 + (j - 1) * 3 + 2] = z;

                        //记录模型中三个坐标轴方向的最大最小值
                        if (i == 0 && j == 1) {
                            model.minX = model.maxX = x;
                            model.minY = model.maxY = y;
                            model.minZ = model.maxZ = z;
                        } else {
                            model.minX = Math.min(model.minX, x);
                            model.minY = Math.min(model.minY, y);
                            model.minZ = Math.min(model.minZ, z);
                            model.maxX = Math.max(model.maxX, x);
                            model.maxY = Math.max(model.maxY, y);
                            model.maxZ = Math.max(model.maxZ, z);
                        }
                    }
                }
                short r = Util.byte2ToShort(facetBytes, stlOffset);
                stlOffset = stlOffset + 2;
                remarks[i] = r;
            }
        } catch (Exception e) {
            if (stlLoadListener != null) {
                stlLoadListener.onFailure(e);
            } else {
                e.printStackTrace();
            }
        }
        //将读取的数据设置到Model对象中
        model.setVerts(verts);
        model.setVnorms(vnorms);
        model.setRemarks(remarks);

    }

    public static interface StlLoadListener {
        void onstart();

        void onLoading(int cur, int total);

        void onFinished();

        void onFailure(Exception e);
    }
}

注意到,我们需要频繁的将byte数组转为short、float类型,我们直接把这些函数装到一个工具类Util中:

package com.hc.opengl;

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

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class Util {

    public static FloatBuffer floatToBuffer(float[] a) {
        //先初始化buffer,数组的长度*4,因为一个float占4个字节
        ByteBuffer bb = ByteBuffer.allocateDirect(a.length * 4);
        //数组排序用nativeOrder
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer buffer = bb.asFloatBuffer();
        buffer.put(a);
        buffer.position(0);
        return buffer;
    }

    public static int byte4ToInt(byte[] bytes, int offset) {
        int b3 = bytes[offset + 3] & 0xFF;
        int b2 = bytes[offset + 2] & 0xFF;
        int b1 = bytes[offset + 1] & 0xFF;
        int b0 = bytes[offset + 0] & 0xFF;
        return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
    }

    public static short byte2ToShort(byte[] bytes, int offset) {
        int b1 = bytes[offset + 1] & 0xFF;
        int b0 = bytes[offset + 0] & 0xFF;
        return (short) ((b1 << 8) | b0);
    }

    public static float byte4ToFloat(byte[] bytes, int offset) {

        return Float.intBitsToFloat(byte4ToInt(bytes, offset));
    }

}

为了更好的表示三维坐标系下的一个点,我们定义Point类:

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class Point {
    public float x;
    public float y;
    public float z;

    public Point(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;

    }
}

2 编写Render

上一节我们只是拿数据而已,还没开始绘制,真正的大招现在才开始。因为我们目标是显示任意模型,因此,必须把模型移动到我们的“视野”中,才能看得到(当然了,如果图形本身就是在我们的视野中,那就不一定需要这样的操作了)。废话不多说,直接看源码:

 /**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/7/28.
 */
public class GLRenderer implements GLSurfaceView.Renderer {

    private Model model;
    private Point mCenterPoint;
    private Point eye = new Point(0, 0, -3);
    private Point up = new Point(0, 1, 0);
    private Point center = new Point(0, 0, 0);
    private float mScalef = 1;
    private float mDegree = 0;

    public GLRenderer(Context context) {
        try {

            model = new STLReader().parserBinStlInAssets(context, "huba.stl");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void rotate(float degree) {
        mDegree = degree;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕和深度缓存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);


        gl.glLoadIdentity();// 重置当前的模型观察矩阵


        //眼睛对着原点看 
        GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
                center.y, center.z, up.x, up.y, up.z);

        //为了能有立体感觉,通过改变mDegree值,让模型不断旋转
        gl.glRotatef(mDegree, 0, 1, 0);

        //将模型放缩到View刚好装下
        gl.glScalef(mScalef, mScalef, mScalef);
        //把模型移动到原点
        gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
                -mCenterPoint.z);


        //===================begin==============================//

        //允许给每个顶点设置法向量
        gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
        // 允许设置顶点
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        // 允许设置颜色

        //设置法向量数据源
        gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
        // 设置三角形顶点数据源
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());

        // 绘制三角形
        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);

        // 取消顶点设置
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        //取消法向量设置
        gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);

        //=====================end============================//

    }


    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        // 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角,(width, height)指定了视口的大小
        gl.glViewport(0, 0, width, height);

        gl.glMatrixMode(GL10.GL_PROJECTION); // 设置投影矩阵
        gl.glLoadIdentity(); // 设置矩阵为单位矩阵,相当于重置矩阵
        GLU.gluPerspective(gl, 45.0f, ((float) width) / height, 1f, 100f);// 设置透视范围

        //以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();


    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        gl.glEnable(GL10.GL_DEPTH_TEST); // 启用深度缓存
        gl.glClearDepthf(1.0f); // 设置深度缓存值
        gl.glDepthFunc(GL10.GL_LEQUAL); // 设置深度缓存比较函数
        gl.glShadeModel(GL10.GL_SMOOTH);// 设置阴影模式GL_SMOOTH
        float r = model.getR();
        //r是半径,不是直径,因此用0.5/r可以算出放缩比例
        mScalef = 0.5f / r;
        mCenterPoint = model.getCentrePoint();
    }
}

在MainActivity中不断调用旋转函数:

package com.hc.opengl;


public class MainActivity extends AppCompatActivity {

    private boolean supportsEs2;
    private GLSurfaceView glView;
    private float rotateDegreen = 0;
    private GLRenderer glRenderer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        checkSupported();

        if (supportsEs2) {
            glView = new GLSurfaceView(this);
            glRenderer = new GLRenderer(this);
            glView.setRenderer(glRenderer);
            setContentView(glView);
        } else {
            setContentView(R.layout.activity_main);
            Toast.makeText(this, "当前设备不支持OpenGL ES 2.0!", Toast.LENGTH_SHORT).show();
        }
    }

    public void rotate(float degree) {
        glRenderer.rotate(degree);
        glView.invalidate();
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            rotate(rotateDegreen);
        }
    };

    @Override
    protected void onResume() {
        super.onResume();
        if (glView != null) {
            glView.onResume();

            //不断改变rotateDegreen值,实现旋转
            new Thread() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            sleep(100);

                            rotateDegreen += 5;
                            handler.sendEmptyMessage(0x001);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }
            }.start();
        }


    }

    private void checkSupported() {
        ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
        ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        supportsEs2 = configurationInfo.reqGlEsVersion >= 0x2000;

        boolean isEmulator = Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
                && (Build.FINGERPRINT.startsWith("generic")
                || Build.FINGERPRINT.startsWith("unknown")
                || Build.MODEL.contains("google_sdk")
                || Build.MODEL.contains("Emulator")
                || Build.MODEL.contains("Android SDK built for x86"));

        supportsEs2 = supportsEs2 || isEmulator;
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (glView != null) {
            glView.onPause();
        }
    }


}

3 最后一步

一切看起来都已经完成了,但似乎少了点什么。啊哈~,少了STL文件,其实网上有很多STL模型文件免费下载,大家可以随便搜索。我下载了一个胡巴的模型:

模型截图

模型截图

下载完成后,运行如下:

运行结果

看到结果是不是觉得很失望?貌似看不到轮廓,其实,主要是跟灯光有关,我们程序中没有设置灯光。我们知道,我们在真实世界中看到物体主要是物体表面发生漫反射。我们所看到的物体跟光源的位置、物体的材质等等有关。另外,也可以通过贴纹理来做到。但是到目前为止,我们还没有这些知识,代码里面也没有涉及到这些,因此我们这能看到当前这个样子。后面我们会继续深入学习相关知识,欢迎关注~。

好啦,最后献上源码吧~,注意,下载的源码中Model类的getCentrePoint函数需要修改,请以本文中的Model类为主。

源码地址http://download.csdn.net/detail/huachao1001/9588619

2016-04-01 14:47:26 yuchen56 阅读数 1186

最近在研究怎么在Android 项目中添加Unity3D建模。在这里写个博客,算是总结一下。

首先在unity中把需要的模型建好,然后在unity 的 【File】 中选择【Build Setting】如图所示:


点开后会见到如下窗口:


选择Android 后,勾选右侧 【Google Android Project】 这个时候【Export】会变成可点击状态。

点击后选择要保存到路径就可以自动生成了。

生成的目录结构如下图所示:


多么熟悉的目录结构。

接下来你就可以自己熟悉的IDE中尽情的使用了。

2012-06-26 18:20:36 lwuit 阅读数 2372

opengl里的对球形的建模:首先要把球形表式由点组成的模型。现在的问题是:怎么把这个球用点集进行表示。

代码如下:

Java代码 复制代码 收藏代码
  1. final int UNIT_SIZE=10000
  2. ArrayList<Integer> alVertix=new ArrayList<Integer>();//存放顶点坐标的ArrayList 
  3. final int angleSpan=18;//将球进行单位切分的角度 
  4.    for(int vAngle=-90;vAngle<=90;vAngle=vAngle+angleSpan){//垂直方向angleSpan度一份 
  5.     for(int hAngle=0;hAngle<360;hAngle=hAngle+angleSpan)//水平方向angleSpan度一份 
  6.     {//纵向横向各到一个角度后计算对应的此点在球面上的坐标 
  7.         double xozLength=scale*UNIT_SIZE*Math.cos(Math.toRadians(vAngle)); 
  8.         int x=(int)(xozLength*Math.cos(Math.toRadians(hAngle))); 
  9.         int z=(int)(xozLength*Math.sin(Math.toRadians(hAngle))); 
  10.         int y=(int)(scale*UNIT_SIZE*Math.sin(Math.toRadians(vAngle))); 
  11.         //将计算出来的XYZ坐标加入存放顶点坐标的ArrayList 
  12.         alVertix.add(x);alVertix.add(y);alVertix.add(z); 
  13.     } 
  14.    }     

讲解:

1. 首先把球想成由很多的半径不一样的圆所组成的。把球想像成由很多平形于x,z平面的圆所组成的。

2. x,z轴的圆的角度是由0-360,y轴的变动由-90到90。

3. 在java里,三角函数要用弧度计算,而我们平实一般都是角度来计算。其实不管三角函数用的是角度和弧度,其都是度量的是角的大小,所以对于同一个角,其三角函数的值都是一样的(不管是用角度计算,还是用弧度计算)。所以sin(x)就不要注重其x是角度还是弧度,只要关心,其是由对边/斜边(前题是直角三角形)。

4. 如果对于计算球上每个点的坐标,不太理解,可以在笛卡尔级坐标系里,画一个点,再把这个点进行映射到三个坐标轴上去,就可以了。

注:角度和弧度的理解:http://hi.baidu.com/kent_edwin/blog/item/9425f0029e06967e3812bb28.html


2017-01-10 02:11:19 junzia 阅读数 12824

在博主《OpenGLES系列》文章中,最开始的几篇讲的就是OpenGL世界中各种形体的构建,但是那些形体都是规则的简单形体,遇到复杂的形体,比如说一个人、一朵花,怎么办呢?自然是通过其他工具类似于Maya、3DMax等3D建模工具,做好模型导出来,然后用OpenGLES加载导出的模型文件。模型的加载大同小异,本篇博客是以Obj格式的3D模型为例。

模型文件

本篇博客例子中加载的是一个帽子,资源是在网上随便找的一个。加载出来如图所示:
这里写图片描述
格式如下:

# File exported by ZBrush version 4.2
# www.zbrush.com
#Vertex Count 4898
#Face Count 4848
#Auto scale x=0.211538 y=0.211538 z=0.211538
#Auto offset x=-0.000000 y=-0.412507 z=-0.000000
v -0.62500745 3.93329608 0.0000001
v -0.00002446 3.32622414 1.33471741
v 1.47657442 2.55452877 1.37523436
v -1.01934254 3.90772931 0.00000007
...省略若干行...
g default
f 990 991 987 986
f 991 874 873 987
f 972 971 991 990
f 971 55 874 991
f 987 992 988 986
...省略若干行

加载这个模型文件前,我们需要先知道这些数据代表的是什么。针对这个文件,#号开头的,是描述模型文件的相关信息。以v开头的,表示的是顶点坐标。以f开头的,表示一个面,后面跟的四个值是索引。一个v,后面的三个数,代表一个点的xyz,4个点组成了一个四边形。
为什么是4个点?不是说在OpenGLES中基本几何是三角形么?这样问就有点尴尬了,因为模型文件是我在网上随便下的,自己选的模型,跪着也要加载出来。有什么关系,一个四边形不就是两个三角形么。
这个模型文件只有v、f两类数据,但是一个炫酷的模型,往往是包含很多数据的,主要的数据类型如下:

  1. 顶点数据(Vertex data):

    • v 几何体顶点(Geometric vertices)
    • vt 贴图坐标点(Texture vertices)
    • vn 顶点法线(Vertex normals)
    • vp 参数空格顶点 (Parameter space vertices)
  2. 自由形态曲线(Free-form curve)/表面属性(surface attributes):

    • deg 度(Degree)
    • bmat 基础矩阵(Basis matrix)
    • step 步尺寸(Step size)
    • cstype 曲线或表面类型 (Curve or surface type)
  3. 元素(Elements):

    • p 点(Point)
    • l 线(Line)
    • f 面(Face)
    • curv 曲线(Curve)
    • curv2 2D曲线(2D curve)
    • surf 表面(Surface)
  4. 自由形态曲线(Free-form curve)/表面主体陈述(surface body statements):

    • parm 参数值(Parameter values )
    • trim 外部修剪循环(Outer trimming loop)
    • hole 内部整修循环(Inner trimming loop)
    • scrv 特殊曲线(Special curve)
    • sp 特殊的点(Special point)
    • end 结束陈述(End statement)
  5. 自由形态表面之间的连接(Connectivity between free-form surfaces):

    • con 连接 (Connect)
  6. 成组(Grouping):

    • g 组名称(Group name)
    • s 光滑组(Smoothing group)
    • mg 合并组(Merging group)
    • o 对象名称(Object name)
  7. 显示(Display)/渲染属性(render attributes):

    • bevel 导角插值(Bevel interpolation)
    • c_interp 颜色插值(Color interpolation)
    • d_interp 溶解插值(Dissolve interpolation)
    • lod 细节层次(Level of detail)
    • usemtl 材质名称(Material name)
    • mtllib 材质库(Material library)
    • shadow_obj 投射阴影(Shadow casting)
    • trace_obj 光线跟踪(Ray tracing)
    • ctech 曲线近似技术(Curve approximation technique)
    • stech 表面近似技术 (Surface approximation technique)

模型加载

知道了模型文件的内容和格式,加载起来就不是什么问题了:

public class ObjReader {

    public static void read(InputStream stream,Obj3D obj3D){
        ArrayList<Float> alv=new ArrayList<Float>();//原始顶点坐标列表
        ArrayList<Float> alvResult=new ArrayList<Float>();//结果顶点坐标列表
        ArrayList<Float> norlArr=new ArrayList<>();
        float[] ab=new float[3],bc=new float[3],norl=new float[3];
        try{
            InputStreamReader isr=new InputStreamReader(stream);
            BufferedReader br=new BufferedReader(isr);
            String temps=null;
            while((temps=br.readLine())!=null)
            {
                String[] tempsa=temps.split("[ ]+");
                if(tempsa[0].trim().equals("v")) {//此行为顶点坐标
                    alv.add(Float.parseFloat(tempsa[1]));
                    alv.add(Float.parseFloat(tempsa[2]));
                    alv.add(Float.parseFloat(tempsa[3]));
                }  else if(tempsa[0].trim().equals("f")) {//此行为三角形面
                    int a=Integer.parseInt(tempsa[1])-1;
                    int b=Integer.parseInt(tempsa[2])-1;
                    int c=Integer.parseInt(tempsa[3])-1;
                    int d=Integer.parseInt(tempsa[4])-1;
                    //abc和acd两个三角形组成的四边形

                    alvResult.add(alv.get(a*3));
                    alvResult.add(alv.get(a*3+1));
                    alvResult.add(alv.get(a*3+2));
                    alvResult.add(alv.get(b*3));
                    alvResult.add(alv.get(b*3+1));
                    alvResult.add(alv.get(b*3+2));
                    alvResult.add(alv.get(c*3));
                    alvResult.add(alv.get(c*3+1));
                    alvResult.add(alv.get(c*3+2));

                    alvResult.add(alv.get(a*3));
                    alvResult.add(alv.get(a*3+1));
                    alvResult.add(alv.get(a*3+2));
                    alvResult.add(alv.get(c*3));
                    alvResult.add(alv.get(c*3+1));
                    alvResult.add(alv.get(c*3+2));
                    alvResult.add(alv.get(d*3));
                    alvResult.add(alv.get(d*3+1));
                    alvResult.add(alv.get(d*3+2));

                    //这里也是因为下载模型文件的坑。下了个出了顶点和面啥也没有的模型文件
                    //为了有3d效果,给它加个光照,自己计算顶点法线
                    //用面法向量策略。按理说点法向量更适合这种光滑的3D模型,但是计算起来太复杂了,so
                    //既然主要讲3D模型加载,就先用面法向量策略来吧
                    //通常3D模型里面会包含法向量信息的。
                    //法向量的计算,ABC三个空间点,他们的法向量为向量AB与向量BC的外积,所以有:
                    for (int i=0;i<3;i++){
                        ab[i]=alv.get(a*3+i)-alv.get(b*3+i);
                        bc[i]=alv.get(b*3+i)-alv.get(c*3+i);
                    }
                    norl[0]=ab[1]*bc[2]-ab[2]*bc[1];
                    norl[1]=ab[2]*bc[0]-ab[0]*bc[2];
                    norl[2]=ab[0]*bc[1]-ab[1]*bc[0];

                    //上面两个三角形,传入了6个顶点,这里循环6次,简单粗暴
                    for (int i=0;i<6;i++){
                        norlArr.add(norl[0]);
                        norlArr.add(norl[1]);
                        norlArr.add(norl[2]);
                    }
                }
            }

            //这些就是比较熟悉的了,一切都为了能够把数据给GPU
            int size=alvResult.size();
            float[] vXYZ=new float[size];
            for(int i=0;i<size;i++){
                vXYZ[i]=alvResult.get(i);
            }
            ByteBuffer byteBuffer=ByteBuffer.allocateDirect(4*size);
            byteBuffer.order(ByteOrder.nativeOrder());
            obj3D.vert=byteBuffer.asFloatBuffer();
            obj3D.vert.put(vXYZ);
            obj3D.vert.position(0);
            obj3D.vertCount=size/3;
            int vbSize=norlArr.size();
            float[] vbArr=new float[size];
            for(int i=0;i<size;i++){
                vbArr[i]=norlArr.get(i);
            }
            ByteBuffer vb=ByteBuffer.allocateDirect(4*vbSize);
            vb.order(ByteOrder.nativeOrder());
            obj3D.vertNorl=vb.asFloatBuffer();
            obj3D.vertNorl.put(vbArr);
            obj3D.vertNorl.position(0);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static class Obj3D{
        public FloatBuffer vert;
        public int vertCount;
        public FloatBuffer vertNorl;
    }
}

模型渲染

模型的渲染,和之前绘制各种形体也差不多了,往GPU传数据就不用说了,为了让3D模型呈现出立体效果,示例中,增加了简单而不靠谱的光照。所以看得出来,虽然加载出来有立体效果,但是能看到比较明显的网格。当然,光照不是本篇博客的重点,在后续博客里面再详细讨论下光照的问题。
顶点Shader为:

attribute vec3 vPosition;
attribute vec2 vCoord;
uniform mat4 vMatrix;

varying vec2 textureCoordinate;

attribute vec3 vNormal;         //法向量
varying vec4 vDiffuse;          //用于传递给片元着色器的散射光最终强度


//返回散射光强度
vec4 pointLight(vec3 normal,vec3 lightLocation,vec4 lightDiffuse){
    //变换后的法向量
    vec3 newTarget=normalize((vMatrix*vec4(normal+vPosition,1)).xyz-(vMatrix*vec4(vPosition,1)).xyz);
    //表面点与光源的方向向量
    vec3 vp=normalize(lightLocation-(vMatrix*vec4(vPosition,1)).xyz);
    return lightDiffuse*max(0.0,dot(newTarget,vp));
}

void main(){
    gl_Position = vMatrix*vec4(vPosition,1);
    textureCoordinate = vCoord;

   vec4 at=vec4(1.0,1.0,1.0,1.0);   //光照强度
   vec3 pos=vec3(50.0,200.0,50.0);      //光照位置
   vDiffuse=pointLight(vNormal,pos,at);
}

片元Shader:

precision mediump float;
varying vec2 textureCoordinate;
uniform sampler2D vTexture;
varying vec4 vDiffuse;//接收从顶点着色器过来的散射光分量
void main() {
    vec4 finalColor=vec4(1.0);
    //给此片元颜色值
    gl_FragColor=finalColor*vDiffuse+finalColor*vec4(0.15,0.15,0.15,1.0);
}

着色器中散射光强度的计算,是根据散射光的公式来的,光照公式在Android OpenGLES2.0(一)——了解OpenGLES2.0光照中有讲到。

编译着色器,linkProgram,传入从Obj文件读取的值,然后和渲染一个立方体一样,渲染出模型就OK了。

源码

所有的代码全部在一个项目中,托管在Github上——Android OpenGLES 2.0系列博客的Demo


欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/54300202]


3D基础--Vertex

阅读数 786

android学习路线图

阅读数 323

没有更多推荐了,返回首页