Android OpenGL添加光照和材料属性

Igor Android评论7,995字数 6834阅读22分46秒阅读模式

在上一篇文章【 Android OpenGL显示任意3D模型文件 】中,我们学习了如何读取并显示STL格式的3D文件,但是,最后,看到的并没有添加光照效果,导致虽然模型在旋转,但是我们看到的画面却像一个平面。今天我们开始学习如何给模型添加灯照效果,以及如何为模型添加材料属性,使得最终看到的旋转模型真正为3D效果。首先,看看最终效果,如下图所示:

Android OpenGL添加光照和材料属性-图片1
光照效果
Android OpenGL添加光照和材料属性-图片1
截图
Android OpenGL添加光照和材料属性-图片1
材质效果

1 光照效果

因为我们所做的立体效果是根据真实世界原理来计算的,所以很有必要去了解在现实世界中,我们所看到的一个物体有哪些光。

1.1 真实世界中的光照

我们知道,在黑暗中,当我们将手电筒对准某个物体时,我们所看到的该物体的“亮度”有3种:

  • 物体表面发生镜面反射部分(Specular),一般是白色。
  • 物体表面发生漫反射部分(Diffuse),一般是物体表面的颜色。
  • 物体表面没有照射到光的部分,即通过环境光(Ambient)照射,在黑暗中环境光是黑色。

如下图所示(图片出自www.guidebee.info):

Android OpenGL添加光照和材料属性-图片2
光照图示

从上图中也可以看出,光源的位置也会影响到我们所看到的最终画面。显然,我们只需控制好光源位置、镜面反射颜色、漫反射颜色、环境光颜色这四个参数,就可以做到了。

1.2 Android OpenGL相关API

1.2.1 光源 GL10.GL_LIGHT0

0号光源,该光源的默认颜色为白色,即RGBA(1.0,1.0,1.0,1.0),漫反射和镜面反射也为白色。类似的,还有其他光源如GL10.GL_LIGHT1,系统提供了0~78种光源,其他的光源默认为黑色,即RGBA(0.0,0.0,0.0,1.0).

开启光源也非常简单:

  1. //启用光照功能
  2. gl.glEnable(GL10.GL_LIGHTING);
  3. //开启0号灯
  4. gl.glEnable(GL10.GL_LIGHT0);

1.2.2 设置各种反射光颜色

一旦开启了光照功能,就可以通过glLightfv函数来指定各种反射光的颜色了,glLightfv函数如下:

  1. public void glLightfv(int light,int pname, FloatBuffer params)
  2. public void glLightfv(int light,int pname,float[] params,int offset)
  3. public void glLightf(int light,int pname,float param)

其中,

  • light: 指光源的序号,OpenGL ES可以设置从07共八个光源。
  • pname: 光源参数名称,可以有如下:
  • GL_SPOT_EXPONENT
  • GL_SPOT_CUTOFF
  • GL_CONSTANT_ATTENUATION
  • GL_LINEAR_ATTENUATION
  • GL_QUADRATIC_ATTENUATION
  • GL_AMBIENT(用于设置环境光颜色)
  • GL_DIFFUSE(用于设置漫反射光颜色)
  • GL_SPECULAR(用于设置镜面反射光颜色)
  • GL_SPOT_DIRECTION
  • GL_POSITION(用于设置光源位置)
  • params: 参数的值(数组或是Buffer类型),数组里面含有4个值分别表示R,G,B,A。

指定光源的位置的参数为GL_POSITION,位置的值为(x,y,z,w),如果是平行光则将w 设为0,此时,(x,y,z)为平行光的方向:

1.3 代码实现

在上一篇的基础上,直接修改GLRenderer.java文件,添加一个openLight函数:

  1. public void openLight(GL10 gl) {
  2. gl.glEnable(GL10.GL_LIGHTING);
  3. gl.glEnable(GL10.GL_LIGHT0);
  4. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, Util.floatToBuffer(ambient));
  5. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, Util.floatToBuffer(diffuse));
  6. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, Util.floatToBuffer(specular));
  7. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, Util.floatToBuffer(lightPosition));
  8. }

另外,分别添加我们设定的各种反射光的颜色:

  1. float[] ambient = {0.9f, 0.9f, 0.9f, 1.0f,};
  2. float[] diffuse = {0.5f, 0.5f, 0.5f, 1.0f,};
  3. float[] specular = {1.0f, 1.0f, 1.0f, 1.0f,};
  4. float[] lightPosition = {0.5f, 0.5f, 0.5f, 0.0f,};

