-
-
Notifications
You must be signed in to change notification settings - Fork 36k
Examples: Add webgpu_reflection_blurred
#31116
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
6e0e3df
fix clear texture and facing away if `bounce` is `false`
sunag ee5c3ae
add `premult`, `unpremult`
sunag d2a56d5
cleanup
sunag 34bd1ac
Merge branch 'dev' into dev-premult
sunag ad31882
add `hashBlur` options { mask, premultipliedAlpha }
sunag dba50fa
add docs and cleanup
sunag 589f8be
add `webgpu_reflection_blurred` example
sunag 1091534
Merge branch 'dev' into dev-reflection-blurred
sunag cf523a9
Merge branch 'dev' into dev-reflection-blurred
sunag eedf858
update webgpu_reflection_blurred
sunag 63f184d
update webgpu_reflection_blurred
sunag 14b186c
Update webgpu_reflection_blurred.html
sunag File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<title>three.js webgpu - blurred reflection</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 - blurred reflection | ||
</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 { Fn, vec4, fract, abs, uniform, pow, color, max, length, rangeFogFactor, sub, reflector, normalWorld, hue, time, mix, positionWorld } from 'three/tsl'; | ||
|
||
import { hashBlur } from 'three/addons/tsl/display/hashBlur.js'; | ||
|
||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; | ||
|
||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | ||
|
||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; | ||
|
||
import Stats from 'three/addons/libs/stats.module.js'; | ||
|
||
let camera, scene, renderer; | ||
let model, mixer, clock; | ||
let controls; | ||
let stats; | ||
let gui; | ||
|
||
init(); | ||
|
||
async function init() { | ||
|
||
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.25, 30 ); | ||
camera.position.set( - 2.5, 2, 2.5 ); | ||
camera.lookAt( 0, .4, 0 ); | ||
|
||
scene = new THREE.Scene(); | ||
scene.backgroundNode = hue( normalWorld.y.mix( 0, color( 0x0066ff ) ).mul( .1 ), time ); | ||
|
||
const waterAmbientLight = new THREE.HemisphereLight( 0xffffff, 0x0066ff, 10 ); | ||
scene.add( waterAmbientLight ); | ||
|
||
clock = new THREE.Clock(); | ||
|
||
// animated model | ||
|
||
const gltfLoader = new GLTFLoader(); | ||
gltfLoader.load( 'models/gltf/Michelle.glb', function ( gltf ) { | ||
|
||
model = gltf.scene; | ||
model.children[ 0 ].children[ 0 ].castShadow = true; | ||
|
||
mixer = new THREE.AnimationMixer( model ); | ||
|
||
const action = mixer.clipAction( gltf.animations[ 0 ] ); | ||
action.play(); | ||
|
||
scene.add( model ); | ||
|
||
} ); | ||
|
||
// textures | ||
|
||
const textureLoader = new THREE.TextureLoader(); | ||
|
||
const uvMap = textureLoader.load( 'textures/uv_grid_directx.jpg' ); | ||
uvMap.colorSpace = THREE.SRGBColorSpace; | ||
|
||
// uv map for debugging | ||
|
||
const uvMaterial = new THREE.MeshStandardNodeMaterial( { | ||
map: uvMap, | ||
side: THREE.DoubleSide | ||
} ); | ||
|
||
const uvMesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), uvMaterial ); | ||
uvMesh.position.set( 0, 1, - 3 ); | ||
scene.add( uvMesh ); | ||
|
||
// circle effect | ||
|
||
const drawCircle = Fn( ( [ pos, radius, width, power, color, timer = time.mul( .5 ) ] ) => { | ||
|
||
// https://www.shadertoy.com/view/3tdSRn | ||
|
||
const dist1 = length( pos ); | ||
dist1.assign( fract( dist1.mul( 5.0 ).sub( fract( timer ) ) ) ); | ||
const dist2 = dist1.sub( radius ); | ||
const intensity = pow( radius.div( abs( dist2 ) ), width ); | ||
const col = color.rgb.mul( intensity ).mul( power ).mul( max( sub( 0.8, abs( dist2 ) ), 0.0 ) ); | ||
|
||
return col; | ||
|
||
} ); | ||
|
||
const circleFadeY = positionWorld.y.mul( .7 ).oneMinus().max( 0 ); | ||
const animatedColor = mix( color( 0x74ccf4 ), color( 0x7f00c5 ), positionWorld.xz.distance( 0 ).div( 10 ).clamp() ); | ||
const animatedCircle = hue( drawCircle( positionWorld.xz.mul( .1 ), 0.5, 0.8, .01, animatedColor ).mul( circleFadeY ), time ); | ||
|
||
const floorLight = new THREE.PointLight( 0xffffff ); | ||
floorLight.colorNode = animatedCircle.mul( 50 ); | ||
scene.add( floorLight ); | ||
|
||
// reflection | ||
|
||
const roughness = uniform( .9 ); | ||
const radius = uniform( 0.2 ); | ||
|
||
const reflection = reflector( { resolution: 1, depth: true, bounces: false } ); // 0.5 is half of the rendering view | ||
const reflectionDepth = reflection.getDepthNode(); | ||
reflection.target.rotateX( - Math.PI / 2 ); | ||
scene.add( reflection.target ); | ||
|
||
const floorMaterial = new THREE.MeshStandardNodeMaterial(); | ||
floorMaterial.transparent = true; | ||
floorMaterial.colorNode = Fn( () => { | ||
|
||
// ranges adjustment | ||
|
||
const radiusRange = mix( 0.01, 0.1, radius ); // range [ 0.01, 0.1 ] | ||
const roughnessRange = mix( 0.3, 0.03, roughness ); // range [ 0.03, 0.3 ] | ||
|
||
// blur the reflection | ||
|
||
const reflectionBlurred = hashBlur( reflection, radiusRange, { | ||
mask: reflectionDepth, | ||
premultipliedAlpha: true | ||
} ); | ||
|
||
// reflection composite | ||
|
||
const reflectionMask = reflectionBlurred.a.mul( reflectionDepth ).remapClamp( 0, roughnessRange ); | ||
const reflectionItensity = .1; | ||
const reflectionMixFactor = reflectionMask.mul( roughness.mul( 2 ).min( 1 ) ); | ||
const reflectionFinal = mix( reflection.rgb, reflectionBlurred.rgb, reflectionMixFactor ).mul( reflectionItensity ); | ||
|
||
// mix reflection with animated circle | ||
|
||
const output = animatedCircle.add( reflectionFinal ); | ||
|
||
// falloff opacity by distance like an opacity-fog | ||
|
||
const opacity = rangeFogFactor( 7, 25 ).oneMinus(); | ||
|
||
// final output | ||
|
||
return vec4( output, opacity ); | ||
|
||
} )(); | ||
|
||
const floor = new THREE.Mesh( new THREE.BoxGeometry( 50, .001, 50 ), floorMaterial ); | ||
floor.position.set( 0, 0, 0 ); | ||
scene.add( floor ); | ||
|
||
// renderer | ||
|
||
renderer = new THREE.WebGPURenderer( { antialias: true } ); | ||
renderer.setPixelRatio( window.devicePixelRatio ); | ||
renderer.setSize( window.innerWidth, window.innerHeight ); | ||
renderer.setAnimationLoop( animate ); | ||
renderer.toneMapping = THREE.NeutralToneMapping; | ||
renderer.toneMappingExposure = 1.3; | ||
document.body.appendChild( renderer.domElement ); | ||
|
||
gui = new GUI(); | ||
gui.add( roughness, 'value', 0, 1 ).name( 'roughness' ); | ||
gui.add( radius, 'value', 0, 1 ).name( 'radius' ); | ||
|
||
stats = new Stats(); | ||
document.body.appendChild( stats.dom ); | ||
|
||
controls = new OrbitControls( camera, renderer.domElement ); | ||
controls.minDistance = 1; | ||
controls.maxDistance = 10; | ||
controls.maxPolarAngle = Math.PI / 2; | ||
//controls.autoRotate = true; | ||
controls.autoRotateSpeed = - .1; | ||
controls.target.set( 0, .5, 0 ); | ||
controls.update(); | ||
|
||
// events | ||
|
||
window.addEventListener( 'resize', onWindowResize ); | ||
|
||
} | ||
|
||
function onWindowResize() { | ||
|
||
camera.aspect = window.innerWidth / window.innerHeight; | ||
camera.updateProjectionMatrix(); | ||
|
||
renderer.setSize( window.innerWidth, window.innerHeight ); | ||
|
||
} | ||
|
||
function animate() { | ||
|
||
stats.update(); | ||
|
||
controls.update(); | ||
|
||
const delta = clock.getDelta(); | ||
|
||
if ( model ) { | ||
|
||
mixer.update( delta ); | ||
|
||
} | ||
|
||
renderer.render( scene, camera ); | ||
|
||
} | ||
|
||
</script> | ||
</body> | ||
</html> |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example looks awesome!
The only thing I would suggest to change is the default resolution of the reflector. On my macMini M2 Pro, the current example runs around 30 FPS. Using the half resolution for the reflection, the frame rate is back to 60 FPS. Since the reflections are blurred, you barely see a difference in quality anyway (mostly at the plane with the uv texture).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we set resolution to
0.5
on a non-retina screen the reflection becomes pixelated:Instead of
0.5
maybe something like1 / devicePixelRatio
would work better?