【问题标题】:OpenGL: Render one or multiple parts of a 3D-Model using one VAO(LWJGL)OpenGL:使用一个 VAO(LWJGL)渲染 3D 模型的一个或多个部分
【发布时间】:2016-02-10 18:43:48
【问题描述】:

我目前正在开发一个 LWJGL 程序,该程序显示一个带有纹理和材质的 .obj 模型(尚未添加渲染材质,但对于这个问题并不重要)。加载文件并存储它们的数据是没有问题的 - 最后我有一个 WaveFrontObject.class (如下所示),其中包含关于我的模型的所有必需信息,包括它的单个部件/组的列表。同样使用 glVertex3f()、glTextureCoord2f() 和 glNormal3f() 渲染它们效果很好。但由于一些模型变得相当大(8MB 或更多),我想使用 VAO 和 VBO 使我的渲染算法更高效。

我的问题是,我想渲染一个、全部或一组自定义模型部件/组对象,并对它们应用自定义平移/旋转和缩放。 为了保持干净和简单,我只想每个实体使用一个 VAO。所以我需要以某种方式添加/删除我的顶点-VBO 和索引VBO 的某些部分,或者至少以某种方式决定应该实际渲染模型的哪些部分。 这样做的最佳方法是什么?

这是我目前所拥有的 - 抱歉,如果有一些不必要的代码部分,我尝试了很多东西但没有成功,所以我决定来这里询问:

WaveFrontObject.class(主要的 OBJLoader&OBJModel.class):

    package objmodels;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;

import Toolbox.FileUtils;
import program.RLMEditor;
import shaders.StaticShader;
import textures.ModelTexture;

public class WaveFrontObject {

    private String fileName;

    public List<Vector3f> vertices = new ArrayList<Vector3f>();
    public List<Vector3f> normals = new ArrayList<Vector3f>();
    public List<Vector2f> textureCoordinates = new ArrayList<Vector2f>();
    public List<GroupObject> groupObjects = new ArrayList<GroupObject>();

    private GroupObject currentGroupObject = null;

    public int textureID;
    public boolean loadedTexture = false;

    public File texture;

    public int numLines;

    public RLMEditor program;

    public WaveFrontObject(File objFile,RLMEditor parent, boolean transferProgress) {
        this.textureID = ModelTexture.getStandardModel().getTextureID();
        this.fileName = objFile.getPath();
        this.program = parent;
        if (isValidOBJFile(objFile)) {
            loadOBJModel(objFile, transferProgress);
        }
    }

    public WaveFrontObject(File objFile) {
        this.textureID = ModelTexture.getStandardModel().getTextureID();
        this.fileName = objFile.getPath();
        if (isValidOBJFile(objFile)) {
            loadOBJModel(objFile, false);
        }
    }

    private void loadOBJModel(File objFile, boolean transferProgress) {
        if (transferProgress)
            try {
                //program.loadingProgress = new FrameProgressBar(numLines);
                //program.loadingProgress.setVisible(true);
                numLines = FileUtils.countLines(objFile.getPath());
            } catch (IOException e1) {
                e1.printStackTrace();
            }

        try {
            FileReader fileReader;
            fileReader = new FileReader(objFile);
            BufferedReader reader = new BufferedReader(fileReader);

            String line = null;
            int lineCount = 0;
            while ((line = reader.readLine()) != null) {
                lineCount++;
                if (transferProgress) {
                       // program.loadingProgress.addStep(1);
                }
                if (line.startsWith("#") || line.isEmpty()) {
                } else if (line.startsWith("v ")) {
                    System.out.println(line);
                    Vector3f vertex = parseVertex(line.split(" "));
                    vertices.add(vertex);
                } else if (line.startsWith("vn ")) {
                    Vector3f normal = parseNormal(line.split(" "));
                    normals.add(normal);
                } else if (line.startsWith("vt ")) {
                    Vector2f texture = parseTexture(line.split(" "));
                    textureCoordinates.add(texture);
                } else if (line.startsWith("f ")) {
                    if (currentGroupObject == null) {
                        currentGroupObject = new GroupObject("default");
                    }

                    Face face = parseFace(line, lineCount);

                    if (face != null) {
                        currentGroupObject.faces.add(face);
                    }
                } else if (line.startsWith("o ") || line.startsWith("g ")) {
                    GroupObject group = parseGroupObject(line, lineCount);

                    if (group != null) {
                        if (currentGroupObject != null) {
                            groupObjects.add(currentGroupObject);
                        }
                    }

                    currentGroupObject = group;
                    currentGroupObject.validate();
                    if(canAddGroupObject(currentGroupObject)){
                        groupObjects.add(currentGroupObject);
                    }else
                    System.out.println("The GroupObject '"+currentGroupObject.name+"' was already loaded!");
                }

            }
            //Parsing textureCoords to shader

            reader.close();
        } catch (FileNotFoundException e) {
            System.err.println("[FILENAME ERROR] The following File could not be found: " + fileName + "!");
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("[IO ERROR] FileReader for file: " + fileName + " could not be created!");
            e.printStackTrace();
        }
    }

