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

WebGPURenderer: MeshMatcapNodeMaterial #28259

Merged
merged 2 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@
"webgpu_materials",
"webgpu_materials_displacementmap",
"webgpu_materials_lightmap",
"webgpu_materials_matcap",
"webgpu_materials_sss",
"webgpu_materials_transmission",
"webgpu_materials_video",
Expand Down
1 change: 1 addition & 0 deletions examples/jsm/nodes/materials/Materials.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { default as MeshPhongNodeMaterial } from './MeshPhongNodeMaterial.js';
export { default as MeshStandardNodeMaterial } from './MeshStandardNodeMaterial.js';
export { default as MeshPhysicalNodeMaterial } from './MeshPhysicalNodeMaterial.js';
export { default as MeshSSSNodeMaterial } from './MeshSSSNodeMaterial.js';
export { default as MeshMatcapNodeMaterial } from './MeshMatcapNodeMaterial.js';
export { default as PointsNodeMaterial } from './PointsNodeMaterial.js';
export { default as SpriteNodeMaterial } from './SpriteNodeMaterial.js';
export { default as ShadowNodeMaterial } from './ShadowNodeMaterial.js';
56 changes: 56 additions & 0 deletions examples/jsm/nodes/materials/MeshMatcapNodeMaterial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js';
import { materialReference } from '../accessors/MaterialReferenceNode.js';
import { diffuseColor } from '../core/PropertyNode.js';
import { vec2, vec3 } from '../shadernode/ShaderNode.js';
import { positionViewDirection } from '../accessors/PositionNode.js';
import { MeshMatcapMaterial } from 'three';
import { transformedNormalView } from '../accessors/NormalNode.js';
import { mix } from '../math/MathNode.js';

const defaultValues = new MeshMatcapMaterial();

class MeshMatcapNodeMaterial extends NodeMaterial {

constructor( parameters ) {

super();

this.isMeshMatcapNodeMaterial = true;

this.lights = false;

this.setDefaultValues( defaultValues );

this.setValues( parameters );

}

setupVariants( builder ) {

const x = vec3( positionViewDirection.z, 0.0, positionViewDirection.x.negate() ).normalize();
const y = positionViewDirection.cross( x );

const uv = vec2( x.dot( transformedNormalView ), y.dot( transformedNormalView ) ).mul( 0.495 ).add( 0.5 ) ; // 0.495 to remove artifacts caused by undersized matcap disks

let matcapColor;

if ( builder.material.matcap ) {

matcapColor = materialReference( 'matcap', 'texture' ).context( { getUV: () => uv } );

} else {

matcapColor = vec3( mix( 0.2, 0.8, uv.y ) ); // default if matcap is missing

}

diffuseColor.rgb.mulAssign( matcapColor.rgb );

}

}


export default MeshMatcapNodeMaterial;

addNodeMaterial( 'MeshMatcapNodeMaterial', MeshMatcapNodeMaterial );
Binary file added examples/screenshots/webgpu_materials_matcap.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
274 changes: 274 additions & 0 deletions examples/webgpu_materials_matcap.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - materials - matcap</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>

<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgpu materials matcap<br />
Drag-and-drop JPG, PNG, WebP, AVIF, or EXR MatCap image files<br/>
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/",
"three/nodes": "./jsm/nodes/Nodes.js"
}
}
</script>

<script type="module">

import * as THREE from 'three';

import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';

import { MeshMatcapNodeMaterial } from 'three/nodes';
Fixed Show fixed Hide fixed

let mesh, renderer, scene, camera;

const API = {
color: 0xffffff, // sRGB
exposure: 1.0
};

init();

function init() {

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

// tone mapping
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = API.exposure;

// scene
scene = new THREE.Scene();

// camera
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
camera.position.set( 0, 0, 13 );

// controls
const controls = new OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', render );
controls.enableZoom = false;
controls.enablePan = false;

// manager
const manager = new THREE.LoadingManager( render );

// matcap
const loaderEXR = new EXRLoader( manager );
const matcap = loaderEXR.load( 'textures/matcaps/040full.exr' );

// normalmap
const loader = new THREE.TextureLoader( manager );

const normalmap = loader.load( 'models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg' );

// model
new GLTFLoader( manager ).load( 'models/gltf/LeePerrySmith/LeePerrySmith.glb', function ( gltf ) {

mesh = gltf.scene.children[ 0 ];
mesh.position.y = - 0.25;

mesh.material = new THREE.MeshMatcapMaterial( {

color: new THREE.Color().setHex( API.color ).convertSRGBToLinear(),
matcap: matcap,
normalMap: normalmap

} );

scene.add( mesh );

} );

// gui
const gui = new GUI();

gui.addColor( API, 'color' )
.listen()
.onChange( function () {

mesh.material.color.set( API.color ).convertSRGBToLinear();
render();

} );

gui.add( API, 'exposure', 0, 2 )
.onChange( function () {

renderer.toneMappingExposure = API.exposure;
render();

} );

gui.domElement.style.webkitUserSelect = 'none';

// drag 'n drop
initDragAndDrop();

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

renderer.setSize( window.innerWidth, window.innerHeight );

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

render();

}

function render() {

renderer.renderAsync( scene, camera );

}

//
// drag and drop anywhere in document
//

function updateMatcap( texture ) {

if ( mesh.material.matcap ) {

mesh.material.matcap.dispose();

}

mesh.material.matcap = texture;

texture.needsUpdate = true;

mesh.material.needsUpdate = true; // because the color space can change

render();

}

function handleJPG( event ) { // PNG, WebP, AVIF, too

function imgCallback( event ) {

const texture = new THREE.Texture( event.target );

texture.colorSpace = THREE.SRGBColorSpace;

updateMatcap( texture );

}

const img = new Image();

img.onload = imgCallback;

img.src = event.target.result;

}

function handleEXR( event ) {

const contents = event.target.result;

const loader = new EXRLoader();

loader.setDataType( THREE.HalfFloatType );

const texData = loader.parse( contents );

const texture = new THREE.DataTexture();

texture.image.width = texData.width;
texture.image.height = texData.height;
texture.image.data = texData.data;

texture.format = texData.format;
texture.type = texData.type;
texture.colorSpace = THREE.LinearSRGBColorSpace;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = false;
texture.flipY = false;

updateMatcap( texture );

}

function loadFile( file ) {

const filename = file.name;
const extension = filename.split( '.' ).pop().toLowerCase();

if ( extension === 'exr' ) {

const reader = new FileReader();

reader.addEventListener( 'load', function ( event ) {

handleEXR( event );

} );

reader.readAsArrayBuffer( file );

} else { // 'jpg', 'png'

const reader = new FileReader();

reader.addEventListener( 'load', function ( event ) {

handleJPG( event );

} );

reader.readAsDataURL( file );

}

}

function initDragAndDrop() {

document.addEventListener( 'dragover', function ( event ) {

event.preventDefault();
event.dataTransfer.dropEffect = 'copy';

} );

document.addEventListener( 'drop', function ( event ) {

event.preventDefault();

loadFile( event.dataTransfer.files[ 0 ] );

} );

}

</script>

</body>
</html>
Loading