Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -306,6 +306,7 @@
"webgpu_camera",
"webgpu_camera_array",
"webgpu_camera_logarithmicdepthbuffer",
"webgpu_centroid_sampling",
"webgpu_clearcoat",
"webgpu_clipping",
"webgpu_compute_audio",
Expand Down
Binary file added examples/screenshots/webgpu_centroid_sampling.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
290 changes: 290 additions & 0 deletions examples/webgpu_centroid_sampling.html
Original file line number Diff line number Diff line change
@@ -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>
46 changes: 46 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -1609,6 +1609,32 @@ export const TimestampQuery = {
RENDER: 'render'
};

/**
* Represents mouse buttons and interaction types in context of controls.
*
* @type {ConstantsInterpolationSamplingType}
* @constant
*/
export const InterpolationSamplingType = {
PERSPECTIVE: 'perspective',
LINEAR: 'linear',
FLAT: 'flat'
};

/**
* Represents the different interpolation sampling modes.
*
* @type {ConstantsInterpolationSamplingMode}
* @constant
*/
export const InterpolationSamplingMode = {
NORMAL: 'normal',
CENTROID: 'centroid',
SAMPLE: 'sample',
FLAT_FIRST: 'flat first',
FLAT_EITHER: 'flat either'
};

/**
* This type represents mouse buttons and interaction types in context of controls.
*
Expand Down Expand Up @@ -1638,3 +1664,23 @@ export const TimestampQuery = {
* @property {string} COMPUTE - A `compute` timestamp query.
* @property {string} RENDER - A `render` timestamp query.
**/

/**
* Represents the different interpolation sampling types.
*
* @typedef {Object} ConstantsInterpolationSamplingType
* @property {string} PERSPECTIVE - Perspective-correct interpolation.
* @property {string} LINEAR - Linear interpolation.
* @property {string} FLAT - Flat interpolation.
*/

/**
* Represents the different interpolation sampling modes.
*
* @typedef {Object} ConstantsInterpolationSamplingMode
* @property {string} NORMAL - Normal sampling mode.
* @property {string} CENTROID - Centroid sampling mode.
* @property {string} SAMPLE - Sample-specific sampling mode.
* @property {string} FLAT_FIRST - Flat interpolation using the first vertex.
* @property {string} FLAT_EITHER - Flat interpolation using either vertex.
*/
6 changes: 4 additions & 2 deletions src/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1848,9 +1848,11 @@ class NodeBuilder {
* @param {(VaryingNode|PropertyNode)} node - The varying node.
* @param {?string} name - The varying's name.
* @param {string} [type=node.getNodeType( this )] - The varying's type.
* @param {?string} interpolationType - The interpolation type of the varying.
* @param {?string} interpolationSampling - The interpolation sampling type of the varying.
* @return {NodeVar} The node varying.
*/
getVaryingFromNode( node, name = null, type = node.getNodeType( this ) ) {
getVaryingFromNode( node, name = null, type = node.getNodeType( this ), interpolationType = null, interpolationSampling = null ) {

const nodeData = this.getDataFromNode( node, 'any' );

Expand All @@ -1863,7 +1865,7 @@ class NodeBuilder {

if ( name === null ) name = 'nodeVarying' + index;

nodeVarying = new NodeVarying( name, type );
nodeVarying = new NodeVarying( name, type, interpolationType, interpolationSampling );

varyings.push( nodeVarying );

Expand Down
Loading