M3G教程:进阶篇(二)灯光

 

灯光(Lighting)

java.lang.Object

 +-javax.microedition.m3g.Object3D

   +-javax.microedition.m3g.Transformable

     +-javax.microedition.m3g.Node

       +-javax.microedition.m3g.Light

      在一个没有光线的房间中,所有的东西看上去都是黑的。那么前面的示例中没有光线,怎么还能看到东西呢?顶点颜色和材质是不需要光线的,它们永远显示为定义好的颜色。但光线会使它们发生一些变化,可增加景深。

      光线的方向会根据对象的位置发生反射。如果您用手电筒垂直地照射您面前的镜子,那么光线会反射到您身上。如果镜子是倾斜的,则光线的入射角和反射角是完全相同的。总的来说,您需要一个与照射平面相垂直的方向向量。这一向量就称为法线向量 或简称为法线。M3G 会根据法线、光源位置和摄像机位置计算着色的情况。

      此外,法线是各顶点都具备的属性,各顶点之间的像素着色既可采用插值法(PolygonMode.SHADE_SMOOTH)也可从三角形的第三个顶点处选取(PolygonMode.SHADE_FLAT)。由于立方体有 8 个顶点,支持法线的方法之一就是指定从立方体中心指向各角的向量,如图 7a 所示。但这样做可能会导致立方体着色不当。有三个面的颜色可能会相同,其中有些边成为不可见状态,使立方体看上去缺乏棱角。这显然更适合球体,不太适合立方体。图 7b 展示了如何为每边使用 4 条法线 —— 共 24 条,从而创建棱角分明的边线。由于一个顶点只能有一条法线,所以还要复制顶点。

 

图 7.带有法线向量的立方体:a) 8 条法线;b) 24 条法线(每边 4 条)

 

 

      可使用法线计算光线后,还需要告诉 M3G 您需要什么类型的光线。光线来源于不同形式:灯泡、太阳、手电筒等等。在 M3G 中的对应术语分别为全向光、定向光和聚光。

  • 全向光是从一个点发出的,并平均地照射各个方向。没有灯罩的灯泡发出的就是这样的光。
  • 定向光向一个方向发出平行的光线。太阳离我们的距离非常远,所以可以将其光线视为平行的。定向光没有位置,只有方向。
  • 手电筒或剧场中使用的聚光灯发射出的光线就是聚光。其光线呈锥形,与圆锥相交的平面上的对象会被照亮。

      在真实世界中,光线还会从对象上反射回来而将周围照亮。如果您打开卧室灯,就会发现即便没有能直接照射到床底下的光线,但床下仍会被照亮。Raytracer 通过追踪从摄像机到光源的路径而清晰真实地展示了图像,但需要很长时间。要获得交互式帧频,必须满足一个简单的模型:环境光。环境光以不变的频率从各方向照亮对象。您可以用环境光模拟前面的卧室场景,将所有对象都照亮到一定程度,从而提供了另外一个全向光源。

Light.OMNI:全向光

Light.SPOT:聚光

Light.AMBIENT:环境光

Light.DIRECTIONAL:定向光

 

 

材质(Material)

An Appearance component encapsulating material attributes for lighting computations. Other attributes required for lighting are defined in Light, PolygonMode and VertexBuffer.

 

The diagram below illustrates how the final, lit color is obtained for a vertex. Lighting is disabled for a submesh if it has a null Material, and enabled otherwise. If lighting is disabled, the final vertex color is taken from the associated VertexBuffer as such. If lighting is enabled, the final color is computed according to the OpenGL 1.3 lighting equation (p. 48), using the material colors specified here. Finally, if vertex color tracking is enabled, the AMBIENT and DIFFUSE material colors are replaced with the per-vertex colors or the default color obtained from the VertexBuffer.

Lighting is computed according to the OpenGL 1.3 specification, section 2.13.1, with the following exceptions:

  • the secondary color is not supported;
  • the same Material is used for both the front face and the back face;
  • vertex color tracking is limited to AMBIENT_AND_DIFFUSE;
  • for an ambient Light, the diffuse and specular intensities are zero;
  • for a directional or positional Light, the ambient intensity is zero;
  • the diffuse and specular Light intensities can not be set separately;
  • the global scene ambient color acs is not supported;

Diffuse:漫反射,反射光均匀地分散到各个方向。

Ambient:环境反射,由环境光源反射的光线。

Emissive:放射光,一个像炽热的物体那样发射光线的对象。

Specular:镜面反射,光线从有光亮平面的对象反射回来。

      环境反射仅对环境光起作用,因此,使用全向光是无效的。漫反射材质组件会造成一种不光滑的表面,而放射光组件则制造出一种发光效果。镜面反射颜色组件强调了发亮的效果。此外,您还可以通过使用更多的三角形改进明暗对比的着色质量。

可以使用 Material.setVertexColorTrackingEnable() 为环境反射和漫反射使用顶点颜色,不必使用Material.setColor()

 

转换(Transform)

      本示例中使用了Transform将摄影机向后移动以便全方位观看圆环,通过同样的方式我们也可以操作任意的Object3D对象。

      您可以通过数学方式将转换表示为矩阵操作。一个向量 —— 例如,摄像机位置 —— 乘以恰当的平移矩阵从而得到相应移动的向量。Transform 对象就表示了这样的一个矩阵。对于绝大多数普通转换来说,M3G 提供了 3 种便于使用的接口,隐藏了底层的数学计算:

  •  Transform.postScale(float sx, float sy, float sz):在 x、y、z 方向伸缩 3D 对象。大于 1 的值将按照给定因数扩大对象;0 和 1 之间的值将缩小对象。负值则同时执行伸缩和镜像操作。
  • Transform.postTranslate(float tx, float ty, float tz):通过为 x、y 和 z 坐标增加指定值移动 3D 对象。负值则表示向负轴方向移动对象。
  • Transform.postRotate(float angle, float ax, float ay, float az):按给定角度绕穿过(0,0,0)和(ax,ay,az)的轴旋转对象。角度为正值,则表示若您顺着正旋转轴方向观察,对象是按顺时针旋转的。例如,postRotate(30, 1, 0, 0) 将绕 x 轴将对象旋转 30 度。

      所有操作名都是以 "post" 开头的,表示当前 Transform 对象是从右边与给定转换矩阵相乘的 —— 矩阵操作的顺序是非常重要的。如果您向右旋转 90 度,然后走两步,这时您所处的位置显然与先走两步再转身不同。您可以在各步行指令之后调用两个 post 方法 postRotate() 和 postTranslate(),从而获得上面的步行指令。调用顺序决定了所获得的步行指令。由于使用的是后乘,所以您最后使用的转换会首先应用。

      M3G 有一个 Transform 类和一个 Transformable 接口。所有快速模式的 API 均可接受 Transform 对象作为参数,用于修改其关联的 3D 对象。另外,在保留模式下使用 Transformable 接口来转换作为 3D 世界一部分的节点。

 

A generic 4x4 floating point matrix, representing a transformation. By default, all methods dealing with Transform objects operate on arbitrary 4x4 matrices. Any exceptions to this rule are documented explicitly at the method level.

Even though arbitrary 4x4 matrices are generally allowed, using non-invertible (singular) matrices may produce undefined results or an arithmetic exception in some situations. Specifically, if the modelview matrix of an object is non-invertible, the results of normal vector transformation and fogging are undefined for that object.

 

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.m3g.Appearance;
import javax.microedition.m3g.Camera;
import javax.microedition.m3g.Graphics3D;
import javax.microedition.m3g.IndexBuffer;
import javax.microedition.m3g.Light;
import javax.microedition.m3g.Material;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.PolygonMode;
import javax.microedition.m3g.Transform;
import javax.microedition.m3g.TriangleStripArray;
import javax.microedition.m3g.VertexArray;
import javax.microedition.m3g.VertexBuffer;
import javax.microedition.m3g.World;


public class M3GCanvas extends GameCanvas implements Runnable {
	
	public static final int FPS = 20;	//每秒绘制的帧数

	private Graphics3D g3d;
	private World world;
	private boolean runnable=true;
	private Thread thread;
	private Mesh mesh;
	private Camera camera;
	private Light light;
	private Material material;

	protected M3GCanvas() {
		super(false);
		setFullScreenMode(true);
		g3d = Graphics3D.getInstance();
		world = new World();
		
		camera = new Camera();
		world.addChild(camera);
		
		float w = getWidth();
		float h = getHeight();
		camera.setPerspective(60.0f, w / h, 0.1f, 80f);
		
		Transform cameraTransform = new Transform();
		cameraTransform.postTranslate(0.0f, 15.0f, 10.0f);
		camera.setTransform(cameraTransform);
		camera.setOrientation(-10,1,0,0);
		
		mesh = createCube();
		mesh.translate(0.0f, 0.0f, -50.0f);
        
        light=new Light();
        nextLightMode(light);
        Transform lightTransform = new Transform();
        lightTransform.postTranslate(0.0f, 0.0f, -4.5f);
        light.setOrientation(-30,1,0,0);
//        light.setOrientation(30,0,1,0);
        light.setColor(0xFFFFFF);
        g3d.resetLights();
        g3d.addLight(light,lightTransform);
        
        world.addChild(light);
        
        world.addChild(mesh);
        world.setActiveCamera(camera);
	}

	public void run() {
		Graphics g = getGraphics();
		while (runnable) {
			long startTime = System.currentTimeMillis();
			
			mesh.postRotate(1.0f, 0.0f, 1.0f, 0.0f);
			
			try {
//				g3d.bindTarget(g);
				g3d.bindTarget(g, true, Graphics3D.DITHER | Graphics3D.TRUE_COLOR);
				g3d.render(world);
			} finally {
				g3d.releaseTarget();
			}
			g.setColor(0xFF00FF);
            g.drawString("material:"+materailInfo, 10, 10, Graphics.LEFT|Graphics.TOP);
            g.drawString("light:"+lightInfo, 10, 30, Graphics.LEFT|Graphics.TOP);
			flushGraphics();
			
			long endTime = System.currentTimeMillis();
            long costTime = endTime - startTime;
            if(costTime<1000/FPS)
            {
                try{
                  Thread.sleep(1000/FPS-costTime);
                }
                catch(Exception e){
                   e.printStackTrace();
                }
            }
		}
		System.out.println("Canvas stopped");

	}
	
	public void start()
	{
		thread=new Thread(this);
		thread.start();
	}
	
	public void stop()
	{
		this.runnable=false;
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	protected void keyPressed(int keyCode) {
		switch(keyCode)
		{
		case KEY_NUM2:
			nextMaterial();
			break;
		case KEY_NUM4:
			nextLightMode(light);
			break;
		case KEY_NUM6:
		case KEY_NUM8:
		}
	}
	
	private int materialTypeIndex=0;
	private String materailInfo=null;
	private void nextMaterial()
	{
		materialTypeIndex++;
		materialTypeIndex=(materialTypeIndex+4)%4;
		switch(materialTypeIndex)
		{
		case 0:
			material.setColor(Material.EMISSIVE, 0x00FF0000);
			materailInfo="EMISSIVE";
			System.out.println("use material:EMISSIVE");
			break;
		case 1:
			material.setColor(Material.AMBIENT, 0x00FF0000);
			materailInfo="AMBIENT";
			System.out.println("use material:AMBIENT");
			break;
		case 2:
			material.setColor(Material.DIFFUSE, 0x00FF0000);
			materailInfo="DIFFUSE";
			System.out.println("use material:DIFFUSE");
			break;
		case 3:
			material.setColor(Material.SPECULAR, 0x00FF0000);
			materailInfo="SPECULAR";
			System.out.println("use material:SPECULAR");
			break;
		}
		
	}
	
	private int modeIndex=0;
	private String lightInfo=null;
	protected void nextLightMode(Light light)
	{
		modeIndex++;
		modeIndex=(modeIndex+4)%4;
		switch (modeIndex)
		{
	    case 0:
	    	light.setMode(Light.AMBIENT);
	        light.setIntensity(1.0f);
	        lightInfo="AMBIENT";
	        System.out.println("use light mode:AMBIENT");
	        break;
	    case 1:
	        light.setMode(Light.DIRECTIONAL);
	        light.setIntensity(1.0f);
	        lightInfo="DIRECTIONAL";
	        System.out.println("use light mode:DIRECTIONAL");
	        break;
	    case 2:
	        light.setMode(Light.OMNI);
	        light.setIntensity(1.0f);
	        lightInfo="OMNI";
	        System.out.println("use light mode:OMNI");
	        break;
	    case 3:
	        light.setMode(Light.SPOT);
	        light.setSpotAngle(45.0f);
	        light.setIntensity(2.0f);
	        lightInfo="SPOT";
	        System.out.println("use light mode:SPOT");
	        break;
	   }
	}
	
	private Mesh createCube(){
		
		short[] vert = { 10, 10, 10, -10, 10, 10, 10, -10, 10, -10, -10,10, // front
				-10, 10, -10, 10, 10, -10, -10, -10, -10, 10, -10, -10, // back
				-10, 10, 10, -10, 10, -10, -10, -10, 10, -10, -10, -10, // left
				10, 10, -10, 10, 10, 10, 10, -10, -10, 10, -10, 10, // right
				10, 10, -10, -10, 10, -10, 10, 10, 10, -10, 10, 10, // top
				10, -10, 10, -10, -10, 10, 10, -10, -10, -10, -10, -10 }; // bottom
		VertexArray vertArray = new VertexArray(vert.length / 3, 3, 2);
		vertArray.set(0, vert.length / 3, vert);
		byte[] norm = { 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0,
				-127, 0, 0, -127, 0, 0, -127, 0, 0, -127, -127, 0, 0, -127,
				0, 0, -127, 0, 0, -127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0,
				0, 127, 0, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0,
				0, -127, 0, 0, -127, 0, 0, -127, 0, 0, -127, 0 };
		VertexArray normArray = new VertexArray(norm.length / 3, 3, 1);
		normArray.set(0, norm.length / 3, norm);
		int[] stripLen = { 4, 4, 4, 4, 4, 4 };
		IndexBuffer indexBuffer = new TriangleStripArray(0, stripLen);
		VertexBuffer vertexBuffer = new VertexBuffer();
		vertexBuffer.setPositions(vertArray, 1.0f, null);
		vertexBuffer.setNormals(normArray);
		Appearance cubeAppearance = new Appearance();
		PolygonMode pm = new PolygonMode();
        pm.setShading(PolygonMode.SHADE_SMOOTH);
        pm.setCulling(PolygonMode.CULL_NONE);

        material = new Material();
        nextMaterial();

        Appearance app = new Appearance();
        app.setPolygonMode(pm);
		cubeAppearance.setMaterial(material);
		Mesh mesh = new Mesh(vertexBuffer, indexBuffer, cubeAppearance);
		return mesh;
	}

}

 

运行结果如下:

 

本文部分内容引用自:http://dev.csdn.net/develop/article/80/80828.shtm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值