【Opengl Android】在安卓上渲染一个obj模型

Opengl 专栏收录该内容
1 篇文章 0 订阅

工具:eclipse

一、获得一个obj文件并分析内容

3D溜溜网 点击打开链接 可以下载到很多模型文件 用3dmax打开 导出外部格式文件obj  即可得到一个obj模型

将格式改为txt  即可看到里面的数据内容

有些模型文件还会带有mlt文件

数据详解请转——https://www.douban.com/note/142379570/

二、获得obj文件中的数据

一是自己分析里面的数据 请转 https://blog.csdn.net/xiaxl/article/details/77048507

提供了mlt和obj模型的解析方法,大概是将每一行的数据都遍历 分析格式 存入数组 最后会得到几个顶点,法向量,贴图一一对应的数组

二就是 比较懒得我就开始努力的找包 了

首先是obj2opengl.pl  一个可以通过命令行将obj模型自动分析然后转出成.h文件的库,里面会生成等数量的vertices、texture和normal数组

请转http://maider.blog.sohu.com/281704711.html

但.h放在android项目中不能直接使用(当然可以直接将.h里的数据复制到一个专门存数据的新类中,但数据少的时候还行,比如一个立方体可能也就几十行数据,但复杂的模型就会比较困难,需要导入大量float数组,这点上面链接中的博客也有说到解决方法)

或者是用jni方式来解析c++文件

BUT 本懒还是选择勤奋的找包

然后 org.obj2openjl 了解一下 https://github.com/miffels/org.obj2openjl

主要就是

RawOpenGLModel openGLModel = new Obj2OpenJL().convert("file");
OpenGLModelData openGLModelData = openGLModel.normalize().center().getDataForGLDrawElements();

float[] Vertices = openGLModelData.getVertices();
float[] Normals = openGLModelData.getNormals();
float[] TexCoords = openGLModelData.getTextureCoordinates();

然后将你的obj文件放到assets文件下就可以了

这个时候解析完的数据就已经赋给了以上数组 我们就可以开始渲染了


三、Android 中的 Opengl渲染

推荐<<Opengl ES 2 for Android>>这本书吧 你会详细了解在Android中opengl的渲染流程,摄像机设置,投影设置

还有一些这方面的书籍我也都一起打包了 https://download.csdn.net/download/qq_35263780/10366356


就这我自己的小项目讲一讲吧


首先是Activity的主类

获取opengl 读取版本信息检查是否支持  得到一个opengl的视图类 在其中开始渲染renderer类中的东西了

