You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于JavaScript实现2D图像转3D图像的问题求助及方案建议

Fixing Your 2D-to-3D Image Conversion with Three.js

Hey there! Let's break down why your current setup isn't giving you that crisp, game-like 3D effect, then walk through updated solutions to get you the result you want.

First, What's Wrong With the Current Code?

Your approach has a few key issues holding it back:

  • Outdated Three.js Version: You're using r80, which is ancient by Three.js standards (we're on r160+ now). A lot of APIs you're using (like the custom CanvasGeometry hack, old OrbitControls setup, and Face3) are deprecated or removed, leading to broken logic and missing features.
  • Flat Material: MeshBasicMaterial doesn't react to lighting—so even if you have depth, your object will look like a flat plane floating in space, not a 3D object.
  • Incomplete Geometry: Your custom geometry only builds partial faces for each pixel, missing bottom/back faces and proper vertex connections. This leads to a glitchy, unpolished look.
  • Weak Lighting: A single PointLight with no ambient fill light leaves dark areas completely black, killing any sense of depth.

Solution 1: Pixelated 3D Blocks (Game-Style)

This uses modern Three.js features like InstancedMesh (for performance) and lit materials to create that classic voxel-like 3D effect from your image.

Updated HTML

First, swap out the old CDNs for modern, supported versions:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>2D Image to 3D Voxels</title>
    <style>
        body { margin: 0; overflow: hidden; }
        #container { width: 100vw; height: 100vh; }
    </style>
</head>
<body>
    <div id="container"></div>
    <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/controls/OrbitControls.js"></script>
    <script src="js/index.js"></script>
</body>
</html>

Revised JavaScript

This code renders each non-transparent pixel as a 3D cube, with proper lighting and performance optimizations:

"use strict";

const container = document.getElementById('container');
let scene, camera, renderer, controls;
const imgSampleSize = 16; // Adjust for more/less detail
const voxelDepth = 0.5; // How tall each 3D pixel is

// Load your image
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => createVoxelModel(img);
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxEAAAsRAX9kX5EAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAACSSURBVDhPnZDdDYAgDIS7iokDOYsj+Ox8zuAWta0F2ggFfPigP3cXAiDiNHDSkagJIti0XC/TAdY8HVAzyzwJIlpm2aWiRWSWfS5o8Vl2zIwTHzcVKhoxM74xISNmxjeVgMjMfAcmpGdmfLPuCBsVGmL/pEUpkplu6fUFvZAslAA1O4Hu7cwigpHPalFe8CsA4QHINOMS97iyIAAAAABJRU5ErkJggg==";

function initScene() {
    // Base scene setup
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xcccccc);

    // Camera
    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.set(0, 4, 4);
    camera.lookAt(scene.position);

    // Orbit controls for easy navigation
    controls = new THREE.OrbitControls(camera, container);
    controls.enableDamping = true; // Smooth camera movement

    // Lighting: Ambient + Directional for natural depth
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(5, 5, 5);
    directionalLight.castShadow = true;
    scene.add(directionalLight);

    // Renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true; // Enable shadows for extra realism
    container.appendChild(renderer.domElement);

    // Handle window resizing
    window.addEventListener('resize', onWindowResize);

    // Render loop
    function render() {
        requestAnimationFrame(render);
        controls.update();
        renderer.render(scene, camera);
    }
    render();
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

