THREE.js (r60) PointLight не отражается специальным плоским объектом (карта высот из изображения)

ОБНОВЛЕНИЕ Причина проблемы найдена — см. раздел «Обновление» в конце вопроса.


У меня есть сложное приложение, использующее THREE.js (r60), которое добавляет в основную сцену специальный плоский объект. Геометрия плоскости определяется картой высот из внутреннего изображения uri base64 (размер 16x16, 32x32 или 64x64 пикселей). Сцена имеет два статических источника света (рассеивающий и направленный) и один подвижный точечный источник света, который включается и выключается.

В сложном приложении точечный свет не отражается плоским объектом . (Точечный свет переключается нажатием клавиши или кнопки «R»).

Я сделал первый пример JSFiddle, используя последнюю версию THREE.js (r70), где свет работает нормально.

[Обновление] Теперь я сделал второй пример JSFiddle, используя более старую библиотеку THREE.js (r60). тоже нормально работает.

Я подозреваю, что проблема в сложном приложении (r60) может иметь какое-то отношение к емкости системы и/или времени/последовательности. Емкость определенно является проблемой, потому что другие более простые объекты сцены (коробки и цилиндры) показывают индивидуальные реакции или отсутствие реакции на точечный свет, которые варьируются от одного запуска приложения к другому, по-видимому, в зависимости от общего уровня активности системы (процессор, использование памяти). Эти более простые объекты могут отражаться в одном прогоне, но не в следующем. Но плоский объект с картой высот постоянно не отражает точечный источник света. Такое поведение наблюдается на (i) ноутбуке с Win7 и (ii) планшете Android Kitkat.

Процесс картирования высот может быть частью причины. Я говорю это, потому что, когда я комментирую плоскость с картой высот и активирую простой объект похожей плоскости (со случайно назначенными z-уровнями), последняя плоскость ведет себя так, как ожидалось (то есть отражает точечный свет).

Я предполагаю, что обычный подход сейчас будет состоять в том, чтобы обновить мое сложное приложение до r70 (нетривиальный шаг), а затем начать отключать фрагменты приложения, чтобы сузить причину. Однако может случиться так, что способ реализации карты высот (например, с обратным вызовом) является фактором, объясняющим неспособность плоскости карты высоты отражать точечный свет.

[ПЕРЕЗАПИСАН] Поэтому я был бы признателен, если бы кто-нибудь мог взглянуть на код в правильно работающем, ранее цитированном (r70) пример JSFiddle и указать на любые вопиющие ошибки дизайна, которые (при применении в более сложных, сильно загруженных приложениях) могут привести к тому, что плоскость с картой высоты не будет отражать точечный свет.

Полный код (javascript, а не html или css) (r70) JSFiddle: -

//... Heightmap from Image file 
//... see http://danni-three.blogspot.co.uk/2013/09/threejs-heightmaps.html

var camera, scene, renderer;
var lpos_x = -60,lpos_y = 20,lpos_z = 100;
var mz = 1;
var time = 0, dt = 0;
var MyPlane, HPlane;

base64_imgData = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/4QBoRXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAAExAAIAAAASAAAATgAAAAAAAAB4AAAAAQAAAHgAAAABUGFpbnQuTkVUIHYzLjUuMTAA/9sAQwANCQoLCggNCwsLDw4NEBQhFRQSEhQoHR4YITAqMjEvKi4tNDtLQDQ4RzktLkJZQkdOUFRVVDM/XWNcUmJLU1RR/9sAQwEODw8UERQnFRUnUTYuNlFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFR/8AAEQgAIAAgAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A19Z8SXdu5KOMKxBAFOi1uTUdNJguxFcAchv6Vz2so/mzKc8sc8VX8MyQjUVWYNweCO9AEsOuX8s+xrqWQh8DJ4rsJCphSN3Czsm7ArG1bT7fSFe7EZJzuX3J6VQsdRnvryJ2+/wooA6O501JY7yRh0U8Vyg1WzsghhsAkqnBO4nd713t8NsEqIhJfqRXEahotxPJlISDnOaANzWvL1rR4JiTG6ryorG0C2aDUI02lhu6kVZ02wvLVSpYtu65yRXQaZYvDL5rhRx2oA//2Q==";

