Bladeren bron

feat:搭建项目架构,使用单线程渲染图像

yang yi 2 weken geleden
commit
1d10912f46

+ 23 - 0
pom.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>space.anyi</groupId>
+    <artifactId>render</artifactId>
+    <version>0.0.1</version>
+    <description>
+        This is a project to render fractal animations using Java.
+        GLSL代码来源(https://x.com/XorDev)
+        将GLSL渲染图像的代码翻译为Java代码
+        使用Java通过CPU渲染图像保存问PPM格式
+        使用FFmpeg将PPM格式转换为MP4格式
+    </description>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+    </properties>
+
+</project>

+ 16 - 0
src/main/java/space/anyi/render/Application.java

@@ -0,0 +1,16 @@
+package space.anyi.render;
+
+/**
+ * @ProjectName: render
+ * @FileName: Application
+ * @Author: 杨逸
+ * @Data:2025/11/19 22:21
+ * @Description:
+ */
+public class Application {
+    public static void main(String[] args) {
+        First first = new First(800, 600, 60, 10);
+        first.setTotalFrames(377);
+        first.render();
+    }
+}

+ 101 - 0
src/main/java/space/anyi/render/First.java

@@ -0,0 +1,101 @@
+package space.anyi.render;
+
+import space.anyi.render.core.Render;
+
+/**
+ * @ProjectName: render
+ * @FileName: Frist
+ * @Author: 杨逸
+ * @Data:2025/11/19 22:34
+ * @Description: 分形噪声生成
+ */
+
+/**
+ * 对应的glsl代码
+ * vec2 p=(FC.xy*2.-r)/r.y,l,v=p*(1.-(l+=abs(.7-dot(p,p))))/.2;for(float i;i++<8.;o+=(sin(v.xyyx)+1.)*abs(v.x-v.y)*.2)v+=cos(v.yx*i+vec2(0,i)+t)/i+.7;o=tanh(exp(p.y*vec4(1,-1,-2,0))*exp(-4.*l.x)/o);
+ */
+public class First extends Render {
+    public First() {
+        super();
+    }
+
+    public First(int width, int height, int fps, int duration) {
+        super(width, height, fps, duration);
+    }
+
+    @Override
+    public float[] generateFrame(int width, int height, float time) {
+        float[] resolution = {(float) width, (float) height};
+        float[] imageData = new float[width * height * 3];
+
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                // 坐标归一化 (翻转y轴以适应图像坐标系)
+                float[] fragCoord = {(float) x, (float) (height - 1 - y)};
+                float[] p = {
+                        (fragCoord[0] * 2.0f - resolution[0]) / resolution[1],
+                        (fragCoord[1] * 2.0f - resolution[1]) / resolution[1]
+                };
+
+                float l = Math.abs(0.7f - dot(p, p));
+                float[] v = {
+                        p[0] * (1.0f - l) / 0.2f,
+                        p[1] * (1.0f - l) / 0.2f
+                };
+
+                float[] o = {0.0f, 0.0f, 0.0f, 0.0f}; // 输出颜色
+
+                // 分形噪声迭代
+                for (int i = 1; i <= 8; i++) {
+                    float[] tempV = v.clone();
+
+                    // v += cos(v.yx*i + vec2(0,i) + t)/i + 0.7
+                    float[] cosInput = {
+                            tempV[1] * i + time, // v.y * i + 0 + t
+                            tempV[0] * i + i + time  // v.x * i + i + t
+                    };
+
+                    float[] cosResult = {
+                            (float) Math.cos(cosInput[0]),
+                            (float) Math.cos(cosInput[1])
+                    };
+
+                    v[0] += cosResult[0] / i + 0.7f;
+                    v[1] += cosResult[1] / i + 0.7f;
+
+                    // o += (sin(v.xyyx) + 1.0) * abs(v.x - v.y) * 0.2
+                    float[] sinInput = {v[0], v[1], v[1], v[0]}; // v.xyyx
+                    float[] sinResult = new float[4];
+                    for (int j = 0; j < 4; j++) {
+                        sinResult[j] = (float) Math.sin(sinInput[j]);
+                    }
+
+                    float absDiff = Math.abs(v[0] - v[1]);
+                    for (int j = 0; j < 4; j++) {
+                        o[j] += (sinResult[j] + 1.0f) * absDiff * 0.2f;
+                    }
+                }
+
+                // 最终颜色计算: tanh(exp(p.y*vec4(1,-1,-2,0)) * exp(-4*l) / o)
+                float expL = (float) Math.exp(-4.0f * l);
+                float[] colorComponents = new float[3];
+                float[] multipliers = {1.0f, -1.0f, -2.0f};
+
+                for (int i = 0; i < 3; i++) {
+                    float expPy = (float) Math.exp(p[1] * multipliers[i]);
+                    colorComponents[i] = expPy * expL / (o[i] + 0.0001f); // 避免除零
+                    colorComponents[i] = (float) Math.tanh(colorComponents[i]);
+                    // 限制在 [0,1] 范围
+                    colorComponents[i] = Math.max(0, Math.min(1, colorComponents[i]));
+                }
+
+                int index = (y * width + x) * 3;
+                imageData[index] = colorComponents[0];     // R
+                imageData[index + 1] = colorComponents[1]; // G
+                imageData[index + 2] = colorComponents[2]; // B
+            }
+        }
+
+        return imageData;
+    }
+}