function createVoxelModel(image) {
    initScene();

    // Extract pixel data from the image
    const canvas = document.createElement('canvas');
    canvas.width = imgSampleSize;
    canvas.height = imgSampleSize;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0, imgSampleSize, imgSampleSize);
    const pixelData = ctx.getImageData(0, 0, imgSampleSize, imgSampleSize).data;

    const voxelSize = 1 / imgSampleSize;
    let visibleVoxels = 0;

    // First count visible (non-transparent) pixels
    for (let y = 0; y < imgSampleSize; y++) {
        for (let x = 0; x < imgSampleSize; x++) {
            const alpha = pixelData[(y * imgSampleSize + x) * 4 + 3];
            if (alpha !== 0) visibleVoxels++;
        }
    }

    // Use InstancedMesh to render all voxels efficiently
    const voxelGeometry = new THREE.BoxGeometry(voxelSize, voxelSize, voxelDepth);
    const voxelMaterial = new THREE.MeshLambertMaterial({ vertexColors: true });
    const instancedMesh = new THREE.InstancedMesh(voxelGeometry, voxelMaterial, visibleVoxels);
    instancedMesh.castShadow = true;
    scene.add(instancedMesh);

    // Position and color each voxel
    let instanceIndex = 0;
    const matrix = new THREE.Matrix4();
    const color = new THREE.Color();

    for (let y = 0; y < imgSampleSize; y++) {
        for (let x = 0; x < imgSampleSize; x++) {
            const pixelIndex = (y * imgSampleSize + x) * 4;
            const r = pixelData[pixelIndex] / 255;
            const g = pixelData[pixelIndex + 1] / 255;
            const b = pixelData[pixelIndex + 2] / 255;
            const alpha = pixelData[pixelIndex + 3];

            if (alpha === 0) continue;

            // Center the voxel grid
            const posX = (x / imgSampleSize - 0.5) * imgSampleSize * voxelSize;
            const posY = -(y / imgSampleSize - 0.5) * imgSampleSize * voxelSize;
            const posZ = voxelDepth / 2;

            matrix.setPosition(posX, posY, posZ);
            instancedMesh.setMatrixAt(instanceIndex, matrix);

            color.setRGB(r, g, b);
            instancedMesh.setColorAt(instanceIndex, color);

            instanceIndex++;
        }
    }

    // Update instance data
    instancedMesh.instanceMatrix.needsUpdate = true;
    instancedMesh.instanceColor.needsUpdate = true;
}

Solution 2: Heightmap Terrain Style

If you want a smooth, terrain-like 3D effect (where pixel brightness controls height), use this approach instead:

function createHeightmapModel(image) {
    initScene();

    const canvas = document.createElement('canvas');
    canvas.width = imgSampleSize;
    canvas.height = imgSampleSize;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0, imgSampleSize, imgSampleSize);
    const pixelData = ctx.getImageData(0, 0, imgSampleSize, imgSampleSize).data;

    // Create a plane with segments matching our image size
    const geometry = new THREE.PlaneGeometry(1, 1, imgSampleSize - 1, imgSampleSize - 1);
    const vertices = geometry.attributes.position.array;

    // Adjust vertex height based on pixel brightness
    for (let i = 0; i < vertices.length; i += 3) {
        const x = Math.floor((i / 3) % imgSampleSize);
        const y = Math.floor((i / 3) / imgSampleSize);
        const pixelIndex = (y * imgSampleSize + x) * 4;
        // Calculate grayscale value for height
        const gray = (pixelData[pixelIndex] + pixelData[pixelIndex+1] + pixelData[pixelIndex+2]) / (3 * 255);
        vertices[i + 2] = gray * voxelDepth; // Set Z-axis height
    }

    geometry.attributes.position.needsUpdate = true;
    geometry.computeVertexNormals(); // Make lighting look smooth

    // Apply the original image as a texture
    const material = new THREE.MeshPhongMaterial({
        map: new THREE.CanvasTexture(canvas),
        side: THREE.DoubleSide,
        shininess: 30
    });

    const mesh = new THREE.Mesh(geometry, material);
    mesh.rotation.x = -Math.PI / 2; // Lay the plane flat like terrain
    mesh.receiveShadow = true;
    scene.add(mesh);
}

Quick Tips to Boost Effect

  • Increase Detail: Bump up imgSampleSize to 64 or 128 for a smoother, more detailed model (just note that higher sizes need better performance, which InstancedMesh handles well).
  • Adjust Depth: Tweak voxelDepth to make the 3D effect more or less pronounced.
  • Add Shadows: We already enabled shadows in the code, but you can adjust shadow resolution or softness for better results.
  • Use PBR Materials: Swap MeshLambertMaterial for MeshStandardMaterial and add an environment map for hyper-realistic lighting.

内容的提问来源于stack exchange,提问作者Karan shah

火山引擎 最新活动