Skip to content

Commit 32ed46b

Browse files
committed
Refactor imageLight to work in WebGPU
1 parent c7f940a commit 32ed46b

7 files changed

Lines changed: 670 additions & 129 deletions

File tree

preview/index.html

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@
2929
let tex;
3030
let font;
3131
let redFilter;
32+
let env;
3233

3334
p.setup = async function () {
34-
await p.createCanvas(400, 400, p.WEBGPU);
35+
await p.createCanvas(400, 400, p.WEBGL);
36+
env = await p.loadImage('img/spheremap.jpg');
3537
font = await p.loadFont(
3638
'font/PlayfairDisplay.ttf'
3739
);
@@ -97,7 +99,7 @@
9799
p.draw = function () {
98100
p.clear();
99101
p.push();
100-
p.clip(() => p.rect(-50, -50, 200, 200));
102+
//p.clip(() => p.rect(-50, -50, 200, 200));
101103
/*p.orbitControl();
102104
p.push();
103105
p.textAlign(p.CENTER, p.CENTER);
@@ -116,16 +118,19 @@
116118
p.orbitControl();
117119
const t = p.millis() * 0.002;
118120
p.background(200);
121+
p.push();
122+
p.imageLight(env);
119123
p.shader(sh);
120124
// p.strokeShader(ssh)
121-
p.ambientLight(150);
122-
p.directionalLight(100, 100, 100, 0, 1, -1);
123-
p.pointLight(155, 155, 155, 0, -200, 500);
125+
p.ambientLight(10);
126+
//p.directionalLight(100, 100, 100, 0, 1, -1);
127+
//p.pointLight(155, 155, 155, 0, -200, 500);
124128
p.specularMaterial(255);
125-
p.shininess(300);
129+
p.shininess(50);
130+
p.metalness(100);
126131
//p.stroke('white');
127132
p.noStroke();
128-
for (const [i, c] of ['red', 'lime', 'blue'].entries()) {
133+
for (const [i, c] of ['red', 'gray', 'blue'].entries()) {
129134
p.push();
130135
p.fill(c);
131136
p.translate(
@@ -143,6 +148,7 @@
143148
}
144149
p.pop();
145150
}
151+
p.pop();
146152

147153
// Test beginShape/endShape with immediate mode shapes
148154
p.push();
@@ -174,7 +180,7 @@
174180

175181
p.pop();
176182

177-
p.filter(p.BLUR, 10)
183+
// p.filter(p.BLUR, 10)
178184
p.pop();
179185
};
180186
};

src/core/p5.Renderer3D.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,6 +1820,152 @@ export class Renderer3D extends Renderer {
18201820
return makeFilterShader(this, operation, p5);
18211821
}
18221822

1823+
/*
1824+
* As part of imageLight(): we need to create a texture representing
1825+
* the diffused light hitting an object from each angle. This will
1826+
* accumulate light from angles in a hemisphere, weighted according to
1827+
* how head-on the light angle is.
1828+
*
1829+
* This method returns a p5.Framebuffer that stores these values, mapping
1830+
* an angle to each pixel. This creates and caches textures for reuse, since
1831+
* creating this texture is somewhat expensive.
1832+
*/
1833+
getDiffusedTexture(input) {
1834+
// if one already exists for a given input image
1835+
if (this.diffusedTextures.get(input) != null) {
1836+
return this.diffusedTextures.get(input);
1837+
}
1838+
// if not, only then create one
1839+
let newFramebuffer;
1840+
// hardcoded to 200px, because it's going to be blurry and smooth
1841+
let smallWidth = 200;
1842+
let width = smallWidth;
1843+
let height = Math.floor(smallWidth * (input.height / input.width));
1844+
newFramebuffer = new Framebuffer(this, {
1845+
width,
1846+
height,
1847+
density: 1,
1848+
});
1849+
// create framebuffer is like making a new sketch, all functions on main
1850+
// sketch it would be available on framebuffer
1851+
if (!this.diffusedShader) {
1852+
this.diffusedShader = this._createImageLightShader("diffused");
1853+
}
1854+
newFramebuffer.draw(() => {
1855+
this.shader(this.diffusedShader);
1856+
this._setImageLightShaderUniforms(this.diffusedShader, input);
1857+
this.states.setValue("strokeColor", null);
1858+
this.noLights();
1859+
this.plane(width, height);
1860+
});
1861+
this.diffusedTextures.set(input, newFramebuffer);
1862+
return newFramebuffer;
1863+
}
1864+
1865+
/*
1866+
* used in imageLight,
1867+
* To create a texture from the input non blurry image, if it doesn't already exist
1868+
* Creating 8 different levels of textures according to different
1869+
* sizes and storing them in `levels` array
1870+
* Creating a new Mipmap texture with that `levels` array
1871+
* Storing the texture for input image in map called `specularTextures`
1872+
* maps the input Image to a p5.MipmapTexture
1873+
*/
1874+
getSpecularTexture(input) {
1875+
// check if already exits (there are tex of diff resolution so which one to check)
1876+
// currently doing the whole array
1877+
if (this.specularTextures.get(input) != null) {
1878+
return this.specularTextures.get(input);
1879+
}
1880+
// Hardcoded size
1881+
const size = 512;
1882+
let tex;
1883+
let count = Math.floor(Math.log2(size)) + 1; // Actual number of mip levels from size down to 1x1
1884+
1885+
if (!this.specularShader) {
1886+
this.specularShader = this._createImageLightShader("specular");
1887+
}
1888+
1889+
// Prepare mipmap level accumulator
1890+
const mipmapData = this._prepareMipmapData(size, count);
1891+
1892+
const framebuffer = new Framebuffer(this, {
1893+
width: size,
1894+
height: size,
1895+
density: 1,
1896+
});
1897+
1898+
// currently only 8 levels
1899+
// This loop calculates 8 framebuffers of varying size of canvas
1900+
// and corresponding different roughness levels.
1901+
// Roughness increases with the decrease in canvas size,
1902+
// because rougher surfaces have less detailed/more blurry reflections.
1903+
let mipLevel = 0;
1904+
for (let w = size; w >= 1; w /= 2) {
1905+
framebuffer.resize(w, w);
1906+
let currCount = Math.log(w) / Math.log(2);
1907+
let roughness = 1 - currCount / count;
1908+
framebuffer.draw(() => {
1909+
this.shader(this.specularShader);
1910+
this.clear();
1911+
this._setImageLightShaderUniforms(
1912+
this.specularShader,
1913+
input,
1914+
roughness,
1915+
);
1916+
this.states.setValue("strokeColor", null);
1917+
this.noLights();
1918+
this.plane(w, w);
1919+
});
1920+
1921+
// Accumulate framebuffer content for this mip level
1922+
this._accumulateMipLevel(framebuffer, mipmapData, mipLevel, w, w);
1923+
mipLevel++;
1924+
}
1925+
1926+
// Free the Framebuffer
1927+
framebuffer.remove();
1928+
1929+
// Create the final MipmapTexture from accumulated data
1930+
tex = this._finalizeMipmapTexture(mipmapData);
1931+
this.specularTextures.set(input, tex);
1932+
return tex;
1933+
}
1934+
1935+
/*
1936+
* Abstract methods to be implemented by specific renderers
1937+
*/
1938+
_createImageLightShader(type) {
1939+
throw new Error(
1940+
"_createImageLightShader must be implemented by the renderer",
1941+
);
1942+
}
1943+
1944+
_setImageLightShaderUniforms(shader, input, roughness) {
1945+
shader.setUniform("environmentMap", input);
1946+
if (roughness !== undefined) {
1947+
shader.setUniform("roughness", roughness);
1948+
}
1949+
}
1950+
1951+
_createMipmapTexture(levels) {
1952+
throw new Error("_createMipmapTexture must be implemented by the renderer");
1953+
}
1954+
1955+
_prepareMipmapData(size, mipLevels) {
1956+
throw new Error("_prepareMipmapData must be implemented by the renderer");
1957+
}
1958+
1959+
_accumulateMipLevel(framebuffer, mipmapData, mipLevel, width, height) {
1960+
throw new Error("_accumulateMipLevel must be implemented by the renderer");
1961+
}
1962+
1963+
_finalizeMipmapTexture(mipmapData) {
1964+
throw new Error(
1965+
"_finalizeMipmapTexture must be implemented by the renderer",
1966+
);
1967+
}
1968+
18231969
remove() {
18241970
if (this._textCanvas) {
18251971
this._textCanvas.parentElement.removeChild(this._textCanvas);

src/webgl/p5.RendererGL.js

Lines changed: 64 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -887,106 +887,82 @@ class RendererGL extends Renderer3D {
887887
return code;
888888
}
889889

890-
// TODO move to super class
891890
/*
892-
* used in imageLight,
893-
* To create a blurry image from the input non blurry img, if it doesn't already exist
894-
* Add it to the diffusedTexture map,
895-
* Returns the blurry image
896-
* maps a Image used by imageLight() to a p5.Framebuffer
891+
* WebGL-specific implementation of imageLight shader creation
897892
*/
898-
getDiffusedTexture(input) {
899-
// if one already exists for a given input image
900-
if (this.diffusedTextures.get(input) != null) {
901-
return this.diffusedTextures.get(input);
902-
}
903-
// if not, only then create one
904-
let newFramebuffer;
905-
// hardcoded to 200px, because it's going to be blurry and smooth
906-
let smallWidth = 200;
907-
let width = smallWidth;
908-
let height = Math.floor(smallWidth * (input.height / input.width));
909-
newFramebuffer = new Framebuffer(this, {
910-
width,
911-
height,
912-
density: 1,
913-
});
914-
// create framebuffer is like making a new sketch, all functions on main
915-
// sketch it would be available on framebuffer
916-
if (!this.diffusedShader) {
917-
this.diffusedShader = this._pInst.createShader(
893+
_createImageLightShader(type) {
894+
if (type === 'diffused') {
895+
return this._pInst.createShader(
918896
defaultShaders.imageLightVert,
919897
defaultShaders.imageLightDiffusedFrag
920898
);
899+
} else if (type === 'specular') {
900+
return this._pInst.createShader(
901+
defaultShaders.imageLightVert,
902+
defaultShaders.imageLightSpecularFrag
903+
);
921904
}
922-
newFramebuffer.draw(() => {
923-
this.shader(this.diffusedShader);
924-
this.diffusedShader.setUniform("environmentMap", input);
925-
this.states.setValue("strokeColor", null);
926-
this.noLights();
927-
this.plane(width, height);
928-
});
929-
this.diffusedTextures.set(input, newFramebuffer);
930-
return newFramebuffer;
905+
throw new Error(`Unknown imageLight shader type: ${type}`);
931906
}
932907

933-
// TODO move to super class
908+
934909
/*
935-
* used in imageLight,
936-
* To create a texture from the input non blurry image, if it doesn't already exist
937-
* Creating 8 different levels of textures according to different
938-
* sizes and atoring them in `levels` array
939-
* Creating a new Mipmap texture with that `levels` array
940-
* Storing the texture for input image in map called `specularTextures`
941-
* maps the input Image to a p5.MipmapTexture
910+
* WebGL-specific implementation of mipmap texture creation
942911
*/
943-
getSpecularTexture(input) {
944-
// check if already exits (there are tex of diff resolution so which one to check)
945-
// currently doing the whole array
946-
if (this.specularTextures.get(input) != null) {
947-
return this.specularTextures.get(input);
948-
}
949-
// Hardcoded size
950-
const size = 512;
951-
let tex;
952-
const levels = [];
953-
const framebuffer = new Framebuffer(this, {
954-
width: size,
955-
height: size,
956-
density: 1,
957-
});
958-
let count = Math.log(size) / Math.log(2);
959-
if (!this.specularShader) {
960-
this.specularShader = this._pInst.createShader(
961-
defaultShaders.imageLightVert,
962-
defaultShaders.imageLightSpecularFrag
912+
_createMipmapTexture(levels) {
913+
return new MipmapTexture(this, levels, {});
914+
}
915+
916+
/*
917+
* Prepare array to collect ImageData levels for WebGL
918+
*/
919+
_prepareMipmapData(size, mipLevels) {
920+
return { levels: [], size, mipLevels };
921+
}
922+
923+
/*
924+
* Accumulate ImageData from framebuffer for WebGL
925+
*/
926+
_accumulateMipLevel(framebuffer, mipmapData, mipLevel, width, height) {
927+
const imageData = framebuffer.get().drawingContext.getImageData(0, 0, width, height);
928+
mipmapData.levels.push(imageData);
929+
}
930+
931+
/*
932+
* Create final MipmapTexture from collected ImageData for WebGL
933+
*/
934+
_finalizeMipmapTexture(mipmapData) {
935+
return new MipmapTexture(this, mipmapData.levels, {});
936+
}
937+
938+
createMipmapTextureHandle({ levels, format, dataType, width, height }) {
939+
const gl = this.GL;
940+
const texture = gl.createTexture();
941+
942+
gl.bindTexture(gl.TEXTURE_2D, texture);
943+
944+
// Determine GL format and data type
945+
const glFormat = gl.RGBA;
946+
const glDataType = gl.UNSIGNED_BYTE;
947+
948+
for (let level = 0; level < levels.length; level++) {
949+
gl.texImage2D(
950+
gl.TEXTURE_2D,
951+
level,
952+
glFormat,
953+
glFormat,
954+
glDataType,
955+
levels[level]
963956
);
964957
}
965-
// currently only 8 levels
966-
// This loop calculates 8 framebuffers of varying size of canvas
967-
// and corresponding different roughness levels.
968-
// Roughness increases with the decrease in canvas size,
969-
// because rougher surfaces have less detailed/more blurry reflections.
970-
for (let w = size; w >= 1; w /= 2) {
971-
framebuffer.resize(w, w);
972-
let currCount = Math.log(w) / Math.log(2);
973-
let roughness = 1 - currCount / count;
974-
framebuffer.draw(() => {
975-
this.shader(this.specularShader);
976-
this.clear();
977-
this.specularShader.setUniform("environmentMap", input);
978-
this.specularShader.setUniform("roughness", roughness);
979-
this.states.setValue("strokeColor", null);
980-
this.noLights();
981-
this.plane(w, w);
982-
});
983-
levels.push(framebuffer.get().drawingContext.getImageData(0, 0, w, w));
984-
}
985-
// Free the Framebuffer
986-
framebuffer.remove();
987-
tex = new MipmapTexture(this, levels, {});
988-
this.specularTextures.set(input, tex);
989-
return tex;
958+
959+
// Set mipmap-appropriate filtering
960+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
961+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
962+
963+
gl.bindTexture(gl.TEXTURE_2D, null);
964+
965+
return { texture, glFormat, glDataType };
990966
}
991967

992968
/* Binds a buffer to the drawing context

0 commit comments

Comments
 (0)