Skip to content
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

WebGLRenderer: Fix setRenderTarget()'s activeMipmapLevel #26347

Merged
merged 9 commits into from
Jul 18, 2023
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"webgl_materials_cubemap_dynamic",
"webgl_materials_cubemap_refraction",
"webgl_materials_cubemap_mipmaps",
"webgl_materials_cubemap_render_to_mipmaps",
"webgl_materials_curvature",
"webgl_materials_displacementmap",
"webgl_materials_envmaps",
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"webgl_materials_blending_custom": [ "alpha" ],
"webgl_materials_channels": [ "normal", "depth", "rgba packing" ],
"webgl_materials_cubemap_mipmaps": [ "envmap" ],
"webgl_materials_cubemap_render_to_mipmaps": [ "envmap", "renderTarget", "mipmap" ],
"webgl_materials_envmaps_hdr": [ "rgbm" ],
"webgl_materials_lightmap": [ "shadow" ],
"webgl_materials_physical_clearcoat": [ "anisotropy" ],
Expand Down
278 changes: 278 additions & 0 deletions examples/webgl_materials_cubemap_render_to_mipmaps.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - materials - cubemap mipmaps</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="container"></div>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - rendering to cubemap mip levels demo.<br/>
Left: original cubemap<br/>
Right: generated cubemap<br/>
</div>

<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let container;
let camera, scene, renderer;

const CubemapFilterShader = {
uniforms: {
cubeTexture: { value: null },
mipIndex: { value: 0 },
},

vertexShader: /* glsl */ `

varying vec3 vWorldDirection;

#include <common>

void main() {
vWorldDirection = transformDirection(position, modelMatrix);
#include <begin_vertex>
#include <project_vertex>
gl_Position.z = gl_Position.w; // set z to camera.far
}

`,

fragmentShader: /* glsl */ `

uniform samplerCube cubeTexture;
varying vec3 vWorldDirection;

uniform float mipIndex;

#include <common>

void main() {
vec3 cubeCoordinates = normalize(vWorldDirection);

// Colorize mip levels
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
if (mipIndex == 0.0) color.rgb = vec3(1.0, 1.0, 1.0);
else if (mipIndex == 1.0) color.rgb = vec3(0.0, 0.0, 1.0);
else if (mipIndex == 2.0) color.rgb = vec3(0.0, 1.0, 1.0);
else if (mipIndex == 3.0) color.rgb = vec3(0.0, 1.0, 0.0);
else if (mipIndex == 4.0) color.rgb = vec3(1.0, 1.0, 0.0);

gl_FragColor = textureCube(cubeTexture, cubeCoordinates, 0.0) * color;
}

`,
};


init();
animate();


async function loadCubeTexture( urls ) {

return new Promise( function ( resolve ) {

new THREE.CubeTextureLoader().load( urls, function ( cubeTexture ) {

resolve( cubeTexture );

} );


} );

}

function allocateCubemapRenderTarget( cubeMapSize ) {

const params = {
magFilter: THREE.LinearFilter,
minFilter: THREE.LinearMipMapLinearFilter,
generateMipmaps: false,
type: THREE.HalfFloatType,
format: THREE.RGBAFormat,
colorSpace: THREE.LinearSRGBColorSpace,
depthBuffer: false,
};

const rt = new THREE.WebGLCubeRenderTarget( cubeMapSize, params );

const mipLevels = Math.log( cubeMapSize ) * Math.LOG2E + 1.0;
for ( let i = 0; i < mipLevels; i ++ ) rt.texture.mipmaps.push( {} );
Copy link
Collaborator

@Mugen87 Mugen87 Jul 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrdoob Explicitly defining the mipmaps of the cube render target like demonstrated here triggers the new code path.


rt.texture.mapping = THREE.CubeReflectionMapping;
return rt;

}

