首页 技术 正文
技术 2022年11月7日
0 收藏 836 点赞 425 浏览 13763 个字

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的最简单的方式。所以,运行一下看看:

Libgdx New 3D API 教程之 — Libgdx中使用Materials

现在,我们要给每一个球指定不同的颜色,首先要改一改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;
#endifuniform 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中,我们给它设定了一个随机的颜色。运行结果是这样的:

Libgdx New 3D API 教程之 — Libgdx中使用Materials

使用随机的颜色并没有让我们更好的控制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);
...
}

Libgdx New 3D API 教程之 — Libgdx中使用Materials

使用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 uniform
else
... 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;@Override
public void init () {
...
u_worldTrans = program.getUniformLocation("u_worldTrans");
u_colorU = program.getUniformLocation("u_colorU");
u_colorV = program.getUniformLocation("u_colorV");
}
...
@Override
public 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.

Libgdx New 3D API 教程之 — Libgdx中使用Materials

在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。现在运行一下:

Libgdx New 3D API 教程之 — Libgdx中使用Materials

跟我们的预期不一样,因为还没有改过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);
}

现在看一下运行效果:

Libgdx New 3D API 教程之 — Libgdx中使用Materials

好多了,现在我们可以使用自定义的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);
}
}
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,104
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,581
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,428
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,200
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,835
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,918