hefeihvac_java/node_modules/claygl/src/util/sh.js

217 lines
6.4 KiB
JavaScript
Raw Normal View History

2024-04-07 18:15:00 +08:00
// 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;