    private boolean canAddGroupObject(GroupObject currentGroupObject) {
        for(GroupObject groupObject:groupObjects){
            if(groupObject.name.equalsIgnoreCase(currentGroupObject.name)){
                return false;
            }
        }
        return true;
    }

    public void renderAll(StaticShader shader) {
        if (currentGroupObject != null) {
            //GL11.glBegin(currentGroupObject.drawingMode);
        } else {
            //GL11.glBegin(GL11.GL_TRIANGLES);
        }
        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
        renderAllObjects(shader);
        //GL11.glEnd();
    }

    private void renderAllObjects(StaticShader shader) {
        for (GroupObject object : groupObjects) {
            object.render(shader);
        }
    }

    public void renderOnly(String... groupNames) {
        for (GroupObject groupObject : groupObjects) {
            for (String groupName : groupNames) {
                if (groupName.equalsIgnoreCase(groupObject.name)) {
                    renderObject(groupObject, program.window.shader);
                    //groupObject.render(program.window.shader);
                }
            }
        }
    }

    private void renderObject(GroupObject groupObject, StaticShader shader) {
             // Bind to the VAO that has all the information about the vertices
            GL30.glBindVertexArray(groupObject.vaoID);
            GL20.glEnableVertexAttribArray(0);

            // Bind to the index VBO that has all the information about the order of the vertices
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, groupObject.vboiID);

