从网格生成高度图

问题描述 投票:0回答:1

<!--B"H-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Random Terrain Heightmap Generator</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { display: block; }
    </style>
</head>
<body>
    <script type="module">
        console.log("B\"H")
        import * as THREE from 'https://awtsmoos.com/games/scripts/build/three.module.js';
        import { OrbitControls } from "https://awtsmoos.com/games/scripts/jsm/controls/OrbitControls.js"
        import HeightMapGenerator from "https://awtsmoos.com/games/mitzvahWorld/editor/lib/HeightmapGenerator.js"

        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        // Create a basic plane geometry with randomized heights
        const geometry = new THREE.PlaneGeometry(20, 20, 64, 64);
        for (let i = 0; i < geometry.attributes.position.array.length; i += 2) {
            geometry.attributes.position.array[i] += Math.random() * 2 - 1;
        }
        geometry.computeVertexNormals();

        // Lambert material with lighting
        const material = new THREE.MeshLambertMaterial({
            color: 0xffff00,
            side: THREE.DoubleSide
        });
        const plane = new THREE.Mesh(geometry, material);
        scene.add(plane);
        plane.rotation.x = Math.PI/2;

        // Ambient light
        const ambientLight = new THREE.AmbientLight(0x404040, 1); // adjust color and intensity
        scene.add(ambientLight);

        // Directional light
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); // adjust color and intensity
        directionalLight.position.set(2, 5, 3);
        scene.add(directionalLight);

        camera.position.set(5, 10, 10); // Adjusted camera position
        camera.lookAt(plane.position);

        const controls = new OrbitControls(camera, renderer.domElement);

        const heightmapGenerator = new HeightMapGenerator(plane, scene);

        async function generateHeightmap() {
            heightmapGenerator.generateHeightMap();
            var png = await heightmapGenerator
            .getPNGfromHeightmap()

            console.log(window.h=png)

            var img = document.createElement("img");
            img.src=png;
            document.body.innerHTML = ""
            document.body.appendChild(img)
            console.log("Heightmap generated!");
        }

        var clicked = false

        onclick =async () =>  {
            if(clicked) return;
            clicked = true;
            await generateHeightmap();
        }

        renderer.render(scene, camera);
        animate();

        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
    </script>
</body>
</html>

我正在尝试制作一个高度图生成器,以网格作为输入并下载高度图。我应该采取不同的措施才能正确获得官方高度图吗?我计算网格的深度并将其渲染为纹理,但我不确定是否达到了所需的结果。

/**
 * B"H
 */
import * as THREE from 'https://awtsmoos.com/games/scripts/build/three.module.js';

export default class HeightMapGenerator {
    constructor(mesh, scene, mapWidth = 512, mapHeight = 512) {
        this.mesh = mesh;
        this.scene = scene;
        this.mapWidth = mapWidth;
        this.mapHeight = mapHeight;
        this.orthoCamera = null;
        this.renderTarget = null;
        this.heightMap = null;
        this.renderer = new THREE.WebGLRenderer();

        this.init();
    }

    init() {
        this.setupCamera();
        this.setupRenderTarget();
        this.generateHeightMap();
    }

    setupCamera() {
        const boundingBox = new THREE.Box3().setFromObject(this.mesh);
        const size = boundingBox.getSize(new THREE.Vector3());
        this.boundingBox = boundingBox;
        console.log(window.g=size)
        this.orthoCamera = new THREE.OrthographicCamera(
            boundingBox.min.x, boundingBox.max.x,
            boundingBox.max.z, boundingBox.min.z,
            0.01, 123456789
        );
        this.orthoCamera.position.set(0, boundingBox.max.y + 1, 0);
        this.orthoCamera.lookAt(0, boundingBox.min.y, 0);

        // Add the orthographic camera to the scene
        this.scene.add(this.orthoCamera);

    }

    setupRenderTarget() {
        this.renderTarget = new THREE.WebGLRenderTarget(this.mapWidth, this.mapHeight, {
            minFilter: THREE.LinearFilter,
            magFilter: THREE.LinearFilter,
            format: THREE.RGBAFormat
        });
    }

