Libgdx 自定义着色器逐顶点属性
Libgdx custom shader per-vertex attribute
经过几天的挣扎,我来到了这里。我正在尝试将自定义的每顶点 vec3 属性传递给基于 this 教程的自定义着色器。本教程描述了如何传递实际工作正常的自定义制服。然而,当我试图修改代码以传递我的自定义逐顶点属性时,似乎没有任何东西被传输到顶点着色器,我不知道如何让它工作。
到目前为止,我已经完成了以下操作:
我用 modelBuilder.createBox() 创建了几个盒子(所以我确定每个模型都有 24 个顶点)
然后我生成了一个包含实际属性数据的 FloatBuffer,如下所示:
int[] data = new int[]{x1, y1, z1, x1, y1, z1, ...}
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
然后我在初始化相应的属性位置变量(成功,a_coord >= 0):
a_coord = program.getAttributeLocation("a_coord");
在那之后,在自定义着色器的 render(Renderable)
方法中的 libgdx 端,我将缓冲区传递给 OpenGL,如下所示:
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
我的自定义顶点着色器如下:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
//my custom attribute
attribute vec2 a_coord;
void main() {
v_texCoord0 = a_texCoord0;
float posY = a_position.y + a_coord.y;
gl_Position = u_projTrans * u_worldTrans * vec4(a_position.x, posY, a_position.z, 1.0);
}
问题
目前a_coord对于每个顶点都是0。我缺少什么以及如何将自定义属性正确传递给顶点着色器?
我猜问题出在 VBO 字段的某个地方以及 libGDX 将属性数据传递给顶点的方式,但我仍然无法弄清楚如何让它工作。
如果有人能在这个问题上指出正确的方向,我将很高兴。
完整代码:
主 AplicationListener class:
public class ProtoGame implements ApplicationListener {
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
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 = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
ModelBuilder modelBuilder = new ModelBuilder();
model = modelBuilder.createBox(1f, 1f, 1f,
new Material(),
VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
//this is where I'll put per-vertex attribute data for every instance
//but for now it's hardcoded in the Shader class so the data is the same across instances
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
@Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
@Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
@Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
}
自定义 libgdx 着色器 class:
public class TestShader implements Shader {
private FloatBuffer mAttributeBuffer;
ShaderProgram program;
Camera camera;
RenderContext context;
int u_projTrans;
int u_worldTrans;
int u_colorU;
int u_colorV;
int a_coord;
private static String getCustomVertexShader() {
return Gdx.files.internal("shader/test.vertex.glsl").readString();
}
private static String getCustomFragmentShader() {
return Gdx.files.internal("shader/test.fragment.glsl").readString();
}
@Override
public void init() {
program = new ShaderProgram(getCustomVertexShader(), getCustomFragmentShader());
if (!program.isCompiled())
throw new GdxRuntimeException(program.getLog());
//tutorial's logic to init custom uniform locations
u_projTrans = program.getUniformLocation("u_projTrans");
u_worldTrans = program.getUniformLocation("u_worldTrans");
u_colorU = program.getUniformLocation("u_colorU");
u_colorV = program.getUniformLocation("u_colorV");
//initing custom attribute location
a_coord = program.getAttributeLocation("a_coord");
//generating data and passing it to nio Buffer
float data[] = generateData();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
}
private float[] generateData() {
Vector3[] dataArray = new Vector3[1];
dataArray[0] = new Vector3(2, 2, 2);
int components = 3;
int vertexPerModel = 24;
float[] data = new float[dataArray.length * components * vertexPerModel];
for(int i = 0; i < dataArray.length; ++i){
int i3 = i * components;
for(int j = 0; j < vertexPerModel; ++j) {
int j3 = j * components;
data[i3 + 0 + j3] = dataArray[i].x;
data[i3 + 1 + j3] = dataArray[i].y;
data[i3 + 2 + j3] = dataArray[i].z;
}
}
return data;
}
@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(GL20.GL_LEQUAL);
context.setCullFace(GL20.GL_BACK);
}
@Override
public void render(Renderable renderable) {
program.setUniformMatrix(u_worldTrans, renderable.worldTransform);
//tutorial's logic to pass uniform
DoubleColorAttribute attribute = ((DoubleColorAttribute) renderable.material.get(DoubleColorAttribute.DiffuseUV));
program.setUniformf(u_colorU, attribute.color1.r, attribute.color1.g, attribute.color1.b);
program.setUniformf(u_colorV, attribute.color2.r, attribute.color2.g, attribute.color2.b);
//passing my custom attributes to the vertex shader
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
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 renderable) {
return renderable.material.has(DoubleColorAttribute.DiffuseUV);
}
}
我终于能够将自定义属性传递给顶点着色器了!非常感谢@Xoppa 为我指明了正确的方向。
这是我到目前为止的工作解决方案(我愿意就如何以更优雅的方式实施它提出任何进一步的建议):
首先,正如 Xoppa 在评论中所述,在构建模型时需要创建一个提供自定义顶点结构的模型。所以模型创建可能看起来像这样:
VertexAttribute posAttr = new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE);
...
VertexAttribute customVertexAttr = new VertexAttribute(512, 3, "a_custom");
VertexAttributes vertexAttributes = new VertexAttributes(
posAttr,
...
customVertexAttr);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
model = modelBuilder.end();
或与MeshBuilder
相同:
MeshBuilder meshBuilder = new MeshBuilder();
VertexAttributes vertexAttributes = new VertexAttributes(...);
meshBuilder.begin(vertexAttributes);
meshBuilder.part("box", GL20.GL_TRIANGLES);
meshBuilder.setColor(color);
meshBuilder.box(1f, 1f, 1f);
Mesh mesh = meshBuilder.end();
此代码将根据提供的属性创建包含附加数据的顶点模型。是时候填充相应的顶点数组了。你需要一个网格 - 它存储顶点数组 - 一个接一个顶点接一个顶点的打包属性的平面数组。所以你需要的是每个顶点的一些属性以及需要修改的属性的偏移量。 Mesh 存储所有这些数据:
Mesh mesh = model.meshes.get(0);
int numVertices = mesh.getNumVertices();
// vertex size and offset are in byte so we need to divide it by 4
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
//it's possible to use usage int here passed previously to VertexAttribute constructor.
VertexAttribute customAttribute = mesh.getVertexAttribute(512)
int offset = customAttribute.offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
我们已准备好传递数据:
List<Vector3> customData ...
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = customData.get(i).x;
vertices[index + 1] = customData.get(i).y;
vertices[index + 2] = customData.get(i).z;
}
并且不要忘记将更新后的顶点数组传回网格:
mesh.updateVertices(0, vertices);
就是这样。
这里还有一个辅助方法的实现,它使用 Usage
标志和自定义属性来创建默认属性的混合:
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
完整来源:
public class ProtoGame implements ApplicationListener {
private static final int CUSTOM_ATTRIBUTE_USAGE = 512;
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
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 = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
Model model = createModelWithCustomAttributes();
Mesh mesh = model.meshes.get(0);
setCustomAttributeData(mesh);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
@Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
@Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
private Model createModelWithCustomAttributes() {
int defaultAttributes = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates;
VertexAttribute customVertexAttr = new VertexAttribute(CUSTOM_ATTRIBUTE_USAGE, 3, "a_custom");
List<VertexAttribute> customAttributeList = new ArrayList<VertexAttribute>();
customAttributeList.add(customVertexAttr);
VertexAttributes vertexAttributes = createMixedVertexAttribute(defaultAttributes, customAttributeList);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
return modelBuilder.end();
}
private void setCustomAttributeData(Mesh mesh) {
int numVertices = mesh.getNumVertices();
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
int offset = mesh.getVertexAttribute(CUSTOM_ATTRIBUTE_USAGE).offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = i;
vertices[index + 1] = i;
vertices[index + 2] = i;
}
mesh.updateVertices(0, vertices);
}
@Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
}
顶点着色器:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
attribute vec3 a_custom;
void main() {
v_texCoord0 = a_texCoord0;
float posX = a_position.x + a_custom.x;
float posY = a_position.y + a_custom.y;
float posZ = a_position.z + a_custom.z;
gl_Position = u_projTrans * u_worldTrans * vec4(posX, posY, posZ, 1.0);
}
片段着色器
#ifdef GL_ES
precision mediump float;
#endif
uniform 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);
}
实际上,可以直接调用 setVertexAttributes 并使其在不需要网格的情况下工作。只需要像这样扩展 ShaderProgram
public class ShaderProgramExtended extends ShaderProgram {
public ShaderProgramExtended(String v, String f){
super(v,f);
}
/*
This is VERY NAUGHTY. Mario and Nathan probably made it private for a reason
*/
public int getProgram(){
int result;
try{
Field field = ShaderProgram.class.getDeclaredField("program");
field.setAccessible(true);
Object value = field.get(this);
field.setAccessible(false);
if (value == null) {
result = 0;
}else{
result = (Integer) value;
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
public void begin(int program){
Gdx.gl20.glUseProgram(program);
}
public void draw(int mode, int first, int count){
Gdx.gl20.glDrawArrays(mode,first,count);
}
然后像往常一样调用它 "minor" 将着色器对象整数发送到新的 begin 方法
public class TestlibGDXv2 extends ApplicationAdapter {
private final String tag = (this).getClass().getSimpleName();
String message = "";
private static final int FLOAT_BYTES = 4;
FloatBuffer vertexData;
FloatBuffer colorData;
ShaderProgramExtended shader;
private static final String COLOR_ATTRIBUTE = ShaderProgram.COLOR_ATTRIBUTE;
private int aColourLocation;
private static final String POSITION_ATTRIBUTE = ShaderProgram.POSITION_ATTRIBUTE;
private int aPositionLocation;
/*
Anti-clockwise winding order. Note, we could share two of the vertices. Haven't for clarity.
*/
float[] vertices = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
-0.5f, 0.5f,
0.5f, -0.5f,
0.5f, 0.5f
};
/*
Need to colour each vertex, so need 6.
*/
float[] colors = {1.0f, 0.0f, 0.0f, 1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
1.0f,0.0f,0.0f,1.0f
};
@Override
public void create() {
/*
Convert from Dalvik VM to OpenGL native
*/
vertexData = ByteBuffer.allocateDirect(vertices.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
colorData = ByteBuffer.allocateDirect(colors.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(vertices).position(0);
colorData.put(colors).position(0);
initialiseShaders();
}
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
shader.begin(shader.getProgram());
shader.setVertexAttribute(aPositionLocation, 2, GL20.GL_FLOAT, false, 0, vertexData);
shader.enableVertexAttribute(aPositionLocation);
shader.setVertexAttribute(aColourLocation, 4, GL20.GL_FLOAT, false, 0, colorData);
shader.enableVertexAttribute(aColourLocation);
shader.draw(GL20.GL_TRIANGLES, 0, 6);
shader.end();
}
private void initialiseShaders() {
String vertexShaderSource =
"#version 130\n" +
"attribute vec4 " + POSITION_ATTRIBUTE + ";\n" + // x,y,z and w
"attribute vec4 " + COLOR_ATTRIBUTE + ";\n" + // r,g,b and a
"varying vec4 v_color;\n" + // pass to fragment shader
"void main(){\n" +
" v_color = "+ COLOR_ATTRIBUTE + ";\n" +
" gl_Position = " + POSITION_ATTRIBUTE + ";\n" +
"}";
String fragmentShaderSource =
"#version 130\n" +
"#ifdef GL_ES\n" +
" precision mediump float;\n" + // medium a good balance between speed and quality
"#endif\n" +
"varying vec4 v_color;\n" + // incoming from vertex shader
"void main(){\n" +
" gl_FragColor = v_color;\n" +
"}";
shader = new ShaderProgramExtended(vertexShaderSource,fragmentShaderSource);
aPositionLocation = shader.getAttributeLocation(POSITION_ATTRIBUTE);
aColourLocation = shader.getAttributeLocation(COLOR_ATTRIBUTE);
}
}
属性 "a_color"(来自 ShaderProgram.COLOR_ATTRIBUTE)可以很容易地成为 "a_custom"。
我在尝试从 WebGL 书籍中学习 OpenGL ES 时使用了这种方法 - 它们比 libGDX OpenGL 书籍多得多。但是,仅将上述内容用于学习,着色器现在不再由 libGDX 管理,因此当上下文丢失时将无法在 Android 上很好地工作。不过台式机还行。
干杯
约翰
根据 Xoppa 的评论,我设法使自定义属性起作用。其实很简单!这是一个 ES 3.0 示例:
#version 300 es
//v30.glsl
in vec3 vertexPosition;
in vec3 vertexColor;
out vec3 outColor;
void main()
{
outColor = vertexColor;
gl_Position = vec4(vertexPosition, 1.0);
}
片段着色器:
#version 300 es
//f30.glsl
precision mediump float;
in vec3 outColor;
out vec4 fragColor;
void main()
{
fragColor = vec4(outColor, 1.0);
}
Gl30Mesh.java
package com.example.jgles;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
public class Gl30Mesh extends ApplicationAdapter
{
private Mesh triangleMesh;
private ShaderProgram passShader;
@Override
public void create()
{
final float [] combinedData = {
-0.8f, -0.8f, 0.0f, 1.0f, 0.0f, 0.0f,
0.8f, -0.8f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 1.0f
};
VertexAttribute vertexChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexPosition");
VertexAttribute colorChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexColor");
String vertexShader = Gdx.files.internal("v30.glsl").readString();
String fragmentShader = Gdx.files.internal("f30.glsl").readString();
passShader = new ShaderProgram(vertexShader, fragmentShader);
if (!passShader.isCompiled()){
throw new IllegalStateException(passShader.getLog());
}
passShader.bind();
triangleMesh = new Mesh(true, 3, 0, vertexChannel, colorChannel);
triangleMesh.setVertices(combinedData);
}
@Override
public void render()
{
Gdx.gl.glViewport(0, 0, 640, 480);
Gdx.gl.glClearColor(0, 0, 1.0f, 0);
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
triangleMesh.render(passShader, GL30.GL_TRIANGLES);
}
}
在我看来,当您已经在使用自定义着色器时,您可以使用着色器中命名的任何内容,而不是 POSITION_ATTRIBUTE、NORMAL_ATTRIBUTE 等。正如 Xoppa 所说,ShaderProgram.setVertexAttribute根本行不通。
经过几天的挣扎,我来到了这里。我正在尝试将自定义的每顶点 vec3 属性传递给基于 this 教程的自定义着色器。本教程描述了如何传递实际工作正常的自定义制服。然而,当我试图修改代码以传递我的自定义逐顶点属性时,似乎没有任何东西被传输到顶点着色器,我不知道如何让它工作。
到目前为止,我已经完成了以下操作:
我用 modelBuilder.createBox() 创建了几个盒子(所以我确定每个模型都有 24 个顶点)
然后我生成了一个包含实际属性数据的 FloatBuffer,如下所示:
int[] data = new int[]{x1, y1, z1, x1, y1, z1, ...}
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
然后我在初始化相应的属性位置变量(成功,a_coord >= 0):
a_coord = program.getAttributeLocation("a_coord");
在那之后,在自定义着色器的 render(Renderable)
方法中的 libgdx 端,我将缓冲区传递给 OpenGL,如下所示:
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
我的自定义顶点着色器如下:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
//my custom attribute
attribute vec2 a_coord;
void main() {
v_texCoord0 = a_texCoord0;
float posY = a_position.y + a_coord.y;
gl_Position = u_projTrans * u_worldTrans * vec4(a_position.x, posY, a_position.z, 1.0);
}
问题
目前a_coord对于每个顶点都是0。我缺少什么以及如何将自定义属性正确传递给顶点着色器?
我猜问题出在 VBO 字段的某个地方以及 libGDX 将属性数据传递给顶点的方式,但我仍然无法弄清楚如何让它工作。
如果有人能在这个问题上指出正确的方向,我将很高兴。
完整代码:
主 AplicationListener class:
public class ProtoGame implements ApplicationListener {
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
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 = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
ModelBuilder modelBuilder = new ModelBuilder();
model = modelBuilder.createBox(1f, 1f, 1f,
new Material(),
VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
//this is where I'll put per-vertex attribute data for every instance
//but for now it's hardcoded in the Shader class so the data is the same across instances
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
@Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
@Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
@Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
}
自定义 libgdx 着色器 class:
public class TestShader implements Shader {
private FloatBuffer mAttributeBuffer;
ShaderProgram program;
Camera camera;
RenderContext context;
int u_projTrans;
int u_worldTrans;
int u_colorU;
int u_colorV;
int a_coord;
private static String getCustomVertexShader() {
return Gdx.files.internal("shader/test.vertex.glsl").readString();
}
private static String getCustomFragmentShader() {
return Gdx.files.internal("shader/test.fragment.glsl").readString();
}
@Override
public void init() {
program = new ShaderProgram(getCustomVertexShader(), getCustomFragmentShader());
if (!program.isCompiled())
throw new GdxRuntimeException(program.getLog());
//tutorial's logic to init custom uniform locations
u_projTrans = program.getUniformLocation("u_projTrans");
u_worldTrans = program.getUniformLocation("u_worldTrans");
u_colorU = program.getUniformLocation("u_colorU");
u_colorV = program.getUniformLocation("u_colorV");
//initing custom attribute location
a_coord = program.getAttributeLocation("a_coord");
//generating data and passing it to nio Buffer
float data[] = generateData();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
}
private float[] generateData() {
Vector3[] dataArray = new Vector3[1];
dataArray[0] = new Vector3(2, 2, 2);
int components = 3;
int vertexPerModel = 24;
float[] data = new float[dataArray.length * components * vertexPerModel];
for(int i = 0; i < dataArray.length; ++i){
int i3 = i * components;
for(int j = 0; j < vertexPerModel; ++j) {
int j3 = j * components;
data[i3 + 0 + j3] = dataArray[i].x;
data[i3 + 1 + j3] = dataArray[i].y;
data[i3 + 2 + j3] = dataArray[i].z;
}
}
return data;
}
@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(GL20.GL_LEQUAL);
context.setCullFace(GL20.GL_BACK);
}
@Override
public void render(Renderable renderable) {
program.setUniformMatrix(u_worldTrans, renderable.worldTransform);
//tutorial's logic to pass uniform
DoubleColorAttribute attribute = ((DoubleColorAttribute) renderable.material.get(DoubleColorAttribute.DiffuseUV));
program.setUniformf(u_colorU, attribute.color1.r, attribute.color1.g, attribute.color1.b);
program.setUniformf(u_colorV, attribute.color2.r, attribute.color2.g, attribute.color2.b);
//passing my custom attributes to the vertex shader
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
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 renderable) {
return renderable.material.has(DoubleColorAttribute.DiffuseUV);
}
}
我终于能够将自定义属性传递给顶点着色器了!非常感谢@Xoppa 为我指明了正确的方向。
这是我到目前为止的工作解决方案(我愿意就如何以更优雅的方式实施它提出任何进一步的建议):
首先,正如 Xoppa 在评论中所述,在构建模型时需要创建一个提供自定义顶点结构的模型。所以模型创建可能看起来像这样:
VertexAttribute posAttr = new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE);
...
VertexAttribute customVertexAttr = new VertexAttribute(512, 3, "a_custom");
VertexAttributes vertexAttributes = new VertexAttributes(
posAttr,
...
customVertexAttr);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
model = modelBuilder.end();
或与MeshBuilder
相同:
MeshBuilder meshBuilder = new MeshBuilder();
VertexAttributes vertexAttributes = new VertexAttributes(...);
meshBuilder.begin(vertexAttributes);
meshBuilder.part("box", GL20.GL_TRIANGLES);
meshBuilder.setColor(color);
meshBuilder.box(1f, 1f, 1f);
Mesh mesh = meshBuilder.end();
此代码将根据提供的属性创建包含附加数据的顶点模型。是时候填充相应的顶点数组了。你需要一个网格 - 它存储顶点数组 - 一个接一个顶点接一个顶点的打包属性的平面数组。所以你需要的是每个顶点的一些属性以及需要修改的属性的偏移量。 Mesh 存储所有这些数据:
Mesh mesh = model.meshes.get(0);
int numVertices = mesh.getNumVertices();
// vertex size and offset are in byte so we need to divide it by 4
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
//it's possible to use usage int here passed previously to VertexAttribute constructor.
VertexAttribute customAttribute = mesh.getVertexAttribute(512)
int offset = customAttribute.offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
我们已准备好传递数据:
List<Vector3> customData ...
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = customData.get(i).x;
vertices[index + 1] = customData.get(i).y;
vertices[index + 2] = customData.get(i).z;
}
并且不要忘记将更新后的顶点数组传回网格:
mesh.updateVertices(0, vertices);
就是这样。
这里还有一个辅助方法的实现,它使用 Usage
标志和自定义属性来创建默认属性的混合:
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
完整来源:
public class ProtoGame implements ApplicationListener {
private static final int CUSTOM_ATTRIBUTE_USAGE = 512;
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
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 = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
Model model = createModelWithCustomAttributes();
Mesh mesh = model.meshes.get(0);
setCustomAttributeData(mesh);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
@Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
@Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
private Model createModelWithCustomAttributes() {
int defaultAttributes = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates;
VertexAttribute customVertexAttr = new VertexAttribute(CUSTOM_ATTRIBUTE_USAGE, 3, "a_custom");
List<VertexAttribute> customAttributeList = new ArrayList<VertexAttribute>();
customAttributeList.add(customVertexAttr);
VertexAttributes vertexAttributes = createMixedVertexAttribute(defaultAttributes, customAttributeList);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
return modelBuilder.end();
}
private void setCustomAttributeData(Mesh mesh) {
int numVertices = mesh.getNumVertices();
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
int offset = mesh.getVertexAttribute(CUSTOM_ATTRIBUTE_USAGE).offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = i;
vertices[index + 1] = i;
vertices[index + 2] = i;
}
mesh.updateVertices(0, vertices);
}
@Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
}
顶点着色器:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
attribute vec3 a_custom;
void main() {
v_texCoord0 = a_texCoord0;
float posX = a_position.x + a_custom.x;
float posY = a_position.y + a_custom.y;
float posZ = a_position.z + a_custom.z;
gl_Position = u_projTrans * u_worldTrans * vec4(posX, posY, posZ, 1.0);
}
片段着色器
#ifdef GL_ES
precision mediump float;
#endif
uniform 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);
}
实际上,可以直接调用 setVertexAttributes 并使其在不需要网格的情况下工作。只需要像这样扩展 ShaderProgram
public class ShaderProgramExtended extends ShaderProgram {
public ShaderProgramExtended(String v, String f){
super(v,f);
}
/*
This is VERY NAUGHTY. Mario and Nathan probably made it private for a reason
*/
public int getProgram(){
int result;
try{
Field field = ShaderProgram.class.getDeclaredField("program");
field.setAccessible(true);
Object value = field.get(this);
field.setAccessible(false);
if (value == null) {
result = 0;
}else{
result = (Integer) value;
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
public void begin(int program){
Gdx.gl20.glUseProgram(program);
}
public void draw(int mode, int first, int count){
Gdx.gl20.glDrawArrays(mode,first,count);
}
然后像往常一样调用它 "minor" 将着色器对象整数发送到新的 begin 方法
public class TestlibGDXv2 extends ApplicationAdapter {
private final String tag = (this).getClass().getSimpleName();
String message = "";
private static final int FLOAT_BYTES = 4;
FloatBuffer vertexData;
FloatBuffer colorData;
ShaderProgramExtended shader;
private static final String COLOR_ATTRIBUTE = ShaderProgram.COLOR_ATTRIBUTE;
private int aColourLocation;
private static final String POSITION_ATTRIBUTE = ShaderProgram.POSITION_ATTRIBUTE;
private int aPositionLocation;
/*
Anti-clockwise winding order. Note, we could share two of the vertices. Haven't for clarity.
*/
float[] vertices = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
-0.5f, 0.5f,
0.5f, -0.5f,
0.5f, 0.5f
};
/*
Need to colour each vertex, so need 6.
*/
float[] colors = {1.0f, 0.0f, 0.0f, 1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
1.0f,0.0f,0.0f,1.0f
};
@Override
public void create() {
/*
Convert from Dalvik VM to OpenGL native
*/
vertexData = ByteBuffer.allocateDirect(vertices.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
colorData = ByteBuffer.allocateDirect(colors.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(vertices).position(0);
colorData.put(colors).position(0);
initialiseShaders();
}
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
shader.begin(shader.getProgram());
shader.setVertexAttribute(aPositionLocation, 2, GL20.GL_FLOAT, false, 0, vertexData);
shader.enableVertexAttribute(aPositionLocation);
shader.setVertexAttribute(aColourLocation, 4, GL20.GL_FLOAT, false, 0, colorData);
shader.enableVertexAttribute(aColourLocation);
shader.draw(GL20.GL_TRIANGLES, 0, 6);
shader.end();
}
private void initialiseShaders() {
String vertexShaderSource =
"#version 130\n" +
"attribute vec4 " + POSITION_ATTRIBUTE + ";\n" + // x,y,z and w
"attribute vec4 " + COLOR_ATTRIBUTE + ";\n" + // r,g,b and a
"varying vec4 v_color;\n" + // pass to fragment shader
"void main(){\n" +
" v_color = "+ COLOR_ATTRIBUTE + ";\n" +
" gl_Position = " + POSITION_ATTRIBUTE + ";\n" +
"}";
String fragmentShaderSource =
"#version 130\n" +
"#ifdef GL_ES\n" +
" precision mediump float;\n" + // medium a good balance between speed and quality
"#endif\n" +
"varying vec4 v_color;\n" + // incoming from vertex shader
"void main(){\n" +
" gl_FragColor = v_color;\n" +
"}";
shader = new ShaderProgramExtended(vertexShaderSource,fragmentShaderSource);
aPositionLocation = shader.getAttributeLocation(POSITION_ATTRIBUTE);
aColourLocation = shader.getAttributeLocation(COLOR_ATTRIBUTE);
}
}
属性 "a_color"(来自 ShaderProgram.COLOR_ATTRIBUTE)可以很容易地成为 "a_custom"。
我在尝试从 WebGL 书籍中学习 OpenGL ES 时使用了这种方法 - 它们比 libGDX OpenGL 书籍多得多。但是,仅将上述内容用于学习,着色器现在不再由 libGDX 管理,因此当上下文丢失时将无法在 Android 上很好地工作。不过台式机还行。
干杯
约翰
根据 Xoppa 的评论,我设法使自定义属性起作用。其实很简单!这是一个 ES 3.0 示例:
#version 300 es
//v30.glsl
in vec3 vertexPosition;
in vec3 vertexColor;
out vec3 outColor;
void main()
{
outColor = vertexColor;
gl_Position = vec4(vertexPosition, 1.0);
}
片段着色器:
#version 300 es
//f30.glsl
precision mediump float;
in vec3 outColor;
out vec4 fragColor;
void main()
{
fragColor = vec4(outColor, 1.0);
}
Gl30Mesh.java
package com.example.jgles;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
public class Gl30Mesh extends ApplicationAdapter
{
private Mesh triangleMesh;
private ShaderProgram passShader;
@Override
public void create()
{
final float [] combinedData = {
-0.8f, -0.8f, 0.0f, 1.0f, 0.0f, 0.0f,
0.8f, -0.8f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 1.0f
};
VertexAttribute vertexChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexPosition");
VertexAttribute colorChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexColor");
String vertexShader = Gdx.files.internal("v30.glsl").readString();
String fragmentShader = Gdx.files.internal("f30.glsl").readString();
passShader = new ShaderProgram(vertexShader, fragmentShader);
if (!passShader.isCompiled()){
throw new IllegalStateException(passShader.getLog());
}
passShader.bind();
triangleMesh = new Mesh(true, 3, 0, vertexChannel, colorChannel);
triangleMesh.setVertices(combinedData);
}
@Override
public void render()
{
Gdx.gl.glViewport(0, 0, 640, 480);
Gdx.gl.glClearColor(0, 0, 1.0f, 0);
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
triangleMesh.render(passShader, GL30.GL_TRIANGLES);
}
}
在我看来,当您已经在使用自定义着色器时,您可以使用着色器中命名的任何内容,而不是 POSITION_ATTRIBUTE、NORMAL_ATTRIBUTE 等。正如 Xoppa 所说,ShaderProgram.setVertexAttribute根本行不通。