            // Draw the vertices
            GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, groupObject.getNumVertices());

            // Put everything back to default (deselect)
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
            GL20.glDisableVertexAttribArray(0);
            GL30.glBindVertexArray(0);
    }

    public void renderPart(String partName) {
        for (GroupObject groupObject : groupObjects) {
            if (partName.equalsIgnoreCase(groupObject.name)) {
                System.out.println("Rendering " + groupObject.name);
                groupObject.render(program.window.shader);
            }
        }
    }

    private GroupObject parseGroupObject(String line, int lineCount) {
        GroupObject group = null;

        if (isValidGroupObjectLine(line)) {
            String trimmedLine = line.substring(line.indexOf(" ") + 1);

            if (trimmedLine.length() > 0) {
                group = new GroupObject(trimmedLine);
            }
        } else {
            System.err.println("[OBJ FORMAT ERROR]");
            System.out.print("Parser was unable to read line number " + lineCount + "!");
        }

        return group;
    }

    private boolean isValidGroupObjectLine(String line) {
        return line.split(" ").length == 2;
    }

    private Face parseFace(String line, int lineCount) {
        Face face = null;
        String[] faceVertices = line.split(" ");
        String[] subTokens = null;
        if (faceVertices.length == 4) {
            currentGroupObject.drawingMode = GL11.GL_TRIANGLES;
            face = new Face();
            face.vertices = new Vector3f[faceVertices.length - 1];
            face.vertexNormals = new Vector3f[faceVertices.length - 1];
            face.textureCoords = new Vector2f[faceVertices.length - 1];

            for (int i = 1; i < faceVertices.length; i++) {
                subTokens = faceVertices[i].split("/");

                int currentVertexPointer = Integer.parseInt(subTokens[0]);
                face.indices.add(currentVertexPointer);

                face.vertices[i - 1] = vertices.get(Integer.parseInt(subTokens[0]) - 1);
                face.textureCoords[i - 1] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1);
                face.vertexNormals[i - 1] = normals.get(Integer.parseInt(subTokens[2]) - 1);
            }

            face.faceNormal = face.calculateFaceNormal();

        }

        if (faceVertices.length == 5) {
            currentGroupObject.drawingMode = GL11.GL_QUADS;
            System.err.println("[OBJ FORMAT ERROR]");
            System.out.print("The .obj parser can currently only load models with triangulated faces!");
        }

        return face;
    }

    private Vector2f parseTexture(String[] textureData) {
        Vector2f texture = new Vector2f(Float.parseFloat(textureData[1]), Float.parseFloat(textureData[2]));
        return texture;
    }

    private Vector3f parseNormal(String[] normalData) {
        Vector3f normal = new Vector3f(Float.parseFloat(normalData[1]), Float.parseFloat(normalData[2]),
                Float.parseFloat(normalData[3]));
        return normal;
    }

    private Vector3f parseVertex(String[] vertexData) {
        Vector3f vertex = new Vector3f(Float.parseFloat(vertexData[1]), Float.parseFloat(vertexData[2]),
                Float.parseFloat(vertexData[3]));
        return vertex;
    }

    private boolean isValidVertex(String[] vertexData) {
        return vertexData.length == 4;
    }

    private boolean isValidNormal(String[] normalData) {
        return normalData.length == 4;
    }

    private boolean isValidTexture(String[] textureData) {
        return textureData.length == 4;
    }

    private boolean isValidOBJFile(File objfile) {

        return objfile.getName().endsWith(".obj") || objfile.getName().endsWith(".OBJ");
    }

    public File getFileLocation() {
        return new File(fileName);
    }

    public void bindTexture(File textureFile) {
        Texture texture = null;
        try {
            texture = TextureLoader.getTexture("png", new FileInputStream(textureFile));
            this.textureID = texture.getTextureID();
            this.texture = textureFile;
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public float[] getVertexArray(){
        float[] verticesArray = new float[vertices.size()*3];
        for(int i = 0; i<vertices.size(); i++){
            verticesArray[i*3]=vertices.get(i).x;
            verticesArray[i*3+1]=vertices.get(i).y;
            verticesArray[i*3+2]=vertices.get(i).z;

        }

        return verticesArray;
    }

    public float[] getTextureArray(){
        float[] texturesArray = new float[textureCoordinates.size()*2];
        for(int i = 0; i<textureCoordinates.size(); i++){
            texturesArray[i*2]=textureCoordinates.get(i).x;
            texturesArray[i*2+1]=textureCoordinates.get(i).y;

        }
        return texturesArray;
    }

    public void cleanUp(){
        for(GroupObject groupObject:groupObjects){
            groupObject.cleanUp();
        }
    }

}

GroupObject.class - 包含来自单个模型部件的所有面、名称和 glDrawingMode(目前只能是 GL11.GL_TRIANGLES):

    package objmodels;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.vector.Vector3f;

import shaders.StaticShader;

    public class GroupObject {
        public int vaoID;
        public int vboID;
        public int vboiID;

        private boolean isValid; //Used to set the vertex-&indicesArray only once 
        public String name;
        public List<Face> faces = new ArrayList<Face>();
        public int drawingMode;
        private FloatBuffer verticesBuffer;
        private IntBuffer indicesBuffer;
        private int numIndices;

        public GroupObject(String name) {
            this.name = name;
        }

        public void prepareRendering(){
            vaoID = GL30.glGenVertexArrays();
            GL30.glBindVertexArray(vaoID);

            // Create a new VBO for the indices and select it (bind)
            vboID = GL15.glGenBuffers();
            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
            GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
            // Put the VBO in the attributes list at index 0
            GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
            // Deselect (bind to 0) the VBO
            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

            // Deselect (bind to 0) the VAO
            GL30.glBindVertexArray(0);

            vboiID = GL15.glGenBuffers();
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiID);
            GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
            // Deselect (bind to 0) the VBO
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        }

        public void render(StaticShader shader) {
             // Bind to the VAO that has all the information about the vertices
            GL30.glBindVertexArray(vaoID);
            GL20.glEnableVertexAttribArray(0);

            // Bind to the index VBO that has all the information about the order of the vertices
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiID);

            // Draw the vertices
            GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, getNumVertices());

            // Put everything back to default (deselect)
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
            GL20.glDisableVertexAttribArray(0);
            GL30.glBindVertexArray(0);
        }

        public void cleanUp(){
            // Disable the VBO index from the VAO attributes list
            GL20.glDisableVertexAttribArray(0);

            // Delete the vertex VBO
            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
            GL15.glDeleteBuffers(vboID);

            // Delete the index VBO
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
            GL15.glDeleteBuffers(vboiID);

            // Delete the VAO
            GL30.glBindVertexArray(0);
            GL30.glDeleteVertexArrays(vaoID);
        }

        public void renderFaces(StaticShader shader) {
            if (faces.size() > 0) {
                for (Face face : faces) {
                    face.addFaceForRender(shader);
                }
            }
        }

        public void validate() {
            this.isValid=true;
            this.verticesBuffer = getVertexBuffer();
            this.indicesBuffer = getIndicesBuffer();
        }

        public FloatBuffer getVertexBuffer() {
            float[] verticesArray = new float[getNumVertices()];
            for (int facePointer = 0; facePointer < faces.size(); facePointer++) {
                Face face = faces.get(facePointer);
                for (Vector3f faceVertex : face.vertices) {
                    verticesArray[facePointer] = faceVertex.x;
                    verticesArray[facePointer + 1] = faceVertex.y;
                    verticesArray[facePointer + 2] = faceVertex.z;
                }
            }

            FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(verticesArray.length);
            verticesBuffer.put(verticesArray);
            verticesBuffer.flip();

            return verticesBuffer;
        }

        public IntBuffer getIndicesBuffer() {
            List<Integer> indices = new ArrayList<Integer>();
            for (int facePointer = 0; facePointer < faces.size(); facePointer++) {
                Face face = faces.get(facePointer);
                int[] indicesArray = face.getIndicesArray();
                for (int i = 0; i < indicesArray.length; i++) {
                    indices.add(indicesArray[i]);
                    numIndices++;
                }
            }

            int[] indicesArray = new int[indices.size()];
            for (int index = 0; index < indices.size(); index++) {
                indicesArray[index] = indices.get(index);
            }

            IntBuffer indicesBuffer = BufferUtils.createIntBuffer(indicesArray.length);
            indicesBuffer.put(indicesArray);
            indicesBuffer.flip();

            return indicesBuffer;
        }

        public int getNumVertices() {
            int number = 0;
            for (Face face : faces) {
                number += face.getNumVertices();
            }
            return number;
        }

    }