+ 23 - 0
src/main/java/space/anyi/render/core/GLSL.java

@@ -0,0 +1,23 @@
+package space.anyi.render.core;
+
+/**
+ * @ProjectName: render
+ * @FileName: GLSL
+ * @Author: 杨逸
+ * @Data:2025/11/19 22:22
+ * @Description:
+ */
+public interface GLSL {
+    /**
+     * 生成一帧画面
+     * @param width 画面宽度
+     * @param height 画面高度
+     * @param time 时间
+     * @return {@code float[] }
+     * @description: 将GLSL代码翻译为Java代码
+     * @author: 杨逸
+     * @data:2025/11/19 22:22:45
+     * @since 1.0.0
+     */
+    float[] generateFrame(int width, int height, float time);
+}

+ 125 - 0
src/main/java/space/anyi/render/core/Render.java

@@ -0,0 +1,125 @@
+package space.anyi.render.core;
+
+import space.anyi.render.utils.PPMUtil;
+
+/**
+ * @ProjectName: render
+ * @FileName: Render
+ * @Author: 杨逸
+ * @Data:2025/11/19 22:29
+ * @Description:
+ */
+public abstract class Render implements GLSL {
+    /**
+     * 画面宽度
+     * 默认分辨率为 3840*2160
+     */
+    private int width = 3840;
+    /**
+     * 画面高度
+     */
+    private int height = 2160;
+    /**
+     * 刷新率
+     */
+    private int fps = 60;
+    /**
+     * 图像时长
+     */
+    private int duration = 10; // 秒
+    /**
+     * 总帧数
+     */
+    private int totalFrames = fps * duration;
+    /**
+     * 输出目录
+     *
+     * @see String
+     */
+    private String outputDir = "fractal_animation";
+
+    public Render() {
+    }
+
+    public Render(int width, int height, int fps, int duration) {
+        this.width = width;
+        this.height = height;
+        this.fps = fps;
+        this.duration = duration;
+        this.totalFrames = fps * duration;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    public int getFps() {
+        return fps;
+    }
+
+    public void setFps(int fps) {
+        this.fps = fps;
+    }
+
+    public int getDuration() {
+        return duration;
+    }
+
+    public void setDuration(int duration) {
+        this.duration = duration;
+    }
+
+    public int getTotalFrames() {
+        return totalFrames;
+    }
+
+    public void setTotalFrames(int totalFrames) {
+        this.totalFrames = totalFrames;
+    }
+
+    public String getOutputDir() {
+        return outputDir;
+    }
+
+    public void setOutputDir(String outputDir) {
+        this.outputDir = outputDir;
+    }
+
+    public void render() {
+        System.out.println("开始生成分形动画...");
+        System.out.println("分辨率: " + width + "x" + height);
+        System.out.println("帧率: " + fps + " Hz");
+        System.out.println("时长: " + duration + " 秒");
+        System.out.println("总帧数: " + totalFrames);
+        System.out.println("输出目录: " + outputDir);
+        PPMUtil.createDirectory(outputDir);
+
+        for (int frame = 0; frame < totalFrames; frame++) {
+            float time = frame / (float) fps; // 计算当前时间(秒)
+            float[] imageData = generateFrame(width, height, time);
+            PPMUtil.saveAsPPM(imageData, width, height, outputDir + "/frame_" + String.format("%05d", frame) + ".ppm");
+
+            // 显示进度
+            if (frame % 60 == 0) {
+                double progress = (frame + 1) * 100.0 / totalFrames;
+                System.out.printf("进度: %.1f%% (帧 %d/%d)%n", progress, frame + 1, totalFrames);
+            }
+        }
+    }
+
+    protected float dot(float[] a, float[] b) {
+        return a[0] * b[0] + a[1] * b[1];
+    }
+}

+ 62 - 0
src/main/java/space/anyi/render/utils/PPMUtil.java

@@ -0,0 +1,62 @@
+package space.anyi.render.utils;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * @ProjectName: render
+ * @FileName: PPMUtil
+ * @Author: 杨逸
+ * @Data:2025/11/19 22:24
+ * @Description: PPM文件工具类
+ */
+public class PPMUtil {
+    /**
+     * @param imageData 图像数据
+     * @param width 画面宽度
+     * @param height 画面高度
+     * @param filename 文件名
+     * @description:
+     * @author: 杨逸
+     * @data:2025/11/19 22:25:14
+     * @since 1.0.0
+     */
+    public static void saveAsPPM(float[] imageData, int width, int height, String filename) {
+        try (PrintWriter writer = new PrintWriter(new FileWriter(filename))) {
+            // PPM头部
+            writer.println("P3");
+            writer.println(width + " " + height);
+            writer.println("255"); // 最大颜色值
+
+            // 像素数据
+            for (int i = 0; i < imageData.length; i += 3) {
+                int r = (int) (imageData[i] * 255);
+                int g = (int) (imageData[i + 1] * 255);
+                int b = (int) (imageData[i + 2] * 255);
+
+                writer.println(r + " " + g + " " + b);
+            }
+        } catch (IOException e) {
+            System.err.println("保存PPM文件失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建文件夹
+     * @param dirName 文件夹名称
+     * @description:
+     * @author: 杨逸
+     * @data:2025/11/19 22:46:28
+     * @since 1.0.0
+     */
+    public static void createDirectory(String dirName) {
+        try {
+            Files.createDirectories(Paths.get(dirName));
+        } catch (IOException e) {
+            System.err.println("创建目录失败: " + e.getMessage());
+        }
+    }
+}

+ 1 - 0
src/main/resources/ffpmge.txt

@@ -0,0 +1 @@
+ffmpeg -i fractal_animation/frame_%05d.ppm -c:v libx264 -pix_fmt yuv420p -r 60 fractal_animation.mp4

+ 83 - 0
src/main/resources/prompt.md

@@ -0,0 +1,83 @@
+## 输入案例
+
+```glsl
+vec2 p=(FC.xy*2.-r)/r.y,l,v=p*(1.-(l+=abs(.7-dot(p,p))))/.2;for(float i;i++<8.;o+=(sin(v.xyyx)+1.)*abs(v.x-v.y)*.2)v+=cos(v.yx*i+vec2(0,i)+t)/i+.7;o=tanh(exp(p.y*vec4(1,-1,-2,0))*exp(-4.*l.x)/o);
+```
+
+## 输出案例
+
+```java
+private static float[] generateFrame(int width, int height, float time) {
+        float[] resolution = {(float) width, (float) height};
+        float[] imageData = new float[width * height * 3];
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                // 坐标归一化 (翻转y轴以适应图像坐标系)
+                float[] fragCoord = {(float) x, (float) (height - 1 - y)};
+                float[] p = {
+                        (fragCoord[0] * 2.0f - resolution[0]) / resolution[1],
+                        (fragCoord[1] * 2.0f - resolution[1]) / resolution[1]
+                };
+                float l = Math.abs(0.7f - dot(p, p));
+                float[] v = {
+                        p[0] * (1.0f - l) / 0.2f,
+                        p[1] * (1.0f - l) / 0.2f
+                };
+                float[] o = {0.0f, 0.0f, 0.0f, 0.0f}; // 输出颜色
+                // 分形噪声迭代
+                for (int i = 1; i <= 8; i++) {
+                    float[] tempV = v.clone();
+                    // v += cos(v.yx*i + vec2(0,i) + t)/i + 0.7
+                    float[] cosInput = {
+                            tempV[1] * i + time, // v.y * i + 0 + t
+                            tempV[0] * i + i + time  // v.x * i + i + t
+                    };
+                    float[] cosResult = {
+                            (float) Math.cos(cosInput[0]),
+                            (float) Math.cos(cosInput[1])
+                    };
+                    v[0] += cosResult[0] / i + 0.7f;
+                    v[1] += cosResult[1] / i + 0.7f;
+                    // o += (sin(v.xyyx) + 1.0) * abs(v.x - v.y) * 0.2
+                    float[] sinInput = {v[0], v[1], v[1], v[0]}; // v.xyyx
+                    float[] sinResult = new float[4];
+                    for (int j = 0; j < 4; j++) {
+                        sinResult[j] = (float) Math.sin(sinInput[j]);
+                    }
+                    float absDiff = Math.abs(v[0] - v[1]);
+                    for (int j = 0; j < 4; j++) {
+                        o[j] += (sinResult[j] + 1.0f) * absDiff * 0.2f;
+                    }
+                }
+                // 最终颜色计算: tanh(exp(p.y*vec4(1,-1,-2,0)) * exp(-4*l) / o)
+                float expL = (float) Math.exp(-4.0f * l);
+                float[] colorComponents = new float[3];
+                float[] multipliers = {1.0f, -1.0f, -2.0f};
+
+                for (int i = 0; i < 3; i++) {
+                    float expPy = (float) Math.exp(p[1] * multipliers[i]);
+                    colorComponents[i] = expPy * expL / (o[i] + 0.0001f); // 避免除零
+                    colorComponents[i] = (float) Math.tanh(colorComponents[i]);
+                    // 限制在 [0,1] 范围
+                    colorComponents[i] = Math.max(0, Math.min(1, colorComponents[i]));
+                }
+                int index = (y * width + x) * 3;
+                imageData[index] = colorComponents[0];     // R
+                imageData[index + 1] = colorComponents[1]; // G
+                imageData[index + 2] = colorComponents[2]; // B
+            }
+        }
+        return imageData;
+    }
+```
+
+## 实际输入
+
+```glsl
+for(float i,z,d,h;i++<8e1;o+=vec4(9,5,h+t,1)/d)
+{vec3 p=z*normalize(FC.rgb*2.-r.xyy),a;p.z+=9.;a=mix(dot(a+=.5,p)*a,p,sin(h=dot(p,p/p)-t))+cos(h)*cross(a,p);
+for(d=0.;d++<9.;a+=.3*sin(a*d).zxy);z+=d=length(a.xz)/15.;}
+o=tanh(o/1e4);
+```
+
+>参考输入输出的案例,将实际输入的glsl代码翻译为Java代码,仅输出对应的Java方法即可