Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ ProjectedMaterial also supports **instanced meshes** via three.js' [InstancedMes
<a href="https://marcofugaro.github.io/three-projected-material/multiple-projections"><img width="274" src="examples/screenshots/multiple-projections.png" /></a>
<a href="https://marcofugaro.github.io/three-projected-material/instancing"><img width="274" src="examples/screenshots/instancing.png" /></a>
<a href="https://marcofugaro.github.io/three-projected-material/multiple-projections-instancing"><img width="274" src="examples/screenshots/multiple-projections-instancing.png" /></a>
<a href="https://marcofugaro.github.io/three-projected-material/stop-propagation"><img width="274" src="examples/screenshots/stop-propagation.png" /></a>
</p>

## API Reference
Expand All @@ -103,6 +104,7 @@ Create a new material to later use for a mesh.
| `textureOffset` | `new THREE.Vector2()` | Offset the texture in a x or y direction. The unit system goes from 0 to 1, from the bottom left corner to the top right corner of the projector camera frustum. |
| `cover` | false | Wheter the texture should act like [`background-size: cover`](https://css-tricks.com/almanac/properties/b/background-size/) on the projector frustum. By default it works like [`background-size: contain`](https://css-tricks.com/almanac/properties/b/background-size/). |
| `backgroundOpacity` | 1 | The opacity of the part of the mesh which is not covered by the projected texture. You can set this to 0 if you don't want the non-projected part of the mesh to be shown. |
| `depthMap` | | A depth map texture to prevent the projected texture to be rendered on geometry that is obscured from the point of view of the camera |
| `...options` | | Other options you pass to any three.js material like `color`, `opacity`, `envMap` and so on. The material is built from a [MeshPhysicalMaterial](https://threejs.org/docs/index.html#api/en/materials/MeshPhysicalMaterial), so you can pass any property of that material and of its parent [MeshStandardMaterial](https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial). |

These properties are exposed as properties of the material, so you can change them later.
Expand Down
86 changes: 69 additions & 17 deletions build/ProjectedMaterial.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {
_classPrivateFieldLooseBase(this, _saveDimensions)[_saveDimensions]();
}
}
get depthMap() {
return this.uniforms.depthMap.value;
}
set depthMap(depthMap) {
if (!depthMap?.isTexture) {
throw new Error('Invalid texture set to the ProjectedMaterial');
}
this.uniforms.depthMap.value = depthMap;
}
get textureScale() {
return _classPrivateFieldLooseBase(this, _textureScale)[_textureScale];
}
Expand Down Expand Up @@ -152,6 +161,7 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {
textureOffset = new THREE__namespace.Vector2(),
backgroundOpacity = 1,
cover = false,
depthMap = null,
...options
} = _temp === void 0 ? {} : _temp;
if (!texture.isTexture) {
Expand Down Expand Up @@ -243,19 +253,31 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {
},
textureOffset: {
value: textureOffset
},
depthMap: {
value: depthMap
},
isOrthographicCamera: {
value: this.camera.isOrthographicCamera
}
};
this.onBeforeCompile = shader => {
// expose also the material's uniforms
Object.assign(this.uniforms, shader.uniforms);
shader.uniforms = this.uniforms;
if (this.camera.isOrthographicCamera) {
shader.defines.ORTHOGRAPHIC = '';

// if (this.camera.isOrthographicCamera) {
// shader.defines.ORTHOGRAPHIC = ''
// }

if (depthMap) {
shader.defines.STOP_PROPAGATION = '';
}
shader.vertexShader = monkeyPatch(shader.vertexShader, {
header: /* glsl */`
uniform mat4 viewMatrixCamera;
uniform mat4 projectionMatrixCamera;
uniform bool isOrthographicCamera;

#ifdef USE_INSTANCING
attribute vec4 savedModelMatrix0;
Expand All @@ -268,9 +290,9 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {

varying vec3 vSavedNormal;
varying vec4 vTexCoords;
#ifndef ORTHOGRAPHIC
// #ifndef ORTHOGRAPHIC
varying vec4 vWorldPosition;
#endif
// #endif
`,
main: /* glsl */`
#ifdef USE_INSTANCING
Expand All @@ -284,14 +306,17 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {

vSavedNormal = mat3(savedModelMatrix) * normal;
vTexCoords = projectionMatrixCamera * viewMatrixCamera * savedModelMatrix * vec4(position, 1.0);
#ifndef ORTHOGRAPHIC
vWorldPosition = savedModelMatrix * vec4(position, 1.0);
#endif
// #ifndef ORTHOGRAPHIC
if (!isOrthographicCamera) {
vWorldPosition = savedModelMatrix * vec4(position, 1.0);
}
// #endif
`
});
shader.fragmentShader = monkeyPatch(shader.fragmentShader, {
header: /* glsl */`
uniform sampler2D projectedTexture;
uniform sampler2D depthMap;
uniform bool isTextureLoaded;
uniform bool isTextureProjected;
uniform float backgroundOpacity;
Expand All @@ -300,12 +325,14 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {
uniform float widthScaled;
uniform float heightScaled;
uniform vec2 textureOffset;
uniform mat4 projectionMatrixCamera;
uniform bool isOrthographicCamera;

varying vec3 vSavedNormal;
varying vec4 vTexCoords;
#ifndef ORTHOGRAPHIC
// #ifndef ORTHOGRAPHIC
varying vec4 vWorldPosition;
#endif
// #endif

float mapRange(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
Expand All @@ -316,6 +343,9 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {
float w = max(vTexCoords.w, 0.0);

vec2 uv = (vTexCoords.xy / w) * 0.5 + 0.5;
#ifdef STOP_PROPAGATION
vec2 uvDepthMap = uv;
#endif

uv += textureOffset;

Expand All @@ -327,20 +357,42 @@ class ProjectedMaterial extends THREE__namespace.MeshPhysicalMaterial {
bool isInTexture = (max(uv.x, uv.y) <= 1.0 && min(uv.x, uv.y) >= 0.0);

// this makes sure we don't render also the back of the object
#ifdef ORTHOGRAPHIC
vec3 projectorDirection = projDirection;
#else
vec3 projectorDirection = normalize(projPosition - vWorldPosition.xyz);
#endif
vec3 projectorDirection;
if (isOrthographicCamera) {
projectorDirection = projDirection;
} else {
projectorDirection = normalize(projPosition - vWorldPosition.xyz);
}
float dotProduct = dot(vSavedNormal, projectorDirection);

bool isFacingProjector = dotProduct > 0.0000001;

bool isInShadow = false;
#ifdef STOP_PROPAGATION
vec4 depthMapColor = texture2D(depthMap, uvDepthMap);

float depth = depthMapColor.x;

if (isOrthographicCamera) {
float z = (vTexCoords.z + 1.0) / 2.0;
isInShadow = depth < z - 0.001 || z < 0.0 || z > 1.0;
} else {
float z_ndc = 2.0 * depth - 1.0;

float a = projectionMatrixCamera[2][2];
float b = projectionMatrixCamera[3][2];
float z_eye = b / (a + z_ndc);

isInShadow = vTexCoords.w > z_eye + 0.1 || depth == 1.0;
// isInShadow = vTexCoords.w > 50.0;
// isInShadow = depth > 0.98;
}
#endif

vec4 diffuseColor = vec4(diffuse, opacity * backgroundOpacity);

if (isFacingProjector && isInTexture && isTextureLoaded && isTextureProjected) {
if (isFacingProjector && isInTexture && isTextureLoaded && isTextureProjected && !isInShadow) {
vec4 textureColor = texture2D(projectedTexture, uv);

// apply the material opacity
textureColor.a *= opacity;

Expand Down Expand Up @@ -471,7 +523,7 @@ function _saveCameraMatrices2() {
this.uniforms.viewMatrixCamera.value.copy(viewMatrixCamera);
this.uniforms.projectionMatrixCamera.value.copy(projectionMatrixCamera);
this.uniforms.projPosition.value.setFromMatrixPosition(modelMatrixCamera);
this.uniforms.projDirection.value.set(0, 0, 1).applyMatrix4(modelMatrixCamera);
this.uniforms.projDirection.value.set(0, 0, 1).transformDirection(modelMatrixCamera);

// tell the shader we've projected
this.uniforms.isTextureProjected.value = true;
Expand Down
86 changes: 69 additions & 17 deletions build/ProjectedMaterial.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {
_classPrivateFieldLooseBase(this, _saveDimensions)[_saveDimensions]();
}
}
get depthMap() {
return this.uniforms.depthMap.value;
}
set depthMap(depthMap) {
if (!depthMap?.isTexture) {
throw new Error('Invalid texture set to the ProjectedMaterial');
}
this.uniforms.depthMap.value = depthMap;
}
get textureScale() {
return _classPrivateFieldLooseBase(this, _textureScale)[_textureScale];
}
Expand Down Expand Up @@ -129,6 +138,7 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {
textureOffset = new THREE.Vector2(),
backgroundOpacity = 1,
cover = false,
depthMap = null,
...options
} = _temp === void 0 ? {} : _temp;
if (!texture.isTexture) {
Expand Down Expand Up @@ -220,19 +230,31 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {
},
textureOffset: {
value: textureOffset
},
depthMap: {
value: depthMap
},
isOrthographicCamera: {
value: this.camera.isOrthographicCamera
}
};
this.onBeforeCompile = shader => {
// expose also the material's uniforms
Object.assign(this.uniforms, shader.uniforms);
shader.uniforms = this.uniforms;
if (this.camera.isOrthographicCamera) {
shader.defines.ORTHOGRAPHIC = '';

// if (this.camera.isOrthographicCamera) {
// shader.defines.ORTHOGRAPHIC = ''
// }

if (depthMap) {
shader.defines.STOP_PROPAGATION = '';
}
shader.vertexShader = monkeyPatch(shader.vertexShader, {
header: /* glsl */`
uniform mat4 viewMatrixCamera;
uniform mat4 projectionMatrixCamera;
uniform bool isOrthographicCamera;

#ifdef USE_INSTANCING
attribute vec4 savedModelMatrix0;
Expand All @@ -245,9 +267,9 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {

varying vec3 vSavedNormal;
varying vec4 vTexCoords;
#ifndef ORTHOGRAPHIC
// #ifndef ORTHOGRAPHIC
varying vec4 vWorldPosition;
#endif
// #endif
`,
main: /* glsl */`
#ifdef USE_INSTANCING
Expand All @@ -261,14 +283,17 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {

vSavedNormal = mat3(savedModelMatrix) * normal;
vTexCoords = projectionMatrixCamera * viewMatrixCamera * savedModelMatrix * vec4(position, 1.0);
#ifndef ORTHOGRAPHIC
vWorldPosition = savedModelMatrix * vec4(position, 1.0);
#endif
// #ifndef ORTHOGRAPHIC
if (!isOrthographicCamera) {
vWorldPosition = savedModelMatrix * vec4(position, 1.0);
}
// #endif
`
});
shader.fragmentShader = monkeyPatch(shader.fragmentShader, {
header: /* glsl */`
uniform sampler2D projectedTexture;
uniform sampler2D depthMap;
uniform bool isTextureLoaded;
uniform bool isTextureProjected;
uniform float backgroundOpacity;
Expand All @@ -277,12 +302,14 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {
uniform float widthScaled;
uniform float heightScaled;
uniform vec2 textureOffset;
uniform mat4 projectionMatrixCamera;
uniform bool isOrthographicCamera;

varying vec3 vSavedNormal;
varying vec4 vTexCoords;
#ifndef ORTHOGRAPHIC
// #ifndef ORTHOGRAPHIC
varying vec4 vWorldPosition;
#endif
// #endif

float mapRange(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
Expand All @@ -293,6 +320,9 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {
float w = max(vTexCoords.w, 0.0);

vec2 uv = (vTexCoords.xy / w) * 0.5 + 0.5;
#ifdef STOP_PROPAGATION
vec2 uvDepthMap = uv;
#endif

uv += textureOffset;

Expand All @@ -304,20 +334,42 @@ class ProjectedMaterial extends THREE.MeshPhysicalMaterial {
bool isInTexture = (max(uv.x, uv.y) <= 1.0 && min(uv.x, uv.y) >= 0.0);

// this makes sure we don't render also the back of the object
#ifdef ORTHOGRAPHIC
vec3 projectorDirection = projDirection;
#else
vec3 projectorDirection = normalize(projPosition - vWorldPosition.xyz);
#endif
vec3 projectorDirection;
if (isOrthographicCamera) {
projectorDirection = projDirection;
} else {
projectorDirection = normalize(projPosition - vWorldPosition.xyz);
}
float dotProduct = dot(vSavedNormal, projectorDirection);

bool isFacingProjector = dotProduct > 0.0000001;

bool isInShadow = false;
#ifdef STOP_PROPAGATION
vec4 depthMapColor = texture2D(depthMap, uvDepthMap);

float depth = depthMapColor.x;

if (isOrthographicCamera) {
float z = (vTexCoords.z + 1.0) / 2.0;
isInShadow = depth < z - 0.001 || z < 0.0 || z > 1.0;
} else {
float z_ndc = 2.0 * depth - 1.0;

float a = projectionMatrixCamera[2][2];
float b = projectionMatrixCamera[3][2];
float z_eye = b / (a + z_ndc);

isInShadow = vTexCoords.w > z_eye + 0.1 || depth == 1.0;
// isInShadow = vTexCoords.w > 50.0;
// isInShadow = depth > 0.98;
}
#endif

vec4 diffuseColor = vec4(diffuse, opacity * backgroundOpacity);

if (isFacingProjector && isInTexture && isTextureLoaded && isTextureProjected) {
if (isFacingProjector && isInTexture && isTextureLoaded && isTextureProjected && !isInShadow) {
vec4 textureColor = texture2D(projectedTexture, uv);

// apply the material opacity
textureColor.a *= opacity;

Expand Down Expand Up @@ -448,7 +500,7 @@ function _saveCameraMatrices2() {
this.uniforms.viewMatrixCamera.value.copy(viewMatrixCamera);
this.uniforms.projectionMatrixCamera.value.copy(projectionMatrixCamera);
this.uniforms.projPosition.value.setFromMatrixPosition(modelMatrixCamera);
this.uniforms.projDirection.value.set(0, 0, 1).applyMatrix4(modelMatrixCamera);
this.uniforms.projDirection.value.set(0, 0, 1).transformDirection(modelMatrixCamera);

// tell the shader we've projected
this.uniforms.isTextureProjected.value = true;
Expand Down
11 changes: 11 additions & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,17 @@ <h2>Examples</h2>
>
</div>
</div>
<div>
<a href="stop-propagation.html"><img src="screenshots/stop-propagation.png" /></a>
<div class="didascalia">
<a href="stop-propagation" class="title-link">Stop Propagation</a>
<a
href="https://github.com/marcofugaro/three-projected-material/blob/master/examples/stop-propagation.html"
target="_blank"
>(source)</a
>
</div>
</div>
</div>
</div>
</body>
Expand Down
Binary file added examples/screenshots/stop-propagation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading