Created
June 19, 2025 15:26
-
-
Save munshkr/1cb96d272e136f2117cfa1e7dfc6ce4e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class AnaglyphEffect { | |
| constructor(p5inst) { | |
| this.pInst = p5inst; | |
| // TODO | |
| // the original library (camera3D) had divergence + swapLeftRight at 1 | |
| // but the terrain example makes it look like left / right are | |
| // backwards w/ red over left eye... ??? | |
| this.divergence = 1; | |
| this.swapLeftRight = -1; | |
| this.adjustTargetFactor = 1; | |
| this.useAsymmetricFrustum = true; | |
| this.cameraDivergenceX = 0; | |
| this.cameraDivergenceY = 0; | |
| this.cameraDivergenceZ = 0; | |
| this.frustrumSkew = 0; | |
| this.RAD_TO_DEG = 57.2957795130823208767981548; | |
| this.LEFT_IMG = 0; | |
| this.RIGHT_IMG = 1; | |
| this.shaderLoaded = false; | |
| this.fc = -1; | |
| // this.pInst = p5; | |
| // this.init(); | |
| } | |
| init() { | |
| if (this.pInst.frameCount != this.fc) { | |
| this.fc = this.pInst.frameCount; | |
| const vert = | |
| "#ifdef GL_ES \n" + | |
| "precision mediump float; \n" + | |
| "#endif \n" + | |
| "attribute vec3 aPosition;" + | |
| "attribute vec2 aTexCoord;" + | |
| "varying vec2 vTexCoord;" + | |
| "void main() {" + | |
| "vTexCoord = aTexCoord;" + | |
| "vec4 positionVec4 = vec4(aPosition, 1.0);" + | |
| "positionVec4.xy = positionVec4.xy * 2.0 - 1.0;" + | |
| "gl_Position = positionVec4;" + | |
| "}"; | |
| const frag = | |
| "#ifdef GL_ES \n" + | |
| "precision mediump float; \n" + | |
| "#endif \n" + | |
| "varying vec2 vTexCoord;" + | |
| "uniform vec2 u_resolution;" + | |
| "uniform sampler2D mapLeft;" + | |
| "uniform sampler2D mapRight;" + | |
| "mat3 colorMatrixLeft = mat3(" + | |
| "0.456100, - 0.0400822, - 0.0152161," + | |
| "0.500484, - 0.0378246, - 0.0205971," + | |
| "0.176381, - 0.0157589, - 0.00546856" + | |
| ");" + | |
| "mat3 colorMatrixRight = mat3(" + | |
| "- 0.0434706, 0.378476, - 0.0721527," + | |
| "- 0.0879388, 0.73364, - 0.112961," + | |
| "- 0.00155529, - 0.0184503, 1.2264" + | |
| ");" + | |
| "float lin( float c ) {" + | |
| "return c <= 0.04045 ? c * 0.0773993808 :" + | |
| "pow( c * 0.9478672986 + 0.0521327014, 2.4 );" + | |
| "}" + | |
| "vec4 lin( vec4 c ) {" + | |
| "return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );" + | |
| "}" + | |
| "float dev( float c ) {" + | |
| "return c <= 0.0031308 ? c * 12.92" + | |
| ": pow( c, 0.41666 ) * 1.055 - 0.055;" + | |
| "}" + | |
| "void main() {" + | |
| "vec2 uv = vTexCoord;" + | |
| "vec4 colorL = lin( texture2D( mapLeft, uv ) );" + | |
| "vec4 colorR = lin( texture2D( mapRight, uv ) );" + | |
| "vec3 color = clamp(" + | |
| "colorMatrixLeft * colorL.rgb +" + | |
| "colorMatrixRight * colorR.rgb, 0., 1. );" + | |
| "gl_FragColor = vec4(" + | |
| "dev( color.r ), dev( color.g ), dev( color.b )," + | |
| "max( colorL.a, colorR.a ) );" + | |
| "}"; | |
| this.config = { | |
| cameraPositionX: 0, | |
| cameraPositionY: 0, | |
| cameraPositionZ: | |
| this.pInst.height / 2 / this.pInst.tan(this.pInst.PI / 6), | |
| cameraTargetX: 0, | |
| cameraTargetY: 0, | |
| cameraTargetZ: 0, | |
| cameraUpX: 0, | |
| cameraUpY: 1, | |
| cameraUpZ: 0, | |
| frustumLeft: -this.pInst.width / 2, | |
| frustumRight: this.pInst.width / 2, | |
| frustumBottom: -this.pInst.height / 2, | |
| frustumTop: this.pInst.height / 2, | |
| frustumNear: 0, | |
| frustumFar: this.pInst.max(this.pInst.width, this.pInst.height), | |
| fovy: this.pInst.PI / 3, | |
| }; | |
| this.recalculateCameraSettings(); | |
| this.imgLeft = this.pInst.createGraphics( | |
| this.pInst.width, | |
| this.pInst.height, | |
| this.pInst.WEBGL | |
| ); | |
| this.imgRight = this.pInst.createGraphics( | |
| this.pInst.width, | |
| this.pInst.height, | |
| this.pInst.WEBGL | |
| ); | |
| this.output = this.pInst.createGraphics( | |
| this.pInst.width, | |
| this.pInst.height, | |
| this.pInst.WEBGL | |
| ); | |
| this.theShader = this.output.createShader(vert, frag); | |
| this.shaderLoaded = true; | |
| // TODO - I don't think the anaglyph matrices are setup to handle | |
| // alpha channel - effect ruined if background is cleared | |
| // this.imgLeft.setAttributes('alpha', true); | |
| // this.imgRight.setAttributes('alpha', true); | |
| // this.output.setAttributes('alpha', true); | |
| } | |
| } | |
| draw(scene) { | |
| if (this.theShader && this.shaderLoaded) { | |
| this.drawScene(this.LEFT_IMG, this.imgLeft, scene); | |
| this.drawScene(this.RIGHT_IMG, this.imgRight, scene); | |
| this.updateShader(); | |
| } else { | |
| this.drawScene(this.LEFT_IMG, this.imgLeft, scene); | |
| this.pInst.push(); | |
| this.pInst.image( | |
| this.imgLeft, | |
| -this.pInst.width / 2, | |
| -this.pInst.height / 2 | |
| ); | |
| this.pInst.pop(); | |
| } | |
| } | |
| setDivergence(divergence = 1) { | |
| this.divergence = divergence; | |
| } | |
| updateShader() { | |
| this.theShader.setUniform("u_resolution", [ | |
| this.pInst.width, | |
| this.pInst.height, | |
| ]); | |
| this.theShader.setUniform("mapLeft", this.imgLeft); | |
| this.theShader.setUniform("mapRight", this.imgRight); | |
| this.output.clear(); | |
| this.output.shader(this.theShader); | |
| this.output.rect(0, 0, this.pInst.width, this.pInst.height); | |
| // why shader flips y? | |
| this.pInst.push(); | |
| this.pInst.scale(1, -1); | |
| this.pInst.image( | |
| this.output, | |
| -this.pInst.width / 2, | |
| -this.pInst.height / 2 | |
| ); | |
| this.pInst.pop(); | |
| } | |
| drawStereoImages(left, right, x = 0, y = 0) { | |
| if (this.theShader && this.shaderLoaded) { | |
| this.drawImage(left, this.imgLeft, x, y); | |
| this.drawImage(right, this.imgRight, x, y); | |
| this.updateShader(); | |
| } else { | |
| this.drawImage(left, this.imgLeft); | |
| } | |
| } | |
| drawImage(img, pg, x = 0, y = 0) { | |
| if (img) { | |
| // in WEBGL mode, origin is at center | |
| // for some reason shader flips images? | |
| // have to reverse them in the y | |
| pg.push(); | |
| pg.clear(); | |
| pg.translate(x, y); | |
| pg.scale(1, -1); | |
| pg.image(img, 0, 0); | |
| pg.pop(); | |
| } | |
| } | |
| drawScene(side, pg, scene) { | |
| pg.push(); | |
| pg.clear(); | |
| // for some reason shader flips images? | |
| // have to reverse them in the y | |
| // but not clear how / if this changed from p5.js 1.4 to 1.9 | |
| this.getCamera(side, pg); | |
| scene(pg); | |
| pg.pop(); | |
| } | |
| recalculateCameraSettings() { | |
| this.perspective(); | |
| let dx = | |
| this.adjustTargetFactor * | |
| (this.config.cameraPositionX - this.config.cameraTargetX); | |
| let dy = | |
| this.adjustTargetFactor * | |
| (this.config.cameraPositionY - this.config.cameraTargetY); | |
| let dz = | |
| this.adjustTargetFactor * | |
| (this.config.cameraPositionZ - this.config.cameraTargetZ); | |
| let diverge = | |
| -(this.swapLeftRight * this.divergence) / | |
| (this.config.fovy * this.RAD_TO_DEG); | |
| this.cameraDivergenceX = | |
| (dy * this.config.cameraUpZ - this.config.cameraUpY * dz) * diverge; | |
| this.cameraDivergenceY = | |
| (dz * this.config.cameraUpX - this.config.cameraUpZ * dx) * diverge; | |
| this.cameraDivergenceZ = | |
| (dx * this.config.cameraUpY - this.config.cameraUpX * dy) * diverge; | |
| let distanceToTarget = Math.sqrt(dx * dx + dy * dy + dz * dz); | |
| let cameraDivergenceDistance = | |
| Math.sign(this.swapLeftRight * this.divergence) * | |
| Math.sqrt( | |
| this.cameraDivergenceX * this.cameraDivergenceX + | |
| this.cameraDivergenceY * this.cameraDivergenceY + | |
| this.cameraDivergenceZ * this.cameraDivergenceZ | |
| ); | |
| this.frustrumSkew = | |
| (cameraDivergenceDistance * this.config.frustumNear) / distanceToTarget; | |
| } | |
| getCamera(side, pg) { | |
| if (this.useAsymmetricFrustum) { | |
| this.drawAsymmetricFrustrum(side, pg); | |
| } else { | |
| this.drawSymmetricFrustrum(side, pg); | |
| } | |
| } | |
| drawAsymmetricFrustrum(side, pg) { | |
| if (side == this.LEFT_IMG) { | |
| pg.camera( | |
| this.config.cameraPositionX + this.cameraDivergenceX, | |
| this.config.cameraPositionY + this.cameraDivergenceY, | |
| this.config.cameraPositionZ + this.cameraDivergenceZ, | |
| this.config.cameraTargetX + this.cameraDivergenceX, | |
| this.config.cameraTargetY + this.cameraDivergenceY, | |
| this.config.cameraTargetZ + this.cameraDivergenceZ, | |
| this.config.cameraUpX, | |
| this.config.cameraUpY, | |
| this.config.cameraUpZ | |
| ); | |
| pg.frustum( | |
| this.config.frustumLeft - this.frustrumSkew, | |
| this.config.frustumRight - this.frustrumSkew, | |
| this.config.frustumBottom, | |
| this.config.frustumTop, | |
| this.config.frustumNear, | |
| this.config.frustumFar | |
| ); | |
| } else if (side == this.RIGHT_IMG) { | |
| pg.camera( | |
| this.config.cameraPositionX - this.cameraDivergenceX, | |
| this.config.cameraPositionY - this.cameraDivergenceY, | |
| this.config.cameraPositionZ - this.cameraDivergenceZ, | |
| this.config.cameraTargetX - this.cameraDivergenceX, | |
| this.config.cameraTargetY - this.cameraDivergenceY, | |
| this.config.cameraTargetZ - this.cameraDivergenceZ, | |
| this.config.cameraUpX, | |
| this.config.cameraUpY, | |
| this.config.cameraUpZ | |
| ); | |
| pg.frustum( | |
| this.config.frustumLeft + this.frustrumSkew, | |
| this.config.frustumRight + this.frustrumSkew, | |
| this.config.frustumBottom, | |
| this.config.frustumTop, | |
| this.config.frustumNear, | |
| this.config.frustumFar | |
| ); | |
| } | |
| } | |
| drawSymmetricFrustrum(side, pg) { | |
| if (side == LEFT_IMG) { | |
| pg.camera( | |
| this.config.cameraPositionX + this.cameraDivergenceX, | |
| this.config.cameraPositionY + this.cameraDivergenceY, | |
| this.config.cameraPositionZ + this.cameraDivergenceZ, | |
| this.config.cameraTargetX, | |
| this.config.cameraTargetY, | |
| this.config.cameraTargetZ, | |
| this.config.cameraUpX, | |
| this.config.cameraUpY, | |
| this.config.cameraUpZ | |
| ); | |
| } else if (side == this.RIGHT_IMG) { | |
| pg.camera( | |
| this.config.cameraPositionX - this.cameraDivergenceX, | |
| this.config.cameraPositionY - this.cameraDivergenceY, | |
| this.config.cameraPositionZ - this.cameraDivergenceZ, | |
| this.config.cameraTargetX, | |
| this.config.cameraTargetY, | |
| this.config.cameraTargetZ, | |
| this.config.cameraUpX, | |
| this.config.cameraUpY, | |
| this.config.cameraUpZ | |
| ); | |
| } | |
| } | |
| perspective() { | |
| let cameraZ = | |
| this.pInst.height / 2 / Math.tan((this.pInst.PI * 60.0) / 360.0); | |
| let fovy = this.pInst.PI / 3; | |
| let aspect = this.pInst.width / this.pInst.height; | |
| let zNear = cameraZ / 10; | |
| let zFar = cameraZ * 10; | |
| let ymax = zNear * Math.tan(fovy / 2); | |
| let ymin = -ymax; | |
| let xmin = ymin * aspect; | |
| let xmax = ymax * aspect; | |
| this.frustum(xmin, xmax, ymin, ymax, zNear, zFar); | |
| } | |
| frustum(left, right, bottom, top, near, far) { | |
| this.config.frustumLeft = left; | |
| this.config.frustumRight = right; | |
| this.config.frustumBottom = bottom; | |
| this.config.frustumTop = top; | |
| this.config.frustumNear = near; | |
| this.config.frustumFar = far; | |
| this.config.fovy = 2 * Math.atan(top / near); | |
| } | |
| resize() { | |
| this.recalculateCameraSettings(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment