|
|
@@ -0,0 +1,290 @@
|
|
|
+<html lang="en">
|
|
|
+ <head>
|
|
|
+ <title>three.js webgpu - centroid sampling</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>
|
|
|
+ <style>
|
|
|
+ body {
|
|
|
+ margin: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ #demo {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .renderer-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ #antialising-disabled {
|
|
|
+ border-right: 1px solid black;
|
|
|
+ }
|
|
|
+
|
|
|
+ canvas {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+ <body>
|
|
|
+ <div id="demo">
|
|
|
+ <div id="antialising-disabled" class="renderer-wrapper">
|
|
|
+ <div>antialising disabled</div>
|
|
|
+ </div>
|
|
|
+ <div id="antialising-enabled" class="renderer-wrapper">
|
|
|
+ <div>antialising enabled</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script type="importmap">
|
|
|
+ {
|
|
|
+ "imports": {
|
|
|
+ "three": "../build/three.webgpu.js",
|
|
|
+ "three/webgpu": "../build/three.webgpu.js",
|
|
|
+ "three/tsl": "../build/three.tsl.js",
|
|
|
+ "three/addons/": "./jsm/"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ </script>
|
|
|
+
|
|
|
+ <script type="module">
|
|
|
+
|
|
|
+ import * as THREE from 'three';
|
|
|
+ import { varying, uv, texture, Fn } from 'three/tsl';
|
|
|
+
|
|
|
+ import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
|
|
+
|
|
|
+ let rendererAntialiasingEnabled;
|
|
|
+ let rendererAntialiasingDisabled;
|
|
|
+ let camera;
|
|
|
+ let scene;
|
|
|
+ let gui;
|
|
|
+
|
|
|
+ const effectController = {
|
|
|
+ sampling: 'normal'
|
|
|
+ };
|
|
|
+
|
|
|
+ const atlasCanvas = document.createElement( 'canvas' );
|
|
|
+ atlasCanvas.width = 16;
|
|
|
+ atlasCanvas.height = 16;
|
|
|
+
|
|
|
+ const ctx = atlasCanvas.getContext( '2d' );
|
|
|
+ ctx.fillStyle = 'red';
|
|
|
+ ctx.fillRect( 0, 0, 8, 8 );
|
|
|
+
|
|
|
+ const redUVs = [ 0, 1, 0.5, 1, 0.5, 0.5, 0, 0.5 ];
|
|
|
+ ctx.fillStyle = 'green';
|
|
|
+ ctx.fillRect( 8, 0, 8, 8 );
|
|
|
+
|
|
|
+ const greenUVs = [ 1, 1, 0.5, 1, 0.5, 0.5, 1, 0.5 ];
|
|
|
+
|
|
|
+ ctx.fillStyle = 'blue';
|
|
|
+ ctx.fillRect( 0, 8, 8, 8 );
|
|
|
+
|
|
|
+ const blueUVs = [ 0, 0, 0.5, 0, 0.5, 0.5, 0, 0.5 ];
|
|
|
+
|
|
|
+ ctx.fillStyle = 'yellow';
|
|
|
+ ctx.fillRect( 8, 8, 8, 8 );
|
|
|
+
|
|
|
+ const yellowUVs = [ 1, 0, 0.5, 0, 0.5, 0.5, 1, 0.5 ];
|
|
|
+
|
|
|
+ const faces = [ redUVs, greenUVs, blueUVs, yellowUVs ];
|
|
|
+
|
|
|
+ const canvasTexture = new THREE.CanvasTexture( atlasCanvas );
|
|
|
+ canvasTexture.colorSpace = THREE.SRGBColorSpace;
|
|
|
+ canvasTexture.mapping = THREE.UVMapping;
|
|
|
+ canvasTexture.wrapS = THREE.RepeatWrapping;
|
|
|
+ canvasTexture.wrapT = THREE.RepeatWrapping;
|
|
|
+ canvasTexture.magFilter = THREE.NearestFilter;
|
|
|
+ canvasTexture.minFilter = THREE.NearestFilter;
|
|
|
+ canvasTexture.format = THREE.RGBAFormat;
|
|
|
+ canvasTexture.type = THREE.UnsignedByteType;
|
|
|
+
|
|
|
+ const forceWebGL = false;
|
|
|
+
|
|
|
+ init();
|
|
|
+
|
|
|
+ function init() {
|
|
|
+
|
|
|
+ camera = new THREE.PerspectiveCamera();
|
|
|
+ camera.fov = 60;
|
|
|
+ camera.near = 1;
|
|
|
+ camera.far = 2100;
|
|
|
+ camera.position.z = 50;
|
|
|
+
|
|
|
+ scene = new THREE.Scene();
|
|
|
+
|
|
|
+ const makeFaceGeometry = ( uvs ) => {
|
|
|
+
|
|
|
+ const geometry = new THREE.BufferGeometry();
|
|
|
+ const positions = [ - 1, - 1, 0, 1, - 1, 0, 1, 1, 0, - 1, 1, 0 ];
|
|
|
+ geometry.setAttribute(
|
|
|
+ 'position',
|
|
|
+ new THREE.BufferAttribute( new Float32Array( positions ), 3 )
|
|
|
+ );
|
|
|
+
|
|
|
+ const indices = [ 0, 1, 2, 2, 3, 0 ];
|
|
|
+ geometry.setIndex( indices );
|
|
|
+
|
|
|
+ geometry.setAttribute(
|
|
|
+ 'uv',
|
|
|
+ new THREE.BufferAttribute( new Float32Array( uvs ), 2 )
|
|
|
+ );
|
|
|
+
|
|
|
+ return geometry;
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const material = new THREE.MeshBasicNodeMaterial();
|
|
|
+ const testUV = varying( uv(), 'testUV' );
|
|
|
+
|
|
|
+ const createShader = ( type, sampling ) => {
|
|
|
+
|
|
|
+ return Fn( () => {
|
|
|
+
|
|
|
+ testUV.setInterpolation( type, sampling );
|
|
|
+
|
|
|
+ return texture( canvasTexture, testUV ).rgb;
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const withFlatFirstShader = createShader( 'flat', 'first' );
|
|
|
+ const withFlatEitherShader = createShader( 'flat', 'either' );
|
|
|
+
|
|
|
+ const withSampleShader = Fn( () => {
|
|
|
+
|
|
|
+ testUV.setInterpolation( THREE.InterpolationSamplingType.PERSPECTIVE, THREE.InterpolationSamplingMode.SAMPLE );
|
|
|
+
|
|
|
+ return texture( canvasTexture, testUV ).rgb;
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ const withInterpolationShader = Fn( () => {
|
|
|
+
|
|
|
+ testUV.setInterpolation( THREE.InterpolationSamplingType.PERSPECTIVE, THREE.InterpolationSamplingMode.CENTROID );
|
|
|
+
|
|
|
+ return texture( canvasTexture, testUV ).rgb;
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ const withoutInterpolationShader = Fn( () => {
|
|
|
+
|
|
|
+ return texture( canvasTexture, uv() ).rgb;
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ material.colorNode = withoutInterpolationShader();
|
|
|
+
|
|
|
+ const faceMeshes = [];
|
|
|
+
|
|
|
+ for ( let x = - 5; x < 5; x ++ ) {
|
|
|
+
|
|
|
+ for ( let y = - 5; y < 5; y ++ ) {
|
|
|
+
|
|
|
+ const face = faces[ Math.floor( Math.random() * faces.length ) ];
|
|
|
+ const geometry = makeFaceGeometry( face );
|
|
|
+ const mesh = new THREE.Mesh( geometry, material );
|
|
|
+ mesh.position.set( x * 2, y * 2, 0 );
|
|
|
+ faceMeshes.push( mesh );
|
|
|
+ scene.add( mesh );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create Standard Renderer
|
|
|
+ rendererAntialiasingDisabled = new THREE.WebGPURenderer( {
|
|
|
+ antialias: false,
|
|
|
+ forceWebGL: forceWebGL
|
|
|
+ } );
|
|
|
+
|
|
|
+ rendererAntialiasingDisabled.setPixelRatio( window.devicePixelRatio );
|
|
|
+ rendererAntialiasingDisabled.setSize( window.innerWidth / 2, window.innerHeight );
|
|
|
+ rendererAntialiasingDisabled.setAnimationLoop( animateStandard );
|
|
|
+
|
|
|
+ // Create antialiased renderer
|
|
|
+ rendererAntialiasingEnabled = new THREE.WebGPURenderer( {
|
|
|
+ antialias: true,
|
|
|
+ forceWebGL: forceWebGL
|
|
|
+ } );
|
|
|
+
|
|
|
+ document.body.querySelector( '#antialising-enabled' ).appendChild( rendererAntialiasingEnabled.domElement );
|
|
|
+ rendererAntialiasingEnabled.setPixelRatio( window.devicePixelRatio );
|
|
|
+ rendererAntialiasingEnabled.setSize( window.innerWidth / 2, window.innerHeight );
|
|
|
+ rendererAntialiasingEnabled.setAnimationLoop( animateAliased );
|
|
|
+
|
|
|
+ document.body.querySelector( '#antialising-disabled' ).appendChild( rendererAntialiasingDisabled.domElement );
|
|
|
+ document.body.querySelector( '#antialising-disabled' ).appendChild( rendererAntialiasingDisabled.domElement );
|
|
|
+
|
|
|
+ onWindowResize();
|
|
|
+
|
|
|
+ window.addEventListener( 'resize', onWindowResize );
|
|
|
+
|
|
|
+ gui = new GUI();
|
|
|
+ gui.add( effectController, 'sampling', [
|
|
|
+ THREE.InterpolationSamplingMode.NORMAL,
|
|
|
+ THREE.InterpolationSamplingMode.CENTROID,
|
|
|
+ THREE.InterpolationSamplingMode.SAMPLE,
|
|
|
+ THREE.InterpolationSamplingMode.FLAT_FIRST,
|
|
|
+ THREE.InterpolationSamplingMode.FLAT_EITHER
|
|
|
+ ] ).onChange( () => {
|
|
|
+
|
|
|
+ const interpolationShaderLib = {
|
|
|
+ [ THREE.InterpolationSamplingMode.NORMAL ]: withoutInterpolationShader,
|
|
|
+ [ THREE.InterpolationSamplingMode.CENTROID ]: withInterpolationShader,
|
|
|
+ [ THREE.InterpolationSamplingMode.SAMPLE ]: withSampleShader,
|
|
|
+ [ THREE.InterpolationSamplingMode.FLAT_FIRST ]: withFlatFirstShader,
|
|
|
+ [ THREE.InterpolationSamplingMode.FLAT_EITHER ]: withFlatEitherShader
|
|
|
+ };
|
|
|
+
|
|
|
+ const shader = interpolationShaderLib[ effectController.sampling ];
|
|
|
+
|
|
|
+ for ( let i = 0; i < faceMeshes.length; i ++ ) {
|
|
|
+
|
|
|
+ faceMeshes[ i ].material.colorNode = shader();
|
|
|
+ faceMeshes[ i ].material.needsUpdate = true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function onWindowResize() {
|
|
|
+
|
|
|
+ const halfWidth = window.innerWidth / 2;
|
|
|
+ rendererAntialiasingDisabled.setSize( halfWidth, window.innerHeight );
|
|
|
+ rendererAntialiasingEnabled.setSize( halfWidth, window.innerHeight );
|
|
|
+ const aspect = ( halfWidth ) / window.innerHeight;
|
|
|
+
|
|
|
+ camera.aspect = aspect;
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function animateStandard() {
|
|
|
+
|
|
|
+ rendererAntialiasingDisabled.render( scene, camera );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function animateAliased() {
|
|
|
+
|
|
|
+ rendererAntialiasingEnabled.render( scene, camera );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ </script>
|
|
|
+ </body>
|
|
|
+</html>
|