Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a Method (panorama(img)) which adds a sphereMapped Background. #6808

Merged
merged 17 commits into from
Feb 27, 2024
68 changes: 59 additions & 9 deletions src/webgl/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ import p5 from '../core/main';
* @param {p5.Color} color color as a <a href="#/p5.Color">p5.Color</a>
* @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);
Expand Down Expand Up @@ -229,7 +229,7 @@ p5.prototype.ambientLight = function(v1, v2, v3, a) {
* @param {p5.Color} color color as a <a href="#/p5.Color">p5.Color</a>
* @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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -592,13 +592,63 @@ 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;
};

/**
* Creates a Panorama with given image.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably take this line out if we move that last paragraph up to the top, it serves as a pretty good intro.

*
*
* `panorama(img)` is a method designed to transform a standard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could try to explain what kind of images this is intended to be used with? Possibly mentioning things like maps as a common example of a 360 degree view mapped to a rectangular image, and maybe how HDRIs are also in this format

* image into a 360-degree view. It operates on the concept
* of sphere mapping, where the image is manipulated to
* resemble a sphere by adjusting camera angles. Utilizing
* this method, users can obtain a complete 360-degree view
* of a scene.
*
* Using Panorama is straightforward. Similar to calling a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than describing what users can do, we can address this directly to users like instructions, e.g. "Similar to calling background(color), call panorama(img) before drawing your scene to create a 360-degree background out of your image." I think the paragraph above could be rephrased similarly.

Also, we generally stay away from calling anything simple or straightforward so that people don't feel bad if they struggle using something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section where we describe it as being similar to background is actually maybe a good thing to start this documentation with instead of ending it with it, what do you think?

* `background(color)`, users only need to call the `panorama(img)`, and
* beneath it, anything created will form a 360-degree scene.
* To enable 360-degree viewing, it is essential to invoke
* `orbitControl()`; otherwise, the method will not function as intended.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of saying it's essential, since it still works if you programmatically animate a camera, we can say to either use orbitControl or try changing the orientation of the camera to see different parts of the background.

* @method panorama
* @param {p5.image} img
perminder-17 marked this conversation as resolved.
Show resolved Hide resolved
* @example
* <div class="notest">
* <code>
* 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();
* }
* </code>
* </div>
* @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
Expand Down Expand Up @@ -634,7 +684,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)');
Expand Down Expand Up @@ -698,7 +748,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
Expand Down Expand Up @@ -892,7 +942,7 @@ p5.prototype.lightFalloff = function(
* @param {Number} [angle]
* @param {Number} [concentration]
*/
p5.prototype.spotLight = function(
p5.prototype.spotLight = function (
v1,
v2,
v3,
Expand Down Expand Up @@ -1153,7 +1203,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);

Expand Down
32 changes: 27 additions & 5 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
24 changes: 24 additions & 0 deletions src/webgl/shaders/sphereMapping.frag
Original file line number Diff line number Diff line change
@@ -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;
}
Loading