Skip to content

An error occurred when SkyMesh and ao were used simultaneously #31367

Closed
@fmruin

Description

@fmruin

Description

SkyMesh does not have a normal. When using pass for multiple objectives and obtaining normal, the following error message appears.

Color target has no corresponding fragment stage output but writeMask (ColorWriteMask::(Red|Green|Blue|Alpha)) is not zero.

  • While validating targets[1] framebuffer output.
  • While validating fragment state.
  • While calling [Device].CreateRenderPipeline([RenderPipelineDescriptor ""renderPipeline_NodeMaterial_41""]).

[Invalid RenderPipeline "renderPipeline_NodeMaterial_41"] is invalid.

  • While encoding [RenderPassEncoder (unlabeled)].SetPipeline([Invalid RenderPipeline "renderPipeline_NodeMaterial_41"]).
  • While finishing [CommandEncoder "renderContext_15"].

Reproduction steps

  1. Add both the sky and post-processing to the scene simultaneously

Code

// code goes here
import * as THREE from 'three';
import { pass, mrt, output, normalView, mix } from 'three/tsl';
import { ao } from 'three/addons/tsl/display/GTAONode.js';
import { denoise } from 'three/addons/tsl/display/DenoiseNode.js';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { SkyMesh } from 'three/addons/objects/SkyMesh.js';

let camera, scene, renderer, postProcessing, controls;

let aoPass, denoisePass, blendPassAO, blendPassDenoise, scenePassColor;
let sky, sun;
const params = {
	distanceExponent: 1,
	distanceFallOff: 1,
	radius: 0.25,
	scale: 1,
	thickness: 1,
	denoised: false,
	enabled: true,
	denoiseRadius: 5,
	lumaPhi: 5,
	depthPhi: 5,
	normalPhi: 5
};

const gui = new GUI();
init();