glSurfaceView = new GLSurfaceView(this);
final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 20000;
		
		if (supportsEs2){
			glSurfaceView.setEGLContextClientVersion(2);
			try {
				glSurfaceView.setRenderer(new FirstOpenGLProjectRender(this));
			} catch (Exception e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
setContentView(glSurfaceView);

在Activity类中,还有一些处理android activity 生命周期的事件 类似框架如下

public class FirstOpenGLProjectActivity extends AppCompatActivity {

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

	@Override
	protected void onPause(){
		super.onPause();
		
	}
	
	@Override
	protected void onResume(){
		super.onResume();	
	}
}

创建renderer类(继承 Renderer )


在其中也应有一些处理生命周期的函数

包括

public void onSurfaceCreated(GL10 gl, EGLConfig config)

public void onSurfaceChanged(GL10 gl, int width, int height)

public void onDrawFrame(GL10 gl)

故名思议 一个是渲染前调用,一个是当屏幕改变时调用,一个是时刻调用

有点像js里的start() update()这样的函数机制

旋转缩放动态效果(投影矩阵变换)大概都是放在onSurfaceChanged中的(不对的话请纠正我)

那么接下来我们就需要获取我们的数组信息 并将其传递给底层的opengl渲染

	private final FloatBuffer VertexBuffer;
	private final FloatBuffer NormalBuffer;
	private final FloatBuffer TexureBuffer;
        private int VertexCount;
	   InputStream it = null;
		try {
			it = context.getAssets().open("obj1.obj");
		} catch (IOException e) {
			e.printStackTrace();
		}
		RawOpenGLModel openGLModel = new Obj2OpenJL().convert(it);
		OpenGLModelData openGLModelData = openGLModel.normalize().center().getDataForGLDrawArrays();			

		
		float[] Vertices = openGLModelData.getVertices();
		VertexCount = Vertices.length / 3; 
		VertexBuffer = ByteBuffer.allocateDirect(Vertices.length * 4).order(ByteOrder.nativeOrder())
	            .asFloatBuffer();
		VertexBuffer.put(Vertices);	
		VertexBuffer.position(0);//从0开始读
		
		float[] Normals = openGLModelData.getNormals();
		NormalBuffer = ByteBuffer.allocateDirect(Normals.length * 4).order(ByteOrder
                .nativeOrder()).asFloatBuffer();
        NormalBuffer.put(Normals);
        NormalBuffer.position(0);
		
		float[] TexCoords = openGLModelData.getTextureCoordinates();
        TexureBuffer = ByteBuffer.allocateDirect(TexCoords.length * 4).order(ByteOrder
                .nativeOrder()).asFloatBuffer();
        TexureBuffer.put(TexCoords);
        TexureBuffer.position(0);
		  

如上的第一段代码,应该在这篇博客打开头就见到了,我们直接分析了资源文件中的

obj1.obj

并通过

openGLModelData.getVertices()

等方式得到了三个数组

大家应该也注意到了 我们将得到的数据信息又传递给了一个FloatBuffer类型的变量,这和opengl的机制有关,我们只有通过这个数据流才能讲这些信息传递给底层着色器,从而输出渲染所需要的顶点和颜色信息

可参考<<Opengl ES 2 for Android>>的2.4节 会有详细介绍

所以说到着色器

我们在项目中如何建立着色器呢

在这个文件夹下新建项目文件 并保存为glsl文件  vertex即顶点着色器 fragment即片段着色器

故名思议 顶点着色器里一定要有

gl_Position = 什么什么的;

片段着色器里一定要有

gl_FragColor = 什么什么的;

然后这两个文件是用c语言的语法编译的 ,简单的着色器里面大概都只有一个void main()函数,

那么变量的类型和传值的方式

请转 https://www.cnblogs.com/salam/archive/2016/01/08/5113572.html

说的肯定比我详细认真


不过因为opengl是这样的语言机制,发生bug不会报错,并不是特别好找(安卓应用闪退了一个星期都没找到原因 一直以为是哪机制不对),也是因为刚开始接触对着色器的理解不是特别深,最开始就会出现很多奇怪的问题,像我就是一直没明白法向量和光线那块,所以gl_FlagColor的数据不太对,不知道BUG在哪也只能硬着头皮找资料,多看别人的代码,渐渐一点点好些,最后运出了完整的小实例也是美滋滋,所以gl_position和gl_FlagColor是要数据量是要对应的阿 不然就会闪退(是最开始我就是因为不会处理法向量的数据 瞎几把定义了颜色),如果运出来不闪退就是一片黑,那就是

你着色器是不是哪里变量名拼错了

你着色器里类型定义对了么 什么时候是mat3 什么时候是vec4

类型数据读的有没有问题阿

你渲染的顺序对不对阿

什么之类的

如果是看书跟教程基本上也就会出现第一种问题吧哈哈哈(是我了

不过现在应该也有编译着色器的办法了吧

所以安利 https://www.jianshu.com/p/8687a040eb48

[大概安利了很多博客 虽然我懒是一方面 但是真的让我受益很多 看这些博客肯定比看我的要有用多了… 也不浪费你们找资料的时间了哈哈]

还有觉得这句话很有趣

希望我以后能体会到这种感觉吧哈哈哈

扯回来

上我得着色器代码

顶点着色器

uniform mat4 u_Matrix;//变换矩阵  
uniform mat4 u_MVPMatrix; //总变换矩阵  


attribute vec3 a_Position;
attribute vec3 a_Normal;
attribute vec2 a_TextureCoord;


varying vec3 v_FragPosition;
varying vec3 v_Normal;
varying vec2 v_TextureCoord;   		

void main()                    
{
    v_TextureCoord = a_TextureCoord;  
    v_Normal = a_Normal;      
    v_FragPosition = vec3(u_Matrix * vec4(a_Position,1.0)); 
                
    gl_Position = u_MVPMatrix *  vec4(a_Position,1);

}   

片段着色器

precision mediump float; 							

varying vec3 v_FragPosition;
varying vec3 v_Normal;
varying vec2 v_TextureCoord; //纹理坐标易变变量  两位

uniform sampler2D u_Texture; //纹理采样器,代表一副纹理        	   								
uniform vec3 u_LightLocation; 
  
void main()                    		
{    
    vec3 normal = normalize(v_Normal); 
    vec3 lightDir = normalize(u_LightLocation-v_FragPosition);
    float factor = max(0.0, dot(normal, lightDir));
    vec4 diffuse = factor * vec4(1.0,1.0,1.0,1.0);                         	
    gl_FragColor = (diffuse + vec4(0.6,0.6,0.6,1))*texture2D(u_Texture, vec2(v_TextureCoord.s,v_TextureCoord.t));                              		
}

总结一下就是  attribute 和 uniform 类型的变量要在renderer类中有定义 需要从中传值过来(floatbuffer),类似属性值, 而Varying类的变量是两个着色器之间要需要相互传递的数据,比如我在顶点着色器中定义了一个

varying vec3 v_Normal;

那在片段着色器里也一定要有

varying vec3 v_Normal;

这个值可以是从 attribute 类型的变量中获取的

 v_Normal = a_Normal;    

写好了着色器代码之后 就要在renderer类中编译和连接它

在onSurfaceCreated调用此方法

public void UseProgram(){
		 String vertexShaderSource = TextResourceReader
				 .readTextFileFromResource(context, R.raw.simple_vertex_shader);  //连接到glsl文件 simple_vertex_shader
		 String fragmentShaderSource = TextResourceReader
				 .readTextFileFromResource(context, R.raw.simple_fragment_shader);//连接到glsl文件 simple_fragment_shader
		 int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
		 int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);
		 //连接着色器们
		 program = ShaderHelper.linkProgram(vertexShader, fragmentShader);  //将顶点着色器和片段着色器 连接到程序
		
		 if(LoggerConfig.ON){
			 ShaderHelper.validateProgram(program);
		 }
		 //在绘制任何东西到屏幕上时都要用这里定义的程序
		 glUseProgram(program);
	}

接下来就要把我们之前获取到的数据流 传递给着色器

定义变量

	private int aPositionHandle;

获得着色器中的属性位置 并赋给变量

 aPositionHandle = glGetAttribLocation(program,"a_Position"); //获得simple_vertex_shader中属性位置 分配位置编号

和数据流联系起来 开始读数据

glVertexAttribPointer(aPositionHandle,3,GL_FLOAT,
				 false, 0, VertexBuffer);
glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, Buffer ptr)

分别是(属性位置;一个顶点有多少个分量;数据类型;默认整型数据;告诉Opengl去哪里,读数据在缓冲区vertexData可以找到aPositionLocation对应的数据)

最后

glEnableVertexAttribArray(aPositionHandle);  //使能

至此我们的着色器就得到了所有顶点数据

其他属性的数据获取也是相似的

但材质的纹理图还需要另外初始化


	//初始化纹理
	public void initTexture() {
	    int[] textures = new int[1];//生成纹理ID
	    glGenTextures(
	        1,//产生的纹理id的数量
	        textures,//纹理id的数组
	        0//偏移量
	        );
	    textureId = textures[0];//获取产生的纹理id
	    glBindTexture(GL_TEXTURE_2D, textureId);//绑定纹理Id
	    glTexParameterf(GL_TEXTURE_2D,//设置MIN采样方式
	    GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	    glTexParameterf(GL_TEXTURE_2D,//设置MAG采样方式
	        GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	    glTexParameterf(GL_TEXTURE_2D,//设置S轴拉伸方式
	        GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	    glTexParameterf(GL_TEXTURE_2D, //设置T轴拉伸方式
	        GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	    //通过输入流加载图片
	    InputStream is = context.getResources().openRawResource(//建立只想纹理图的流
	        R.drawable.t4);
	    Bitmap bitmapTmp;
	    try {bitmapTmp = BitmapFactory.decodeStream(is);}//从流中加载图片内容
	    finally{try{is.close();}catch(IOException e){e.printStackTrace();}}
	    GLUtils.texImage2D(//实际加载纹理进显存
	        GL_TEXTURE_2D,//纹理类型
	        0,//纹理的层次,0表示基本图像层,可以理解为直接贴图
	        bitmapTmp,//纹理图像
	        0//纹理边框尺寸
	        );
	    bitmapTmp.recycle();//纹理加载成功后释放内存中的纹理图,否则纹理较多时会造成内存崩溃
	}
纹理
		 initTexture();
		 glUniform1i(uTextureHandle, 0);//对应0号纹理单元
		 
		 glActiveTexture(GL_TEXTURE0 + 0);
		 glBindTexture(GL_TEXTURE_2D, textureId);

具体请转 https://blog.csdn.net/sz66cm/article/details/54317272

将获得的数据全赋给着色器后 运行可以看到

【只加了纹理没有光线】

【加了光线】


那 最后源码

https://download.csdn.net/download/qq_35263780/10366532






  • 4
    点赞
  • 3
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值