217 lines
6.4 KiB
JavaScript
217 lines
6.4 KiB
JavaScript
// Spherical Harmonic Helpers
|
|
import Texture from '../Texture';
|
|
import FrameBuffer from '../FrameBuffer';
|
|
import Texture2D from '../Texture2D';
|
|
import Pass from '../compositor/Pass';
|
|
import vendor from '../core/vendor';
|
|
import Skybox from '../plugin/Skybox';
|
|
import Skydome from '../plugin/Skydome';
|
|
import EnvironmentMapPass from '../prePass/EnvironmentMap';
|
|
import Scene from '../Scene';
|
|
import vec3 from '../glmatrix/vec3';
|
|
var sh = {};
|
|
|
|
import projectEnvMapShaderCode from './shader/projectEnvMap.glsl.js';
|
|
|
|
var targets = ['px', 'nx', 'py', 'ny', 'pz', 'nz'];
|
|
|
|
// Project on gpu, but needs browser to support readPixels as Float32Array.
|
|
function projectEnvironmentMapGPU(renderer, envMap) {
|
|
var shTexture = new Texture2D({
|
|
width: 9,
|
|
height: 1,
|
|
type: Texture.FLOAT
|
|
});
|
|
var pass = new Pass({
|
|
fragment: projectEnvMapShaderCode
|
|
});
|
|
pass.material.define('fragment', 'TEXTURE_SIZE', envMap.width);
|
|
pass.setUniform('environmentMap', envMap);
|
|
|
|
var framebuffer = new FrameBuffer();
|
|
framebuffer.attach(shTexture);
|
|
pass.render(renderer, framebuffer);
|
|
|
|
framebuffer.bind(renderer);
|
|
// TODO Only chrome and firefox support Float32Array
|
|
var pixels = new vendor.Float32Array(9 * 4);
|
|
renderer.gl.readPixels(0, 0, 9, 1, Texture.RGBA, Texture.FLOAT, pixels);
|
|
|
|
var coeff = new vendor.Float32Array(9 * 3);
|
|
for (var i = 0; i < 9; i++) {
|
|
coeff[i * 3] = pixels[i * 4];
|
|
coeff[i * 3 + 1] = pixels[i * 4 + 1];
|
|
coeff[i * 3 + 2] = pixels[i * 4 + 2];
|
|
}
|
|
framebuffer.unbind(renderer);
|
|
|
|
framebuffer.dispose(renderer);
|
|
pass.dispose(renderer);
|
|
return coeff;
|
|
}
|
|
|
|
function harmonics(normal, index){
|
|
var x = normal[0];
|
|
var y = normal[1];
|
|
var z = normal[2];
|
|
|
|
if (index === 0) {
|
|
return 1.0;
|
|
}
|
|
else if (index === 1) {
|
|
return x;
|
|
}
|
|
else if (index === 2) {
|
|
return y;
|
|
}
|
|
else if (index === 3) {
|
|
return z;
|
|
}
|
|
else if (index === 4) {
|
|
return x * z;
|
|
}
|
|
else if (index === 5) {
|
|
return y * z;
|
|
}
|
|
else if (index === 6) {
|
|
return x * y;
|
|
}
|
|
else if (index === 7) {
|
|
return 3.0 * z * z - 1.0;
|
|
}
|
|
else {
|
|
return x * x - y * y;
|
|
}
|
|
}
|
|
|
|
var normalTransform = {
|
|
px: [2, 1, 0, -1, -1, 1],
|
|
nx: [2, 1, 0, 1, -1, -1],
|
|
py: [0, 2, 1, 1, -1, -1],
|
|
ny: [0, 2, 1, 1, 1, 1],
|
|
pz: [0, 1, 2, -1, -1, -1],
|
|
nz: [0, 1, 2, 1, -1, 1]
|
|
};
|
|
|
|
// Project on cpu.
|
|
function projectEnvironmentMapCPU(renderer, cubePixels, width, height) {
|
|
var coeff = new vendor.Float32Array(9 * 3);
|
|
var normal = vec3.create();
|
|
var texel = vec3.create();
|
|
var fetchNormal = vec3.create();
|
|
for (var m = 0; m < 9; m++) {
|
|
var result = vec3.create();
|
|
for (var k = 0; k < targets.length; k++) {
|
|
var pixels = cubePixels[targets[k]];
|
|
|
|
var sideResult = vec3.create();
|
|
var divider = 0;
|
|
var i = 0;
|
|
var transform = normalTransform[targets[k]];
|
|
for (var y = 0; y < height; y++) {
|
|
for (var x = 0; x < width; x++) {
|
|
|
|
normal[0] = x / (width - 1.0) * 2.0 - 1.0;
|
|
// TODO Flip y?
|
|
normal[1] = y / (height - 1.0) * 2.0 - 1.0;
|
|
normal[2] = -1.0;
|
|
vec3.normalize(normal, normal);
|
|
|
|
fetchNormal[0] = normal[transform[0]] * transform[3];
|
|
fetchNormal[1] = normal[transform[1]] * transform[4];
|
|
fetchNormal[2] = normal[transform[2]] * transform[5];
|
|
|
|
texel[0] = pixels[i++] / 255;
|
|
texel[1] = pixels[i++] / 255;
|
|
texel[2] = pixels[i++] / 255;
|
|
// RGBM Decode
|
|
var scale = pixels[i++] / 255 * 8.12;
|
|
texel[0] *= scale;
|
|
texel[1] *= scale;
|
|
texel[2] *= scale;
|
|
|
|
vec3.scaleAndAdd(sideResult, sideResult, texel, harmonics(fetchNormal, m) * -normal[2]);
|
|
// -normal.z equals cos(theta) of Lambertian
|
|
divider += -normal[2];
|
|
}
|
|
}
|
|
vec3.scaleAndAdd(result, result, sideResult, 1 / divider);
|
|
}
|
|
|
|
coeff[m * 3] = result[0] / 6.0;
|
|
coeff[m * 3 + 1] = result[1] / 6.0;
|
|
coeff[m * 3 + 2] = result[2] / 6.0;
|
|
}
|
|
return coeff;
|
|
}
|
|
|
|
/**
|
|
* @param {clay.Renderer} renderer
|
|
* @param {clay.Texture} envMap
|
|
* @param {Object} [textureOpts]
|
|
* @param {Object} [textureOpts.lod]
|
|
* @param {boolean} [textureOpts.decodeRGBM]
|
|
*/
|
|
sh.projectEnvironmentMap = function (renderer, envMap, opts) {
|
|
|
|
// TODO sRGB
|
|
|
|
opts = opts || {};
|
|
opts.lod = opts.lod || 0;
|
|
|
|
var skybox;
|
|
var dummyScene = new Scene();
|
|
var size = 64;
|
|
if (envMap.textureType === 'texture2D') {
|
|
skybox = new Skydome({
|
|
scene: dummyScene,
|
|
environmentMap: envMap
|
|
});
|
|
}
|
|
else {
|
|
size = (envMap.image && envMap.image.px) ? envMap.image.px.width : envMap.width;
|
|
skybox = new Skybox({
|
|
scene: dummyScene,
|
|
environmentMap: envMap
|
|
});
|
|
}
|
|
// Convert to rgbm
|
|
var width = Math.ceil(size / Math.pow(2, opts.lod));
|
|
var height = Math.ceil(size / Math.pow(2, opts.lod));
|
|
var rgbmTexture = new Texture2D({
|
|
width: width,
|
|
height: height
|
|
});
|
|
var framebuffer = new FrameBuffer();
|
|
skybox.material.define('fragment', 'RGBM_ENCODE');
|
|
if (opts.decodeRGBM) {
|
|
skybox.material.define('fragment', 'RGBM_DECODE');
|
|
}
|
|
skybox.material.set('lod', opts.lod);
|
|
var envMapPass = new EnvironmentMapPass({
|
|
texture: rgbmTexture
|
|
});
|
|
var cubePixels = {};
|
|
for (var i = 0; i < targets.length; i++) {
|
|
cubePixels[targets[i]] = new Uint8Array(width * height * 4);
|
|
var camera = envMapPass.getCamera(targets[i]);
|
|
camera.fov = 90;
|
|
framebuffer.attach(rgbmTexture);
|
|
framebuffer.bind(renderer);
|
|
renderer.render(dummyScene, camera);
|
|
renderer.gl.readPixels(
|
|
0, 0, width, height,
|
|
Texture.RGBA, Texture.UNSIGNED_BYTE, cubePixels[targets[i]]
|
|
);
|
|
framebuffer.unbind(renderer);
|
|
}
|
|
|
|
skybox.dispose(renderer);
|
|
framebuffer.dispose(renderer);
|
|
rgbmTexture.dispose(renderer);
|
|
|
|
return projectEnvironmentMapCPU(renderer, cubePixels, width, height);
|
|
};
|
|
|
|
export default sh;
|