async function init() {

	camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 50 );
	camera.position.set( 1, 1.3, 5 );

	scene = new THREE.Scene();

	renderer = new THREE.WebGPURenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setAnimationLoop( animate );
	document.body.appendChild( renderer.domElement );

	await renderer.init();

	const environment = new RoomEnvironment();
	const pmremGenerator = new THREE.PMREMGenerator( renderer );

	scene.background = new THREE.Color( 0x666666 );
	scene.environment = pmremGenerator.fromScene( environment ).texture;
	environment.dispose();
	pmremGenerator.dispose();

	//

	controls = new OrbitControls( camera, renderer.domElement );
	controls.target.set( 0, 0.5, - 1 );
	controls.update();
	// controls.enablePan = false;
	// controls.enableDamping = true;
	// controls.minDistance = 2;
	// controls.maxDistance = 8;
	//

	postProcessing = new THREE.PostProcessing( renderer );

	const scenePass = pass( scene, camera);
	scenePass.setMRT( mrt( {
		output: output,
		normal: normalView
	} ) );

	scenePassColor = scenePass.getTextureNode( 'output' );
	const scenePassNormal = scenePass.getTextureNode( 'normal' );
	const scenePassDepth = scenePass.getTextureNode( 'depth' );

	// ao

	aoPass = ao( scenePassDepth, scenePassNormal, camera );
	aoPass.resolutionScale = 0.5; // running AO in half resolution is often sufficient
	blendPassAO = aoPass.getTextureNode().mul( scenePassColor );

	// denoise (optional, use it if you need best quality but is has a noticeable hit on performance)

	denoisePass = denoise( aoPass.getTextureNode(), scenePassDepth, scenePassNormal, camera );
	blendPassDenoise = denoisePass.mul( scenePassColor );

	postProcessing.outputNode = blendPassAO;
	
	
	const dracoLoader = new DRACOLoader();
	dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
	dracoLoader.setDecoderConfig( { type: 'js' } );
	const loader = new GLTFLoader();
	loader.setDRACOLoader( dracoLoader );
	loader.setPath( 'models/gltf/' );

	const gltf = await loader.loadAsync( 'minimalistic_modern_bedroom.glb' );

	const model = gltf.scene;
	model.position.set( 0, 1, 0 );
	scene.add( model );

	model.traverse( ( o ) => {

		// Transparent objects (e.g. loaded via GLTFLoader) might have "depthWrite" set to "false".
		// This is wanted when rendering the beauty pass however it produces wrong results when computing
		// AO since depth and normal data are out of sync. Computing normals from depth by not using MRT
		// can mitigate the issue although the depth information (and thus the normals) are not correct in
		// first place. Besides, normal estimation is computationally more expensive than just sampling a
		// normal texture. So depending on your scene, consider to enable "depthWrite" for all transparent objects.

		if ( o.material ) o.material.depthWrite = true;

	} );

	window.addEventListener( 'resize', onWindowResize );
	initSky()
	console.log(scene)
	//

	
	gui.title( 'AO settings' );
	gui.add( params, 'distanceExponent' ).min( 1 ).max( 4 ).onChange( updateParameters );
	gui.add( params, 'distanceFallOff' ).min( 0.01 ).max( 1 ).onChange( updateParameters );
	gui.add( params, 'radius' ).min( 0.01 ).max( 1 ).onChange( updateParameters );
	gui.add( params, 'scale' ).min( 0.01 ).max( 2 ).onChange( updateParameters );
	gui.add( params, 'thickness' ).min( 0.01 ).max( 2 ).onChange( updateParameters );
	gui.add( params, 'enabled' ).onChange( updatePassChain );
	const folder = gui.addFolder( 'Denoise settings' );
	folder.add( params, 'denoiseRadius' ).min( 0.01 ).max( 10 ).name( 'radius' ).onChange( updateParameters );
	folder.add( params, 'lumaPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
	folder.add( params, 'depthPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
	folder.add( params, 'normalPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
	folder.add( params, 'denoised' ).name( 'enabled' ).onChange( updatePassChain );

}
function initSky() {

	// Add Sky
	sky = new SkyMesh();
	sky.scale.setScalar( 450000 );
	scene.add( sky );

	sun = new THREE.Vector3();

	/// GUI

	const effectController = {
		turbidity: 10,
		rayleigh: 3,
		mieCoefficient: 0.005,
		mieDirectionalG: 0.7,
		elevation: 2,
		azimuth: 180,
		exposure: renderer.toneMappingExposure
	};

	function guiChanged() {

		sky.turbidity.value = effectController.turbidity;
		sky.rayleigh.value = effectController.rayleigh;
		sky.mieCoefficient.value = effectController.mieCoefficient;
		sky.mieDirectionalG.value = effectController.mieDirectionalG;

		const phi = THREE.MathUtils.degToRad( 90 - effectController.elevation );
		const theta = THREE.MathUtils.degToRad( effectController.azimuth );

		sun.setFromSphericalCoords( 1, phi, theta );

		sky.sunPosition.value.copy( sun );

		renderer.toneMappingExposure = effectController.exposure;
	}

	gui.add( effectController, 'turbidity', 0.0, 20.0, 0.1 ).onChange( guiChanged );
	gui.add( effectController, 'rayleigh', 0.0, 4, 0.001 ).onChange( guiChanged );
	gui.add( effectController, 'mieCoefficient', 0.0, 0.1, 0.001 ).onChange( guiChanged );
	gui.add( effectController, 'mieDirectionalG', 0.0, 1, 0.001 ).onChange( guiChanged );
	gui.add( effectController, 'elevation', 0, 90, 0.1 ).onChange( guiChanged );
	gui.add( effectController, 'azimuth', - 180, 180, 0.1 ).onChange( guiChanged );
	gui.add( effectController, 'exposure', 0, 1, 0.0001 ).onChange( guiChanged );

	guiChanged();

}

function updatePassChain() {
	if ( params.enabled === true ) {
		if ( params.denoised === true ) {
			postProcessing.outputNode = blendPassDenoise;
		} else {
			postProcessing.outputNode = blendPassAO;
		}
	} else {
		postProcessing.outputNode = scenePassColor;
	}
	postProcessing.needsUpdate = true;
}

function updateParameters() {
	aoPass.distanceExponent.value = params.distanceExponent;
	aoPass.distanceFallOff.value = params.distanceFallOff;
	aoPass.radius.value = params.radius;
	aoPass.scale.value = params.scale;
	aoPass.thickness.value = params.thickness;

	denoisePass.radius.value = params.denoiseRadius;
	denoisePass.lumaPhi.value = params.lumaPhi;
	denoisePass.depthPhi.value = params.depthPhi;
	denoisePass.normalPhi.value = params.normalPhi;
}

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

function animate() {
	controls.update();
	postProcessing.render();
}

Live example

Screenshots

Image

Version

r177,r178

Device

Desktop

Browser

Chrome

OS

Windows

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions