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根本行不通。