最后,在onSurfaceCreated函数里面调用一下openLight(gl);函数即可。最终效果如下:

Android OpenGL添加光照和材料属性-图片3
光照效果

2 材料属性

前面我们提到了可以为模型设置不同的材料属性,本节中,我们一起学习如何为模型设定不同的材料属性。我们知道,同样是一束光,照在不同颜色材料的物体上面,我们所看到的是不同的,反射出来的不仅仅颜色不同,光泽也是不同的。换句话说,不同的材质对最终的渲染效果影响很大!

材料的属性设置和光源的设置有些类似,用到的函数

  1. public void glMaterialf(int face,int pname,float param)
  2. public void glMaterialfv(int face,int pname,float[] params,int offset)
  3. public void glMaterialfv(int face,int pname,FloatBuffer params)

其中,

  • face : 在OpenGL ES中只能使用GL_FRONT_AND_BACK,表示修改物体的前面和后面的材质光线属性。
  • pname: 参数类型,这些参数用在光照方程。可以取如下值:
  • GL_AMBIENT
  • GL_DIFFUSE
  • GL_SPECULAR
  • GL_EMISSION
  • GL_SHININESS
  • param:指定反射的颜色。

跟设置光照类似,设置材料属性首先需要定义各种反射光的颜色:

  1. float[] materialAmb = {0.4f, 0.4f, 1.0f, 1.0f};
  2. float[] materialDiff = {0.0f, 0.0f, 1.0f, 1.0f};//漫反射设置蓝色
  3. float[] materialSpec = {1.0f, 0.5f, 0.0f, 1.0f};

然后就是将这些颜色通过glMaterialfv函数设置进去:

  1. public void enableMaterial(GL10 gl) {
  2. //材料对环境光的反射情况
  3. gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, Util.floatToBuffer(materialAmb));
  4. //散射光的反射情况
  5. gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, Util.floatToBuffer(materialDiff));
  6. //镜面光的反射情况
  7. gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, Util.floatToBuffer(materialSpec));
  8. }

当然了,最后也别忘记了在onSurfaceCreated函数中调用 enableMaterial(gl);,最后看看效果:

Android OpenGL添加光照和材料属性-图片4
材质效果

3 完整的GLRenderer类

最后项目代码就不上传了,直接参考上一篇的文章中的源码即可,本位值修改了GLRenderer类,把该类的完整源码贴上:

  1. package com.hc.opengl;
  2. import android.content.Context;
  3. import android.opengl.GLSurfaceView;
  4. import android.opengl.GLU;
  5. import java.io.IOException;
  6. import javax.microedition.khronos.egl.EGLConfig;
  7. import javax.microedition.khronos.opengles.GL10;
  8. /**
  9. * Package com.hc.opengl
  10. * Created by HuaChao on 2016/8/9.
  11. */
  12. public class GLRenderer implements GLSurfaceView.Renderer {
  13. private Model model;
  14. private Point mCenterPoint;
  15. private Point eye = new Point(0, 0, -3);
  16. private Point up = new Point(0, 1, 0);
  17. private Point center = new Point(0, 0, 0);
  18. private float mScalef = 1;
  19. private float mDegree = 0;
  20. public GLRenderer(Context context) {
  21. try {
  22. model = new STLReader().parserBinStlInAssets(context, "huba.stl");
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. public void rotate(float degree) {
  28. mDegree = degree;
  29. }
  30. @Override
  31. public void onDrawFrame(GL10 gl) {
  32. // 清除屏幕和深度缓存
  33. gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
  34. gl.glLoadIdentity();// 重置当前的模型观察矩阵
  35. //眼睛对着原点看
  36. GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
  37. center.y, center.z, up.x, up.y, up.z);
  38. //为了能有立体感觉,通过改变mDegree值,让模型不断旋转
  39. gl.glRotatef(mDegree, 0, 1, 0);
  40. //将模型放缩到View刚好装下
  41. gl.glScalef(mScalef, mScalef, mScalef);
  42. //把模型移动到原点
  43. gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
  44. -mCenterPoint.z);
  45. //===================begin==============================//
  46. //允许给每个顶点设置法向量
  47. gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
  48. // 允许设置顶点
  49. gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  50. // 允许设置颜色
  51. //设置法向量数据源
  52. gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
  53. // 设置三角形顶点数据源
  54. gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());
  55. // 绘制三角形
  56. gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);
  57. // 取消顶点设置
  58. gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
  59. //取消法向量设置
  60. gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
  61. //=====================end============================//
  62. }
  63. @Override
  64. public void onSurfaceChanged(GL10 gl, int width, int height) {
  65. // 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角,(width, height)指定了视口的大小
  66. gl.glViewport(0, 0, width, height);
  67. gl.glMatrixMode(GL10.GL_PROJECTION); // 设置投影矩阵
  68. gl.glLoadIdentity(); // 设置矩阵为单位矩阵,相当于重置矩阵
  69. GLU.gluPerspective(gl, 45.0f, ((float) width) / height, 1f, 100f);// 设置透视范围
  70. //以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
  71. gl.glMatrixMode(GL10.GL_MODELVIEW);
  72. gl.glLoadIdentity();
  73. }
  74. @Override
  75. public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  76. gl.glEnable(GL10.GL_DEPTH_TEST); // 启用深度缓存
  77. gl.glClearColor(0f, 0f, 0f, 0f);// 设置深度缓存值
  78. gl.glDepthFunc(GL10.GL_LEQUAL); // 设置深度缓存比较函数
  79. gl.glShadeModel(GL10.GL_SMOOTH);// 设置阴影模式GL_SMOOTH
  80. //开启光
  81. openLight(gl);
  82. enableMaterial(gl);
  83. float r = model.getR();
  84. //r是半径,不是直径,因此用0.5/r可以算出放缩比例
  85. mScalef = 0.5f / r;
  86. mCenterPoint = model.getCentrePoint();
  87. }
  88. float[] ambient = {0.9f, 0.9f, 0.9f, 1.0f};
  89. float[] diffuse = {0.5f, 0.5f, 0.5f, 1.0f};
  90. float[] specular = {1.0f, 1.0f, 1.0f, 1.0f};
  91. float[] lightPosition = {0.5f, 0.5f, 0.5f, 0.0f};
  92. public void openLight(GL10 gl) {
  93. gl.glEnable(GL10.GL_LIGHTING);
  94. gl.glEnable(GL10.GL_LIGHT0);
  95. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, Util.floatToBuffer(ambient));
  96. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, Util.floatToBuffer(diffuse));
  97. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, Util.floatToBuffer(specular));
  98. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, Util.floatToBuffer(lightPosition));
  99. }
  100. float[] materialAmb = {0.4f, 0.4f, 1.0f, 1.0f,};
  101. float[] materialDiff = {0.0f, 0.0f, 1.0f, 1.0f,};
  102. float[] materialSpec = {1.0f, 0.5f, 0.0f, 1.0f,};
  103. public void enableMaterial(GL10 gl) {
  104. //材料对环境光的反射情况
  105. gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, Util.floatToBuffer(materialAmb));
  106. //散射光的反射情况
  107. gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, Util.floatToBuffer(materialDiff));
  108. //镜面光的反射情况
  109. gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, Util.floatToBuffer(materialSpec));
  110. }
  111. }

最后感谢大家的关注,欢迎关注huachao1001的博客,http://blog.csdn.net/huachao10019

作者:huachao1001
链接:https://www.jianshu.com/p/f16fcd35d91e
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

来源: Android OpenGL添加光照和材料属性 - 简书

文章末尾固定信息

weinxin
我的微信
我的微信
一个码农、工程狮、集能量和智慧于一身的、DIY高手、小伙伴er很多的、80后奶爸。
 
Igor
  • 本文由 Igor 发表于 2018-11-0721:58:26
Vue使用JsBridge与APP交互 Web服务器

Vue使用JsBridge与APP交互

现在在做的项目是 hybrid 开发,H5 页面会嵌入到 IOS 客户端 app 中,于是就涉及到了 H5 与 IOS 交互的问题。在这里记录一下项目中用到的交互方式,重点介绍 WebViewJava...
OpenGL

如何选择 HTML5 游戏引擎

原生手游市场已是红海,腾讯、网易等寡头独霸天下,H5游戏市场或将成为下一个风口。据笔者所知,很多H5游戏开发团队由于选择引擎不慎导致项目甚至团队夭折。如何选择适合团队和项目的引擎,笔者通过学习和项目实...
匿名

发表评论

匿名网友
:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:
确定

拖动滑块以完成验证
加载中...