Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MRT needs a filter if there are several objects with different shader counts in the scene #28204

Closed
Spiri0 opened this issue Apr 24, 2024 · 3 comments

Comments

@Spiri0
Copy link
Contributor

Spiri0 commented Apr 24, 2024

Description

I use the outputStruct node to be able to store the result of several shaders with a renderTarget in textures. Everything works perfectly so far.
If you have several objects in the scene you have to render the relevant object that uses several shaders directly in the renderTarget if not all objects have several shaders.

renderer.setRenderTarget(renderTarget);
renderer.render( object, camera );
renderer.setRenderTarget( null );

The reason is because the renderTarget expects the same count of shaders from all objects in the scene as in the renderTarget count. As a rule, you only want to equip certain objects with multiple shaders.
So if you have an object in the scene that uses two shaders and one that only uses one, then there will be endless warnings in the console because the renderTarget also expects two shaders from the second object.

If you have a complex dynamic object hierarchy and use groups for it, unfortunately it doesn't work with direct rendering because groups aren't objects and it has to be the scene.

Solution: With a count > 1, the renderTarget must ignore objects with only one shader when processing count 2. Analogously with a count of 3 if an object only has 2 shaders

Reproduction steps

  1. create an object with outputStruct material and use two very simple shaders.
  2. create a second object with a simple material
  3. use a renderTarget with count: 2 to store the output of both shaders of the first object.

Code

import * as THREE from "https://esm.sh/three";
import {MeshBasicNodeMaterial, wgslFn, vec4, texture, attribute, outputStruct } from "https://esm.sh/three/nodes";
import {OrbitControls} from "https://esm.sh/three/addons/controls/OrbitControls.js";
import Stats from "https://esm.sh/three/addons/libs/stats.module.js";
import WebGPURenderer from "https://esm.sh/three/addons/renderers/webgpu/WebGPURenderer.js";
import QuadMesh from "https://esm.sh/three/addons/objects/QuadMesh.js";


let scene = new THREE.Scene();
scene.background = new THREE.Color(0x00001f);
let renderer = new WebGPURenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);
let camera = new THREE.PerspectiveCamera(50.0, window.innerWidth/window.innerHeight, 0.5, 10000);

camera.position.set(3, 3, 3);

let controls = new OrbitControls(camera, renderer.domElement);
let quad, renderTarget, material;

window.addEventListener("resize", onWindowResize, false);

init();
await render();


function init() {
  
  renderTarget = new THREE.RenderTarget(512, 512, {count: 2});

  const fragmentShader1 = wgslFn(`
    fn main1(
    ) -> vec4<f32> {
      return vec4<f32>(1, 0, 0, 1);
    }
  `);

  const fragmentShader2 = wgslFn(`
    fn main2(
    ) -> vec4<f32> {
      return vec4<f32>(0, 1, 0, 1);
    }
  `);
  
  const fragmentShader3 = wgslFn(`
    fn main3(
    ) -> vec4<f32> {
      return vec4<f32>(0, 0, 1, 1);
    }
  `);
  
  material = new MeshBasicNodeMaterial();
  material.outputNode = outputStruct(
    fragmentShader1(),
    fragmentShader2(),
    fragmentShader3()
  );

  
  const geometry1 = new THREE.BoxGeometry(1, 1, 1);
  const box1 = new THREE.Mesh(geometry1, material);
  scene.add(box1);
  
  //no errors without this box. The reason is that with count 2 in the renderTarget the renderTarget expects 2 outputs from all meshes in the scene.
  const geometry2 = new THREE.BoxGeometry(1, 1, 1);
  const material2 = new MeshBasicNodeMaterial();
  material2.colorNode = vec4(0, 0, 1, 1);
  const box2 = new THREE.Mesh(geometry2, material2);
  box2.position.set(3, 0, -2);
  scene.add(box2);
  
}  

async function render() {
  requestAnimationFrame(render);
  await renderer.renderAsync(scene, camera);
 
  renderer.setRenderTarget(renderTarget);
  renderer.render( scene, camera );
  renderer.setRenderTarget( null );
}

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


Live example

[* jsfiddle-latest-release

Screenshots

No response

Version

r163

Device

No response

Browser

No response

OS

No response

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 26, 2024

When you configure in webgl2_multiple_rendertargets the render target with a count of 3, the following WebGL warning in the console appears:

GL_INVALID_OPERATION: Active draw buffers with missing fragment shader outputs.

The issue could be fixed by making sure the object's shader writes data to the new layout(location = 2). Or by not rendering the object at all.

I'm not sure if the engine should automatically handle this use case. It seems to me the the developer instead is responsible to configure its scene correctly. So if you have objects with different shader outputs in your scene, you could

a) create multiple scenes and render them separately with different render targets.
b) use Layers or Object3D.visible and configure the objects with a respective value that you want include/exclude from rendering.

The problem is that objects with non-matching outputs are some sort of invalid input for MRT in the first place. It must be defined how objects are treated if they write e.g. no normal or velocity values and that is an application specific thing.

@Spiri0
Copy link
Contributor Author

Spiri0 commented Apr 26, 2024

I use different layers. But your comment is correct because that's exactly why I introduced the layers and I use 4 of them.
According to logic there should be no conflicts.

Yesterday I worked more intensively on the readRenderTargetPixels topic. To do this, I have to familiarize myself with webgl much more intensively. I actually only deal with WebGPU

Please take the issue out so I can examine it more intensively with the layers.

I'm juggling with a little too many topics at the moment and am becoming less attentive as a result. I'm usually very thorough with my analyses. I'll slow down a bit.

@Mugen87 Mugen87 closed this as not planned Won't fix, can't repro, duplicate, stale Apr 26, 2024
@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 26, 2024

Don't worry! The discussions here at GitHub are also intended to sort out one thoughts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants