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; +}