Skip to content

Commit b5ff213

Browse files
authored
Merge pull request #21244 from DavidPeicho/feature/copy3d
Add `copyTextureToTexture3D()` to WebGLRenderer
2 parents d04f8c2 + c2d3e24 commit b5ff213

File tree

5 files changed

+441
-0
lines changed

5 files changed

+441
-0
lines changed

docs/api/en/renderers/WebGLRenderer.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ <h3>[method:null copyFramebufferToTexture]( [param:Vector2 position], [param:Tex
310310
<h3>[method:null copyTextureToTexture]( [param:Vector2 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )</h3>
311311
<p>Copies all pixels of a texture to an existing texture starting from the given position. Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D].</p>
312312

313+
<h3>[method:null copyTextureToTexture3D]( [param:Box3 sourceBox], [param:Vector2 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )</h3>
314+
<p>Copies the pixels of a texture in the bounds '[page:Box3 sourceBox]' in the desination texture starting from the given position. Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D].</p>
315+
313316
<h3>[method:null dispose]( )</h3>
314317
<p>Dispose of the current rendering context.</p>
315318

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@
307307
"webgl2_materials_texture3d",
308308
"webgl2_multisampled_renderbuffers",
309309
"webgl2_rendertarget_texture2darray",
310+
"webgl2_materials_texture3d_partialupdate.html",
310311
"webgl2_volume_cloud",
311312
"webgl2_volume_instancing",
312313
"webgl2_volume_perlin"
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgl2 - volume - cloud</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> webgl2 - volume - cloud
13+
</div>
14+
15+
<script type="module">
16+
import * as THREE from '../build/three.module.js';
17+
import { OrbitControls } from './jsm/controls/OrbitControls.js';
18+
import { ImprovedNoise } from './jsm/math/ImprovedNoise.js';
19+
20+
import { GUI } from './jsm/libs/dat.gui.module.js';
21+
import { WEBGL } from './jsm/WebGL.js';
22+
23+
if ( WEBGL.isWebGL2Available() === false ) {
24+
25+
document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
26+
27+
}
28+
29+
const INITIAL_CLOUD_SIZE = 128;
30+
31+
let renderer, scene, camera;
32+
let mesh;
33+
let prevTime = performance.now();
34+
let cloudTexture = null;
35+
36+
init();
37+
animate();
38+
39+
function generateCloudTexture( size, scaleFactor = 1.0 ) {
40+
41+
const data = new Uint8Array( size * size * size );
42+
const scale = scaleFactor * 10.0 / size;
43+
44+
let i = 0;
45+
const perlin = new ImprovedNoise();
46+
const vector = new THREE.Vector3();
47+
48+
for ( let z = 0; z < size; z ++ ) {
49+
50+
for ( let y = 0; y < size; y ++ ) {
51+
52+
for ( let x = 0; x < size; x ++ ) {
53+
54+
const dist = vector.set( x, y, z ).subScalar( size / 2 ).divideScalar( size ).length();
55+
const fadingFactor = ( 1.0 - dist ) * ( 1.0 - dist );
56+
data[ i ] = ( 128 + 128 * perlin.noise( x * scale / 1.5, y * scale, z * scale / 1.5 ) ) * fadingFactor;
57+
58+
i ++;
59+
60+
}
61+
62+
}
63+
64+
}
65+
66+
return new THREE.DataTexture3D( data, size, size, size );
67+
68+
}
69+
70+
function init() {
71+
72+
renderer = new THREE.WebGLRenderer();
73+
renderer.setPixelRatio( window.devicePixelRatio );
74+
renderer.setSize( window.innerWidth, window.innerHeight );
75+
document.body.appendChild( renderer.domElement );
76+
77+
scene = new THREE.Scene();
78+
79+
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
80+
camera.position.set( 0, 0, 1.5 );
81+
82+
new OrbitControls( camera, renderer.domElement );
83+
84+
// Sky
85+
86+
const canvas = document.createElement( 'canvas' );
87+
canvas.width = 1;
88+
canvas.height = 32;
89+
90+
const context = canvas.getContext( '2d' );
91+
const gradient = context.createLinearGradient( 0, 0, 0, 32 );
92+
gradient.addColorStop( 0.0, '#014a84' );
93+
gradient.addColorStop( 0.5, '#0561a0' );
94+
gradient.addColorStop( 1.0, '#437ab6' );
95+
context.fillStyle = gradient;
96+
context.fillRect( 0, 0, 1, 32 );
97+
98+
const sky = new THREE.Mesh(
99+
new THREE.SphereGeometry( 10 ),
100+
new THREE.MeshBasicMaterial( { map: new THREE.CanvasTexture( canvas ), side: THREE.BackSide } )
101+
);
102+
scene.add( sky );
103+
104+
// Texture
105+
106+
const texture = new THREE.DataTexture3D(
107+
new Uint8Array( INITIAL_CLOUD_SIZE * INITIAL_CLOUD_SIZE * INITIAL_CLOUD_SIZE ).fill( 0 ),
108+
INITIAL_CLOUD_SIZE,
109+
INITIAL_CLOUD_SIZE,
110+
INITIAL_CLOUD_SIZE
111+
);
112+
texture.format = THREE.RedFormat;
113+
texture.minFilter = THREE.LinearFilter;
114+
texture.magFilter = THREE.LinearFilter;
115+
texture.unpackAlignment = 1;
116+
117+
cloudTexture = texture;
118+
119+
// Material
120+
121+
const vertexShader = /* glsl */`
122+
in vec3 position;
123+
124+
uniform mat4 modelMatrix;
125+
uniform mat4 modelViewMatrix;
126+
uniform mat4 projectionMatrix;
127+
uniform vec3 cameraPos;
128+
129+
out vec3 vOrigin;
130+
out vec3 vDirection;
131+
132+
void main() {
133+
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
134+
135+
vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
136+
vDirection = position - vOrigin;
137+
138+
gl_Position = projectionMatrix * mvPosition;
139+
}
140+
`;
141+
142+
const fragmentShader = /* glsl */`
143+
precision highp float;
144+
precision highp sampler3D;
145+
146+
uniform mat4 modelViewMatrix;
147+
uniform mat4 projectionMatrix;
148+
149+
in vec3 vOrigin;
150+
in vec3 vDirection;
151+
152+
out vec4 color;
153+
154+
uniform vec3 base;
155+
uniform sampler3D map;
156+
157+
uniform float threshold;
158+
uniform float range;
159+
uniform float opacity;
160+
uniform float steps;
161+
uniform float frame;
162+
163+
uint wang_hash(uint seed)
164+
{
165+
seed = (seed ^ 61u) ^ (seed >> 16u);
166+
seed *= 9u;
167+
seed = seed ^ (seed >> 4u);
168+
seed *= 0x27d4eb2du;
169+
seed = seed ^ (seed >> 15u);
170+
return seed;
171+
}
172+
173+
float randomFloat(inout uint seed)
174+
{
175+
return float(wang_hash(seed)) / 4294967296.;
176+
}
177+
178+
vec2 hitBox( vec3 orig, vec3 dir ) {
179+
const vec3 box_min = vec3( - 0.5 );
180+
const vec3 box_max = vec3( 0.5 );
181+
vec3 inv_dir = 1.0 / dir;
182+
vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
183+
vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
184+
vec3 tmin = min( tmin_tmp, tmax_tmp );
185+
vec3 tmax = max( tmin_tmp, tmax_tmp );
186+
float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
187+
float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
188+
return vec2( t0, t1 );
189+
}
190+
191+
float sample1( vec3 p ) {
192+
return texture( map, p ).r;
193+
}
194+
195+
float shading( vec3 coord ) {
196+
float step = 0.01;
197+
return sample1( coord + vec3( - step ) ) - sample1( coord + vec3( step ) );
198+
}
199+
200+
void main(){
201+
vec3 rayDir = normalize( vDirection );
202+
vec2 bounds = hitBox( vOrigin, rayDir );
203+
204+
if ( bounds.x > bounds.y ) discard;
205+
206+
bounds.x = max( bounds.x, 0.0 );
207+
208+
vec3 p = vOrigin + bounds.x * rayDir;
209+
vec3 inc = 1.0 / abs( rayDir );
210+
float delta = min( inc.x, min( inc.y, inc.z ) );
211+
delta /= steps;
212+
213+
// Jitter
214+
215+
// Nice little seed from
216+
// https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/
217+
uint seed = uint( gl_FragCoord.x ) * uint( 1973 ) + uint( gl_FragCoord.y ) * uint( 9277 ) + uint( frame ) * uint( 26699 );
218+
vec3 size = vec3( textureSize( map, 0 ) );
219+
float randNum = randomFloat( seed ) * 2.0 - 1.0;
220+
p += rayDir * randNum * ( 1.0 / size );
221+
222+
//
223+
224+
vec4 ac = vec4( base, 0.0 );
225+
226+
for ( float t = bounds.x; t < bounds.y; t += delta ) {
227+
228+
float d = sample1( p + 0.5 );
229+
230+
d = smoothstep( threshold - range, threshold + range, d ) * opacity;
231+
232+
float col = shading( p + 0.5 ) * 3.0 + ( ( p.x + p.y ) * 0.25 ) + 0.2;
233+
234+
ac.rgb += ( 1.0 - ac.a ) * d * col;
235+
236+
ac.a += ( 1.0 - ac.a ) * d;
237+
238+
if ( ac.a >= 0.95 ) break;
239+
240+
p += rayDir * delta;
241+
242+
}
243+
244+
color = ac;
245+
246+
if ( color.a == 0.0 ) discard;
247+
248+
}
249+
`;
250+
251+
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
252+
const material = new THREE.RawShaderMaterial( {
253+
glslVersion: THREE.GLSL3,
254+
uniforms: {
255+
base: { value: new THREE.Color( 0x798aa0 ) },
256+
map: { value: texture },
257+
cameraPos: { value: new THREE.Vector3() },
258+
threshold: { value: 0.25 },
259+
opacity: { value: 0.25 },
260+
range: { value: 0.1 },
261+
steps: { value: 100 },
262+
frame: { value: 0 }
263+
},
264+
vertexShader,
265+
fragmentShader,
266+
side: THREE.BackSide,
267+
transparent: true
268+
} );
269+
270+
mesh = new THREE.Mesh( geometry, material );
271+
scene.add( mesh );
272+
273+
//
274+
275+
const parameters = {
276+
threshold: 0.25,
277+
opacity: 0.25,
278+
range: 0.1,
279+
steps: 100
280+
};
281+
282+
function update() {
283+
284+
material.uniforms.threshold.value = parameters.threshold;
285+
material.uniforms.opacity.value = parameters.opacity;
286+
material.uniforms.range.value = parameters.range;
287+
material.uniforms.steps.value = parameters.steps;
288+
289+
}
290+
291+
const gui = new GUI();
292+
gui.add( parameters, 'threshold', 0, 1, 0.01 ).onChange( update );
293+
gui.add( parameters, 'opacity', 0, 1, 0.01 ).onChange( update );
294+
gui.add( parameters, 'range', 0, 1, 0.01 ).onChange( update );
295+
gui.add( parameters, 'steps', 0, 200, 1 ).onChange( update );
296+
297+
window.addEventListener( 'resize', onWindowResize );
298+
299+
}
300+
301+
function onWindowResize() {
302+
303+
camera.aspect = window.innerWidth / window.innerHeight;
304+
camera.updateProjectionMatrix();
305+
306+
renderer.setSize( window.innerWidth, window.innerHeight );
307+
308+
}
309+
310+
let curr = 0;
311+
const countPerRow = 4;
312+
const countPerSlice = countPerRow * countPerRow;
313+
const sliceCount = 4;
314+
const totalCount = sliceCount * countPerSlice;
315+
const margins = 8;
316+
317+
const perElementPaddedSize = ( INITIAL_CLOUD_SIZE - margins ) / countPerRow;
318+
const perElementSize = Math.floor( ( INITIAL_CLOUD_SIZE - 1 ) / countPerRow );
319+
320+
function animate() {
321+
322+
requestAnimationFrame( animate );
323+
324+
const time = performance.now();
325+
if ( time - prevTime > 1500.0 && curr < totalCount ) {
326+
327+
const position = new THREE.Vector3(
328+
Math.floor( curr % countPerRow ) * perElementSize + margins * 0.5,
329+
( Math.floor( ( ( curr % countPerSlice ) / countPerRow ) ) ) * perElementSize + margins * 0.5,
330+
Math.floor( curr / countPerSlice ) * perElementSize + margins * 0.5
331+
).floor();
332+
333+
const maxDimension = perElementPaddedSize - 1;
334+
const box = new THREE.Box3( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( maxDimension, maxDimension, maxDimension ) );
335+
const scaleFactor = ( Math.random() + 0.5 ) * 0.5;
336+
const source = generateCloudTexture( perElementPaddedSize, scaleFactor );
337+
338+
renderer.copyTextureToTexture3D( box, position, source, cloudTexture );
339+
340+
prevTime = time;
341+
342+
curr ++;
343+
344+
}
345+
346+
mesh.material.uniforms.cameraPos.value.copy( camera.position );
347+
// mesh.rotation.y = - performance.now() / 7500;
348+
349+
mesh.material.uniforms.frame.value ++;
350+
351+
renderer.render( scene, camera );
352+
353+
}
354+
355+
</script>
356+
357+
</body>
358+
</html>

0 commit comments

Comments
 (0)