Skip to content

Commit

Permalink
Added support for HDR cubemaps
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsmalm committed Nov 21, 2022
1 parent 45d4dbf commit b68f403
Show file tree
Hide file tree
Showing 89 changed files with 221 additions and 15 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Added support for HDR cubemaps when using `ImageBasedLighting` or `Skybox` (encoded using the RGBE8 format).
- Added support for changing the exposure of a `Skybox`.
- `Skybox` is now rendered with gamma correction.
- Added request options to `glTFAsset.fromURL`.

## [2.0.1] - 2022-11-12
### Fixed
- Fixed an issue which caused a crash when rendering shadows with skinned meshes.
Expand Down
6 changes: 6 additions & 0 deletions src/cubemap/cubemap-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum CubemapFormat {
/** Low dynamic range. */
ldr = "ldr",
/** High dynamic range in RGBE8 format. */
rgbe8 = "rgbe8"
}
4 changes: 4 additions & 0 deletions src/cubemap/cubemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CubemapResource, MipmapResourceArray } from "./cubemap-resource"
import { Color } from "../color"
import { CubemapFaces } from "./cubemap-faces"
import { BufferResource } from "../compatibility/buffer-resource"
import { CubemapFormat } from "./cubemap-format"

/**
* Cubemap which supports multiple user specified mipmaps.
Expand All @@ -21,6 +22,9 @@ export class Cubemap extends BaseTexture<CubemapResource> {
return this.resource.levels
}

/** The format for this cubemap. */
cubemapFormat = CubemapFormat.ldr

