<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@v0.170.0/build/three.webgpu.js",
"three/tsl": "https://cdn.jsdelivr.net/npm/three@v0.170.0/build/three.webgpu.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@v0.170.0/examples/jsm/"
}
}
</script>
<div id="courses"><a href="https://niklever.com/courses" target="_blank">niklever.com/courses</a></div>
<style>
#source {
position: absolute;
top: 0;
left: 0;
width: 50%;
height: 100%;
}
#result {
position: absolute;
top: 0;
right: 0;
width: 50%;
height: 100%;
}
#renderer {
position: absolute;
bottom: 15px;
right: calc( 50% + 15px );
width: 200px;
height: 200px;
z-index: 100;
pointer-events: none;
}
</style>
<div id="source"></div>
<div id="result"></div>
<div id="renderer"></div>
body{
padding: 0;
margin: 0;
}
#courses{
font: bold 30px "Arial";
position: fixed;
left: 20px;
top: 20px;
color: #ffffff;
text-decoration: none;
}
a:link {
color: white;
text-decoration: none;
}
a:hover{
color: #dddd33;
text-decoration: underline;
}
a:visited {
color: white;
text-decoration: none;
}
import * as THREE from 'three';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
init();
function init() {
// add the depedencies
const width = 200;
const height = 200;
const camera = new THREE.PerspectiveCamera( 70, width / height, 0.1, 10 );
camera.position.z = .72;
camera.lookAt( 0, 0, 0 );
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0x222222 );
const rendererDOM = document.getElementById( 'renderer' );
const renderer = new THREE.WebGPURenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( 200, 200 );
rendererDOM.appendChild( renderer.domElement );
const material = new THREE.NodeMaterial();
material.fragmentNode = THREE.vec4( 0, 0, 0, 1 );
const mesh = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
scene.add( mesh );
//
let compiling = false;
renderer.setAnimationLoop( () => {
if ( compiling ) return;
renderer.render( scene, camera );
} );
// editor
window.require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.48.0/min/vs' } } );
require( [ 'vs/editor/editor.main' ], () => {
const options = {
shader: 'fragment',
outputColorSpace: THREE.SRGBColorSpace,
output: 'WGSL',
preview: true
};
let timeout = null;
let rawShader = null;
const editorDOM = document.getElementById( 'source' );
const resultDOM = document.getElementById( 'result' );
const tslCode = `// positionLocal or texture
const { positionLocal, texture, uv, vec4 } = THREE;
output = vec4( positionLocal, 1 );
//const tex = new THREE.TextureLoader().load( 'https://niksfiles.s3.eu-west-2.amazonaws.com/uv_grid.jpg' );
//output = vec4( texture( tex, uv() ).rgb, 1 );
`;
const editor = window.monaco.editor.create( editorDOM, {
value: tslCode,
language: 'javascript',
theme: 'vs-dark',
automaticLayout: true,
minimap: { enabled: false }
} );
const result = window.monaco.editor.create( resultDOM, {
value: '',
language: 'wgsl',
theme: 'vs-dark',
automaticLayout: true,
readOnly: true,
minimap: { enabled: false }
} );
const showCode = () => {
result.setValue( rawShader[ options.shader + 'Shader' ] );
result.revealLine( 1 );
};
const webGLRenderer = new THREE.WebGPURenderer( { forceWebGL: true } );
const build = async () => {
try {
const tslCode = `let output = null;\n${ editor.getValue() }\nreturn { output };`;
const nodes = new Function( 'THREE', tslCode )( THREE );
mesh.material.fragmentNode = nodes.output;
mesh.material.needsUpdate = true;
compiling = true;
if ( options.output === 'WGSL' ) {
rawShader = await renderer.debug.getShaderAsync( scene, camera, mesh );
} else if ( options.output === 'GLSL ES 3.0' ) {
rawShader = await webGLRenderer.debug.getShaderAsync( scene, camera, mesh );
}
compiling = false;
showCode();
// extra debug info
/*const style = 'background-color: #333; color: white; font-style: italic; border: 2px solid #777; font-size: 22px;';
console.log( '%c [ WGSL ] Vertex Shader ', style );
console.log( rawShader.vertexShader );
console.log( '%c [ WGSL ] Fragment Shader ', style );
console.log( rawShader.fragmentShader );/**/
} catch ( e ) {
result.setValue( 'Error: ' + e.message );
}
};
build();
editor.getModel().onDidChangeContent( () => {
if ( timeout ) clearTimeout( timeout );
timeout = setTimeout( build, 1000 );
} );
// gui
const gui = new GUI();
gui.add( options, 'output', [ 'WGSL', 'GLSL ES 3.0' ] ).onChange( build );
gui.add( options, 'shader', [ 'vertex', 'fragment' ] ).onChange( showCode );
gui.add( options, 'outputColorSpace', [ THREE.LinearSRGBColorSpace, THREE.SRGBColorSpace ] ).onChange( ( value ) => {
renderer.outputColorSpace = value;
build();
} );
gui.add( options, 'preview' ).onChange( ( value ) => {
rendererDOM.style.display = value ? '' : 'none';
} );
} );
}
This Pen doesn't use any external CSS resources.