国内的libGDX文章很少,特别是libGDX实现灯光效果,所以就开始总结灯光效果的实现
1.Box2dLights库功能简介
库版本:1.5
目前Box2dLights提供了以下几种灯光效果模拟
- PointLight:点光源,就是中心点发光,向周围辐射,所以这个光源模拟的效果总是圆形的
- ConeLight:圆锥光源,也是从一点发光,但是光照角度和范围可以控制,像手电筒或投影仪那种
- DirectionalLight:模拟无限远的光源,光照效果不会随着距离和方向变弱,所以经常用来模拟阳光
- ChainLight:链式灯光,我们可以通过定义顶点更灵活的控制灯光模拟形状(比如让一个自定义的Body周围发光)
绿色的框 是为了方便看到Body位置,使用Box2DDebugRenderer渲染的
工欲善其事,必先利其器,工具集合
gdx-setup.jar
1. 从libGDX官网下载项目生成工具
https://libgdx.com/wiki/start/setup
2.配置项目
Output folder和Android SDK设置自己的,扩展库选择Box2d和Box2dlights,然后生成项目
3.配置ApplicationConfiguration,初始化ApplicationListener
由于是演示项目,所以直接使用系统创建好的Activity实现类
public class AndroidLauncher extends AndroidApplication {
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
config.a = 8;
config.r = 8;
config.g = 8;
config.b = 8;
initialize(new PointLightTest(), config);
}
}
initialize的第一个参数后面的每个例子都会改成需要的那个类
4.创建一个PointLight
public class PointLightTest implements ApplicationListener {
/** the camera **/
OrthographicCamera camera;
RayHandler rayHandler;
World world;
@Override
public void create() {
// 1. 创建正交投影相机,这里设置的视口直接就是屏幕大小
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
// 2.把相机放到屏幕中心 这两步是为了更直观的看到映射后的效果,不用去计算灯光位置什么
// 的,都放到中心显示
camera.position.set(Gdx.graphics.getWidth() / 2f,Gdx.graphics.getHeight() / 2f, 0);
// 3.更新相机
camera.update();
// 4.创建物理世界
world = new World(new Vector2(0, -10), true);
// 5.创建光束处理器(核心)
rayHandler = new RayHandler(world);
// 6.创建一个点光源
new PointLight(rayHandler, 100, Color.RED, 150f, Gdx.graphics.getWidth() / 2f, Gdx.graphics.getHeight() / 2f);
}
@Override
public void resize(int width, int height) {
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update();
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
world.step(1/60f, 8, 3);
rayHandler.setCombinedMatrix(camera);
rayHandler.updateAndRender();
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
rayHandler.dispose();
}
}
点光源的参数
- rayHandler:我们创建的RayHandler对象
- rays:光束个数,数量越大效果越逼真,但是性能消耗也越大,推荐最大不超过128
- color:灯光颜色
- distance 灯光的辐射距离,越大,灯光辐射的越远
- x:灯光在world中的横向中心点
- y:灯光在world中的纵向中心点
5.创建一个ConeLight
public class ConeLightTest implements ApplicationListener {
/** the camera **/
OrthographicCamera camera;
RayHandler rayHandler;
World world;
@Override
public void create() {
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.position.set(Gdx.graphics.getWidth() / 2f,Gdx.graphics.getHeight() / 2f, 0);
camera.update();
world = new World(new Vector2(0, -10), true);
rayHandler = new RayHandler(world);
new ConeLight(rayHandler, 100, Color.RED, 500f, Gdx.graphics.getWidth() / 2f, Gdx.graphics.getHeight() / 2f, 90, 60);
}
@Override
public void resize(int width, int height) {
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update();
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
world.step(1 / 60f, 8, 3);
rayHandler.setCombinedMatrix(camera);
rayHandler.updateAndRender();
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
rayHandler.dispose();
}
}
和点光源一样的配置,将AndroidLauncher的initialize参数改为ConeLightTest,ConeLight的参数和点光源差不多,多了2个参数
- directionDegree: 光源方向,逆时针为正方向 示例代码设置的90,0的话就是向右
- coneDegree: 锥形光的跨越角度 示例代码设置的60
6. 创建一个DirectionalLight
public class DirectionalLightTest implements ApplicationListener {
/** the camera **/
OrthographicCamera camera;
RayHandler rayHandler;
World world;
protected Body body;
protected Box2DDebugRenderer mWorldDebugger;
private int viewportWidth = 0;
private int viewportHeight = 0;
@Override
public void create() {
viewportWidth = Gdx.graphics.getWidth();
viewportHeight = Gdx.graphics.getHeight();
camera = new OrthographicCamera(viewportWidth, viewportHeight);
camera.position.set( viewportWidth/ 2f,viewportHeight/ 2f, 0);
camera.update();
world = new World(new Vector2(0, -10), true);
mWorldDebugger = new Box2DDebugRenderer();
makeBody();
rayHandler = new RayHandler(world);
new DirectionalLight(rayHandler, 100, Color.WHITE, 0);
}
private void makeBody() {
CircleShape ballShape = new CircleShape();
ballShape.setRadius(50f);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = ballShape;
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.StaticBody;
bodyDef.position.x = viewportWidth/ 2f;
bodyDef.position.y = viewportHeight / 2f;
body = world.createBody(bodyDef);
body.createFixture(fixtureDef);
ballShape.dispose();
}
@Override
public void resize(int width, int height) {
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update();
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
world.step(1 / 60f, 8, 3);
rayHandler.setCombinedMatrix(camera);
rayHandler.updateAndRender();
mWorldDebugger.render(world, camera.combined);
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
rayHandler.dispose();
}
}
看到上面的效果图,可能很多疑问,这是什么鬼。
这个光源比较特殊,他就是无限长的光,参数也比较少
- rayHandler: 和之前一样,RayHandler对象
- rays:光束个数
- color:灯光颜色 示例设置的白色
directionDegree:灯光照射角度,逆时针为正方向
这个光源设置后,如果画面上没有其他东西,就显示一个白屏,这就是这个光的特性,各处都照亮,所以一个屏幕都是白色,所以需要加上一个参照物,也就是示例代码和之前的多了一个body,光线照到body,然后被body遮挡就会显示阴影,也就成了上面示意图的模样,如果设置角度90,阴影就是朝上。
7.创建一个ChainLight
public class ChainLightTest implements ApplicationListener {
/** the camera **/
OrthographicCamera camera;
RayHandler rayHandler;
World world;
protected Body body;
protected Box2DDebugRenderer mWorldDebugger;
private int viewportWidth = 0;
private int viewportHeight = 0;
private ChainLight chainLight;
private float bodyWidth = 50;
private float bodyHeight = 50;
@Override
public void create() {
RayHandler.setGammaCorrection(true);
viewportWidth = Gdx.graphics.getWidth();
viewportHeight = Gdx.graphics.getHeight();
camera = new OrthographicCamera(viewportWidth, viewportHeight);
camera.position.set( viewportWidth/ 2f,viewportHeight/ 2f, 0);
camera.update();
world = new World(new Vector2(0, -10), true);
mWorldDebugger = new Box2DDebugRenderer();
makeBody();
rayHandler = new RayHandler(world);
rayHandler.setAmbientLight(Color.GRAY);
float[] vertices = new float[] {-bodyWidth / 2, -bodyHeight / 2,
bodyWidth / 2, -bodyHeight / 2,
bodyWidth / 2, bodyHeight / 2,
-bodyWidth / 2, bodyHeight / 2,
-bodyWidth / 2, -bodyHeight / 2};
chainLight = new ChainLight(rayHandler, 100, Color.WHITE, 500, -1,
vertices);
chainLight.attachToBody(body, 0);
}
private void makeBody() {
PolygonShape ballShape = new PolygonShape();
ballShape.setAsBox(bodyWidth / 2, bodyHeight / 2);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = ballShape;
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.StaticBody;
bodyDef.position.x = viewportWidth/ 2f;
bodyDef.position.y = viewportHeight / 2f;
body = world.createBody(bodyDef);
body.createFixture(fixtureDef);
ballShape.dispose();
}
@Override
public void resize(int width, int height) {
camera.viewportWidth = width;
camera.viewportHeight = height;
camera.update();
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
world.step(1 / 60f, 8, 3);
rayHandler.setCombinedMatrix(camera);
rayHandler.updateAndRender();
mWorldDebugger.render(world, camera.combined);
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
rayHandler.dispose();
}
}
ChainLight参数不一样的是
- rayDirection: 源码示例给的是1和-1 这个值其实代表发光角度,也可以设置其他数
- chain: 顶点数组
ChainLight根据顶点确定发光体形状,rayDirection和chain的简单规律是
>> 1.1 如果是逆时针加入顶点 -1代表外部发光 1代表内部发光
>> 1.2 如果是顺时针加入顶点 -1代表内部发光 1代表外部发光
ChainLight比较特殊是必须依附于Body,Body决定了他的位置,示例是让一个正方形Body外围发光,顶点按照逆时针顺序添加。
唯一不接的一点是明明最后将原点设置为最后一个顶点,但是左下角却没有发光,不晓得是不是这个库的BUG
如果想定义圆形发光,比矩形麻烦点,需要很多个顶点,这些顶点连线形成这个圆形
RayHandler还有很多属性可以设置来加强模拟效果,可以多出尝试
注意:
- 如果没有调用RayHandler的serAmbinetLight设置环境光,而且开启了RayHandler模拟的话,界面是会一团黑的,你界面上绘制其他Actor是看不见的,除非有光照到他们
- 除了DirectionalLight,其他灯光都可以attachToBody
文章参考资料
- https://github.com/libgdx/box2dlights