Libgdx 自定义着色器 per-vertex 属性

vir*_* us 3 customization shader vertex-shader libgdx

经过几天的挣扎,我来到了这里。我正在尝试根据教程将自定义 per-vertex vec3 属性传递给自定义着色器。本教程介绍了如何传递实际工作正常的自定义制服。但是,当我尝试修改代码以传递我的自定义 per-vertex 属性时,似乎没有任何内容传输到顶点着色器,我无法弄清楚如何使其工作。

到目前为止,我已经完成了以下工作:

我已经用 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); 
Run Code Online (Sandbox Code Playgroud)

然后我正在初始化相应的属性位置变量(成功,a_coord >= 0):

    a_coord = program.getAttributeLocation("a_coord");
Run Code Online (Sandbox Code Playgroud)

在自定义着色器的render(Renderable)方法中的libgdx 端之后,我将缓冲区传递给 OpenGL,如下所示:

    program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
Run Code Online (Sandbox Code Playgroud)

我的自定义顶点着色器如下:

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);
}
Run Code Online (Sandbox Code Playgroud)

问题

目前,每个顶点的 a_coord 都是 0。我缺少什么以及如何将自定义属性正确传递给顶点着色器?

我猜问题出在 VBO 领域的某个地方,以及 libGDX 将属性数据传递给顶点的方式,但我仍然无法弄清楚如何使其工作。

如果有人能在这个问题上指出我正确的方向,我会很高兴。

完整代码:

主应用程序监听器类

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

自定义 libgdx 着色器类:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

vir*_* us 5

最后,我能够将自定义属性传递给顶点着色器!非常感谢@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();
Run Code Online (Sandbox Code Playgroud)

或与以下相同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();
Run Code Online (Sandbox Code Playgroud)

此代码将根据提供的属性创建具有包含附加数据的顶点的模型。是时候填充对应的顶点数组了。为此,您需要一个网格 - 它存储顶点数组 - 一个接一个顶点一个顶点的打包属性的平面数组。所以你需要的是每个顶点的一些属性以及需要修改的属性的偏移量。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);
Run Code Online (Sandbox Code Playgroud)

我们准备传递数据:

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;
}
Run Code Online (Sandbox Code Playgroud)

并且不要忘记将更新的顶点数组传递回网格:

    mesh.updateVertices(0, vertices);
Run Code Online (Sandbox Code Playgroud)

就是这样。

这里还有一个辅助方法的实现,它使用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;
}
Run Code Online (Sandbox Code Playgroud)

完整来源

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() {
}
}
Run Code Online (Sandbox Code Playgroud)

顶点着色器

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);
}
Run Code Online (Sandbox Code Playgroud)

片段着色器

#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);
}
Run Code Online (Sandbox Code Playgroud)