init();
animate();

//==================================================================

function init() {

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 10);
    camera.position.x = 1300;
    camera.position.y = 400;
    camera.position.z = 0;
    camera.lookAt(0, 0, 0);
    scene.add(camera);    

    scene.add(new THREE.AmbientLight(0x001900));

    SunLight = new THREE.DirectionalLight(0xff0000,.3,20000);//...color, intensity, range.
    SunLight.position.set(0, 3000, -8000);
    scene.add(SunLight);

    //POINT LIGHT
    PL_color = 0x0000ff;
    PL_intensity = 10;
    PL_range_to_zero_intensity = 1200;
    PL = new THREE.PointLight(PL_color, PL_intensity, PL_range_to_zero_intensity);
    scene.add(PL);
    PL_pos_x = -100;
    PL_pos_y = -100;
    PL_pos_z = 120;
    PL.position.set(PL_pos_x, PL_pos_y, PL_pos_z);

    //INDICATOR SPHERE
    var s_Geometry = new THREE.SphereGeometry(5, 20, 20);
    var s_Material = new THREE.MeshBasicMaterial({
        color: 0xaaaaff
    });
    i_Sphere = new THREE.Mesh(s_Geometry, s_Material);
    i_Sphere.position.set(PL_pos_x, PL_pos_y, PL_pos_z);
    scene.add(i_Sphere);


    //Plane02
    var Plane02Geo = new THREE.PlaneGeometry(50, 50); //...   
    var Plane02Material = new THREE.MeshPhongMaterial({
        side: THREE.DoubleSide
    }, {
        color: 0xaaaaaa
    });

    Plane02 = new THREE.Mesh(Plane02Geo, Plane02Material);
    Plane02.position.set(0, 0, -120);
    scene.add(Plane02);

    //PEAS

    xxx = SOW_F_Make_peas();

    //RENDERER
    renderer = new THREE.WebGLRenderer({
        antialias: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMapEnabled = true;
    renderer.shadowMapSoft = false;
    document.body.appendChild(renderer.domElement);

    // controls
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    xxx = SOW_F_Make_Heightmap_Object_from_Image_File(scene, camera);

    } //...EOFunction Init

//==================================================================

function animate() {
    dt = 0.1;
    time += dt;

    if (time < 10000) {
        requestAnimationFrame(animate);

        //move point light & indicator sphere

        speed = 16;
        if (Math.abs(PL_pos_z) > 400) mz = (-1)* mz;

        PL_pos_x += 0.01 * speed * mz;
        PL_pos_y += 0.05 * speed * mz;
        PL_pos_z -= 0.2 * speed * mz;

        PL.position.set(PL_pos_x, PL_pos_y, PL_pos_z);
        i_Sphere.position.set(PL_pos_x, PL_pos_y, PL_pos_z);

        renderer.render(scene, camera);
    } else alert("Time=" + time + "Finished");

}
//==================================================================




function SOW_F_Make_Heightmap_Object_from_Image_File(givenScene, givenCamera) {

    //... Read a Heightmap from a coloured image file 
    //... into a (pre-defined global) plane object called HPlane


    MyImage = new Image();

    MyImage.onload = function () {

        var MyPlane_width = 1000;//6000; //...MyPlane width or height are in scene units and do not have to match image width or height
        var MyPlane_height = 1000;//6000;

        var MyPlane_w_segs = MyImage.naturalWidth - 1; //... important that this mapping is correct for texture 1 pixel :: 1 segment.
        var MyPlane_h_segs = MyImage.naturalHeight - 1; //... important that this mapping is correct for texture 1 pixel :: 1 segment.
        var Hgeometry = new THREE.PlaneGeometry(MyPlane_width, MyPlane_height, MyPlane_w_segs, MyPlane_h_segs);


        //var texture = THREE.ImageUtils.loadTexture( '/images/Tri_VP_Texturemap.jpg' );
        var texture = THREE.ImageUtils.loadTexture( base64_imgData );

        //... Choose texture or color
        //var Hmaterial = new THREE.MeshLambertMaterial( { map: texture, side: THREE.DoubleSide} );//....fails 

        var Hmaterial = new THREE.MeshPhongMaterial( { 
            color: 0x111111 , side: THREE.DoubleSide } ); //... works OK

        HPlane = new THREE.Mesh(Hgeometry, Hmaterial);

        //...get Height Data from Image

        var scale = 0.6;//1//6; //0.25;      
        var Height_data = DA_getHeightData(MyImage, scale);

        //... set height of vertices
        X_offset = 0;
        Y_offset = 0;
        Z_offset = -100; //...this will (after rotation) add to the vertical height dimension (+ => up).

        for (var iii = 0; iii < HPlane.geometry.vertices.length; iii++) {
            //HPlane.geometry.vertices[iii].x = X_offset; 
            //HPlane.geometry.vertices[iii].y = Y_offset; 

            HPlane.geometry.vertices[iii].z = Z_offset + Height_data[iii];

        }  

//----------------------------------------------------------------------

//... Must do it in this order...Faces before Vertices
//... see WestLangley's response in  http://stackoverflow.com/questions/13943907/my-object-isnt-reflects-the-light-in-three-js


        HPlane.rotation.x = (-(Math.PI) / 2); //... rotate MyPlane -90 degrees on X
        //alert("Rotated");

        HPlane.geometry.computeFaceNormals(); //... for Lambert & Phong materials
        HPlane.geometry.computeVertexNormals(); //... for Lambert & Phong materials

/*        
        HPlane.updateMatrixWorld();
        HPlane.matrixAutoUpdate = false;
        HPlane.geometry.verticesNeedUpdate = true;
*/
        givenScene.add(HPlane);
        HPlane.position.set(0, -150, 0);//... cosmetic

        //return HPlane; //... not necessary, given that HPlane is global.

    } ;  //... End of MyImage.onload = function ()  



    //===============================================================

    //... *** IMPORTANT ***

    //... Only NOW do we command the script to actually load the image source

    //... This .src statement will load the image from file into MyImage object 
    //... and invoke the pre-associated MyImage.OnLoad function

    //... cause cross-origin problem: MyImage.src = '/images/Tri_VP_Heightmap_64x64.jpg'; //...if image file is local to this html file.

    MyImage.src = base64_imgData;//... uses image data provided in the script to avoid Cross-origin file source restrictions.

} //... End of function SOW_F_Make_Heightmap_Object_from_Image_File

//===========================================================================

function DA_getHeightData(d_img, scale) {

    //... This is used by function SOW_F_Make_Heightmap_Object_from_Image_File.

    //if (scale == undefined) scale=1;

    var canvas = document.createElement('canvas');
    canvas.width = d_img.width; //OK
    canvas.height = d_img.height;
    var context = canvas.getContext('2d');

    var size = d_img.width * d_img.height;
    var data = new Float32Array(size);

    context.drawImage(d_img, 0, 0);

    for (var ii = 0; ii < size; ii++) {
        data[ii] = 0;
    }

    var imgData = context.getImageData(0, 0, d_img.width, d_img.height); 

    var pix = imgData.data; //... Uint(8) UnClamped Array[1024] for a 16x16 = 256 pixel image = 4 slots per pixel.

    var jjj = 0;

    //... presumably each pix cell can have value 0 to 255
    for (var iii = 0; iii < pix.length; iii += 4) {
        var all = pix[iii] + pix[iii + 1] + pix[iii + 2]; 
        //... I guess RGBA and we don't use the fourth cell (A, = Alpha channel)
        jjj++;
        data[jjj] = all * scale / 3; //...original code used 12 not 3 ??? and divided by scale.
        //console.log (iii, all/(3*scale), data[jjj]);
    }

    return data;

} //... end of function DA_getHeightData(d_img,scale)

//==================================================================================================

function SOW_F_Get_A_Plane(givenScene, givenCamera) {

    //...MyPlane width or height are in scene units and do not have to match image width or height
    var MyPlane_width = 1000;
    var MyPlane_height = 1000;

    var MyPlane_w_segs = 64; //... 
    var MyPlane_h_segs = 64; //... 

    geometry = new THREE.PlaneGeometry(MyPlane_width, MyPlane_height, MyPlane_w_segs, MyPlane_h_segs);

    //var material = new THREE.MeshLambertMaterial( { color: 0xeeee00, side: THREE.DoubleSide} ); 

    var material = new THREE.MeshPhongMaterial({
        color: 0xeeee00,side: THREE.DoubleSide
    }); //... OK

    MyPlane = new THREE.Mesh(geometry, material);

    givenScene.add(MyPlane);

    MyPlane.rotation.x = (-(Math.PI) / 2); //  rotate it -90 degrees on X
    MyPlane.position.set(0, 100, 0);

    MyPlane.geometry.computeFaceNormals(); //...for Lambert & Phong materials
    MyPlane.geometry.computeVertexNormals(); //...for Lambert & Phong materials
    /*
    MyPlane.geometry.verticesNeedUpdate = true;
    MyPlane.updateMatrixWorld();
    MyPlane.matrixAutoUpdate = false;
    */

} //... EOF SOW_F_Get_A_Plane
//====================================================================

function SOW_F_Make_peas()
{
//----------------- Make an array of spheres -----------------------

Pea_geometry = new THREE.SphereGeometry(5,16,16);
//Pea_material = new THREE.MeshNormalMaterial({ shading: THREE.SmoothShading});
Pea_material = new THREE.MeshPhongMaterial({ color: 0xaa5522});

        // global... 
        num_peas = 1200;

        for (var iii = 0; iii < num_peas; iii++) 
        {
            //...now global 
            ob_Pea = new THREE.Mesh(Pea_geometry, Pea_material);

            ob_Pea.position.set(
            400 * Math.random() - 150, 
            300 * Math.random() - 150, 
            1200 * Math.random() - 150);        

            scene.add(ob_Pea);//TEST

        }

}

ОБНОВЛЕНИЕ

Похоже, проблема является результатом фазирования. См. этот новый JSFiddle(r70). Pointlight создается в функции init(), но не добавляется в сцену или сразу удаляется из сцены после добавления. Затем создаются различные графические объекты-сетки. Когда точечный свет добавится обратно в сцену (в цикле анимации), будет слишком поздно — объекты сетки не будут освещены точечным источником света.

Процедурное решение состоит в том, чтобы просто не удалять прожекторы со сцены, если они будут использоваться позже. Если их нужно временно «погасить», просто уменьшите интенсивность и увеличьте ее позже: например. myPointLight.intensity = 0.00


person steveOw    schedule 20.02.2015    source источник
comment
В последнем jsfiddle я вижу местность и кучу сфер, освещенных движущимся светом. Разве не так это должно выглядеть?   -  person mrdoob    schedule 20.02.2015
comment
@мрдуб. Правильно, есть только тот, который простой JSFiddle использует r70, и он работает нормально. (Мой последний абзац был плохо сформулирован, я перепишу). Проблема (пока) появляется только в моем сложном приложении с r60. Я еще не смог сделать простой JSFiddle с r60. Я понимаю, что r60 официально не поддерживается, но подумал, что кто-то может заметить явную ошибку дизайна в простом JSFiddle r70 (который использует дизайн, аналогичный моему сложному приложению r60).   -  person steveOw    schedule 20.02.2015
comment
Другой простой JSFiddle (на этот раз с использованием THREE.js r60) по адресу jsfiddle.net/6jen7085/4. Это работает нормально. Исходная проблема со сложным приложением до сих пор не решена.   -  person steveOw    schedule 20.02.2015
comment
@мрдуб. См. обновление в конце основного вопроса. Существует проблема фазирования, если точечный источник света создается, но удаляется из сцены до создания/добавления объектов-сетей. Я не знаю, есть ли в THREE.js какой-то метод для обеспечения регистрации. Подходит как для r60, так и для r70.   -  person steveOw    schedule 21.02.2015