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

Skydome projection to allow dome / box sky texture projections #5886

Merged
merged 20 commits into from Dec 14, 2023
Merged
148 changes: 132 additions & 16 deletions src/scene/scene.js
Expand Up @@ -163,13 +163,53 @@ class Scene extends EventHandler {
*/
sky = null;

/**
* @type {boolean}
* @private
*/
_skyboxProjectionEnabled = false;

/**
* @type {Vec3}
* @private
*/
_skyboxProjectionCenter = new Vec3();

/**
* @type {number}
* @private
*/
_skyboxProjectionElevation = 0;

/**
* @type {number}
* @private
*/
_skyboxProjectionRadius = 100;

/**
* Use physically based units for cameras and lights. When used, the exposure value is ignored.
*
* @type {boolean}
*/
physicalUnits = false;

/**
* Environment lighting atlas
*
* @type {import('../platform/graphics/texture.js').Texture|null}
* @private
*/
_envAtlas = null;

/**
* The skybox cubemap as set by user (gets used when skyboxMip === 0)
*
* @type {import('../platform/graphics/texture.js').Texture|null}
* @private
*/
_skyboxCubeMap = null;

/**
* Create a new Scene instance.
*
Expand All @@ -196,14 +236,6 @@ class Scene extends EventHandler {
this._gammaCorrection = GAMMA_SRGB;
this._toneMapping = 0;

/**
* The skybox cubemap as set by user (gets used when skyboxMip === 0)
*
* @type {import('../platform/graphics/texture.js').Texture}
* @private
*/
this._skyboxCubeMap = null;

/**
* Array of 6 prefiltered lighting data cubemaps.
*
Expand All @@ -212,14 +244,6 @@ class Scene extends EventHandler {
*/
this._prefilteredCubemaps = [];

/**
* Environment lighting atlas
*
* @type {import('../platform/graphics/texture.js').Texture}
* @private
*/
this._envAtlas = null;

// internally generated envAtlas owned by the scene
this._internalEnvAtlas = null;

Expand Down Expand Up @@ -785,6 +809,98 @@ class Scene extends EventHandler {
}
}

updateSkyboxProjection() {

if (this._skyboxProjectionEnabled) {
const scope = this.device.scope;

const center = this._skyboxProjectionCenter;
const radius = this._skyboxProjectionRadius;
const domeElevation = this._skyboxProjectionElevation;
scope.resolve('projectedSkydomeCenter').setValue([
center.x,
center.y * radius,
center.z,
1.0
]);

scope.resolve('projectedSkydomeDome').setValue([
center.x,
center.y + radius * domeElevation,
center.z,
radius * radius
]);

scope.resolve('projectedSkydomePlane').setValue([
0,
1,
0,
-center.y
]);
}
}

/**
* When enabled, the skybox will be projected into a sphere dome and a ground plane. Otherwise
* the skybox will be projected onto an infinite cube. Defaults to false.
*
* @type {boolean}
*/
set skyboxProjectionEnabled(value) {
if (value !== this._skyboxProjectionEnabled) {
this._skyboxProjectionEnabled = value;
this.updateSkyboxProjection();
this.updateShaders = true;
}
}

get skyboxProjectionEnabled() {
return this._skyboxProjectionEnabled;
}

/**
* The center of the skybox projection. The y-component of the vector represents the height of
* the camera from the ground plane. Defaults to (0, 0, 0).
*
* @type {Vec3}
*/
set skyboxProjectionCenter(value) {
this._skyboxProjectionCenter.copy(value);
this.updateSkyboxProjection();
}

get skyboxProjectionCenter() {
return this._skyboxProjectionCenter;
}

/**
* The elevation of the skybox projection - both the sphere and the plane parts. Defaults to 0.
mvaligursky marked this conversation as resolved.
Show resolved Hide resolved
*
* @type {number}
*/
set skyboxProjectionElevation(value) {
this._skyboxProjectionElevation = value;
this.updateSkyboxProjection();
}

get skyboxProjectionElevation() {
return this._skyboxProjectionElevation;
}

/**
* The radius of the skybox projection sphere. Defaults to 100.
*
* @type {number}
*/
set skyboxProjectionRadius(value) {
this._skyboxProjectionRadius = value;
this.updateSkyboxProjection();
}

get skyboxProjectionRadius() {
return this._skyboxProjectionRadius;
}

