用OpenGl库调用Ply 3D模型 Java代码实现

所需依赖:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>plyviewer-lwjgl</artifactId>
    <version>1.0</version>
    <properties>
        <lwjgl.version>3.3.1</lwjgl.version>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <!-- LWJGL Core -->
        <dependency>
            <groupId>org.lwjgl</groupId>
            <artifactId>lwjgl</artifactId>
            <version>${lwjgl.version}</version>
        </dependency>
        <dependency>
            <groupId>org.joml</groupId>
            <artifactId>joml</artifactId>
            <version>1.10.5</version>
        </dependency>

        <dependency>
            <groupId>org.lwjgl</groupId>
            <artifactId>lwjgl-glfw</artifactId>
            <version>${lwjgl.version}</version>
        </dependency>
        <dependency>
            <groupId>org.lwjgl</groupId>
            <artifactId>lwjgl-opengl</artifactId>
            <version>${lwjgl.version}</version>
        </dependency>
        <!-- Native Bindings -->
        <dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl</artifactId><classifier>natives-windows</classifier><version>${lwjgl.version}</version></dependency>
        <dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-glfw</artifactId><classifier>natives-windows</classifier><version>${lwjgl.version}</version></dependency>
        <dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengl</artifactId><classifier>natives-windows</classifier><version>${lwjgl.version}</version></dependency>
    </dependencies>
</project>

BinaryPLYViewer:

package com.example.java3d;

import org.joml.Matrix4f;
import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL33.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.*;


/**
 * Binary or ASCii PLY 文件查看器
 */
public class BinaryPLYViewer {

    private long window;
    private int width = 800;
    private int height = 600;

    private int vao;
    private int vbo;
    private int vertexCount;

    private int shaderProgram;

    // 视角控制变量
    private float yaw = 0.0f;
    private float pitch = 0.0f;
    private float zoom = 1.0f;

    private double lastX = -1;
    private double lastY = -1;
    private boolean rotating = false;

    public void run() {
        init();
        loop();

        // 释放资源
        glDeleteVertexArrays(vao);
        glDeleteBuffers(vbo);
        glDeleteProgram(shaderProgram);

        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);
        glfwTerminate();
    }


    // 初始化
    private void init() {
        GLFWErrorCallback.createPrint(System.err).set();

        if (!glfwInit())
            throw new IllegalStateException("Unable to initialize GLFW");

        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

        window = glfwCreateWindow(width, height, "PLY Viewer", NULL, NULL);
        if (window == NULL)
            throw new RuntimeException("Failed to create GLFW window");

        glfwMakeContextCurrent(window);
        glfwSwapInterval(1); // VSync

        glfwSetFramebufferSizeCallback(window, (w, newW, newH) -> {
            width = newW;
            height = newH;
            glViewport(0, 0, width, height);
        });

        // 鼠标事件回调
        glfwSetCursorPosCallback(window, (win, xpos, ypos) -> {
            if (rotating) {
                if (lastX >= 0 && lastY >= 0) {
                    double dx = xpos - lastX;
                    double dy = ypos - lastY;
                    yaw += dx * 0.3f;
                    pitch += dy * 0.3f;
//                    pitch = Math.max(-89.0f, Math.min(89.0f, pitch)); // 限制pitch范围
                }
                lastX = xpos;
                lastY = ypos;
            }
        });

        glfwSetMouseButtonCallback(window, (win, button, action, mods) -> {
            if (button == GLFW_MOUSE_BUTTON_LEFT) {
                if (action == GLFW_PRESS) {
                    rotating = true;
                    try (MemoryStack stack = stackPush()) {
                        DoubleBuffer px = stack.mallocDouble(1);
                        DoubleBuffer py = stack.mallocDouble(1);
                        glfwGetCursorPos(window, px, py);
                        lastX = px.get(0);
                        lastY = py.get(0);
                    }
                } else if (action == GLFW_RELEASE) {
                    rotating = false;
                    lastX = lastY = -1;
                }
            }
        });

        glfwSetScrollCallback(window, (win, xoffset, yoffset) -> {
            zoom *= 1.0f - yoffset * 0.1f;
            zoom = Math.max(0.1f, Math.min(10.0f, zoom));
        });

        GL.createCapabilities();
        glEnable(GL_PROGRAM_POINT_SIZE);

        shaderProgram = createShaderProgram();

        loadPLY("src/main/resources/c.ply");

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glEnable(GL_DEPTH_TEST);
    }


    // 创建着色器程序
    private int createShaderProgram() {
        String vertexShaderSrc = ""
                + "#version 330 core\n"
                + "layout(location = 0) in vec3 aPos;\n"
                + "layout(location = 1) in vec3 aColor;\n"
                + "uniform mat4 uMVP;\n"
                + "out vec3 vColor;\n"
                + "void main() {\n"
                + "    gl_Position = uMVP * vec4(aPos, 1.0);\n"
                + "    vColor = aColor;\n"
                + "    gl_PointSize = 2.0;\n"
                + "}\n";

        String fragmentShaderSrc = ""
                + "#version 330 core\n"
                + "in vec3 vColor;\n"
                + "out vec4 FragColor;\n"
                + "void main() {\n"
                + "    FragColor = vec4(vColor, 1.0);\n"
                + "}\n";

        int vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, vertexShaderSrc);
        glCompileShader(vertexShader);
        checkCompileErrors(vertexShader, "VERTEX");

        int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, fragmentShaderSrc);
        glCompileShader(fragmentShader);
        checkCompileErrors(fragmentShader, "FRAGMENT");

        int program = glCreateProgram();
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);
        checkCompileErrors(program, "PROGRAM");

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        return program;
    }

    // 检查编译错误
    private void checkCompileErrors(int shader, String type) {
        int success;
        if (type.equals("PROGRAM")) {
            success = glGetProgrami(shader, GL_LINK_STATUS);
            if (success == GL_FALSE) {
                String infoLog = glGetProgramInfoLog(shader);
                System.err.println("ERROR::PROGRAM_LINKING_ERROR\n" + infoLog);
            }
        } else {
            success = glGetShaderi(shader, GL_COMPILE_STATUS);
            if (success == GL_FALSE) {
                String infoLog = glGetShaderInfoLog(shader);
                System.err.println("ERROR::SHADER_COMPILATION_ERROR of type: " + type + "\n" + infoLog);
            }
        }
    }

    // 加载 PLY 文件
    private void loadPLY(String path) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path))) {
            DataInputStream dis = new DataInputStream(bis);
            List<String> headerLines = new ArrayList<>();
            String format = "";
            int vertexCount = 0;

            // --- 读取 header ---
            while (true) {
                String line = readLine(dis);
                if (line == null) break;
                headerLines.add(line);
                if (line.startsWith("format")) {
                    format = line.split(" ")[1].trim();
                } else if (line.startsWith("element vertex")) {
                    vertexCount = Integer.parseInt(line.split(" ")[2]);
                } else if (line.trim().equals("end_header")) {
                    break;
                }
            }

            this.vertexCount = vertexCount;
            FloatBuffer buffer = BufferUtils.createFloatBuffer(vertexCount * 6); // xyz + rgb

            // --- 判断格式并读取数据 ---
            if (format.equals("ascii")) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(bis, StandardCharsets.UTF_8));
                for (int i = 0; i < vertexCount; i++) {
                    String[] parts = reader.readLine().trim().split("\\s+");
                    float x = Float.parseFloat(parts[0]);
                    float y = Float.parseFloat(parts[1]);
                    float z = Float.parseFloat(parts[2]);
                    float r = Integer.parseInt(parts[3]) / 255.0f;
                    float g = Integer.parseInt(parts[4]) / 255.0f;
                    float b = Integer.parseInt(parts[5]) / 255.0f;
                    buffer.put(x).put(y).put(z).put(r).put(g).put(b);
                }
            } else if (format.equals("binary_little_endian")) {
                for (int i = 0; i < vertexCount; i++) {
                    float x = Float.intBitsToFloat(Integer.reverseBytes(dis.readInt()));
                    float y = Float.intBitsToFloat(Integer.reverseBytes(dis.readInt()));
                    float z = Float.intBitsToFloat(Integer.reverseBytes(dis.readInt()));
                    float r = dis.readUnsignedByte() / 255.0f;
                    float g = dis.readUnsignedByte() / 255.0f;
                    float b = dis.readUnsignedByte() / 255.0f;
                    buffer.put(x).put(y).put(z).put(r).put(g).put(b);
                }
            } else {
                throw new IOException("Unsupported PLY format: " + format);
            }

            buffer.flip();

            // --- OpenGL VAO/VBO 初始化 ---
            vao = glGenVertexArrays();
            glBindVertexArray(vao);

            vbo = glGenBuffers();
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);

            glVertexAttribPointer(0, 3, GL_FLOAT, false, 6 * Float.BYTES, 0);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(1, 3, GL_FLOAT, false, 6 * Float.BYTES, 3 * Float.BYTES);
            glEnableVertexAttribArray(1);

            glBindVertexArray(0);

            System.out.println("Loaded PLY with " + vertexCount + " vertices, format: " + format);

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    // 读取一行
    private String readLine(DataInputStream dis) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int c;
        while ((c = dis.read()) != -1) {
            if (c == '\n') break;
            baos.write(c);
        }
        if (baos.size() == 0 && c == -1) return null;
        return baos.toString("UTF-8").trim();
    }

    // 循环
    private void loop() {
        while (!glfwWindowShouldClose(window)) {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            glUseProgram(shaderProgram);

            // 计算 MVP 矩阵
            Matrix4f projection = new Matrix4f()
                    .perspective((float) Math.toRadians(45.0f), (float) width / height, 0.1f, 100.0f);

            Matrix4f view = new Matrix4f()
                    .translate(0, 0, -3.0f * zoom)
                    .rotate((float) Math.toRadians(pitch), 1, 0, 0)
                    .rotate((float) Math.toRadians(yaw), 0, 1, 0);

            Matrix4f model = new Matrix4f();

            Matrix4f mvp = new Matrix4f();
            projection.mul(view, mvp);
            mvp.mul(model);

            int mvpLoc = glGetUniformLocation(shaderProgram, "uMVP");
            FloatBuffer fb = BufferUtils.createFloatBuffer(16);
            mvp.get(fb);
            glUniformMatrix4fv(mvpLoc, false, fb);

            glBindVertexArray(vao);
            glDrawArrays(GL_POINTS, 0, vertexCount);
            glBindVertexArray(0);

            glfwSwapBuffers(window);
            glfwPollEvents();
        }
    }

//    public static void main(String[] args) {
//        new PlyViewer().run();
//    }

    public static void main(String[] args) {
        new BinaryPLYViewer().run();
    }
}

注意事项:

需要根据具体的ply文件内容修改loadPLY()方法 (解析文件属性字段)

例如我的ply文件

ply
format ascii 1.0
comment Created by Open3D
obj_info Generated by CloudCompare!
element vertex 1638113
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
end_header

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值