WebGL demos now decode PNG in C. (#214)
This removes the trick of drawing to a 2D canvas. It was corrupting the alpha in RGBM images, which caused subtle banding in the Suzanne reflection. Moreover it added complexity to the demo code. This commit will increase the pre-zipped WASM size by 17k, but I think it's worth it. This also removes one of the cmgen passes in the WebGL build; we were using PNG instead of RGBM for the skybox to work around the banding issue that this fixes.
This commit is contained in:
@@ -265,6 +265,7 @@ endif()
|
||||
set (FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples)
|
||||
|
||||
if (WEBGL)
|
||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||
add_subdirectory(${FILAMENT}/samples/web)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -77,27 +77,21 @@ add_custom_command(
|
||||
DEPENDS filamesh)
|
||||
|
||||
# Next build two cubemaps:
|
||||
# (1) single miplevel of blurry skybox (png)
|
||||
# (2) all miplevels for the IBL (rgbm)
|
||||
# (1) single miplevel of blurry skybox
|
||||
# (2) all miplevels for the IBL
|
||||
|
||||
set(source_envmap "${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/environments/syferfontein_18d_clear_2k.hdr")
|
||||
|
||||
foreach(FACE nx ny nz px py pz)
|
||||
set(target_skybox ${target_skybox} public/syferfontein_18d_clear_2k/${FACE}.png)
|
||||
set(target_skybox ${target_skybox} public/syferfontein_18d_clear_2k/${FACE}.rgbm)
|
||||
foreach(MIP 0 1 2 3 4 5 6 7 8)
|
||||
set(target_envmap ${target_envmap} public/syferfontein_18d_clear_2k/m${MIP}_${FACE}.rgbm)
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${target_envmap}
|
||||
COMMAND cmgen -x public --format=rgbm --size=256 ${source_envmap}
|
||||
MAIN_DEPENDENCY ${source_envmap}
|
||||
DEPENDS cmgen)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${target_skybox}
|
||||
COMMAND cmgen -x public --format=png --size=512 --extract-blur=0.1 ${source_envmap}
|
||||
OUTPUT ${target_skybox} ${target_envmap}
|
||||
COMMAND cmgen -x public --format=rgbm --size=512 --extract-blur=0.1 ${source_envmap}
|
||||
MAIN_DEPENDENCY ${source_envmap}
|
||||
DEPENDS cmgen)
|
||||
|
||||
@@ -126,7 +120,7 @@ function(add_demo NAME)
|
||||
filaweb.h
|
||||
filaweb.cpp)
|
||||
add_dependencies(${NAME} sample_materials sample_assets)
|
||||
target_link_libraries(${NAME} PRIVATE filament math filamat utils)
|
||||
target_link_libraries(${NAME} PRIVATE filament math utils stb)
|
||||
|
||||
# Copy the generated js and wasm files into the public folder, as well as the app-specific
|
||||
# manually-written HTML file.
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_STDIO
|
||||
#define STBI_ONLY_PNG
|
||||
#include <stb_image.h>
|
||||
|
||||
using namespace filament;
|
||||
using namespace std;
|
||||
|
||||
@@ -56,31 +61,20 @@ Asset getRawFile(const char* name) {
|
||||
}
|
||||
|
||||
Asset getTexture(const char* name) {
|
||||
// Obtain image dimensions from JavaScript.
|
||||
uint32_t dims[2];
|
||||
EM_ASM({
|
||||
var dims = $0 >> 2;
|
||||
var name = UTF8ToString($1);
|
||||
HEAP32[dims] = assets[name].width;
|
||||
HEAP32[dims+1] = assets[name].height;
|
||||
}, dims, name);
|
||||
const uint32_t nbytes = dims[0] * dims[1] * 4;
|
||||
|
||||
// Move the data from JavaScript.
|
||||
Asset result = getRawFile(name);
|
||||
int width, height, ncomp;
|
||||
stbi_info_from_memory(result.data.get(), result.nbytes, &width, &height, &ncomp);
|
||||
const uint32_t nbytes = width * height * 4;
|
||||
uint8_t* texels = new uint8_t[nbytes];
|
||||
EM_ASM({
|
||||
var texels = $0;
|
||||
var name = UTF8ToString($1);
|
||||
var nbytes = $2;
|
||||
HEAPU8.set(assets[name].data, texels);
|
||||
assets[name].data = null;
|
||||
}, texels, name, nbytes);
|
||||
|
||||
stbi_uc* decoded = stbi_load_from_memory(result.data.get(), result.nbytes, &width, &height,
|
||||
&ncomp, 4);
|
||||
memcpy(texels, decoded, nbytes);
|
||||
stbi_image_free(decoded);
|
||||
return {
|
||||
.data = decltype(Asset::data)(texels),
|
||||
.nbytes = nbytes,
|
||||
.width = dims[0],
|
||||
.height = dims[1]
|
||||
.width = uint32_t(width),
|
||||
.height = uint32_t(height)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,17 +90,6 @@ Asset getCubemap(const char* name) {
|
||||
HEAP32[nmips] = assets[name].nmips;
|
||||
}, &nmips, name, &prefix[0]);
|
||||
|
||||
// Obtain dimensions of a face from miplevel 0.
|
||||
uint32_t dims[2];
|
||||
EM_ASM({
|
||||
var dims = $0 >> 2;
|
||||
var name = UTF8ToString($1);
|
||||
var face = UTF8ToString($2);
|
||||
var key = assets[name].name + face;
|
||||
HEAP32[dims] = assets[key].width;
|
||||
HEAP32[dims+1] = assets[key].height;
|
||||
}, dims, name, "m0_px.rgbm");
|
||||
|
||||
// Build a flat list of mips for each cubemap face.
|
||||
Asset* envFaces = new Asset[nmips * 6];
|
||||
for (uint32_t mip = 0, i = 0; mip < nmips; ++mip) {
|
||||
@@ -130,12 +113,12 @@ Asset getCubemap(const char* name) {
|
||||
string key = string(prefix) + suffix;
|
||||
skyFaces[i++] = getTexture(key.c_str());
|
||||
};
|
||||
get("px.png");
|
||||
get("nx.png");
|
||||
get("py.png");
|
||||
get("ny.png");
|
||||
get("pz.png");
|
||||
get("nz.png");
|
||||
get("px.rgbm");
|
||||
get("nx.rgbm");
|
||||
get("py.rgbm");
|
||||
get("ny.rgbm");
|
||||
get("pz.rgbm");
|
||||
get("nz.rgbm");
|
||||
|
||||
// Load the spherical harmonics coefficients.
|
||||
Asset* shCoeffs = new Asset;
|
||||
@@ -145,8 +128,8 @@ Asset getCubemap(const char* name) {
|
||||
return {
|
||||
.data = decltype(Asset::data)(),
|
||||
.nbytes = 0,
|
||||
.width = dims[0],
|
||||
.height = dims[1],
|
||||
.width = envFaces[0].width,
|
||||
.height = envFaces[0].height,
|
||||
.envMipCount = nmips,
|
||||
.envShCoeffs = decltype(Asset::envShCoeffs)(shCoeffs),
|
||||
.envFaces = decltype(Asset::envFaces)(envFaces),
|
||||
|
||||
@@ -66,35 +66,8 @@ function load_rawfile(url) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
let context2d = document.createElement('canvas').getContext('2d');
|
||||
|
||||
// This function does PNG decoding (or JPEG, or whatever), which allows us to avoid including a
|
||||
// texture decoder (such as stb_image) in the WebAssembly module. It always returns RGBA8 data,
|
||||
// regardless of how it is stored in the image file.
|
||||
function load_texture(url) {
|
||||
let promise = new Promise((success, failure) => {
|
||||
let img = new Image();
|
||||
img.src = url;
|
||||
img.onerror = failure;
|
||||
img.onload = () => {
|
||||
context2d.canvas.width = img.width;
|
||||
context2d.canvas.height = img.height;
|
||||
context2d.width = img.width;
|
||||
context2d.height = img.height;
|
||||
context2d.globalCompositeOperation = 'copy';
|
||||
context2d.drawImage(img, 0, 0);
|
||||
var imgdata = context2d.getImageData(0, 0, img.width, img.height).data.buffer;
|
||||
success({
|
||||
kind: 'image',
|
||||
name: url,
|
||||
data: new Uint8Array(imgdata),
|
||||
width: img.width,
|
||||
height: img.height
|
||||
});
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
// The C++ layer uses STB to decode the PNG contents into RGBA data.
|
||||
let load_texture = load_rawfile;
|
||||
|
||||
function load_cubemap(urlprefix, nmips) {
|
||||
let promises = {};
|
||||
@@ -107,7 +80,7 @@ function load_cubemap(urlprefix, nmips) {
|
||||
}
|
||||
}
|
||||
for (let face of 'px nx py ny pz nz'.split(' ')) {
|
||||
name = face + '.png';
|
||||
name = face + '.rgbm';
|
||||
promises[name] = load_texture(urlprefix + name);
|
||||
}
|
||||
let numberRemaining = Object.keys(promises).length;
|
||||
|
||||
Reference in New Issue
Block a user