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
35 changes: 22 additions & 13 deletions src/webgl/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,23 +300,32 @@ p5.prototype.createFilterShader = function (fragSrc) {
}
`;
let defaultVertV2 = `#version 300 es
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;

in vec3 aPosition;
in vec2 aTexCoord;
out vec2 vTexCoord;
in vec3 aPosition;
in vec2 aTexCoord;
in vec3 aNormal;

void main() {
// transferring texcoords for the frag shader
vTexCoord = aTexCoord;
out vec3 faNormal;
out vec3 vNormal;
out vec3 faPosition;
out vec2 vTexCoord;
out vec3 fvNormal;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we don't use these in the shader, we can probably take them out from here


void main() {
// transferring vectors and texcoords for the frag shader
faNormal = aNormal;
fvNormal = vNormal;
faPosition = aPosition;
vTexCoord = aTexCoord;

// copy position with a fourth coordinate for projection (1.0 is normal)
vec4 positionVec4 = vec4(aPosition, 1.0);
// copy position with a fourth coordinate for projection (1.0 is normal)
vec4 positionVec4 = vec4(aPosition, 1.0);

// project to 3D space
gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
}
// project to 3D space
gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
}
`;
let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1;
const shader = new p5.Shader(this._renderer, vertSrc, fragSrc);
Expand Down
23 changes: 20 additions & 3 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 @@ -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 @@ -1012,6 +1018,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
}
filter(...args) {

this.uNMatrix.inverseTranspose(this.uMVMatrix);
let fbo = this.getFilterLayer();

// use internal shader for filter constants BLUR, INVERT, etc
Expand Down Expand Up @@ -1104,6 +1111,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this._pInst.noStroke();
this._pInst.blendMode(constants.BLEND);
this._pInst.shader(this.filterShader);
this.filterShader.setUniform('uNewNormalMatrix', this.uNMatrix.mat3);
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this only is used in the sphere mapping shader, can we do this and the inverseTranspose line in getSphereMapping where we're setting another uniform already?

this.filterShader.setUniform('tex0', target);
this.filterShader.setUniform('texelSize', texelSize);
this.filterShader.setUniform('canvasSize', [target.width, target.height]);
Expand Down Expand Up @@ -1613,6 +1621,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
properties.quadraticAttenuation = this.quadraticAttenuation;

properties._enableLighting = this._enableLighting;
properties.sphereMapping = this.sphereMapping;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the shader doesn't need to be pushed/popped since we can just create it once and then reuse it with different images, so we can probably take this line out

properties._useNormalMaterial = this._useNormalMaterial;
properties._tex = this._tex;
properties.drawMode = this.drawMode;
Expand Down Expand Up @@ -1676,6 +1685,14 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
return this._getImmediateStrokeShader();
}

_getSphereMapping(img) {
this.sphereMapping = this._pInst.createFilterShader(
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this is creating a new shader every frame. Maybe you can wrap lines 1689-1691 call in if (!this.sphereMapping) { ... }? I noticed the frame rate seems to drop in your video when you start using the sphere map background, I'm hoping it's just because of this and it'll run faster afterwards.

sphereMapping
);
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 +2158,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
32 changes: 32 additions & 0 deletions src/webgl/shaders/sphereMapping.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#version 300 es
Copy link
Contributor

Choose a reason for hiding this comment

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

For maximum compatibility, do you think we can do this one in GL ES 100? So, taking out the #version line, and using attribute instead of in, using the spacial variable gl_FragColor instead of defining an out fragColor, and using texture2D() instead of texture()?

Copy link
Contributor Author

@perminder-17 perminder-17 Feb 13, 2024

Choose a reason for hiding this comment

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

Oh.. you mean varying instead of in? I guess attribute is supported in vertex shaders only?

Copy link
Contributor

Choose a reason for hiding this comment

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

oops yes you're right, that should be varying

#define PI 3.141592

precision highp float;

uniform sampler2D uSampler;
uniform mat3 uNewNormalMatrix;
uniform mat3 uCameraRotation;
uniform mat4 uNewModelViewMatrix;
uniform mat4 uModelViewMatrix;

in vec2 vTexCoord;
in vec3 fvNormal;
in vec3 faNormal;
in vec3 faPosition;

out vec4 fragColor;

void main() {
vec4 viewModelPosition = uModelViewMatrix * vec4(faPosition, 1.0);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think uModelViewMatrix here is going to be made for the filter shader's rectangle, so I'm not sure it's what we want for doing reflections. Does it work if you set vViewPosition to all zeros, since we're assuming the position of the camera is negligible, assuming the texture is on an infinitely large sphere? And if so, can we just do n = normalize(vGlovalNormal.xyz) directly without doing a reflect?

vec3 vViewPosition = viewModelPosition.xyz;
vec4 newTexColor = texture(uSampler, vTexCoord);
vec3 vGlobalNormal = uNewNormalMatrix * faNormal ;
vec3 n = reflect(vViewPosition.xyz , (vGlobalNormal.xyz));
n = normalize(n);
vec2 suv;
suv.y = 0.5 + 0.5 * n.y;
suv.x = atan(n.z, n.x) / (2.0 * PI) + 0.5;
newTexColor = texture(uSampler, suv.xy);
Copy link
Contributor

Choose a reason for hiding this comment

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

nice!

vec4 baseColor = newTexColor;
fragColor = baseColor;
}
Loading