/**
* Creates a new cubemap from the specified faces.
* @param faces The faces to create the cubemap from.
Expand Down
56 changes: 50 additions & 6 deletions src/loader/cubemap-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,54 @@ import { Cubemap } from "../cubemap/cubemap"
import { CubemapFaces } from "../cubemap/cubemap-faces"
import { LoaderResourceResponseType } from "../compatibility/compatibility-version"
import { Compatibility } from "../compatibility/compatibility"
import { CubemapFormat } from "../cubemap/cubemap-format"

interface CubemapFileVersion {
format: CubemapFormat
mipmaps: string[]
}

class CubemapFileVersion1 implements CubemapFileVersion {
constructor(private json: any) { }

get format() {
return CubemapFormat.ldr
}

get mipmaps(): string[] {
return this.json
}
}

class CubemapFileVersion2 implements CubemapFileVersion {
constructor(private json: any) { }

get format() {
return <CubemapFormat>this.json.format
}

get mipmaps(): string[] {
return <string[]>this.json.mipmaps
}
}

namespace CubemapFileVersionSelector {
export function getFileVersion(json: any): CubemapFileVersion {
if (json.version === 2) {
return new CubemapFileVersion2(json)
}
return new CubemapFileVersion1(json)
}
}

export const CubemapLoader = {
use: function (resource: any, next: () => void) {
if (resource.extension !== "cubemap") {
return next()
}
let loader = <Loader><unknown>this

const mipmaps = (<string[]>resource.data).map(mipmap => {
const loader = <Loader><unknown>this
const version = CubemapFileVersionSelector.getFileVersion(resource.data)
const mipmaps = version.mipmaps.map(mipmap => {
return Cubemap.faces.map(face => {
return resource.url.substring(0, resource.url.lastIndexOf("/") + 1) + mipmap.replace("{{face}}", face)
})
Expand Down Expand Up @@ -43,7 +82,9 @@ export const CubemapLoader = {
negz: Texture.from(face[5]),
}
})
resource.cubemap = Cubemap.fromFaces(textures)
let cubemap = Cubemap.fromFaces(textures)
cubemap.cubemapFormat = version.format
resource.cubemap = cubemap
binding.detach(); next()
}
}
Expand All @@ -62,7 +103,8 @@ export const CubemapLoader = {
}
const response = await settings.ADAPTER.fetch(url)
const json = await response.json()
const mipmaps = (<string[]>json).map(mipmap => {
const version = CubemapFileVersionSelector.getFileVersion(json)
const mipmaps = version.mipmaps.map(mipmap => {
return Cubemap.faces.map(face => {
return url.substring(0, url.lastIndexOf("/") + 1) + mipmap.replace("{{face}}", face)
})
Expand All @@ -79,7 +121,9 @@ export const CubemapLoader = {
}
textures.push(faceMipMaps)
}
return Cubemap.fromFaces(textures)
let cubemap = Cubemap.fromFaces(textures)
cubemap.cubemapFormat = version.format
return cubemap
},
}

Expand Down
8 changes: 6 additions & 2 deletions src/material/standard/shader/metallic-roughness.frag
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
@import ./functions;
@import ./shadow;
@import ./tonemapping;
@import ./rgbe;

// KHR_lights_punctual extension.
// see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
Expand Down Expand Up @@ -134,10 +135,13 @@ vec3 getIBLContribution(MaterialInfo materialInfo, vec3 n, vec3 v)
vec4 specularSample = _texture(u_SpecularEnvSampler, reflection);
#endif

#ifdef USE_HDR
#if defined(USE_HDR)
// Already linear.
vec3 diffuseLight = diffuseSample.rgb;
vec3 specularLight = specularSample.rgb;
#elif defined(USE_RGBE)
vec3 diffuseLight = decodeRGBE(diffuseSample);
vec3 specularLight = decodeRGBE(specularSample);
#else
vec3 diffuseLight = SRGBtoLINEAR(diffuseSample).rgb;
vec3 specularLight = SRGBtoLINEAR(specularSample).rgb;
Expand Down Expand Up @@ -447,7 +451,7 @@ void main()

#ifndef DEBUG_OUTPUT // no debug

// regular shading
// regular shading
FRAG_COLOR = vec4(toneMap(color) * baseColor.a, baseColor.a);

#else // debug output
Expand Down
15 changes: 15 additions & 0 deletions src/material/standard/shader/rgbe.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
vec4 encodeRGBE(vec3 rgb) {
vec4 vEncoded;
float maxComponent = max(max(rgb.r, rgb.g), rgb.b);
float fExp = ceil(log2(maxComponent));
vEncoded.rgb = rgb / exp2(fExp);
vEncoded.a = (fExp + 128.0) / 255.0;
return vEncoded;
}

vec3 decodeRGBE(vec4 rgbe) {
vec3 vDecoded;
float fExp = rgbe.a * 255.0 - 128.0;
vDecoded = rgbe.rgb * exp2(fExp);
return vDecoded;
}
4 changes: 4 additions & 0 deletions src/material/standard/standard-material-feature-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Capabilities } from "../../capabilities"
import { StandardMaterialMatrixTexture } from "./standard-material-matrix-texture"
import { Debug } from "../../debug"
import { Message } from "../../message"
import { CubemapFormat } from "../../cubemap/cubemap-format"

export namespace StandardMaterialFeatureSet {
export function build(renderer: Renderer, mesh: Mesh3D, geometry: MeshGeometry3D, material: StandardMaterial, lightingEnvironment: LightingEnvironment) {
Expand Down Expand Up @@ -86,6 +87,9 @@ export namespace StandardMaterialFeatureSet {
Debug.warn(Message.imageBasedLightingShaderTextureLodNotSupported)
}
features.push("USE_IBL 1")
if (lightingEnvironment.imageBasedLighting.diffuse.cubemapFormat === CubemapFormat.rgbe8) {
features.push("USE_RGBE 1")
}
}
if (material.shadowCastingLight) {
features.push("USE_SHADOW_MAPPING 1")
Expand Down
25 changes: 24 additions & 1 deletion src/skybox/shader/skybox.frag
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
varying vec3 v_Position;

uniform samplerCube u_EnvironmentSampler;
uniform bool u_RGBE;
uniform float u_Exposure;

const float GAMMA = 2.2;
const float INV_GAMMA = 1.0 / GAMMA;

// linear to sRGB approximation
// see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
vec3 linearToSRGB(vec3 color)
{
return pow(color, vec3(INV_GAMMA));
}

vec3 decodeRGBE(vec4 rgbe) {
vec3 vDecoded;
float fExp = rgbe.a * 255.0 - 128.0;
vDecoded = rgbe.rgb * exp2(fExp);
return vDecoded;
}

void main() {
gl_FragColor = vec4(textureCube(u_EnvironmentSampler, v_Position).rgb, 1.0);
vec4 color = textureCube(u_EnvironmentSampler, v_Position);
if (u_RGBE) {
color = vec4(decodeRGBE(color), 1.0);
}
gl_FragColor = vec4(linearToSRGB(color.rgb * u_Exposure), 1.0);
}
5 changes: 5 additions & 0 deletions src/skybox/skybox-material.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Mesh3D } from "../mesh/mesh"
import { Material } from "../material/material"
import { Shader as Vertex } from "./shader/skybox.vert"
import { Shader as Fragment } from "./shader/skybox.frag"
import { CubemapFormat } from "../cubemap/cubemap-format"

export class SkyboxMaterial extends Material {
private _cubemap: Cubemap
Expand All @@ -28,6 +29,8 @@ export class SkyboxMaterial extends Material {

camera?: Camera

exposure = 1

constructor(cubemap: Cubemap) {
super()
this._cubemap = cubemap
Expand All @@ -43,6 +46,8 @@ export class SkyboxMaterial extends Material {
shader.uniforms.u_View = camera.view.array
shader.uniforms.u_Projection = camera.projection.array
shader.uniforms.u_EnvironmentSampler = this.cubemap
shader.uniforms.u_RGBE = this.cubemap.cubemapFormat === CubemapFormat.rgbe8
shader.uniforms.u_Exposure = this.exposure
}

render(mesh: Mesh3D, renderer: Renderer) {
Expand Down
11 changes: 11 additions & 0 deletions src/skybox/skybox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ export class Skybox extends Container3D {
(<SkyboxMaterial>this._mesh.material).camera = value
}

/**
* The cubemap exposure used when rendering.
*/
get exposure() {
return (<SkyboxMaterial>this._mesh.material).exposure
}

