opengles加载obj格式3D模型含光照和纹理

效果图




顶点加载类

package test.com.opengles9_5;


import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import android.content.res.Resources;
import android.util.Log;
/**
 * Created by hbin on 2016/9/12.
 */
public class LoadUtil {
    //求两个向量的叉积
    public static float[] getCrossProduct(float x1,float y1,float z1,float x2,float y2,float z2)
    {
        //求出两个矢量叉积矢量在XYZ轴的分量ABC
        float A=y1*z2-y2*z1;
        float B=z1*x2-z2*x1;
        float C=x1*y2-x2*y1;

        return new float[]{A,B,C};
    }

    //向量规格化
    public static float[] vectorNormal(float[] vector)
    {
        //求向量的模
        float module=(float)Math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]+vector[2]*vector[2]);
        return new float[]{vector[0]/module,vector[1]/module,vector[2]/module};
    }

    //从obj文件中加载携带顶点信息的物体,并自动计算每个顶点的平均法向量
    public static LoadedObjectVertexNormalTexture loadFromFile
    (String fname, Resources r,MySurfaceView mv)
    {
        //加载后物体的引用
        LoadedObjectVertexNormalTexture lo=null;
        //原始顶点坐标列表--直接从obj文件中加载
        ArrayList<Float> alv=new ArrayList<Float>();
        //顶点组装面索引列表--根据面的信息从文件中加载
        ArrayList<Integer> alFaceIndex=new ArrayList<Integer>();
        //结果顶点坐标列表--按面组织好
        ArrayList<Float> alvResult=new ArrayList<Float>();
        //平均前各个索引对应的点的法向量集合Map
        //此HashMap的key为点的索引, value为点所在的各个面的法向量的集合
        HashMap<Integer,HashSet<Normal>> hmn=new HashMap<Integer,HashSet<Normal>>();
        //原始纹理坐标列表
        ArrayList<Float> alt=new ArrayList<Float>();
        //纹理坐标结果列表
        ArrayList<Float> altResult=new ArrayList<Float>();

        try
        {
            InputStream in=r.getAssets().open(fname);
            InputStreamReader isr=new InputStreamReader(in);
            BufferedReader br=new BufferedReader(isr);
            String temps=null;

            //扫面文件,根据行类型的不同执行不同的处理逻辑
            while((temps=br.readLine())!=null)
            {
                //用空格分割行中的各个组成部分
                String[] tempsa=temps.split("[ ]+");
                if(tempsa[0].trim().equals("v"))
                {//此行为顶点坐标
                    //若为顶点坐标行则提取出此顶点的XYZ坐标添加到原始顶点坐标列表中
                    alv.add(Float.parseFloat(tempsa[1]));
                    alv.add(Float.parseFloat(tempsa[2]));
                    alv.add(Float.parseFloat(tempsa[3]));
                }
                else if(tempsa[0].trim().equals("vt"))
                {//此行为纹理坐标行
                    //若为纹理坐标行则提取ST坐标并添加进原始纹理坐标列表中
                    alt.add(Float.parseFloat(tempsa[1]));
                    alt.add(1-Float.parseFloat(tempsa[2]));
                }
                else if(tempsa[0].trim().equals("f"))
                {//此行为三角形面
		      		/*
		      		 *若为三角形面行则根据 组成面的顶点的索引从原始顶点坐标列表中
		      		 *提取相应的顶点坐标值添加到结果顶点坐标列表中,同时根据三个
		      		 *顶点的坐标计算出此面的法向量并添加到平均前各个索引对应的点
		      		 *的法向量集合组成的Map中
		      		*/

                    int[] index=new int[3];//三个顶点索引值的数组

                    //计算第0个顶点的索引,并获取此顶点的XYZ三个坐标
                    index[0]=Integer.parseInt(tempsa[1].split("/")[0])-1;
                    float x0=alv.get(3*index[0]);
                    float y0=alv.get(3*index[0]+1);
                    float z0=alv.get(3*index[0]+2);
                    alvResult.add(x0);
                    alvResult.add(y0);
                    alvResult.add(z0);

                    //计算第1个顶点的索引,并获取此顶点的XYZ三个坐标
                    index[1]=Integer.parseInt(tempsa[2].split("/")[0])-1;
                    float x1=alv.get(3*index[1]);
                    float y1=alv.get(3*index[1]+1);
                    float z1=alv.get(3*index[1]+2);
                    alvResult.add(x1);
                    alvResult.add(y1);
                    alvResult.add(z1);

                    //计算第2个顶点的索引,并获取此顶点的XYZ三个坐标
                    index[2]=Integer.parseInt(tempsa[3].split("/")[0])-1;
                    float x2=alv.get(3*index[2]);
                    float y2=alv.get(3*index[2]+1);
                    float z2=alv.get(3*index[2]+2);
                    alvResult.add(x2);
                    alvResult.add(y2);
                    alvResult.add(z2);

                    //记录此面的顶点索引
                    alFaceIndex.add(index[0]);
                    alFaceIndex.add(index[1]);
                    alFaceIndex.add(index[2]);

                    //通过三角形面两个边向量0-1,0-2求叉积得到此面的法向量
                    //求0号点到1号点的向量
                    float vxa=x1-x0;
                    float vya=y1-y0;
                    float vza=z1-z0;
                    //求0号点到2号点的向量
                    float vxb=x2-x0;
                    float vyb=y2-y0;
                    float vzb=z2-z0;
                    //通过求两个向量的叉积计算法向量
                    float[] vNormal=vectorNormal(getCrossProduct
                            (
                                    vxa,vya,vza,vxb,vyb,vzb
                            ));
                    for(int tempInxex:index)
                    {//记录每个索引点的法向量到平均前各个索引对应的点的法向量集合组成的Map中
                        //获取当前索引对应点的法向量集合
                        HashSet<Normal> hsn=hmn.get(tempInxex);
                        if(hsn==null)
                        {//若集合不存在则创建
                            hsn=new HashSet<Normal>();
                        }
                        //将此点的法向量添加到集合中
                        //由于Normal类重写了equals方法,因此同样的法向量不会重复出现在此点
                        //对应的法向量集合中
                        hsn.add(new Normal(vNormal[0],vNormal[1],vNormal[2]));
                        //将集合放进HsahMap中
                        hmn.put(tempInxex, hsn);
                    }

                    //将纹理坐标组织到结果纹理坐标列表中
                    //第0个顶点的纹理坐标
                    int indexTex=Integer.parseInt(tempsa[1].split("/")[1])-1;
                    altResult.add(alt.get(indexTex*2));
                    altResult.add(alt.get(indexTex*2+1));
                    //第1个顶点的纹理坐标
                    indexTex=Integer.parseInt(tempsa[2].split("/")[1])-1;
                    altResult.add(alt.get(indexTex*2));
                    altResult.add(alt.get(indexTex*2+1));
                    //第2个顶点的纹理坐标
                    indexTex=Integer.parseInt(tempsa[3].split("/")[1])-1;
                    altResult.add(alt.get(indexTex*2));
                    altResult.add(alt.get(indexTex*2+1));
                }
            }

            //生成顶点数组
            int size=alvResult.size();
            float[] vXYZ=new float[size];
            for(int i=0;i<size;i++)
            {
                vXYZ[i]=alvResult.get(i);
            }

            //生成法向量数组
            float[] nXYZ=new float[alFaceIndex.size()*3];
            int c=0;
            for(Integer i:alFaceIndex)
            {
                //根据当前点的索引从Map中取出一个法向量的集合
                HashSet<Normal> hsn=hmn.get(i);
                //求出平均法向量
                float[] tn=Normal.getAverage(hsn);
                //将计算出的平均法向量存放到法向量数组中
                nXYZ[c++]=tn[0];
                nXYZ[c++]=tn[1];
                nXYZ[c++]=tn[2];
            }

            //生成纹理数组
            size=altResult.size();
            float[] tST=new float[size];
            for(int i=0;i<size;i++)
            {
                tST[i]=altResult.get(i);
            }

            //创建3D物体对象
            lo=new LoadedObjectVertexNormalTexture(mv,vXYZ,nXYZ,tST);
        }
        catch(Exception e)
        {
            Log.d("load error", "load error");
            e.printStackTrace();
        }
        return lo;
    }
}

