diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index f1e52b174e..e356580edf 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -9,10 +9,11 @@ import './p5.Matrix'; import './p5.Framebuffer'; import { readFileSync } from 'fs'; import { join } from 'path'; -import { MipmapTexture } from './p5.Texture'; +import { CubemapTexture, MipmapTexture } from './p5.Texture'; const STROKE_CAP_ENUM = {}; const STROKE_JOIN_ENUM = {}; + let lineDefs = ''; const defineStrokeCapEnum = function (key, val) { lineDefs += `#define STROKE_CAP_${key} ${val}\n`; @@ -83,7 +84,9 @@ const defaultShaders = { pointFrag: readFileSync(join(__dirname, '/shaders/point.frag'), 'utf-8'), imageLightVert: readFileSync(join(__dirname, '/shaders/imageLight.vert'), 'utf-8'), imageLightDiffusedFrag: readFileSync(join(__dirname, '/shaders/imageLightDiffused.frag'), 'utf-8'), - imageLightSpecularFrag: readFileSync(join(__dirname, '/shaders/imageLightSpecular.frag'), 'utf-8') + imageLightSpecularFrag: readFileSync(join(__dirname, '/shaders/imageLightSpecular.frag'), 'utf-8'), + cubemapVertexShader: readFileSync(join(__dirname, '/shaders/cubeVertex.vert'), 'utf-8'), + cubemapFragmentShader: readFileSync(join(__dirname, '/shaders/cubeFragment.frag'), 'utf-8') }; let sphereMapping = defaultShaders.sphereMappingFrag; for (const key in defaultShaders) { @@ -110,6 +113,14 @@ const filterShaderFrags = { }; const filterShaderVert = readFileSync(join(__dirname, '/shaders/filters/default.vert'), 'utf-8'); + +function renderCube() { + push(); // Save the current state of the drawing + translate(0, 0, 0); // Translate to the position where you want to draw the cube + box(2); // Draw a cube with a size of 2 units (you can adjust the size) + pop(); // Restore the previous state of the drawing +} + /** * @module Rendering * @submodule Rendering @@ -450,6 +461,19 @@ p5.RendererGL = class RendererGL extends p5.Renderer { this.GL = this.drawingContext; this._pInst._setProperty('drawingContext', this.drawingContext); + if (!this._pInst.shaderCache) { + this._pInst.shaderCache = {}; // ensures a unique shaderCache for each instance of p5.RendererGL + } + this.getCachedShader = + function (shaderKey, vertexShaderSource, fragmentShaderSource) { + if (!this._pInst.shaderCache[shaderKey]) { + + this._pInst.shaderCache[shaderKey] = this._pInst.createShader( + vertexShaderSource, fragmentShaderSource); + } + return this._pInst.shaderCache[shaderKey]; + }; + // erasing this._isErasing = false; @@ -574,6 +598,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { this._defaultNormalShader = undefined; this._defaultColorShader = undefined; this._defaultPointShader = undefined; + this._defaultCubemapShader=undefined; this.userFillShader = undefined; this.userStrokeShader = undefined; @@ -872,6 +897,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { this._pInst, !isPGraphics ); + this._pInst._setProperty('_renderer', renderer); renderer.resize(w, h); renderer._applyDefaults(); @@ -2051,6 +2077,15 @@ p5.RendererGL = class RendererGL extends p5.Renderer { return this._defaultFontShader; } + _getCubemapShader() { + return this.getCachedShader('_defaultCubemapShader', + this._webGL2CompatibilityPrefix('vert', 'mediump') + + defaultShaders.cubemapVertexShader, + this._webGL2CompatibilityPrefix('frag', 'mediump') + + defaultShaders.cubemapFragmentShader + ); + } + _webGL2CompatibilityPrefix( shaderType, floatPrecision @@ -2103,44 +2138,87 @@ p5.RendererGL = class RendererGL extends p5.Renderer { * maps a p5.Image used by imageLight() to a p5.Framebuffer */ getDiffusedTexture(input) { - // if one already exists for a given input image + // If one already exists for a given input image if (this.diffusedTextures.get(input) != null) { return this.diffusedTextures.get(input); } - // if not, only then create one - let newFramebuffer; - // hardcoded to 200px, because it's going to be blurry and smooth - let smallWidth = 200; - let width = smallWidth; - let height = Math.floor(smallWidth * (input.height / input.width)); - newFramebuffer = this._pInst.createFramebuffer({ - width, height, density: 1 - }); - // create framebuffer is like making a new sketch, all functions on main - // sketch it would be available on framebuffer - if (!this.diffusedShader) { - this.diffusedShader = this._pInst.createShader( - defaultShaders.imageLightVert, - defaultShaders.imageLightDiffusedFrag + + // Create a cubemap texture + const envCubemap = this.GL.createTexture(); + this.GL.bindTexture(this.GL.TEXTURE_CUBE_MAP, envCubemap); + + // Loop through each face and allocate storage + for (let i = 0; i < 6; ++i) { + this.GL.texImage2D( + this.GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, + this.GL.RGB16F, 512, 512, 0, + this.GL.RGB, this.GL.FLOAT, null ); } - newFramebuffer.draw(() => { - this._pInst.shader(this.diffusedShader); - this.diffusedShader.setUniform('environmentMap', input); - this._pInst.noStroke(); - this._pInst.rectMode(constants.CENTER); - this._pInst.noLights(); - this._pInst.rect(0, 0, width, height); + + // Set parameters for the cubemap + this.GL.texParameteri(this.GL.TEXTURE_CUBE_MAP + , this.GL.TEXTURE_WRAP_S, this.GL.CLAMP_TO_EDGE); + this.GL.texParameteri(this.GL.TEXTURE_CUBE_MAP + , this.GL.TEXTURE_WRAP_T, this.GL.CLAMP_TO_EDGE); + this.GL.texParameteri(this.GL.TEXTURE_CUBE_MAP + , this.GL.TEXTURE_WRAP_R, this.GL.CLAMP_TO_EDGE); + this.GL.texParameteri(this.GL.TEXTURE_CUBE_MAP + , this.GL.TEXTURE_MIN_FILTER, this.GL.LINEAR); + this.GL.texParameteri(this.GL.TEXTURE_CUBE_MAP + , this.GL.TEXTURE_MAG_FILTER, this.GL.LINEAR); + + // Get the cubemap shader + const cubemapShader = this._getCubemapShader(); + this._pInst.shader(cubemapShader); + + // Convert p5.Image to WebGLTexture + const texture = new p5.Texture(this, input); + const webglTexture = texture.glTex; + + cubemapShader.setUniform('equirectangularMap', webglTexture); + + this.GL.activeTexture(this.GL.TEXTURE0); + this.GL.bindTexture(this.GL.TEXTURE_2D, input); + + // Create a new framebuffer + let newFramebuffer = this._pInst.createFramebuffer({ + width: 512, + height: 512, + density: 1 }); - this.diffusedTextures.set(input, newFramebuffer); - return newFramebuffer; + + this.GL.bindFramebuffer(this.GL.FRAMEBUFFER, newFramebuffer.handle); + + // Render each face of the cubemap + for (let i = 0; i < 6; ++i) { + this.GL.framebufferTexture2D( + this.GL.FRAMEBUFFER, + this.GL.COLOR_ATTACHMENT0, + this.GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, + envCubemap, + 0 + ); + + this.GL.clear(this.GL.COLOR_BUFFER_BIT | this.GL.DEPTH_BUFFER_BIT); + + renderCube(); // Renders a 1x1 cube + } + + this.GL.bindFramebuffer(this.GL.FRAMEBUFFER, null); + + // Initialize CubemapTexture class with the cubemap texture + let cubemapTexture = new CubemapTexture(this, envCubemap, {}); + this.diffusedTextures.set(input, cubemapTexture); + + return cubemapTexture; } /* * used in imageLight, * To create a texture from the input non blurry image, if it doesn't already exist * Creating 8 different levels of textures according to different - * sizes and atoring them in `levels` array + * sizes and storing them in `levels` array * Creating a new Mipmap texture with that `levels` array * Storing the texture for input image in map called `specularTextures` * maps the input p5.Image to a p5.MipmapTexture @@ -2317,7 +2395,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { // this.activeImageLight has image as a key // look up the texture from the diffusedTexture map let diffusedLight = this.getDiffusedTexture(this.activeImageLight); - shader.setUniform('environmentMapDiffused', diffusedLight); + shader.setUniform('environmentMapDiffusedCubemap', diffusedLight); let specularLight = this.getSpecularTexture(this.activeImageLight); shader.setUniform('environmentMapSpecular', specularLight); diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index 5f0b38ce66..afc4d13d5c 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -852,7 +852,7 @@ p5.Shader = class { uniform.name = uniformName; uniform.type = uniformInfo.type; uniform._cachedData = undefined; - if (uniform.type === gl.SAMPLER_2D) { + if (uniform.type === gl.SAMPLER_2D || uniform.type === gl.SAMPLER_CUBE) { uniform.samplerIndex = samplerIndex; samplerIndex++; this.samplers.push(uniform); @@ -1326,6 +1326,7 @@ p5.Shader = class { } break; case gl.SAMPLER_2D: + case gl.SAMPLER_CUBE: gl.activeTexture(gl.TEXTURE0 + uniform.samplerIndex); uniform.texture = data instanceof p5.Texture ? data : this._renderer.getTexture(data); diff --git a/src/webgl/p5.Texture.js b/src/webgl/p5.Texture.js index 04b59b487d..64e2cfdc3c 100644 --- a/src/webgl/p5.Texture.js +++ b/src/webgl/p5.Texture.js @@ -132,6 +132,8 @@ p5.Texture = class Texture { textureData = this.src.canvas || this.src.elt; } else if (this.isImageData) { textureData = this.src; + } else if (this.isCubemapTexture) { + textureData = this.src; } return textureData; } @@ -196,7 +198,7 @@ p5.Texture = class Texture { /** * Checks if the source data for this texture has changed (if it's * easy to do so) and reuploads the texture if necessary. If it's not - * possible or to expensive to do a calculation to determine wheter or + * possible or too expensive to do a calculation to determine whether or * not the data has occurred, this method simply re-uploads the texture. * @method update */ @@ -513,12 +515,85 @@ export function checkWebGLCapabilities({ GL, webglVersion }) { : gl.getExtension('OES_texture_half_float'); const supportsHalfFloatLinear = supportsHalfFloat && gl.getExtension('OES_texture_half_float_linear'); + const supportsCubemap = (webglVersion === constants.WEBGL2) + || (gl.getExtension('OES_texture_cube_map')); return { float: supportsFloat, floatLinear: supportsFloatLinear, halfFloat: supportsHalfFloat, - halfFloatLinear: supportsHalfFloatLinear + halfFloatLinear: supportsHalfFloatLinear, + cubemap: supportsCubemap }; } +export class CubemapTexture extends p5.Texture { + constructor(renderer, faces, settings) { + super(renderer, faces, settings); + + this.isCubemapTexture = false; // Default value + + if (faces instanceof CubemapTexture) { + this.isCubemapTexture = true; + } + } + + glFilter(_filter) { + const gl = this._renderer.GL; + // TODO: Support other filters if needed + return gl.LINEAR; + } + + _getTextureDataFromSource() { + return this.src; + } + + init(faces) { + const gl = this._renderer.GL; + this.glTex = gl.createTexture(); + + this.bindTexture(); + + // Looping through each face and loading the data + const targets = [ + gl.TEXTURE_CUBE_MAP_POSITIVE_X, + gl.TEXTURE_CUBE_MAP_NEGATIVE_X, + gl.TEXTURE_CUBE_MAP_POSITIVE_Y, + gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, + gl.TEXTURE_CUBE_MAP_POSITIVE_Z, + gl.TEXTURE_CUBE_MAP_NEGATIVE_Z + ]; + // Looping through each face and loading the data + for (let faceIndex = 0; faceIndex < targets.length; faceIndex++) { + // Setting up each face of the cubemap + gl.texImage2D( + targets[faceIndex], + 0, + this.glFormat, + this.width, + this.height, + 0, + this.glFormat, + this.glDataType, + faces[faceIndex] + ); + } + + // Set parameters for the cubemap + gl.texParameteri(gl.TEXTURE_CUBE_MAP + , gl.TEXTURE_MAG_FILTER, this.glMagFilter); + gl.texParameteri(gl.TEXTURE_CUBE_MAP + , gl.TEXTURE_MIN_FILTER, this.glMinFilter); + gl.texParameteri(gl.TEXTURE_CUBE_MAP + , gl.TEXTURE_WRAP_S, this.glWrapS); + gl.texParameteri(gl.TEXTURE_CUBE_MAP + , gl.TEXTURE_WRAP_T, this.glWrapT); + + this.unbindTexture(); + } + + update() { + // Custom update logic, if needed + } +} + export default p5.Texture; diff --git a/src/webgl/shaders/cubeFragment.frag b/src/webgl/shaders/cubeFragment.frag new file mode 100644 index 0000000000..bb49851151 --- /dev/null +++ b/src/webgl/shaders/cubeFragment.frag @@ -0,0 +1,22 @@ +// Adapted from: https://learnopengl.com/PBR/IBL/Diffuse-irradiance +IN vec3 localPos; + +uniform sampler2D equirectangularMap; + +const vec2 invAtan = vec2(0.1591, 0.3183); + +vec2 SampleSphericalMap(vec3 v) +{ + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= invAtan; + uv += 0.5; + return uv; +} + +void main() +{ + vec2 uv = SampleSphericalMap(normalize(localPos)); + vec3 color = TEXTURE(equirectangularMap, uv).rgb; + + OUT_COLOR = vec4(color, 1.0); +} diff --git a/src/webgl/shaders/cubeVertex.vert b/src/webgl/shaders/cubeVertex.vert new file mode 100644 index 0000000000..a6a066c7b3 --- /dev/null +++ b/src/webgl/shaders/cubeVertex.vert @@ -0,0 +1,13 @@ +// Adapted from: https://learnopengl.com/PBR/IBL/Diffuse-irradiance +layout (location = 0) IN vec3 aPosition; + +OUT vec3 localPos; + +uniform mat4 uProjectionMatrix; +uniform mat4 uModelViewMatrix; + +void main() +{ + localPos = aPosition; + gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(localPos, 1.0); +} diff --git a/src/webgl/shaders/lighting.glsl b/src/webgl/shaders/lighting.glsl index b66ac083d1..c597afc05a 100644 --- a/src/webgl/shaders/lighting.glsl +++ b/src/webgl/shaders/lighting.glsl @@ -40,7 +40,7 @@ uniform float uQuadraticAttenuation; // boolean to initiate the calculateImageDiffuse and calculateImageSpecular uniform bool uUseImageLight; // texture for use in calculateImageDiffuse -uniform sampler2D environmentMapDiffused; +uniform samplerCube environmentMapDiffusedCubemap; // texture for use in calculateImageSpecular uniform sampler2D environmentMapSpecular; @@ -112,7 +112,11 @@ vec3 calculateImageDiffuse(vec3 vNormal, vec3 vViewPosition, float metallic){ vec3 worldCameraPosition = vec3(0.0, 0.0, 0.0); // hardcoded world camera position vec3 worldNormal = normalize(vNormal * uCameraRotation); vec2 newTexCoor = mapTextureToNormal( worldNormal ); - vec4 texture = TEXTURE( environmentMapDiffused, newTexCoor ); + + // Sample the diffuse color from the environment map (cube map) using the transformed + // world-normal vector as the direction in the cube map space. + vec4 texture = TEXTURE_CUBE( environmentMapDiffusedCubemap, worldNormal ); + // this is to make the darker sections more dark // png and jpg usually flatten the brightness so it is to reverse that return mix(smoothstep(vec3(0.0), vec3(1.0), texture.xyz), vec3(0.0), metallic); diff --git a/src/webgl/shaders/webgl2Compatibility.glsl b/src/webgl/shaders/webgl2Compatibility.glsl index 9aed0ef22c..ee895a7fa7 100644 --- a/src/webgl/shaders/webgl2Compatibility.glsl +++ b/src/webgl/shaders/webgl2Compatibility.glsl @@ -8,6 +8,7 @@ out vec4 outColor; #define OUT_COLOR outColor #endif #define TEXTURE texture +#define TEXTURE_CUBE texture #else @@ -18,6 +19,7 @@ out vec4 outColor; #endif #define OUT varying #define TEXTURE texture2D +#define TEXTURE_CUBE textureCube #ifdef FRAGMENT_SHADER #define OUT_COLOR gl_FragColor