材料:
我们知道,决定物体实际颜色的是散射光,确切的说是材料的散射光反射率,而且openGL中,我们一般把材料的环境光和反射光反射率设置成同样的值,那么在设置过了全局环境光之后,就要设置材料的反射率:
//设置材料的反射率(环境光和散射光) gl.glLightfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT_AND_DIFFUSE,BufferUtils.arr2FloatBuffer(ambient_and_diffuse));
注:第一个参数为正面和背面的反射率,第二个参数为环境光和散射光反射率,第三个参数同样是,把四个浮点数的分量值组成的float数组转换成缓冲区数据。
这样在启用光照的时候就可以观察到在环境光一定(默认值为0.2)的情况下,材料不同反射率的不同效果。
颜色追踪:
openGL中提供里两种方法设置材料的反射率,如上是一种,还有一种是启用颜色追踪:
颜色追踪顾名思义就是利用组成颜色的四个分量值来表示光的成分,颜色追踪同样是状态机,所以开启颜色追踪:
//启用颜色追踪 gl.glEnable(GL10.GL_COLOR_MATERIAL);此时openGL就会从颜色状态机( gl.glColor4f() )中拿到各个分量值,从而设置光的成分。
光源:
openGL至少支持8中光源:光源0~7。
在我们启用了光照、设置了材料的反射率之后,我们所绘制的物体还是不太容易看清楚,因为此时物体的光照主要来自于全局环境光,而这部分光是很少量的,所以,我们要看的清楚所绘制的物体,就至少要启动一个光源:
//启用光源0 gl.glEnable(GL10.GL_LIGHT0);那么同样的,启用一个光源之后就要为这个光源设置环境光、散射光、以及镜面光(如需)。
//设置光源0的环境光 float [] light0Ambient = { r3,g3,b3,a3 }; gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_AMBIENT,BufferUtils.arr2FloatBuffer(light0Ambient));
//设置光源0的散射光 float [] light0Diffuse = { r4,g4,b4,a4 }; gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE,BufferUtils.arr2FloatBuffer(light0Diffuse));设置好了光源0光的分量后,还需指定光源0的位置:
//光源0的位置(1.0f表示光源就在此处,0表示位于无限远处---平行光) float [] light0Position = {x,y,z,1.0f}; gl.glLightfv( GL10.GL_LIGHT0,GL10.GL_POSITION,BufferUtils.arr2FloatBuffer(light0Position) );注:x,y,z是坐标轴,第四个参数只能有两个值1或者0,为1时代表光源就在此处,为0时代表光源位于无限远处,所以照射过来的光就是平行光。
效果图:
附代码:
public class MyLightingRenderer extends AbstractRenderer{ @Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT);//设置清屏色 gl.glColor4f(1f, 1f, 1f, 1f);//设置绘图颜色 gl.glMatrixMode(GL10.GL_MODELVIEW);//模型视图矩阵 gl.glLoadIdentity();//加载单位矩阵 GLU.gluLookAt(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0);//放置眼球位置 gl.glRotatef(xRotate, 1, 0, 0);//x轴旋转角度 gl.glRotatef(yRotate, 0, 1, 0);//y轴旋转角度 gl.glClear(GL10.GL_DEPTH_BUFFER_BIT); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glColor4f(color_r, color_g, color_b, color_a); gl.glShadeModel(GL10.GL_FLAT); /*********************光照**************************/ if(open_lighting){ //启用光照 gl.glEnable(GL10.GL_LIGHTING); }else{ //禁止光照 gl.glDisable(GL10.GL_LIGHTING); } float [] global_ambient = {r,g,b,a,}; float [] ambient_and_diffuse = {r1,g1,b1,a1}; //设置全局环境光 gl.glLightModelfv(GL10.GL_LIGHT_MODEL_AMBIENT, BufferUtils.arr2FloatBuffer(global_ambient)); //设置材料的反射率(环境光和散射光) gl.glLightfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT_AND_DIFFUSE,BufferUtils.arr2FloatBuffer(ambient_and_diffuse)); //颜色追踪 if(open_ColorMatrial){ //启用颜色追踪 gl.glEnable(GL10.GL_COLOR_MATERIAL); } else { //禁止颜色追踪 gl.glDisable(GL10.GL_COLOR_MATERIAL); } if(open_light0){ //启用光源0 gl.glEnable(GL10.GL_LIGHT0); }else { //禁止光源0 gl.glDisable(GL10.GL_LIGHT0); } //设置光源0的环境光 float [] light0Ambient = { r3,g3,b3,a3 }; gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_AMBIENT,BufferUtils.arr2FloatBuffer(light0Ambient)); //设置光源0的散射光 float [] light0Diffuse = { r4,g4,b4,a4 }; gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE,BufferUtils.arr2FloatBuffer(light0Diffuse)); //光源0的位置(1.0f表示光源就在此处,0表示位于无限远处---平行光) float [] light0Position = {x,y,z,1.0f}; gl.glLightfv( GL10.GL_LIGHT0,GL10.GL_POSITION,BufferUtils.arr2FloatBuffer(light0Position) ); //画一个球 BufferUtils.drawSphere(gl,0.4f,8,8); } }MainActivity:
public class MainActivity extends Activity { private AbstractRenderer renderer; MyGLSurfaceView view; private Dialog d; private Resources r; protected void onCreate(Bundle savedInstanceState) { r = this.getResources(); super.onCreate(savedInstanceState); view = new MyGLSurfaceView(this); //render渲染器 renderer = new MyLightingRenderer(); // view.setEGLConfigChooser(5,6,5,0,8,8); view.setRenderer(renderer); // view.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//默认渲染模式,持续渲染。 // view.setRenderMode( GLSurfaceView.RENDERMODE_WHEN_DIRTY );//脏渲染(命令渲染)脏渲染模式下ondrawframe()方法只调用一次。 setContentView(view); buildDialog(); addCheckBoxEvent(); } //添加复选框事件 private void addCheckBoxEvent() { //匿名内部类:box监听 CompoundButton.OnCheckedChangeListener box1 = new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { /* //通过反射实现 try { int id = buttonView.getId(); String name = r.getResourceName( id ); Log.i("xiaoyu",name); name.substring(name.lastIndexOf("_") + 1); Class clazz = renderer.getClass(); Field f = clazz.getField(name); f.setAccessible(true); f.set(renderer,isChecked); } catch (Exception e) { e.printStackTrace(); }*/ switch (buttonView.getId()){ case R.id.cb_open_lighting: renderer.open_lighting = isChecked; case R.id.cb_open_ColorMatrial: renderer.open_ColorMatrial = isChecked; case R.id.cb_open_light0: renderer.open_light0 = isChecked; } } }; //seekbar监听 final SeekBar.OnSeekBarChangeListener bar1 = new SeekBar.OnSeekBarChangeListener(){ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { float scale = (float)progress/(float)seekBar.getMax(); switch (seekBar.getId()){ //全局环境光的三个seekbar case R.id.sb_r: renderer.r = scale; case R.id.sb_g: renderer.g = scale; case R.id.sb_b: renderer.b = scale; //材料的环境光和散射光的反射率的三个seekbar case R.id.sb_r1: renderer.r1 = scale; case R.id.sb_g1: renderer.g1 = scale; case R.id.sb_b1: renderer.b1 = scale; //颜色追踪器的三个seekbar case R.id.sb_r2: renderer.color_r = scale; case R.id.sb_g2: renderer.color_g = scale; case R.id.sb_b2: renderer.color_b = scale; //光源0环境光的三个seekbar case R.id.sb_r3_ambient: renderer.r3 = scale; case R.id.sb_g3_ambient: renderer.g3 = scale; case R.id.sb_b3_ambient: renderer.b3 = scale; //光源0散射光的三个seekbar case R.id.sb_r3_diffuse: renderer.r4 = scale; case R.id.sb_g3_diffuse: renderer.g4 = scale; case R.id.sb_b3_diffuse: renderer.b4 = scale; //光源0位置 case R.id.sb_x: renderer.x = scale * 20 - 10f; case R.id.sb_y: renderer.y = scale * 20 - 10f; case R.id.sb_z: renderer.z = scale * 20 - 10f; } } public void onStartTrackingTouch(SeekBar seekBar) { } public void onStopTrackingTouch(SeekBar seekBar) { } }; /**************************利用反射动态给CheckBox和SeekBar加监听器*********************************/ try { Class clazz = R.id.class; Field [] fs = clazz.getDeclaredFields();//得到R的所有字段 CheckBox box = null; SeekBar bar = null; //遍历fs,因为R文件中的每个字段都是静态整形值,所以,直接f.get(null) for( Field f : fs ){ int id = (Integer)f.get(null); View view = d.findViewById(id); //判断控件类型,添加不同监听器。 if( view != null ){ if( view instanceof CheckBox ){ box = (CheckBox) view; box.setOnCheckedChangeListener(box1); }else if( view instanceof SeekBar ){ bar = (SeekBar) view; bar.setOnSeekBarChangeListener(bar1); } } } } catch (Exception e) { e.printStackTrace(); } } private void buildDialog() { d = new Dialog(this,android.R.style.Theme_Translucent_NoTitleBar_Fullscreen); d.setContentView(R.layout.light_setting); d.show(); } class MyGLSurfaceView extends GLSurfaceView{ public MyGLSurfaceView(Context context) { super(context); } public MyGLSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); } } //触摸时纵向旋转 @Override public boolean onTouchEvent(MotionEvent event) { float step = 1f; renderer.xRotate = renderer.xRotate - step; return super.onTouchEvent(event); } //按返回键横向旋转 @Override public void onBackPressed() { float step = 1f; renderer.yRotate = renderer.yRotate - step; } }缓冲区工具:
public class BufferUtils { /** * 将浮点数组转换成字节缓冲区 * * @param arr * @return ibb */ public static ByteBuffer array2ByteBuffer(float[] arr) { ByteBuffer ibb = ByteBuffer.allocateDirect(arr.length * 4);//一个浮点数等于四个字节 ibb.order(ByteOrder.nativeOrder());//设置排列顺序,本地顺序 FloatBuffer fbb = ibb.asFloatBuffer();//将字节缓冲转换成浮点缓冲 fbb.put(arr); ibb.position(0);//把缓冲区指针位置定位到0位置 return ibb; } /** * 将list集合转换成字节缓冲区 * * @param list * @return ibb */ public static ByteBuffer list2ByteBuffer(List<Float> list) { ByteBuffer ibb = ByteBuffer.allocateDirect(list.size() * 4);//一个浮点数等于四个字节 ibb.order(ByteOrder.nativeOrder());//设置排列顺序,本地顺序 FloatBuffer fbb = ibb.asFloatBuffer();//将字节缓冲转换成浮点缓冲 //用增强for循环把每个坐标put进去 for (Float f : list) { fbb.put(f); } ibb.position(0);//把缓冲区指针位置定位到0位置 return ibb; } /** * 将list集合转换成Float缓冲区 * * @param list */ public static FloatBuffer list2FloatBuffer(List<Float> list) { ByteBuffer ibb = ByteBuffer.allocateDirect(list.size() * 4);//一个浮点数等于四个字节 ibb.order(ByteOrder.nativeOrder());//设置排列顺序,本地顺序 FloatBuffer fbb = ibb.asFloatBuffer();//将字节缓冲转换成浮点缓冲 //用增强for循环把每个坐标put进去 for (Float f : list) { fbb.put(f); } fbb.position(0);//把缓冲区指针位置定位到0位置 return fbb; } /** * 将浮点数组转换为浮点缓冲区 * * @param coords * @return fbb */ public static FloatBuffer arr2FloatBuffer(float[] coords) { ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);//一个浮点数等于四个字节 ibb.order(ByteOrder.nativeOrder());//设置排列顺序,本地顺序 FloatBuffer fbb = ibb.asFloatBuffer();//将字节缓冲转换成浮点缓冲 fbb.put(coords); fbb.position(0);//把缓冲区指针位置定位到0位置 return fbb; } /** * 将字节数组转换为浮点缓冲区 * * @param arr * @return ibb */ public static ByteBuffer arr2ByteBuffer(byte[] arr) { ByteBuffer ibb = ByteBuffer.allocateDirect(arr.length); ibb.order(ByteOrder.nativeOrder());//设置排列顺序,本地顺序 ibb.put(arr); ibb.position(0); return ibb; } /** * 绘制球体 * * @param gl * @param r * @param stack * @param slice */ public static void drawSphere(GL10 gl, float r, int stack, int slice) { float statckStep = (float) (Math.PI / stack);//单位角度值 float sliceStep = (float) (Math.PI / slice);//水平圆递增的角度 float r0, r1, x0, x1, y0, y1, z0, z1; //r0、r1为圆心引向两个临近切片部分表面的两条线 (x0,y0,z0)和(x1,y1,z1)为临近两个切面的点。 float alpha0 = 0, alpha1 = 0; //前后两个角度 float beta = 0; //切片平面上的角度 List<Float> coordsList = new ArrayList<Float>(); //外层循环 for (int i = 0; i < stack; i++) { alpha0 = (float) (-Math.PI / 2 + (i * statckStep)); alpha1 = (float) (-Math.PI / 2 + ((i + 1) * statckStep)); y0 = (float) (r * Math.sin(alpha0)); r0 = (float) (r * Math.cos(alpha0)); y1 = (float) (r * Math.sin(alpha1)); r1 = (float) (r * Math.cos(alpha1)); //循环每一层圆 for (int j = 0; j <= (slice * 2); j++) { beta = j * sliceStep; x0 = (float) (r0 * Math.cos(beta)); z0 = -(float) (r0 * Math.sin(beta)); x1 = (float) (r1 * Math.cos(beta)); z1 = -(float) (r1 * Math.sin(beta)); coordsList.add(x0); coordsList.add(y0); coordsList.add(z0); coordsList.add(x1); coordsList.add(y1); coordsList.add(z1); } //指定顶点指针 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtils.list2FloatBuffer(coordsList)); //若要画球体,应以画三角形带的方式绘制,为方便观察,此处用画线带的方式 gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, coordsList.size() / 3); } } public static void drawRec(GL10 gl,float r) { float [] coords = { -r,r,0, -r,-r,0, r,r,0, r,-r,0, }; gl.glVertexPointer(3,GL10.GL_FLOAT,0, BufferUtils.array2ByteBuffer(coords));//指定顶点指针 gl.glDrawArrays( GL10.GL_TRIANGLE_STRIP,0,4 ); } }父类:
public abstract class AbstractRenderer implements GLSurfaceView.Renderer{ public float ratio;//视口比例 public float xRotate = 0f;//绕X轴旋转的角度 public float yRotate = 0f;//绕Y轴旋转的角度 @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { /** * 步骤: * 第一步设置清屏色,第二步启用顶点缓冲数组。 */ //设置清屏色 gl.glClearColor(0f, 0f, 0f, 1f); //启用顶点缓冲数组 gl.glEnableClientState( GL10.GL_VERTEX_ARRAY ); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { /**步骤: * 第一步设置视口,再设置平截头体,而要设置平截头体就要先指定矩阵类型为投影矩阵 * 设置过矩阵类型后,就马上要加载单位矩阵。 * * 设置平截头体: * 为了视口所展示的画面不失真,一般设置平截头体的比例与视口比例相同。 * 所以就要先计算视口比例,再应用到平截头体中。 */ //设置视口 gl.glViewport(0,0,width,height); //计算视口比例 ratio = (float)width / (float)height; //设置矩阵模式 gl.glMatrixMode(GL10.GL_PROJECTION); //加载单位矩阵 gl.glLoadIdentity(); //设置平截头体 gl.glFrustumf(ratio,-ratio,-1f,1f,3f,7f);//ratio为with/height,所以高度为1,宽度为ratio } @Override public abstract void onDrawFrame(GL10 gl); //光照 public boolean open_lighting = false; //全局环境光 public float r = 0.2f; public float g = 0.2f; public float b = 0.2f; public float a = 1; //材料的环境光和散射光的反射率 public float r1 = 0; public float g1 = 0; public float b1 = 0; public float a1 = 1; public boolean open_ColorMatrial = false; //颜色追踪 public float color_r = 0.8f; public float color_g = 0.8f; public float color_b = 0.8f; public float color_a = 1; //是否启用光源0 public boolean open_light0; //光源0的环境光 public float r3 = 0; public float g3 = 0; public float b3 = 0; public float a3 = 1; //光源0的散射光 public float r4 = 0; public float g4 = 0; public float b4 = 0; public float a4 = 1; //光源0位置 public float x = 0; public float y = 1f; public float z = 10f; }
布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <CheckBox android:id="@+id/cb_open_lighting" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="启用光照"/> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="---------------------------------------全局环境光---------------------------------------"/> <SeekBar android:id="@+id/sb_r" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_g" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_b" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="-------------材料的环境光和散射光的反射率(相同的值)------------"/> <SeekBar android:id="@+id/sb_r1" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_g1" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_b1" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <CheckBox android:id="@+id/cb_open_ColorMatrial" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="启用颜色追踪(设置材料反射率)"/> <SeekBar android:id="@+id/sb_r2" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_g2" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_b2" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <CheckBox android:id="@+id/cb_open_light0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="启用光源0"/> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="---------------------------------------光源0环境光---------------------------------------"/> <SeekBar android:id="@+id/sb_r3_ambient" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_g3_ambient" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_b3_ambient" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="---------------------------------------光源0散射光---------------------------------------"/> <SeekBar android:id="@+id/sb_r3_diffuse" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_g3_diffuse" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_b3_diffuse" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="---------------------------------------光源0位置(x、y、z轴)---------------------------------------"/> <SeekBar android:id="@+id/sb_x" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_y" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> <SeekBar android:id="@+id/sb_z" android:layout_width="200dip" android:layout_height="wrap_content" android:layout_gravity="right" android:max="100"/> </LinearLayout>