set exposure(value: number) {
(<SkyboxMaterial>this._mesh.material).exposure = value
}

/**
* The cubemap texture used when rendering.
*/
Expand Down
2 changes: 1 addition & 1 deletion test/assets/chromatic/skybox.cubemap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
["diffuse_{{face}}.png"]
["specular_{{face}}_0.png"]
Binary file added test/assets/sunset/brdf.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions test/assets/sunset/diffuse.cubemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 2,
"mipmaps": ["diffuse_{{face}}.png"],
"format": "rgbe8"
}
Binary file added test/assets/sunset/diffuse_negx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/diffuse_negy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/diffuse_negz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/diffuse_posx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/diffuse_posy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/diffuse_posz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions test/assets/sunset/skybox.cubemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 2,
"mipmaps": ["specular_{{face}}_0.png"],
"format": "rgbe8"
}
5 changes: 5 additions & 0 deletions test/assets/sunset/specular.cubemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 2,
"mipmaps": ["specular_{{face}}_0.png","specular_{{face}}_1.png","specular_{{face}}_2.png","specular_{{face}}_3.png","specular_{{face}}_4.png","specular_{{face}}_5.png","specular_{{face}}_6.png","specular_{{face}}_7.png","specular_{{face}}_8.png","specular_{{face}}_9.png"],
"format": "rgbe8"
}
Binary file added test/assets/sunset/specular_negx_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negx_9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/sunset/specular_negy_8.png
Binary file added test/assets/sunset/specular_negy_9.png
Binary file added test/assets/sunset/specular_negz_0.png
Binary file added test/assets/sunset/specular_negz_1.png
Binary file added test/assets/sunset/specular_negz_2.png
Binary file added test/assets/sunset/specular_negz_3.png
Binary file added test/assets/sunset/specular_negz_4.png
Binary file added test/assets/sunset/specular_negz_5.png
Binary file added test/assets/sunset/specular_negz_6.png
Binary file added test/assets/sunset/specular_negz_7.png
Binary file added test/assets/sunset/specular_negz_8.png
Binary file added test/assets/sunset/specular_negz_9.png
Binary file added test/assets/sunset/specular_posx_0.png
Binary file added test/assets/sunset/specular_posx_1.png
Binary file added test/assets/sunset/specular_posx_2.png
Binary file added test/assets/sunset/specular_posx_3.png
Binary file added test/assets/sunset/specular_posx_4.png
Binary file added test/assets/sunset/specular_posx_5.png
Binary file added test/assets/sunset/specular_posx_6.png
Binary file added test/assets/sunset/specular_posx_7.png
Binary file added test/assets/sunset/specular_posx_8.png
Binary file added test/assets/sunset/specular_posx_9.png
Binary file added test/assets/sunset/specular_posy_0.png
Binary file added test/assets/sunset/specular_posy_1.png
Binary file added test/assets/sunset/specular_posy_2.png
Binary file added test/assets/sunset/specular_posy_3.png
Binary file added test/assets/sunset/specular_posy_4.png
Binary file added test/assets/sunset/specular_posy_5.png
Binary file added test/assets/sunset/specular_posy_6.png
Binary file added test/assets/sunset/specular_posy_7.png
Binary file added test/assets/sunset/specular_posy_8.png
Binary file added test/assets/sunset/specular_posy_9.png
Binary file added test/assets/sunset/specular_posz_0.png
Binary file added test/assets/sunset/specular_posz_1.png
Binary file added test/assets/sunset/specular_posz_2.png
Binary file added test/assets/sunset/specular_posz_3.png
Binary file added test/assets/sunset/specular_posz_4.png
Binary file added test/assets/sunset/specular_posz_5.png
Binary file added test/assets/sunset/specular_posz_6.png
Binary file added test/assets/sunset/specular_posz_7.png
Binary file added test/assets/sunset/specular_posz_8.png
Binary file added test/assets/sunset/specular_posz_9.png
6 changes: 3 additions & 3 deletions test/gltf.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from "chai"