Face.class-保存单个Face的信息(Vertices,Normals&Texturecoords):

    package objmodels;

import java.util.ArrayList;
import java.util.List;

import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;

import shaders.StaticShader;

public class Face {

    public Vector3f[] vertices;
    public Vector3f[] vertexNormals;
    public Vector2f[] textureCoords;

    public Vector3f faceNormal;
    public List<Integer> indices = new ArrayList<Integer>();

    public Face() {
        this.vertices = new Vector3f[3];
        this.vertexNormals = new Vector3f[3];
        this.textureCoords = new Vector2f[3];
    }

    public Face(Vector3f[] vertices, Vector3f[] vertexNormals, Vector2f[] textureCoords) {
        this.vertices = vertices;
        this.vertexNormals = vertexNormals;
        this.textureCoords = textureCoords;
        this.faceNormal = calculateFaceNormal();
    }

    public void setVertex(int index, Vector3f vertex, Vector3f normal, Vector2f textureCoords) {
        this.vertices[index] = vertex;
        this.vertexNormals[index] = normal;
        this.textureCoords[index] = textureCoords;
    }

    public Vector3f calculateFaceNormal() {
        Vector3f v1 = new Vector3f(vertices[1].x - vertices[0].x, vertices[1].y - vertices[0].y,
                vertices[1].z - vertices[0].z);
        Vector3f v2 = new Vector3f(vertices[2].x - vertices[0].x, vertices[2].y - vertices[0].y,
                vertices[2].z - vertices[0].z);
        Vector3f normalVector = null;

        normalVector = (Vector3f) Vector3f.cross(v1, v2, normalVector).normalise();

        return new Vector3f((float) normalVector.x, (float) normalVector.y, (float) normalVector.z);
    }

