Libgdx New 3D API 教程之 -- Libgdx中使用Materials
来源:互联网 发布:泉州聚客网络 编辑:程序博客网 时间:2024/05/22 08:20
This blog is a chinese version of xoppa's Libgdx new 3D api tutorial. For English version, please refer to >>LINK<<
在这一章中,你将看到在Libgdx中是如何使用Materials的。Material是基于Shader的,所以这一节其实是上一节教程的续,上一节中,我们自定义了一个shader。如果你还没有自定义shader,我建议你先看一下上一章。
之前,我们仅通过一个Renderable和一个shader,来测试我们的shader。因为简单明了,用来测试是很好的。但真正用起来的话,你还是会用到之前见过的ModelInstance和ModelBatch,这样你可以使用多个shader和model。还好,这些都很容易,我们改一改代码。下面给出ShaderText.java的完整代码作为参考,然后我们会讲讲改了什么:
public class ShaderTest implements ApplicationListener { public PerspectiveCamera cam; public CameraInputController camController; public Shader shader; public Model model; public Array<ModelInstance> instances = new Array<ModelInstance>(); public ModelBatch modelBatch; @Override public void create () { cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); cam.position.set(0f, 8f, 8f); cam.lookAt(0,0,0); cam.near = 0.1f; cam.far = 300f; cam.update(); camController = new CameraInputController(cam); Gdx.input.setInputProcessor(camController); ModelBuilder modelBuilder = new ModelBuilder(); model = modelBuilder.createSphere(2f, 2f, 2f, 20, 20, new Material(), Usage.Position | Usage.Normal | Usage.TextureCoordinates); for (int x = -5; x <= 5; x+=2) { for (int z = -5; z<=5; z+=2) { instances.add(new ModelInstance(model, x, 0, z)); } } shader = new TestShader(); shader.init(); modelBatch = new ModelBatch(); } @Override public void render () { camController.update(); Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); modelBatch.begin(cam); for (ModelInstance instance : instances) modelBatch.render(instance, shader); modelBatch.end(); } @Override public void dispose () { shader.dispose(); model.dispose(); modelBatch.dispose(); } @Override public void resume () { } @Override public void resize (int width, int height) { } @Override public void pause () { }}
首先,我们我们让camera离原点远了些,这样才能看到整个画面。然后,删掉了renderable对象。并用一组ModelInstances取而代之,我们会放一些球体在这个数组中,然后让他们放在一个以XZ轴为平面的格子里。我们还用ModelBatch取代了RenderContext。ModelBatch需要销毁,所以在dispose方法中加了相应的方法。如果你一直有看之前的教程,那这些都讲到过。只有一处是新的,在render方法中。
modelBatch.begin(cam);for (ModelInstance instance : instances) modelBatch.render(instance, shader);modelBatch.end();
这里,我们为ModelInstances指定了渲染时要使用的shader。这是给ModelBatch指定shader的最简单的方式。所以,运行一下看看:
现在,我们要给每一个球指定不同的颜色,首先要改一改shader。我们以前介绍过,shader包含CPU和GPU两部分,而GPU部分是由处理vertex和fragment的代码组成。
下面是改好的test.vertex.glsl文件:
attribute vec3 a_position;attribute vec3 a_normal;attribute vec2 a_texCoord0; uniform mat4 u_worldTrans;uniform mat4 u_projTrans; void main() { gl_Position = u_projTrans * u_worldTrans * vec4(a_position, 1.0);}
改动不大,删掉了v_texCoord0数组,因为接下来fragment shader用不着了。然后再看看test.fragment.glsl文件是怎么样的:
#ifdef GL_ES precision mediump float;#endif uniform vec3 u_color; void main() { gl_FragColor = vec4(u_color, 1.0);}
这里我们也删掉了v_texCoord0,相应的添加了一个uniform:u_color。这个uniform是用来指定fragment颜色的。所以,我们需要在TestShader类中,指定uniform。下面给出参考代码:
public class TestShader implements Shader { ShaderProgram program; Camera camera; RenderContext context; int u_projTrans; int u_worldTrans; int u_color; @Override public void init () { String vert = Gdx.files.internal("data/test.vertex.glsl").readString(); String frag = Gdx.files.internal("data/test.fragment.glsl").readString(); program = new ShaderProgram(vert, frag); if (!program.isCompiled()) throw new GdxRuntimeException(program.getLog()); u_projTrans = program.getUniformLocation("u_projTrans"); u_worldTrans = program.getUniformLocation("u_worldTrans"); u_color = program.getUniformLocation("u_color"); } @Override public void dispose () { program.dispose(); } @Override public void begin (Camera camera, RenderContext context) { this.camera = camera; this.context = context; program.begin(); program.setUniformMatrix(u_projTrans, camera.combined); context.setDepthTest(true, GL20.GL_LEQUAL); context.setCullFace(GL20.GL_BACK); } @Override public void render (Renderable renderable) { program.setUniformMatrix(u_worldTrans, renderable.worldTransform); program.setUniformf(u_color, MathUtils.random(), MathUtils.random(), MathUtils.random()); renderable.mesh.render(program, renderable.primitiveType, renderable.meshPartOffset, renderable.meshPartSize); } @Override public void end () { program.end(); } @Override public int compareTo (Shader other) { return 0; } @Override public boolean canRender (Renderable instance) { return true; }}
仅有一处改动,我们添加了一个u_color,它保存的是u_color这个uniform的地址。而在render中,我们给它设定了一个随机的颜色。运行结果是这样的:
使用随机的颜色并没有让我们更好的控制shader,我想要一种方法,可以给每一个renderable对象指定我想要的颜色。最基本的做法是使用ModelInstance的userData值。我在ShaderTest中是这样写的:
public void create () { ... for (int x = -5; x <= 5; x+=2) { for (int z = -5; z<=5; z+=2) { ModelInstance instance = new ModelInstance(model, x, 0, z); instance.userData = new Color((x+5f)/10f, (z+5f)/10f, 0, 1); instances.add(instance); } } ...}
我直接把想要用的颜色值指定给userData,例子中,就是基于每一个instance位置的渐变色。接下来,通过shader使用这些值:
public void render (Renderable renderable) { ... Color color = (Color)renderable.userData; program.setUniformf(u_color, color.r, color.g, color.b); ...}
使用userData值,将颜色传给了Shader。不过如果你有多个uniform,然后还要使用多个shader的时候,这就会变得乱七八糟,使用这些uniform会变得痛苦不堪。我们需要一个更好的方式来设置model instance中uniform的值。
我们的目标是:我们的shader中一共有3个uniform(u_projTrans, u_wordTrans, u_color)。第一个取决于camera,第二个(renderable.worldTransform)和第三个取决于renderable对象。通常情况下,你可以将uniforms分为三类:
Global(全局):这些值你可以在shader的begin方法中设置。这些是所有renderable通用的,而且中间不会修改。比如u_projTrans就是。
Environmental(环境):这些不是全局的,但也不是与model instance直接相关。大多数时候,这些可能跟instance在场景中的位置有关,比如灯光(renderable.lights)就是一个典型的环境uniform。
Specific(指定):这个就是每个ModelInstance(NodePart)独有的了。独立于场景或场景中的位置,这些值会经常被使用。比如u_worldTrans和u_color。
*这里所说的指定值是说每一个ModelInstance特定的值。你也可以理解为位置或model值。
注意这三组值不仅仅包含uniform。比如顶点属性就是renderable的指定值。在begin方法中设置的深度测试和cull face(这个不知道怎么译),就是全局的。这些设置和值都是GLSL运行上下文中的内容。
当创建Shader的时候(CPU部分),你要一直留意着哪些变量是属于哪一类的,并且,它们是否经常改变。比如ShadowMap纹理就可能是全局的,或环境的,怎么看都不像是指定的。
这篇教程中,我们只看一下specific值,好像之前的u_color一样。这才是跟材料有关的东西。
Material仅仅包含指定值。MeshPart定义了一个渲染一个Renderable的图形。类似的,material定义了如何去渲染这些图形,比如图形的颜色之类,不考虑环境因素。一个renderable总会包含一个material(不可能为空)。一个material本质上来说,就是一个材质属性的数组:
class Material { Array<Material.Attribute> attributes = new Array<Material.Attribute>(); ...}
*上文所说的材质不能为空,是指材质对象不能为null,但是可以是一个空的材质。
简单来说,Material.Attribute描述了值与uniform的设置关系。当然数据类型都可以不同,比如一个color,float,或texture的uniform。因此,Material.Attribute必须要继承每一个指定的类型。Libgdx提供了大部分基本类型,比如:
package com.badlogic.gdx.graphics.g3d.materials;...public class ColorAttribute extends Material.Attribute { public final Color color = new Color(); ...}
还有FloatAttribute和TextureAttribute等类型。因此指定数据就很简单了:
colorAttribute.color.set(Color.RED);
指定uniform时,每一个Material.Attribute都会有一个type值:
public class Material implements Iterable<Material.Attribute>, Comparator<Material.Attribute> { public static abstract class Attribute { public final long type; ... } ...}
一个Shader中,uniform的名字不能重复,类似的,Material中的每一种数据类型也只能定义一个attribute。但是一个uniform只能应用于一个shader,可是material attribute可以很多shader共用。因此material attribute独立于shader。比如,在我们上面定义的shader中,有一个名为u_color的uniform。如果一个attribute定义了一种颜色,这个概念就模糊了。我们需要更准确的定义,这个颜色将会起到什么作用,比如这是一个环境光的颜色。Libgdx已经定义了这样一个材质,可以像这样构建:
ColorAttribute attribute = new ColorAttribute(ColorAttribute.Diffuse, Color.RED);
更简单一点,你可以这样:
ColorAttribute attribute = ColorAttribute.createDiffuse(Color.RED);
ColorAttribute.Diffuse指定了类型,比如,从一个材质中取得环境光的属性:
Material.Attribute attribute = material.get(ColorAttribute.Diffuse);
注意的是,通过ColorAttribute.Diffuse定义的attribute总是可以转换成ColorAttribute。(比如你不能调用 new TextureAttribute(ColorAttribute.Diffuse);这没任何意义)。所以你可以直接转换:
ColorAttribute attribute = (ColorAttribute)material.get(ColorAttribute.Diffuse);
我们把这些代码加到ShaderTest中:
public void create () { ... for (int x = -5; x <= 5; x+=2) { for (int z = -5; z<=5; z+=2) { ModelInstance instance = new ModelInstance(model, x, 0, z); ColorAttribute attr = ColorAttribute.createDiffuse((x+5f)/10f, (z+5f)/10f, 0, 1); instance.materials.get(0).set(attr); instances.add(instance); } } ...}
我们使用ColorAttribute.Diffuse创建了一个ColorAttribute,颜色值基于网格的位置。然后将这个颜色加到instance的仅有的第一个材质中。哦,方法名是set而不是add,因为,如果attribute名是相同的,那就会覆盖。
现在,改成利用这个来指定u_color uniform的值。
public void render (Renderable renderable) { program.setUniformMatrix(u_worldTrans, renderable.worldTransform); Color color = ((ColorAttribute)renderable.material.get(ColorAttribute.Diffuse)).color; program.setUniformf(u_color, color.r, color.g, color.b); ...}
我们取得material中的ColorAttribute.Diffuse值,转换成ColorAttribute,然后得到它的颜色。然后把这个颜色设置给u_color。如果你现在运行的话,跟原来的效果是一样的,不过我们是通过材质,而非userData来实现这一效果。
再仔细看一下刚刚改的render方法,你会发现,如果material中不包含ColorAttribute.Diffuse属性的话,代码就会出很大的问题。所以,我们最好加一个判断:
ColorAttribute attr = (ColorAttribute)renderable.material.get(ColorAttribute.Diffuse);if (attr != null) ... set the uniformelse ... fall back to a default color
有时候这样很有用,但好像我们的shader就没那么好。对于指定的renderable,可能最好的方式是用另一个shader(比如default shader)。我们可以将我们的shader指定给那些包含特定material attribute的renderable对象。像下面这样做:
public boolean canRender (Renderable renderable) { return renderable.material.has(ColorAttribute.Diffuse);}
现在,这个shader就只会有material中包含ColorAttribute.Diffuse属性的时候才会使用了。否则,ModelBatch会使用Default Shader。
要创建一个更复杂的shader,你可能会用到一些非默认的属性值,所以,我们现在就来新建一个,最简单的复杂shader。首先,在test.vertex.glsl中,添加回v_texCoord0:
attribute vec3 a_position;attribute vec3 a_normal;attribute vec2 a_texCoord0;uniform mat4 u_worldTrans;uniform mat4 u_projTrans;varying vec2 v_texCoord0;void main() {v_texCoord0 = a_texCoord0;gl_Position = u_projTrans * u_worldTrans * vec4(a_position, 1.0);}
然后,改一改fragment shader,通过texture coordinates来指定颜色。
#ifdef GL_ES precision mediump float;#endifuniform vec3 u_colorU;uniform vec3 u_colorV;varying vec2 v_texCoord0;void main() {gl_FragColor = vec4(v_texCoord0.x * u_colorU + v_texCoord0.y * u_colorV, 1.0);}
我们使用两个uniform而不是一个来指定pixel color。一个取决于x(u)纹理坐标,另一个取决于y(v)纹理坐标。最后我们在TestShader试一下:
public class TestShader implements Shader {ShaderProgram program;Camera camera;RenderContext context;int u_projTrans;int u_worldTrans;int u_colorU;int u_colorV;@Overridepublic void init () {...u_worldTrans = program.getUniformLocation("u_worldTrans");u_colorU = program.getUniformLocation("u_colorU");u_colorV = program.getUniformLocation("u_colorV");}...@Overridepublic void render (Renderable renderable) {program.setUniformMatrix(u_worldTrans, renderable.worldTransform);Color colorU = ((ColorAttribute)renderable.material.get(ColorAttribute.Diffuse)).color;Color colorV = Color.BLUE;program.setUniformf(u_colorU, colorU.r, colorU.g, colorU.b);program.setUniformf(u_colorV, colorV.r, colorV.g, colorV.b);renderable.mesh.render(program,renderable.primitiveType,renderable.meshPartOffset,renderable.meshPartSize);}...}
取得u_colorU和u_colorV的地址,然后把diffuse color赋给u_colorU,把u_colorV设置成Color.BLUE.
在Shader中,我们已经将diffuse color属性设置为基于x纹理坐标的渐变色。并且Color.BLUE也是基于y纹理坐标的。但如果两个属性都通过material attribute来配置的话就好多了。所以,我们新创建两个material attributes. 一个用于基于U值的diffuse color,另一个用于基于V值的diffuse color。最容易的作法是扩展ColorAttribute类,并且注册要添加的值:
public class TestShader implements Shader { public static class TestColorAttribute extends ColorAttribute { public final static String DiffuseUAlias = "diffuseUColor"; public final static long DiffuseU = register(DiffuseUAlias); public final static String DiffuseVAlias = "diffuseVColor"; public final static long DiffuseV = register(DiffuseVAlias); static { Mask = Mask | DiffuseU | DiffuseV; } public TestColorAttribute (long type, float r, float g, float b, float a) { super(type, r, g, b, a); } } ...}
这个类太简单了,我没创建新的类,而在TestShader中,写了一个静态类。这个类继承自ColorAttribute,因为这是我们要注册的属性类型。在这个类中,有一些public final static的成员变量。首先是DiffuseUAlias, 这个是我们要定义的attribute类型名,这个值会在调用attribute.toString()时返回。接下来,我们将其注册为一个属性类型。register方法返回了type的值(全局唯一),这个我们用来做DiffuseU的初始值。这使得我们可以通过TestColorAttribute.DiffuseU来使用attribute type。就好像我们以前使用ColorAttribute.Diffuse那样。下面的DiffuseVAlias也是一样。
我们现在已经有了两个material attribute了。但因为我们是扩展ColorAttrbiute类,我们还需要告知这个类接收这些属性。下面的Mask = Mask | DiffuseU | DiffuseV就是做这个的。最后,重写了构建器,这样我们才可以构建新的material attribute.
用一下这两个material attributes。首先改ShaderTest class:
public void create () { ... for (int x = -5; x <= 5; x+=2) { for (int z = -5; z<=5; z+=2) { ModelInstance instance = new ModelInstance(model, x, 0, z); ColorAttribute attrU = new TestColorAttribute(TestColorAttribute.DiffuseU, (x+5f)/10f, 1f - (z+5f)/10f, 0, 1); instance.materials.get(0).set(attrU); ColorAttribute attrV = new TestColorAttribute(TestColorAttribute.DiffuseV, 1f - (x+5f)/10f, 0, (z+5f)/10f, 1); instance.materials.get(0).set(attrV); instances.add(instance); } } ...}
我们新建了两个TestColorAttributes,一个是DiffuseU另一个是DiffuseV。我们将其设置为基于网格位置的颜色。最后我们把他们添加到material中,跟以前一样。现在我们需要改变TestShader来使用他们:
public void render (Renderable renderable) { program.setUniformMatrix(u_worldTrans, renderable.worldTransform); Color colorU = ((ColorAttribute)renderable.material.get(TestColorAttribute.DiffuseU)).color; Color colorV = ((ColorAttribute)renderable.material.get(TestColorAttribute.DiffuseV)).color; program.setUniformf(u_colorU, colorU.r, colorU.g, colorU.b); program.setUniformf(u_colorV, colorV.r, colorV.g, colorV.b); renderable.mesh.render(program, renderable.primitiveType, renderable.meshPartOffset, renderable.meshPartSize);}
在render方法中,我们得到两个attributes颜色值,然后相应的设置给uniforms。现在运行一下:
跟我们的预期不一样,因为还没有改过canReader方法。现在我们的material可没有ColorAttribute.Diffuse这个属性,所以ModelBatch使用了default shader。注算在render时候,通过modelBatch.render(instance, shader)指定shader也没用。ModelBatch限制我们使用不能用的shader。我们需要修改TestShader类中的canRender方法,这样ModelBatch才会接受新的material attributes:
public boolean canRender (Renderable renderable) { return renderable.material.has(TestColorAttribute.DiffuseU | TestColorAttribute.DiffuseV);}
现在看一下运行效果:
好多了,现在我们可以使用自定义的material attributes来控制我们的uniforms。
也许你注意到,我用了位或运算来联合多个material attributes类型。比如:
return renderable.material.has(TestColorAttribute.DiffuseU | TestColorAttribute.DiffuseV);
这个只会在material中包含DiffuseU和DiffuseV两个属性的时候才返回true。现在不作多介绍,但记住material attribute可以这样简单快速的联合在一起。
最后再说一些,给material attributes指定uniform不是必须的。比如Libgdx有一个attribute叫作 IntAttribute.CullFace,可以跟context.setCullFace()一起用,而这并不需要设置一下uniform值。同样,一个attributes也不是一定要有一个它自己的uniform,比如上面的例子,在shader中,我们使用了两个color attributes,DiffuseU和DiffuseV。更好的方法,可以仅用一个attribute来包含这两个值:
public class DoubleColorAttribute extends Material.Attribute { public final static String DiffuseUVAlias = "diffuseUVColor"; public final static long DiffuseUV = register(DiffuseUVAlias); public final Color color1 = new Color(); public final Color color2 = new Color(); protected DoubleColorAttribute (long type, Color c1, Color c2) { super(type); color1.set(c1); color2.set(c2); } @Override public Attribute copy () { return new DoubleColorAttribute(type, color1, color2); } @Override protected boolean equals (Attribute other) { DoubleColorAttribute attr = (DoubleColorAttribute)other; return type == other.type && color1.equals(attr.color1) && color2.equals(attr.color2); }}
- Libgdx New 3D API 教程之 -- Libgdx中使用Materials
- Libgdx New 3D API 教程之 -- 使用Libgdx加载3D场景
- Libgdx New 3D API 教程之 -- 使用Libgdx加载模型
- Libgdx New 3D API 教程之 -- 使用Libgdx创建Shader
- Libgdx New 3D API 教程之 -- Libgdx 3D 基础
- Libgdx New 3D API 教程之 -- Libgdx中的3D frustum culling
- Libgdx New 3D API 教程之 -- 加载3D场景的背后-第一部分
- Libgdx New 3D API 教程之 -- 加载3D场景的背后-第二部分
- Libgdx New 3D API 教程之 -- 与三维物体的交互
- Android 3D引擎之libgdx----Eclipse编译libgdx
- libgdx API之提示
- libgdx 3D
- libgdx之加载obj(3D文件)
- libgdx之加载obj(3D文件)
- LibGdx--在LibGdx中使用Toast
- Basic 3D using libGDX
- [libgdx游戏开发教程]使用Libgdx进行游戏开发(3)-给游戏添加一些控制功能
- [libgdx游戏开发教程]使用Libgdx进行游戏开发(3)-给游戏添加一些控制功能
- 串口编程之三:COMSTAT 结构
- 修改dataguard主库redo组数和大小
- android EDITTEXT调用和隐藏输入法
- 如何利用遮罩实现动态划过
- mysqldump指定编码导出数据
- Libgdx New 3D API 教程之 -- Libgdx中使用Materials
- Java中this用法小结
- 串口编程之五:常用函数
- java.util.concurrent包API学习笔记
- GRIDVIEW中使用滚动条
- unpv2中:struct ipc_perm没有名为seq的成员 的解决办法
- 网页优化中,如果图片大小不做定义,则页面需要重新渲染,速度受到影响
- Unbuntu上安装Ultardit
- 关于线程安全和可重入的区别,写得很好所以转过来了