describe("glTF", () => {

it("should display separate correctly using pixi *.*.*", async () => {
it("should render separate correctly using pixi *.*.*", async () => {
let render = (renderer, resources) => {
let model = PIXI3D.Model.from(resources["assets/teapot/teapot-separate.gltf"].gltf)
model.y = -0.8
Expand All @@ -18,7 +18,7 @@ describe("glTF", () => {
})
})

it("should display binary correctly using pixi *.*.*", async () => {
it("should render binary correctly using pixi *.*.*", async () => {
let render = async (renderer, resources) => {
let model = PIXI3D.Model.from(resources["assets/teapot/teapot-binary.glb"].gltf)
model.y = -0.8
Expand All @@ -34,7 +34,7 @@ describe("glTF", () => {
})
})

it("should display embedded correctly using pixi *.*.*", async () => {
it("should render embedded correctly using pixi *.*.*", async () => {
let render = (renderer, resources) => {
let model = PIXI3D.Model.from(resources["assets/teapot/teapot-embedded.gltf"].gltf)
model.y = -0.8
Expand Down
47 changes: 46 additions & 1 deletion test/skybox.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from "chai"

describe("Skybox", () => {

it("should render correctly using pixi *.*.*", async () => {
it("should render correctly from colors using pixi *.*.*", async () => {
let render = (renderer, resources) => {
let skybox = new PIXI3D.Skybox(PIXI3D.Cubemap.fromColors(
new PIXI3D.Color(1, 0, 1),
Expand All @@ -16,4 +16,49 @@ describe("Skybox", () => {
}
await expect(render).to.match("snapshots/ugdpm.png")
})

it("should render correctly with cubemap version 1, ldr and exposure = 1 using pixi *.*.*", async () => {
let render = (renderer, resources) => {
let skybox = new PIXI3D.Skybox(
resources["assets/chromatic/skybox.cubemap"].cubemap)
skybox.rotationQuaternion.setEulerAngles(-45, 270, 0)
skybox.exposure = 1
renderer.render(skybox)
}
await expect(render).to.match("snapshots/ygihm.png", {
resources: [
"assets/chromatic/skybox.cubemap"
]
})
})

it("should render correctly with cubemap version 2, rgbe8 and exposure = 0.1 using pixi *.*.*", async () => {
let render = (renderer, resources) => {
let skybox = new PIXI3D.Skybox(
resources["assets/sunset/skybox.cubemap"].cubemap)
skybox.rotationQuaternion.setEulerAngles(0, 130, 0)
skybox.exposure = 0.1
renderer.render(skybox)
}
await expect(render).to.match("snapshots/sshat.png", {
resources: [
"assets/sunset/skybox.cubemap"
]
})
})

it("should render correctly with cubemap version 2, rgbe8 and exposure = 0.3 using pixi *.*.*", async () => {
let render = (renderer, resources) => {
let skybox = new PIXI3D.Skybox(
resources["assets/sunset/skybox.cubemap"].cubemap)
skybox.rotationQuaternion.setEulerAngles(0, 130, 0)
skybox.exposure = 0.3
renderer.render(skybox)
}
await expect(render).to.match("snapshots/tulow.png", {
resources: [
"assets/sunset/skybox.cubemap"
]
})
})
})
Binary file added test/snapshots/msohy.png
Binary file added test/snapshots/sshat.png
Binary file added test/snapshots/tulow.png
Binary file modified test/snapshots/ugdpm.png
Binary file added test/snapshots/ygihm.png
Loading

0 comments on commit b68f403

Please sign in to comment.