function renderToCubeTexture( cubeMapRenderTarget, sourceCubeTexture ) {

const cameras = [];

for ( let i = 0; i < 6; i ++ ) {

// negative fov is not an error
cameras.push( new THREE.PerspectiveCamera( - 90, 1, 1, 10 ) );

}

cameras[ 0 ].up.set( 0, 1, 0 );
cameras[ 0 ].lookAt( 1, 0, 0 );
cameras[ 1 ].up.set( 0, 1, 0 );
cameras[ 1 ].lookAt( - 1, 0, 0 );
cameras[ 2 ].up.set( 0, 0, - 1 );
cameras[ 2 ].lookAt( 0, 1, 0 );
cameras[ 3 ].up.set( 0, 0, 1 );
cameras[ 3 ].lookAt( 0, - 1, 0 );
cameras[ 4 ].up.set( 0, 1, 0 );
cameras[ 4 ].lookAt( 0, 0, 1 );
cameras[ 5 ].up.set( 0, 1, 0 );
cameras[ 5 ].lookAt( 0, 0, - 1 );

for ( let i = 0; i < 6; i ++ ) cameras[ i ].updateMatrixWorld();

const geometry = new THREE.BoxGeometry( 5, 5, 5 );

const material = new THREE.ShaderMaterial( {
name: 'FilterCubemap',
uniforms: THREE.UniformsUtils.clone( CubemapFilterShader.uniforms ),
vertexShader: CubemapFilterShader.vertexShader,
fragmentShader: CubemapFilterShader.fragmentShader,
side: THREE.BackSide,
blending: THREE.NoBlending,
} );

material.uniforms.cubeTexture.value = sourceCubeTexture;

const mesh = new THREE.Mesh( geometry, material );

const currentRenderTarget = renderer.getRenderTarget();
const currentXrEnabled = renderer.xr.enabled;
renderer.xr.enabled = false;

for ( let faceIndex = 0; faceIndex < 6; faceIndex ++ ) {

let mipIndex = 0;
let mipSize = cubeMapRenderTarget.width;

// Render to each texture mip level
while ( mipSize >= 1 ) {

cubeMapRenderTarget.viewport.set( 0, 0, mipSize, mipSize );
renderer.setRenderTarget( cubeMapRenderTarget, faceIndex, mipIndex );
material.uniforms.mipIndex.value = mipIndex;
material.needsUpdate = true;
renderer.render( mesh, cameras[ faceIndex ] );
mipSize >>= 1;
mipIndex ++;

}

}

renderer.setRenderTarget( currentRenderTarget );
renderer.xr.enabled = currentXrEnabled;

mesh.geometry.dispose();
mesh.material.dispose();

}

function init() {

container = document.createElement( 'div' );
document.body.appendChild( container );

// Create renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );

scene = new THREE.Scene();

camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 500;

// Create controls
const controls = new OrbitControls( camera, renderer.domElement );
controls.minPolarAngle = Math.PI / 4;
controls.maxPolarAngle = Math.PI / 1.5;

window.addEventListener( 'resize', onWindowResize );

// Load a cube texture
const r = 'textures/cube/Park3Med/';
const urls = [
r + 'px.jpg', r + 'nx.jpg',
r + 'py.jpg', r + 'ny.jpg',
r + 'pz.jpg', r + 'nz.jpg'
];

loadCubeTexture( urls ).then( ( cubeTexture ) => {

// Allocate a cube map render target
const cubeMapRenderTarget = allocateCubemapRenderTarget( 512 );

// Render to all the mip levels of cubeMapRenderTarget
renderToCubeTexture( cubeMapRenderTarget, cubeTexture );

// Create geometry
const sphere = new THREE.SphereGeometry( 100, 128, 128 );
let material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: cubeTexture } );

let mesh = new THREE.Mesh( sphere, material );
mesh.position.set( - 100, 0, 0 );
scene.add( mesh );

material = material.clone();
material.envMap = cubeMapRenderTarget.texture;

mesh = new THREE.Mesh( sphere, material );
mesh.position.set( 100, 0, 0 );
scene.add( mesh );

} );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

requestAnimationFrame( animate );
renderer.render( scene, camera );

}

</script>

</body>
</html>
7 changes: 5 additions & 2 deletions src/renderers/WebGLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2097,7 +2097,9 @@ class WebGLRenderer {

if ( renderTarget.isWebGLCubeRenderTarget ) {

framebuffer = __webglFramebuffer[ activeCubeFace ];
if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ];
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved
else framebuffer = __webglFramebuffer[ activeCubeFace ];

isCube = true;

} else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) {
Expand All @@ -2106,7 +2108,8 @@ class WebGLRenderer {

} else {

framebuffer = __webglFramebuffer;
if ( Array.isArray( __webglFramebuffer ) ) framebuffer = __webglFramebuffer[ activeMipmapLevel ];
else framebuffer = __webglFramebuffer;

}

Expand Down