    generateHeightMap() {
        const depthMaterial = new THREE.ShaderMaterial({
            uniforms: {
                boundingBoxMin: { value: this.boundingBox.min },
                boundingBoxMax: { value: this.boundingBox.max }
            },
            vertexShader: /*glsl*/`
                varying vec4 vWorldPosition;
                void main() {
                    vWorldPosition = modelMatrix * vec4(position, 1.0);
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: /*glsl*/`
                uniform vec3 boundingBoxMin;
                uniform vec3 boundingBoxMax;
                varying vec4 vWorldPosition;

                void main() {
                    // Calculate normalized height within bounding box
                    float normalizedHeight = (vWorldPosition.y - boundingBoxMin.y) / (boundingBoxMax.y - boundingBoxMin.y);

                    gl_FragColor = vec4(vec3(normalizedHeight), 1.0);
                }
            `,
            side: THREE.DoubleSide
        });

        this.renderer.setSize(this.mapWidth, this.mapHeight);
        this.renderer.setRenderTarget(this.renderTarget);

        this.scene.overrideMaterial = depthMaterial;

        // Set the mesh to a specific layer
        this.mesh.layers.enable(14);
        this.orthoCamera.layers.set(14)

        this.renderer.render(this.scene, this.orthoCamera);
        this.mesh.layers.disable(14);
        this.scene.overrideMaterial = null;

        this.renderer.setRenderTarget(null);

        const readPixels = new Uint8Array(this.mapWidth * this.mapHeight * 4);
        this.renderer.readRenderTargetPixels(this.renderTarget, 0, 0, this.mapWidth, this.mapHeight, readPixels);

        this.heightMap = new Uint8Array(this.mapWidth * this.mapHeight);
        for (let i = 0; i < this.mapWidth * this.mapHeight; i++) {
            const r = readPixels[i * 4];
            const g = readPixels[i * 4 + 1];
            const b = readPixels[i * 4 + 2];
            const depth = (r + g + b) / 3;
            this.heightMap[i] = depth;
        }
    }

    getHeightAt(x, z, boundingBox) {
        const worldWidth = boundingBox.max.x - boundingBox.min.x;
        const worldHeight = boundingBox.max.z - boundingBox.min.z;
        const cellWidth = worldWidth / this.mapWidth;
        const cellHeight = worldHeight / this.mapHeight;

        const gridX = Math.floor((x - boundingBox.min.x) / cellWidth);
        const gridZ = Math.floor((z - boundingBox.min.z) / cellHeight);

        if (gridX < 0 || gridX >= this.mapWidth || gridZ < 0 || gridZ >= this.mapHeight) {
            return boundingBox.min.y;
        }

        const heightIndex = gridZ * this.mapWidth + gridX;
        const heightValue = this.heightMap[heightIndex];
        const normalizedHeight = heightValue / 255;
        const worldHeightValue = normalizedHeight * (boundingBox.max.y - boundingBox.min.y) + boundingBox.min.y;

        return worldHeightValue;
    }

    updateObjectPosition(object, boundingBox) {
        const position = object.position;
        const height = this.getHeightAt(position.x, position.z, boundingBox);
        if (!object.isJumping) {
            position.y = height;
        }
    }

    downloadHeightmapAsPNG(nm = "BH.png") {
        const heightMap = this.heightMap;
        const mapWidth = this.mapWidth;
        const mapHeight = this.mapHeight;

        // Create a canvas element
        const canvas = document.createElement('canvas');
        canvas.width = mapWidth;
        canvas.height = mapHeight;
        const ctx = canvas.getContext('2d');

        // Create an ImageData object to store the heightmap as image data
        const imageData = ctx.createImageData(mapWidth, mapHeight);

        // Fill the ImageData with heightmap data
        for (let i = 0; i < heightMap.length; i++) {
            const value = heightMap[i];
            // Set R, G, B channels to the heightmap value (greyscale)
            imageData.data[i * 4] = value;
            imageData.data[i * 4 + 1] = value;
            imageData.data[i * 4 + 2] = value;
            // Set the alpha channel to fully opaque
            imageData.data[i * 4 + 3] = 255;
        }

        // Put the ImageData onto the canvas
        ctx.putImageData(imageData, 0, 0);

        // Convert the canvas to a Blob
        canvas.toBlob(function(blob) {
            // Create a download link for the Blob
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = nm;

            // Trigger the download
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }, 'image/png');
    }
}
javascript three.js shader fragment-shader heightmap
1个回答
0
投票

IMO,你的方法可能是一个错误的结局。

您想要的是一个高度图,它包含在一个图像中,可以使用程序方法将其作为单个着色器完成,例如名为“Perlin Noise”的方法。

一个简单的例子:
    https://www.shadertoy.com/view/XdXGW8
  • 一个复杂的示例:
  • https://www.shadertoy.com/view/MdX3Rr
  • 这些来自 IQ,您可以从
这里

开始阅读他关于程序地形生成的文章。 对于具体实现,您从面向相机的平面开始,很可能会更容易使用正交相机控制生成的尺寸,然后有 2 个选项:

“无代码”方式,从此示例
    https://thirdjs.org/examples/#webgl_geometry_terrain
  1. ,您可以在 ImprovedNoise 的帮助下,使用 JavaScript 生成地形的程序数据纹理,使用网格深度材质 对于第二个选项(我认为最好的),请从这里开始学习如何实现噪声着色器
  2. https://thebookofshaders.com/11/
  3. ,使用自定义的ShaderMaterial,它将为你的代码和你的世界带来意义,揭示更高的维度:数学。
© www.soinside.com 2019 - 2024. All rights reserved.