/**
* The lightmap pixel format.
*
Expand Down
64 changes: 63 additions & 1 deletion src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js
Expand Up @@ -3,8 +3,70 @@ varying vec3 vViewDir;

uniform samplerCube texture_cubeMap;

#ifdef PROJECTED_SKYDOME

uniform mat3 cubeMapRotationMatrix;
uniform vec3 view_position; // camera world position
uniform vec4 projectedSkydomeCenter; // x, y, z: world space origin of the tripod, w: blend factor
uniform vec4 projectedSkydomeDome; // x, y, z: world space dome center, w: (dome radius)^2
uniform vec4 projectedSkydomePlane; // x, y, z, w: world space ground plane

void intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
float d = dot(dir, plane.xyz);
if (d != 0.0) {
float n = -(dot(pos, plane.xyz) + plane.w) / d;
if (n >= 0.0 && n < t) {
t = n;
}
}
}

bool intersectSphere(inout float t, vec3 pos, vec3 dir, vec4 sphere) {
vec3 L = sphere.xyz - pos;
float tca = dot(L, dir);

float d2 = sphere.w - (dot(L, L) - tca * tca);
if (d2 >= 0.0) {
float thc = tca + sqrt(d2);
if (thc >= 0.0 && thc < t) {
t = thc;
return true;
}
}

return false;
}

#endif

void main(void) {
vec3 dir=vViewDir;

#ifdef PROJECTED_SKYDOME

// get world space ray
vec3 view_pos = view_position;
vec3 view_dir = normalize(vViewDir);

// intersect ray with world geometry
float t = 8000.0; // max intersection distance
if (intersectSphere(t, view_pos, view_dir, projectedSkydomeDome) && view_dir.y < 0.0) {
intersectPlane(t, view_pos, view_dir, projectedSkydomePlane);
}

// calculate world space intersection
vec3 world_pos = view_pos + view_dir * t;

// get vector from world space pos to tripod origin
vec3 env_dir = normalize(world_pos - projectedSkydomeCenter.xyz);

vec3 dir = mix(view_dir, env_dir, projectedSkydomeCenter.w) * cubeMapRotationMatrix;

#else

vec3 dir = vViewDir;

#endif

dir.x *= -1.0;

vec3 linear = $DECODE(textureCube(texture_cubeMap, fixSeamsStatic(dir, $FIXCONST)));
Expand Down
3 changes: 2 additions & 1 deletion src/scene/shader-lib/programs/skybox.js
Expand Up @@ -8,14 +8,15 @@ import { ShaderGenerator } from './shader-generator.js';
class ShaderGeneratorSkybox extends ShaderGenerator {
generateKey(options) {
return options.type === 'cubemap' ?
`skybox-${options.type}-${options.encoding}-${options.useIntensity}-${options.gamma}-${options.toneMapping}-${options.fixSeams}-${options.mip}` :
`skybox-${options.type}-${options.encoding}-${options.useIntensity}-${options.gamma}-${options.toneMapping}-${options.fixSeams}-${options.mip}-${options.projectionEnabled}` :
`skybox-${options.type}-${options.encoding}-${options.useIntensity}-${options.gamma}-${options.toneMapping}`;
}

createShaderDefinition(device, options) {
let fshader = '';
if (options.type === 'cubemap') {
const mip2size = [128, 64, /* 32 */ 16, 8, 4, 2];
fshader += options.projectionEnabled ? '#define PROJECTED_SKYDOME\n' : '';
fshader += options.mip ? shaderChunks.fixCubemapSeamsStretchPS : shaderChunks.fixCubemapSeamsNonePS;
fshader += options.useIntensity ? shaderChunks.envMultiplyPS : shaderChunks.envConstPS;
fshader += shaderChunks.decodePS;
Expand Down
3 changes: 2 additions & 1 deletion src/scene/sky.js
Expand Up @@ -39,7 +39,8 @@ class Sky {
encoding: texture.encoding,
useIntensity: scene.skyboxIntensity !== 1 || scene.physicalUnits,
gamma: (pass === SHADER_FORWARDHDR ? (scene.gammaCorrection ? GAMMA_SRGBHDR : GAMMA_NONE) : scene.gammaCorrection),
toneMapping: (pass === SHADER_FORWARDHDR ? TONEMAP_LINEAR : scene.toneMapping)
toneMapping: (pass === SHADER_FORWARDHDR ? TONEMAP_LINEAR : scene.toneMapping),
projectionEnabled: scene.skyboxProjectionEnabled
};

if (texture.cubemap) {
Expand Down