Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

load texture from user upload to geometry in three.js

I'm new to three.js and javascript (coding in general) but I'm having a lot of fun experimenting with the possibilities and have created a few small Three.js scenes with lights, custom geometry from 3D JSON models to develop my skills to be able to tackle my ultimate goal of building a 3D website.

I'm currently entirely stuck on the issue of allowing a user to upload an image file from an HTML 'input' file tag, and have that image appear on a PlaneGeometry mesh that already exists in the scene with a dummy texture already loaded into the material.

I can have the texture image appear on the plane without issue, and I have the HTML 'input' file button appearing onscreen, I can click it and it will load an image but it's not linked to anything in three.js and that's where I get stuck, I don't understand how to proceed.

I found this JSFIDDLE EXAMPLE that I can see works but I can't figure out how to use it to add a custom texture to a geometry and material that already exist.

<body>
<!-- three.js container -->
<input id="userImage" type="file" />
<div id="container"></div>
<script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;

    void main() {
        vUv = uv;
        gl_Position = vec4(uv * 2. - vec2(1, 1), 0., 1.);
    }
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
    precision highp float;

    varying vec2 vUv;
    uniform sampler2D texture1;
    void main() {
        gl_FragColor = texture2D(texture1, vUv.xy);
    }
</script>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/three.min.js"></script>
<script>

    init()

    function init(){
        $("#userImage").change(function () {
            var image = document.createElement( 'img' );
            var texture = new THREE.Texture( image );
            image.onload = function()  {
                texture.needsUpdate = true;
        };

        userImage = $("#userImage")[0];
        if (userImage.files && userImage.files[0]) {
            var reader = new FileReader();

            reader.onload = function (e) {
                image.src = e.target.result;
            };

            reader.readAsDataURL(userImage.files[0]);
        }

        camera = new THREE.OrthographicCamera();
        renderer = new THREE.WebGLRenderer({
            antialias: false
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        shader = new THREE.ShaderMaterial({
            vertexShader: document.getElementById('vertexShader').textContent,
            fragmentShader: document.getElementById('fragmentShader').textContent,
            uniforms: {
                texture1: {
                    type: "t",
                    value: texture
                }
            }
        });
        scene = new THREE.Scene();
        scene.add(new THREE.Mesh(new THREE.PlaneGeometry(1), shader));

        $('#container').append(renderer.domElement);

            animate();

        function animate() {
            requestAnimationFrame(animate);

            renderer.render(scene, camera);
        }
    }); 
    }
</script>

Would anyone mind sharing an example of how they would go about allowing a user uploaded texture to be loaded onto an already existing texture, material and object??

image of current three.js result with HTML loader at the top

like image 284
Josh Bowman Avatar asked May 20 '18 04:05

Josh Bowman


Video Answer


1 Answers

Use addEventListener to add a "change" event to the "file" input:

document.getElementById('userImage').addEventListener('change', function(e) {
     .....  
} );

The new file can be get by the target.files[0] property of the event argument:

var userImage = e.target.files[0];     

Use URL.createObjectURL to create a DOMString containing a URL:

var userImageURL = URL.createObjectURL( userImage );

I recommend to use THREE.TextureLoader for loading a texture:

var loader = new THREE.TextureLoader();
loader.setCrossOrigin("");
var texture = loader.load(userImageURL);

See the example, which is based on the code of your question:

init()
  
function init(){
    document.getElementById('userImage').addEventListener('change', function(e) {

    var userImage = e.target.files[0];     
    var userImageURL = URL.createObjectURL( userImage );

    container = document.getElementById('container');      
    camera = new THREE.OrthographicCamera();
    renderer = new THREE.WebGLRenderer({ antialias: false });
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    var loader = new THREE.TextureLoader();
    loader.setCrossOrigin("");
    var texture = loader.load(userImageURL);

    shader = new THREE.ShaderMaterial({
        vertexShader: document.getElementById('vertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent,
        uniforms: {
            texture1: { type: "t", value: texture }
        }
    });
    scene = new THREE.Scene();
    scene.add(new THREE.Mesh(new THREE.PlaneGeometry(1), shader));

    animate();
    function animate() {
        requestAnimationFrame(animate);

        renderer.render(scene, camera);
    }
    } ); 
}
<input id="userImage" type="file" />
<div id="container"></div>
<script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;

    void main() {
        vUv = uv;
        gl_Position = vec4(uv * 2. - vec2(1, 1), 0., 1.);
    }
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
    precision highp float;

    varying vec2 vUv;
    uniform sampler2D texture1;
    void main() {
        gl_FragColor = texture2D(texture1, vUv.xy);
    }
</script>
<script type="text/javascript" src="https://threejs.org/build/three.min.js"></script>
like image 67
Rabbid76 Avatar answered Nov 15 '22 00:11

Rabbid76