Skip to content

Commit f2e4e0f

Browse files
authored
WebGLRenderer: NodeMaterial (#21117)
* draft of WebGLNodeBuilder * Revert "draft of WebGLNodeBuilder" This reverts commit d37f10f. * add UVNode.isUVNode * WebGL: NodeMaterial * new example (colorNode) * add Material.onNodeBuild() * .onNodeBuild for NodeMaterial * cleanup * add example in files.json * add screenshot * rename Material.onNodeBuild() -> Material.onBuild() * fix tabs
1 parent a0f0f8c commit f2e4e0f

File tree

8 files changed

+372
-0
lines changed

8 files changed

+372
-0
lines changed

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
"webgl_materials_envmaps_hdr_nodes",
233233
"webgl_materials_envmaps_pmrem_nodes",
234234
"webgl_materials_nodes",
235+
"webgl_materials_standard_nodes",
235236
"webgl_mirror_nodes",
236237
"webgl_performance_nodes",
237238
"webgl_postprocessing_nodes",

examples/jsm/renderers/nodes/accessors/UVNode.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ class UVNode extends AttributeNode {
88

99
this.index = index;
1010

11+
Object.defineProperty( this, 'isUVNode', { value: true } );
12+
1113
}
1214

1315
getAttributeName( /*builder*/ ) {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import NodeBuilder from '../../nodes/core/NodeBuilder.js';
2+
import NodeSlot from '../../nodes/core/NodeSlot.js';
3+
4+
class WebGLNodeBuilder extends NodeBuilder {
5+
6+
constructor( material, renderer, properties ) {
7+
8+
super( material, renderer );
9+
10+
this.properties = properties;
11+
12+
this._parseMaterial();
13+
14+
}
15+
16+
_parseMaterial() {
17+
18+
const material = this.material;
19+
20+
// parse inputs
21+
22+
if ( material.colorNode !== undefined ) {
23+
24+
this.addSlot( 'fragment', new NodeSlot( material.colorNode, 'COLOR', 'vec4' ) );
25+
26+
}
27+
28+
}
29+
30+
getVaryFromNode( node, type ) {
31+
32+
const vary = super.getVaryFromNode( node, type );
33+
34+
if ( node.isUVNode ) {
35+
36+
vary.name = 'vUv';
37+
38+
}
39+
40+
return vary;
41+
42+
}
43+
44+
getTexture( textureProperty, uvSnippet ) {
45+
46+
return `sRGBToLinear( texture2D( ${textureProperty}, ${uvSnippet} ) )`;
47+
48+
}
49+
50+
getUniformsHeaderSnippet( shaderStage ) {
51+
52+
const uniforms = this.uniforms[ shaderStage ];
53+
54+
let snippet = '';
55+
56+
for ( let uniform of uniforms ) {
57+
58+
if ( uniform.type === 'texture' ) {
59+
60+
snippet += `uniform sampler2D ${uniform.name};`;
61+
62+
} else {
63+
64+
let vectorType = this.getVectorType( uniform.type );
65+
66+
snippet += `uniform ${vectorType} ${uniform.name};`;
67+
68+
}
69+
70+
}
71+
72+
return snippet;
73+
74+
}
75+
76+
getAttributesHeaderSnippet( /*shaderStage*/ ) {
77+
78+
}
79+
80+
getVarysHeaderSnippet( /*shaderStage*/ ) {
81+
82+
}
83+
84+
getVarysBodySnippet( /*shaderStage*/ ) {
85+
86+
}
87+
88+
composeUniforms() {
89+
90+
const uniforms = this.uniforms[ 'fragment' ];
91+
92+
for ( let uniform of uniforms ) {
93+
94+
this.properties.uniforms[ uniform.name ] = uniform;
95+
96+
}
97+
98+
}
99+
100+
build() {
101+
102+
super.build();
103+
104+
this.properties.defines[ 'NODE_HEADER_UNIFORMS' ] = this.defines[ 'fragment' ][ 'NODE_HEADER_UNIFORMS' ];
105+
this.properties.defines[ 'NODE_COLOR' ] = this.defines[ 'fragment' ][ 'NODE_COLOR' ];
106+
107+
this.composeUniforms();
108+
109+
return this;
110+
111+
}
112+
113+
}
114+
115+
export { WebGLNodeBuilder };
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { WebGLNodeBuilder } from './WebGLNodeBuilder.js';
2+
3+
import { Material } from '../../../../../build/three.module.js';
4+
5+
function addCodeAfterSnippet( source, snippet, code ) {
6+
7+
const index = source.indexOf( snippet );
8+
9+
if ( index !== - 1 ) {
10+
11+
const start = source.substring( 0, index + snippet.length );
12+
const end = source.substring( index + snippet.length );
13+
14+
return `${start}\n${code}\n${end}`;
15+
16+
}
17+
18+
return source;
19+
20+
}
21+
22+
Material.prototype.onBuild = function ( parameters, renderer ) {
23+
24+
const nodeBuilder = new WebGLNodeBuilder( this, renderer, parameters ).build();
25+
26+
let fragmentShader = parameters.fragmentShader;
27+
28+
fragmentShader = addCodeAfterSnippet( fragmentShader, '#include <color_pars_fragment>',
29+
`#ifdef NODE_HEADER_UNIFORMS
30+
31+
NODE_HEADER_UNIFORMS
32+
33+
#endif` );
34+
35+
fragmentShader = addCodeAfterSnippet( fragmentShader, '#include <color_fragment>',
36+
`#ifdef NODE_COLOR
37+
38+
diffuseColor *= NODE_COLOR;
39+
40+
#endif` );
41+
42+
parameters.fragmentShader = fragmentShader;
43+
44+
};
26.4 KB
Loading
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgl - materials - standard (nodes)</title>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7+
<link type="text/css" rel="stylesheet" href="main.css">
8+
</head>
9+
10+
<body>
11+
<div id="info">
12+
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl physically based material<br/>
13+
<a href="http://www.polycount.com/forum/showthread.php?t=130641" target="_blank" rel="noopener">Cerberus(FFVII Gun) model</a> by Andrew Maximov.
14+
</div>
15+
16+
<script type="module">
17+
18+
import * as THREE from '../build/three.module.js';
19+
20+
import Stats from './jsm/libs/stats.module.js';
21+
22+
import { GUI } from './jsm/libs/dat.gui.module.js';
23+
import { TrackballControls } from './jsm/controls/TrackballControls.js';
24+
import { OBJLoader } from './jsm/loaders/OBJLoader.js';
25+
import { RGBELoader } from './jsm/loaders/RGBELoader.js';
26+
27+
import './jsm/renderers/webgl/nodes/WebGLNodes.js';
28+
29+
import TextureNode from './jsm/renderers/nodes/inputs/TextureNode.js';
30+
import Vector3Node from './jsm/renderers/nodes/inputs/Vector3Node.js';
31+
import OperatorNode from './jsm/renderers/nodes/math/OperatorNode.js';
32+
33+
const statsEnabled = true;
34+
35+
let container, stats;
36+
37+
let camera, scene, renderer, controls;
38+
39+
init();
40+
animate();
41+
42+
function init() {
43+
44+
container = document.createElement( 'div' );
45+
document.body.appendChild( container );
46+
47+
renderer = new THREE.WebGLRenderer( { antialias: true } );
48+
renderer.setPixelRatio( window.devicePixelRatio );
49+
renderer.setSize( window.innerWidth, window.innerHeight );
50+
container.appendChild( renderer.domElement );
51+
52+
renderer.outputEncoding = THREE.sRGBEncoding;
53+
renderer.toneMapping = THREE.ReinhardToneMapping;
54+
renderer.toneMappingExposure = 3;
55+
56+
//
57+
58+
scene = new THREE.Scene();
59+
60+
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 1000 );
61+
camera.position.z = 2;
62+
63+
controls = new TrackballControls( camera, renderer.domElement );
64+
65+
//
66+
67+
scene.add( new THREE.HemisphereLight( 0x443333, 0x222233, 4 ) );
68+
69+
//
70+
71+
const material = new THREE.MeshStandardMaterial();
72+
73+
new OBJLoader()
74+
.setPath( 'models/obj/cerberus/' )
75+
.load( 'Cerberus.obj', function ( group ) {
76+
77+
const loader = new THREE.TextureLoader()
78+
.setPath( 'models/obj/cerberus/' );
79+
80+
material.roughness = 1; // attenuates roughnessMap
81+
material.metalness = 1; // attenuates metalnessMap
82+
83+
const diffuseMap = loader.load( 'Cerberus_A.jpg' );
84+
diffuseMap.wrapS = THREE.RepeatWrapping;
85+
diffuseMap.encoding = THREE.sRGBEncoding;
86+
87+
//material.map = diffuseMap;
88+
material.colorNode = new OperatorNode( '*', new TextureNode( diffuseMap ), new Vector3Node( material.color ) );
89+
90+
// roughness is in G channel, metalness is in B channel
91+
material.metalnessMap = material.roughnessMap = loader.load( 'Cerberus_RM.jpg' );
92+
material.normalMap = loader.load( 'Cerberus_N.jpg' );
93+
94+
material.roughnessMap.wrapS = THREE.RepeatWrapping;
95+
material.metalnessMap.wrapS = THREE.RepeatWrapping;
96+
material.normalMap.wrapS = THREE.RepeatWrapping;
97+
98+
group.traverse( function ( child ) {
99+
100+
if ( child.isMesh ) {
101+
102+
child.material = material;
103+
104+
}
105+
106+
} );
107+
108+
group.position.x = - 0.45;
109+
group.rotation.y = - Math.PI / 2;
110+
scene.add( group );
111+
112+
} );
113+
114+
const environments = {
115+
116+
'Venice Sunset': { filename: 'venice_sunset_1k.hdr' },
117+
'Overpass': { filename: 'pedestrian_overpass_1k.hdr' }
118+
119+
};
120+
121+
function loadEnvironment(name) {
122+
123+
if ( environments[ name ].texture !== undefined ) {
124+
125+
scene.background = environments[ name ].texture;
126+
scene.environment = environments[ name ].texture;
127+
return;
128+
129+
}
130+
131+
const filename = environments[ name ].filename;
132+
new RGBELoader()
133+
.setDataType( THREE.UnsignedByteType )
134+
.setPath( 'textures/equirectangular/' )
135+
.load( filename, function ( hdrEquirect ) {
136+
137+
const hdrCubeRenderTarget = pmremGenerator.fromEquirectangular( hdrEquirect );
138+
hdrEquirect.dispose();
139+
140+
scene.background = hdrCubeRenderTarget.texture;
141+
scene.environment = hdrCubeRenderTarget.texture;
142+
environments[ name ].texture = hdrCubeRenderTarget.texture;
143+
144+
} );
145+
146+
}
147+
148+
const params = {
149+
150+
environment: Object.keys( environments )[ 0 ]
151+
152+
};
153+
loadEnvironment( params.environment );
154+
155+
const gui = new GUI();
156+
gui.add( params, 'environment', Object.keys( environments ) ).onChange( function( value ) {
157+
158+
loadEnvironment(value);
159+
160+
} );
161+
gui.open();
162+
163+
const pmremGenerator = new THREE.PMREMGenerator( renderer );
164+
pmremGenerator.compileEquirectangularShader();
165+
166+
//
167+
168+
if ( statsEnabled ) {
169+
170+
stats = new Stats();
171+
container.appendChild( stats.dom );
172+
173+
}
174+
175+
window.addEventListener( 'resize', onWindowResize );
176+
177+
}
178+
179+
//
180+
181+
function onWindowResize() {
182+
183+
renderer.setSize( window.innerWidth, window.innerHeight );
184+
185+
camera.aspect = window.innerWidth / window.innerHeight;
186+
camera.updateProjectionMatrix();
187+
188+
}
189+
190+
//
191+
192+
function animate() {
193+
194+
requestAnimationFrame( animate );
195+
196+
controls.update();
197+
renderer.render( scene, camera );
198+
199+
if ( statsEnabled ) stats.update();
200+
201+
}
202+
203+
</script>
204+
205+
</body>
206+
</html>

src/materials/Material.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
7878

7979
isMaterial: true,
8080

81+
onBuild: function ( /* shaderobject, renderer */ ) {},
82+
8183
onBeforeCompile: function ( /* shaderobject, renderer */ ) {},
8284

8385
customProgramCacheKey: function () {

0 commit comments

Comments
 (0)