package test.com.opengles9_5;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.opengl.GLES20;
/**
 * Created by hbin on 2016/9/12.
 * 加载后的物体——仅携带顶点信息,颜色随机
 */
public class LoadedObjectVertexNormalTexture {
    int mProgram;//自定义渲染管线着色器程序id
    int muMVPMatrixHandle;//总变换矩阵引用
    int muMMatrixHandle;//位置、旋转变换矩阵
    int maPositionHandle; //顶点位置属性引用
    int maNormalHandle; //顶点法向量属性引用
    int maLightLocationHandle;//光源位置属性引用
    int maCameraHandle; //摄像机位置属性引用
    int maTexCoorHandle; //顶点纹理坐标属性引用
    String mVertexShader;//顶点着色器代码脚本
    String mFragmentShader;//片元着色器代码脚本

    FloatBuffer   mVertexBuffer;//顶点坐标数据缓冲
    FloatBuffer   mNormalBuffer;//顶点法向量数据缓冲
    FloatBuffer   mTexCoorBuffer;//顶点纹理坐标数据缓冲
    int vCount=0;


    public LoadedObjectVertexNormalTexture(MySurfaceView mv,float[] vertices,float[] normals,float texCoors[])
    {
        //初始化顶点坐标与着色数据
        initVertexData(vertices,normals,texCoors);
        //初始化shader
        initShader(mv);
    }

    //初始化顶点坐标与着色数据的方法
    public void initVertexData(float[] vertices,float[] normals,float texCoors[])
    {
        //顶点坐标数据的初始化================begin============================
        vCount=vertices.length/3;

        //创建顶点坐标数据缓冲
        //vertices.length*4是因为一个整数四个字节
        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());//设置字节顺序
        mVertexBuffer = vbb.asFloatBuffer();//转换为Float型缓冲
        mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据
        mVertexBuffer.position(0);//设置缓冲区起始位置
        //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
        //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题
        //顶点坐标数据的初始化================end============================

        //顶点法向量数据的初始化================begin============================
        ByteBuffer cbb = ByteBuffer.allocateDirect(normals.length*4);
        cbb.order(ByteOrder.nativeOrder());//设置字节顺序
        mNormalBuffer = cbb.asFloatBuffer();//转换为Float型缓冲
        mNormalBuffer.put(normals);//向缓冲区中放入顶点法向量数据
        mNormalBuffer.position(0);//设置缓冲区起始位置
        //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
        //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题
        //顶点着色数据的初始化================end============================

        //顶点纹理坐标数据的初始化================begin============================
        ByteBuffer tbb = ByteBuffer.allocateDirect(texCoors.length*4);
        tbb.order(ByteOrder.nativeOrder());//设置字节顺序
        mTexCoorBuffer = tbb.asFloatBuffer();//转换为Float型缓冲
        mTexCoorBuffer.put(texCoors);//向缓冲区中放入顶点纹理坐标数据
        mTexCoorBuffer.position(0);//设置缓冲区起始位置
        //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
        //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题
        //顶点纹理坐标数据的初始化================end============================
    }

    //初始化shader
    public void initShader(MySurfaceView mv)
    {
        //加载顶点着色器的脚本内容
        mVertexShader=ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
        //加载片元着色器的脚本内容
        mFragmentShader=ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
        //基于顶点着色器与片元着色器创建程序
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        //获取程序中顶点位置属性引用
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        //获取程序中顶点颜色属性引用
        maNormalHandle= GLES20.glGetAttribLocation(mProgram, "aNormal");
        //获取程序中总变换矩阵引用
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        //获取位置、旋转变换矩阵引用
        muMMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMMatrix");
        //获取程序中光源位置引用
        maLightLocationHandle=GLES20.glGetUniformLocation(mProgram, "uLightLocation");
        //获取程序中顶点纹理坐标属性引用
        maTexCoorHandle= GLES20.glGetAttribLocation(mProgram, "aTexCoor");
        //获取程序中摄像机位置引用
        maCameraHandle=GLES20.glGetUniformLocation(mProgram, "uCamera");
    }

    public void drawSelf(int texId)
    {
        //制定使用某套着色器程序
        GLES20.glUseProgram(mProgram);
        //将最终变换矩阵传入着色器程序
        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
        //将位置、旋转变换矩阵传入着色器程序
        GLES20.glUniformMatrix4fv(muMMatrixHandle, 1, false, MatrixState.getMMatrix(), 0);
        //将光源位置传入着色器程序
        GLES20.glUniform3fv(maLightLocationHandle, 1, MatrixState.lightPositionFB);
        //将摄像机位置传入着色器程序
        GLES20.glUniform3fv(maCameraHandle, 1, MatrixState.cameraFB);
        // 将顶点位置数据传入渲染管线
        GLES20.glVertexAttribPointer
                (
                        maPositionHandle,
                        3,
                        GLES20.GL_FLOAT,
                        false,
                        3*4,
                        mVertexBuffer
                );
        //将顶点法向量数据传入渲染管线
        GLES20.glVertexAttribPointer
                (
                        maNormalHandle,
                        3,
                        GLES20.GL_FLOAT,
                        false,
                        3*4,
                        mNormalBuffer
                );
        //为画笔指定顶点纹理坐标数据
        GLES20.glVertexAttribPointer
                (
                        maTexCoorHandle,
                        2,
                        GLES20.GL_FLOAT,
                        false,
                        2*4,
                        mTexCoorBuffer
                );
        //启用顶点位置、法向量、纹理坐标数据
        GLES20.glEnableVertexAttribArray(maPositionHandle);
        GLES20.glEnableVertexAttribArray(maNormalHandle);
        GLES20.glEnableVertexAttribArray(maTexCoorHandle);
        //绑定纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
        //绘制加载的物体
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
    }
}
顶点着色器

uniform mat4 uMVPMatrix; //总变换矩阵
uniform mat4 uMMatrix; //变换矩阵
uniform vec3 uLightLocation;	//光源位置
uniform vec3 uCamera;	//摄像机位置
attribute vec3 aPosition;  //顶点位置
attribute vec3 aNormal;    //顶点法向量
attribute vec2 aTexCoor;    //顶点纹理坐标
//用于传递给片元着色器的变量
varying vec4 ambient;
varying vec4 diffuse;
varying vec4 specular;
varying vec2 vTextureCoord;  
//定位光光照计算的方法
void pointLight(					//定位光光照计算的方法
  in vec3 normal,				//法向量
  inout vec4 ambient,			//环境光最终强度
  inout vec4 diffuse,				//散射光最终强度
  inout vec4 specular,			//镜面光最终强度
  in vec3 lightLocation,			//光源位置
  in vec4 lightAmbient,			//环境光强度
  in vec4 lightDiffuse,			//散射光强度
  in vec4 lightSpecular			//镜面光强度
){
  ambient=lightAmbient;			//直接得出环境光的最终强度  
  vec3 normalTarget=aPosition+normal;	//计算变换后的法向量
  vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
  newNormal=normalize(newNormal); 	//对法向量规格化
  //计算从表面点到摄像机的向量
  vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);  
  //计算从表面点到光源位置的向量vp
  vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);  
  vp=normalize(vp);//格式化vp
  vec3 halfVector=normalize(vp+eye);	//求视线与光线的半向量    
  float shininess=50.0;				//粗糙度,越小越光滑
  float nDotViewPosition=max(0.0,dot(newNormal,vp)); 	//求法向量与vp的点积与0的最大值
  diffuse=lightDiffuse*nDotViewPosition;				//计算散射光的最终强度
  float nDotViewHalfVector=dot(newNormal,halfVector);	//法线与半向量的点积 
  float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess)); 	//镜面反射光强度因子
  specular=lightSpecular*powerFactor;    			//计算镜面光的最终强度
}


void main()     
{ 
   gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置  
   
   vec4 ambientTemp, diffuseTemp, specularTemp;   //存放环境光、散射光、镜面反射光的临时变量      
   pointLight(normalize(aNormal),ambientTemp,diffuseTemp,specularTemp,uLightLocation,vec4(0.15,0.15,0.15,1.0),vec4(0.9,0.9,0.9,1.0),vec4(0.4,0.4,0.4,1.0));
   
   ambient=ambientTemp;
   diffuse=diffuseTemp;
   specular=specularTemp;
   vTextureCoord = aTexCoor;//将接收的纹理坐标传递给片元着色器
}                      

片元着色器

precision mediump float;
         uniform sampler2D sTexture;//纹理内容数据
         //接收从顶点着色器过来的参数
         varying vec4 ambient;
         varying vec4 diffuse;
         varying vec4 specular;
         varying vec2 vTextureCoord;

         void main()
         {
            //将计算出的颜色给此片元
            vec4 finalColor=texture2D(sTexture, vTextureCoord);
            //给此片元颜色值
            gl_FragColor = finalColor*ambient+finalColor*specular+finalColor*diffuse;

    }

3d模型文件(其中  V表示顶点坐标,vn表示顶点法向量,vt表示纹理坐标)

# 3ds Max Wavefront OBJ Exporter v0.94b - (c)2007 guruware
# �������ļ�:13.09.2012 15:36:47

#
# object Teapot01
#

v  15.7604 27.2033 -0.2686
v  14.5306 27.2033 5.9599
v  14.3264 28.0401 5.8730
v  15.5391 28.0401 -0.2686
v  14.5633 28.3191 5.9738
v  15.7959 28.3191 -0.2686
v  15.0453 28.0401 6.1788
v  16.3183 28.0401 -0.2686
v  15.5762 27.2033 6.4048
v  16.8939 27.2033 -0.2686
v  11.1585 27.2033 10.9982
v  11.0014 28.0401 10.8410
v  11.1837 28.3191 11.0233
v  11.5546 28.0401 11.3943
v  11.9633 27.2033 11.8029
v  6.1203 27.2033 14.3702
v  6.0334 28.0401 14.1660
v  6.1342 28.3191 14.4029
v  6.3392 28.0401 14.8849
v  6.5651 27.2033 15.4159
v  -0.1082 27.2033 15.6001
v  -0.1082 28.0401 15.3787
v  -0.1082 28.3191 15.6355
v  -0.1082 28.0401 16.1579
v  -0.1082 27.2033 16.7335
v  -6.7670 27.2033 14.3702
v  -6.4313 28.0401 14.1660
v  -6.4043 28.3191 14.4029
v  -6.5623 28.0401 14.8849
v  -6.7815 27.2033 15.4159
v  -11.7574 27.2033 10.9982
v  -11.3791 28.0401 10.8410
v  -11.4478 28.3191 11.0233
v  -11.7770 28.0401 11.3943
v  -12.1796 27.2033 11.8029
v  -14.8904 27.2033 5.9599
v  -14.6033 28.0401 5.8730
v  
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值