'RenderTarget as new texture returns only a black and white texture

RenderTarget as new texture part 2: If I give a plane a shader and transfer a texture to it and the render works as it should. I then see a plane with the texture that was passed to the shader, wonderful 👍

But if I try to output this result as a new texture via a renderTarget, I get a black and white texture 🙄

who knows why?

var camera, controls, scene, renderer, container, aspect;

var textureCamera
var textureScene;
    
function main() {
init();
animate();
}

function init() {

    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio ); 
    renderer.shadowMap.enabled = true; 
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.setClearColor( 0x000000 );  
    container = document.getElementById('container');
    renderer.setSize(container.clientWidth, container.clientHeight);
    container.appendChild( renderer.domElement );

    aspect = container.clientWidth / container.clientHeight; 
    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x000000 );
    
    camera = new THREE.PerspectiveCamera( 60, container.clientWidth / container.clientHeight, 1, 1000000 );
    camera.position.set(0, 0, 200);

    controls = new THREE.OrbitControls( camera, renderer.domElement );
    controls.enableZoom = true;
    controls.enabled = true;
    controls.target.set(0, 0, 0);
    
    //-------------

    const loader = new THREE.TextureLoader();
    var texture = loader.load("pz.png");   //fine 👍
    //texture = generateTexture(); //returns a black and white texture 🥴
    const geometry = new THREE.PlaneGeometry( 50, 50 );
    const material = customShader(texture);
    const plane = new THREE.Mesh( geometry, material); 
    scene.add( plane );

}//-------End init----------


function animate() {

    requestAnimationFrame( animate );  
    render();
    
}//-------End animate----------

function render() {
    
    //renderer.render(textureScene, textureCamera);   //for test, it works correct 

    camera.updateMatrixWorld();
    camera.updateProjectionMatrix(); 
    renderer.render(scene, camera); 

}//-------End render----------


function generateTexture() {

    var resolution = 2000;
    textureScene = new THREE.Scene();
    textureScene.background = new THREE.Color(0xFFFFFF);

    var textureOptions = { 
        format: THREE.RGBFormat,
        magFilter: THREE.LinearFilter,
        minFilter: THREE.LinearFilter,
    };

    var renderTarget = new THREE.WebGLRenderTarget(resolution, resolution, textureOptions);
    textureCamera = new THREE.PerspectiveCamera(60, 1, 0.1, 100000.0);
        
    textureCamera.position.set(0, 0, 200);
    textureCamera.lookAt(0, 0, 0);

    const loader = new THREE.TextureLoader();
    var texture = loader.load("pz.png");
    const geometry = new THREE.PlaneGeometry( 50, 50 );
    const material = customShader(texture);
    const plane = new THREE.Mesh( geometry, material); 
    textureScene.add( plane );

    renderer.setRenderTarget( renderTarget );
    renderer.clear(); 
    renderer.render( textureScene, textureCamera );
    
    renderer.setRenderTarget(null);
    return renderTarget.texture;
}


function customShader(texture){

    var Vertex =`
    varying vec2 vUv;
    void main() {
        vUv = uv;
        vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
        gl_Position = projectionMatrix * modelViewPosition;
    }`;
    
    var Fragment =`

    uniform sampler2D tex;
    varying vec2 vUv;
    void main() {
        vec4 color = texture2D(tex, vUv);
        gl_FragColor = color;
    }`;
    
    var uniforms = {   
        tex: { value: texture }
    };  
            
    return Shader = new THREE.ShaderMaterial({              
        uniforms: uniforms,         
        vertexShader: Vertex,
        fragmentShader: Fragment,   
        //transparent: true,
        side: THREE.DoubleSide,
    }); 

}


Solution 1:[1]

The problem is that you have to wait until your texture is actually loaded before rendering to the render target. I've updated your code so it used the async/await syntax in order to solve the issue. Especially have a look how generateTexture() has been changed.

let camera, scene, renderer;

init().then(animate);

async function init() {

  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.setClearColor(0x000000);
  document.body.appendChild(renderer.domElement);

  const aspect = window.innerWidth / window.innerHeight;
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x000000);

  camera = new THREE.PerspectiveCamera(60, aspect, 1, 1000000);
  camera.position.set(0, 0, 200);

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

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

  const texture = await generateTexture();
  const geometry = new THREE.PlaneGeometry(50, 50);
  const material = customShader(texture);
  const plane = new THREE.Mesh(geometry, material);
  scene.add(plane);

} //-------End init----------


function animate() {

  requestAnimationFrame(animate);
  render();

} //-------End animate----------

function render() {

  renderer.render(scene, camera);

} //-------End render----------


async function generateTexture() {

  const resolution = 2000;
  const textureScene = new THREE.Scene();
  textureScene.background = new THREE.Color(0xFFFFFF);

  const renderTarget = new THREE.WebGLRenderTarget(resolution, resolution);
  const textureCamera = new THREE.PerspectiveCamera(60, 1, 0.1, 100000.0);

  textureCamera.position.set(0, 0, 200);
  textureCamera.lookAt(0, 0, 0);

  const loader = new THREE.TextureLoader();
  const texture = await loader.loadAsync("https://threejs.org/examples/textures/uv_grid_opengl.jpg");
  const geometry = new THREE.PlaneGeometry(50, 50);
  const material = customShader(texture);
  const plane = new THREE.Mesh(geometry, material);
  textureScene.add(plane);

  renderer.setRenderTarget(renderTarget);
  renderer.clear();
  renderer.render(textureScene, textureCamera);

  renderer.setRenderTarget(null);
  return renderTarget.texture;
}


function customShader(texture) {

  const Vertex = `
    varying vec2 vUv;
    void main() {
        vUv = uv;
        vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
        gl_Position = projectionMatrix * modelViewPosition;
    }`;

  const Fragment = `

    uniform sampler2D tex;
    varying vec2 vUv;
    void main() {
        vec4 color = texture2D(tex, vUv);
        gl_FragColor = color;
    }`;

  const uniforms = {
    tex: {
      value: texture
    }
  };

  return Shader = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: Vertex,
    fragmentShader: Fragment
  });

}
body {
      margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>

Solution 2:[2]

With the above code i recorded the rings of saturn as a texture in a second scene and then projected it onto a sphere in the main scene. The result is exactly as i imagined. The entire code in which everything is complete is, however, quite extensive. so also with shadows on the rings behind saturn, with saturn texture and a scattering atmosphere. The shadows cast on saturn and the rings depend on saturn's position around the sun. The whole thing is constantly updated. The code for the ring shadows alone on the saturn as in the first picture is not too big. I've never made a fiddle before. If anyone is interested in the code, I'm happy to be advised on how to integrate the executable code here.

Texture Camera and texture projection to an object

Final result with all togetherstrong text

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mugen87
Solution 2 Spiri