    public void addFaceForRender(StaticShader shader) {
        Vector3f normal = calculateFaceNormal();
        for (int i = 0; i < vertices.length; i++) {
            {
                // int textureCoordsLocation =
                // GL20.glGetAttribLocation(shader.programID, "textureCoords");
                Vector3f vertex = vertices[i];
                Vector2f textureCoord = textureCoords[i];
                // GL20.glVertexAttrib2f(textureCoordsLocation, textureCoord.x,
                // textureCoord.y);
                GL11.glTexCoord2f(textureCoord.x, textureCoord.y);
                GL11.glVertex3d(vertex.x, vertex.y, vertex.z);
                GL11.glNormal3d(normal.x, normal.y, normal.z);
            }
        }
    }

    public int getNumVertices() {
        return vertices.length;
    }

    public int[] getIndicesArray() {
        int[] indicesArray = new int[indices.size()];
        for (int i = 0; i < indices.size(); i++) {
            indicesArray[i]=indices.get(i);
        }
        return indicesArray;
    }

}

感谢您的建议 - ItAMysterious

【问题讨论】:

  • 您可以使用一个 VAO,并为每组顶点在 VAO 中设置不同的偏移量。

标签: java opengl lwjgl vbo vao


【解决方案1】:

很遗憾,您发布了太多代码(~570 行,哇!)所以我无法全部阅读。但是你解决的是一个很常见的问题,我可以给你一个通用的解决方案。

问题是,“我想使用一个共享的 VAO 绘制不同的模型。”这实际上比听起来容易得多。您可以简单地将所有模型连接到相同的 VBO 中,然后在调用 glDrawElements()glDrawArrays() 时,指定要使用数组的哪一部分(如果需要,使用 glDrawElementsBaseVertex() 而不是 glDrawElements() )。

例如,假设我们有模型 A、B 和 C。假设模型 A 有 1000 个顶点,模型 B 有 500 个,模型 C 有 750 个。

首先,将所有顶点数据连接到一个 VBO 中,并为所有内容使用一个 VAO。现在你可以打电话了:

glDrawArrays(GL_TRIANGLES,    0, 1000); // model A
glDrawArrays(GL_TRIANGLES, 1000,  500); // model B
glDrawArrays(GL_TRIANGLES, 1500,  750); // model C

如果你使用glDrawElements(),你有两个选择。

  • 您可以将范围传递给glDrawElements(),并将索引数组指向组合的 VAO,或者

  • 您可以将范围传递给glDrawElementsBaseVertex(),并使索引数组相对于每个模型中的第一个顶点。

无论哪种方式,减少状态更改次数的常用技术都是常见的。另一种选择是使用glBindVertexBuffer(),每次在您的 VBO 中指定一个不同的偏移量,但这仅在 4.3 的核心中提供,它比您需要的更灵活。

【讨论】:

  • 天啊-非常感谢!我现在要测试一下!
  • 嗯,一个普遍的问题 - 我的程序导致我的计算机不再响应。 glDrawElements 和 glDrawArrays 之间是否存在严重差异,因为您需要 glDrawElements 的索引?
  • @ItsAMysterious:差别不大。但是,如果您的索引超出范围,则可能会出现问题。
  • 啊,好吧 - 如果我不想使用 glDrawElements,我不需要索引吗?
  • 对。如果您有索引,则使用glDrawElements()。如果不这样做,则使用glDrawArrays(),这与使用索引数组0、1、2、3、4...调用glDrawElements() 相同
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多