diff --git a/src/webgl/light.js b/src/webgl/light.js
index 363d0772e5..6729cc18bc 100644
--- a/src/webgl/light.js
+++ b/src/webgl/light.js
@@ -115,7 +115,7 @@ import p5 from '../core/main';
* @param {p5.Color} color color as a p5.Color
* @chainable
*/
-p5.prototype.ambientLight = function(v1, v2, v3, a) {
+p5.prototype.ambientLight = function (v1, v2, v3, a) {
this._assert3d('ambientLight');
p5._validateParameters('ambientLight', arguments);
const color = this.color(...arguments);
@@ -229,7 +229,7 @@ p5.prototype.ambientLight = function(v1, v2, v3, a) {
* @param {p5.Color} color color as a p5.Color
* @chainable
*/
-p5.prototype.specularColor = function(v1, v2, v3) {
+p5.prototype.specularColor = function (v1, v2, v3) {
this._assert3d('specularColor');
p5._validateParameters('specularColor', arguments);
const color = this.color(...arguments);
@@ -327,7 +327,7 @@ p5.prototype.specularColor = function(v1, v2, v3) {
* @param {p5.Vector} direction
* @chainable
*/
-p5.prototype.directionalLight = function(v1, v2, v3, x, y, z) {
+p5.prototype.directionalLight = function (v1, v2, v3, x, y, z) {
this._assert3d('directionalLight');
p5._validateParameters('directionalLight', arguments);
@@ -454,7 +454,7 @@ p5.prototype.directionalLight = function(v1, v2, v3, x, y, z) {
* @param {p5.Vector} position
* @chainable
*/
-p5.prototype.pointLight = function(v1, v2, v3, x, y, z) {
+p5.prototype.pointLight = function (v1, v2, v3, x, y, z) {
this._assert3d('pointLight');
p5._validateParameters('pointLight', arguments);
@@ -592,13 +592,60 @@ p5.prototype.pointLight = function(v1, v2, v3, x, y, z) {
* @alt
* light with slider having a slider for varying roughness
*/
-p5.prototype.imageLight = function(img){
+p5.prototype.imageLight = function (img) {
// activeImageLight property is checked by _setFillUniforms
// for sending uniforms to the fillshader
this._renderer.activeImageLight = img;
this._renderer._enableLighting = true;
};
+/**
+ * The `panorama(img)` method transforms images containing
+ * 360-degree content, such as maps or HDRIs, into immersive
+ * 3D backgrounds that surround your scene. This is similar to calling
+ * `background(color)`; call `panorama(img)` before drawing your
+ * scene to create a 360-degree background from your image. It
+ * operates on the concept of sphere mapping, where the image is
+ * mapped onto an infinitely large sphere based on the angles of the
+ * camera.
+ *
+ * To enable 360-degree viewing, either use `orbitControl()` or try changing
+ * the orientation of the camera to see different parts of the background.
+ *
+ * @method panorama
+ * @param {p5.Image} img A 360-degree image to use as a background panorama
+ * @example
+ *
+ *
+ * let img;
+ * function preload() {
+ * img = loadImage('assets/outdoor_spheremap.jpg');
+ * }
+ * function setup() {
+ * createCanvas(100 ,100 ,WEBGL);
+ * }
+ * function draw() {
+ * panorama(img);
+ * imageMode(CENTER);
+ * orbitControl();
+ * noStroke();
+ * push();
+ * imageLight(img);
+ * specularMaterial('green');
+ * shininess(200);
+ * metalness(100);
+ * sphere(25);
+ * pop();
+ * }
+ *
+ *
+ * @alt
+ * The image transformed into a panoramic scene.
+ */
+p5.prototype.panorama = function (img) {
+ this.filter(this._renderer._getSphereMapping(img));
+};
+
/**
* Places an ambient and directional light in the scene.
* The lights are set to ambientLight(128, 128, 128) and
@@ -634,7 +681,7 @@ p5.prototype.imageLight = function(img){
* @alt
* the light is partially ambient and partially directional
*/
-p5.prototype.lights = function() {
+p5.prototype.lights = function () {
this._assert3d('lights');
// Both specify gray by default.
const grayColor = this.color('rgb(128,128,128)');
@@ -698,7 +745,7 @@ p5.prototype.lights = function() {
* @alt
* Two spheres with different falloff values show different intensity of light
*/
-p5.prototype.lightFalloff = function(
+p5.prototype.lightFalloff = function (
constantAttenuation,
linearAttenuation,
quadraticAttenuation
@@ -892,7 +939,7 @@ p5.prototype.lightFalloff = function(
* @param {Number} [angle]
* @param {Number} [concentration]
*/
-p5.prototype.spotLight = function(
+p5.prototype.spotLight = function (
v1,
v2,
v3,
@@ -1153,7 +1200,7 @@ p5.prototype.spotLight = function(
* Three white spheres. Each appears as a different
* color due to lighting.
*/
-p5.prototype.noLights = function(...args) {
+p5.prototype.noLights = function (...args) {
this._assert3d('noLights');
p5._validateParameters('noLights', args);
diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js
index 7f1069bfd5..5dac06ad24 100644
--- a/src/webgl/p5.RendererGL.js
+++ b/src/webgl/p5.RendererGL.js
@@ -42,6 +42,10 @@ const webgl2CompatibilityShader = readFileSync(
);
const defaultShaders = {
+ sphereMappingFrag: readFileSync(
+ join(__dirname, '/shaders/sphereMapping.frag'),
+ 'utf-8'
+ ),
immediateVert: readFileSync(
join(__dirname, '/shaders/immediate.vert'),
'utf-8'
@@ -80,6 +84,7 @@ const defaultShaders = {
imageLightDiffusedFrag: readFileSync(join(__dirname, '/shaders/imageLightDiffused.frag'), 'utf-8'),
imageLightSpecularFrag: readFileSync(join(__dirname, '/shaders/imageLightSpecular.frag'), 'utf-8')
};
+let sphereMapping = defaultShaders.sphereMappingFrag;
for (const key in defaultShaders) {
defaultShaders[key] = webgl2CompatibilityShader + defaultShaders[key];
}
@@ -497,7 +502,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this.curStrokeColor = this._cachedStrokeStyle = [0, 0, 0, 1];
this.curBlendMode = constants.BLEND;
- this.preEraseBlend=undefined;
+ this.preEraseBlend = undefined;
this._cachedBlendMode = undefined;
if (this.webglVersion === constants.WEBGL2) {
this.blendExt = this.GL;
@@ -559,6 +564,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this.executeRotateAndMove = false;
this.specularShader = undefined;
+ this.sphereMapping = undefined;
this.diffusedShader = undefined;
this._defaultLightShader = undefined;
this._defaultImmediateModeShader = undefined;
@@ -1127,6 +1133,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this._pInst.resetMatrix();
this._pInst.image(fbo, -target.width / 2, -target.height / 2,
target.width, target.height);
+ this._pInst.clearDepth();
this._pInst.pop();
this._pInst.pop();
}
@@ -1186,7 +1193,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this.curFillColor = this._cachedFillStyle.slice();
this.curStrokeColor = this._cachedStrokeStyle.slice();
// Restore blend mode
- this.curBlendMode=this.preEraseBlend;
+ this.curBlendMode = this.preEraseBlend;
this.blendMode(this.preEraseBlend);
// Ensure that _applyBlendMode() sets preEraseBlend back to the original blend mode
this._isErasing = false;
@@ -1676,6 +1683,21 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
return this._getImmediateStrokeShader();
}
+ _getSphereMapping(img) {
+ if (!this.sphereMapping) {
+ this.sphereMapping = this._pInst.createFilterShader(
+ sphereMapping
+ );
+ }
+ this.uNMatrix.inverseTranspose(this.uMVMatrix);
+ this.uNMatrix.invert3x3(this.uNMatrix);
+ this.sphereMapping.setUniform('uFovY', this._curCamera.cameraFOV);
+ this.sphereMapping.setUniform('uAspect', this._curCamera.aspectRatio);
+ this.sphereMapping.setUniform('uNewNormalMatrix', this.uNMatrix.mat3);
+ this.sphereMapping.setUniform('uSampler', img);
+ return this.sphereMapping;
+ }
+
/*
* selects which fill shader should be used based on renderer state,
* for use with begin/endShape and immediate vertex mode.
@@ -2141,9 +2163,9 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
}
/* Binds a buffer to the drawing context
- * when passed more than two arguments it also updates or initializes
- * the data associated with the buffer
- */
+ * when passed more than two arguments it also updates or initializes
+ * the data associated with the buffer
+ */
_bindBuffer(
buffer,
target,
diff --git a/src/webgl/shaders/sphereMapping.frag b/src/webgl/shaders/sphereMapping.frag
new file mode 100644
index 0000000000..baed3b390a
--- /dev/null
+++ b/src/webgl/shaders/sphereMapping.frag
@@ -0,0 +1,24 @@
+#define PI 3.141592
+
+precision highp float;
+
+uniform sampler2D uSampler;
+uniform mat3 uNewNormalMatrix;
+uniform float uFovY;
+uniform float uAspect;
+
+varying vec2 vTexCoord;
+
+void main() {
+ float uFovX = uFovY * uAspect;
+ vec4 newTexColor = texture2D(uSampler, vTexCoord);
+ float angleY = mix(uFovY/2.0, -uFovY/2.0, vTexCoord.y);
+ float angleX = mix(uFovX/2.0, -uFovX/2.0, vTexCoord.x);
+ vec3 rotatedNormal = vec3( angleX, angleY, 1.0 );
+ rotatedNormal = uNewNormalMatrix * normalize(rotatedNormal);
+ vec2 suv;
+ suv.y = 0.5 + 0.5 * (-rotatedNormal.y);
+ suv.x = atan(rotatedNormal.z, rotatedNormal.x) / (2.0 * PI) + 0.5;
+ newTexColor = texture2D(uSampler, suv.xy);
+ gl_FragColor = newTexColor;
+}