I display various 3D models on my website (glb, obj, fbx) and some have textures and some do not. For the one’s with textures, watermarking is easy because I can just add the watermark logo to the textures but for the models without textures, I’m not sure the best way to watermark the models (some models don’t have a UV map either).
In the context of protecting the 3D models from scappers online, what is the best way for adding watermark to the 3D models to dissuade the scrappers? I feel like adding another texture just for watermarking to the models without textures wouldn’t be sufficient since the texture could easily be deleted and the model could still be usable by the scappers.
My first answer was a bit grime, so I’ll try to brighten things up with this one.
If you don’t want the browser/hacker to read your model, you can consider showcasing it through a video stream from a server. Rive.app offers a solution implementing this approach.
Pros:
Protect the source and assets of your model
Cross-Platform Compatibility
Performance Optimization
(You have control over the server’s hardware, GPU, CPU … )
Why not watermark the geometry with a your logo added using a displacement map. Kind of like cattle branding. Something like the decals example, but baked into the geometry
Here’s an idea. The watermark is geometry from extruded text. Cast a ray at the model for the location where the watermark should appear. At the hit location, determine the position and angle of the face and move the water mark to that location. Use merge geometry to make a new model with the water mark. The code would look something like this (not tested).
// create your watermark as extruded text
const textGeometry = new TextGeometry(text, options);
// use raycaster to find a location on the model where you want to put the water mark
const raycaster = new THREE.Raycaster();
raycaster.set(model.position, rayDirection.normalize());
const intersects = raycaster.intersectObject(model);
if (intersects.length > 0) {
const hit = intersects[0];
textGeometry.rotateX(-Math.PI / 2); // Adjust orientation based on your model
textGeometry.translate(hit.point.x, hit.point.y, hit.point.z);
// Adjust textGeometry's position and rotation based on hit.face normal
textGeometry.lookAt(hit.face.normal);
// Merge geometries
const combinedGeometry = BufferGeometryUtils.mergeBufferGeometries([
model.geometry,
watermarkMesh.geometry
], true);
// Replace model geometry with combined geometry
model.geometry.dispose(); // Dispose of the old geometry
model.geometry = combinedGeometry;
You could build a small offline tool that just identifies the ideal position and normal on the model. Applying the watermark could be done separately has part of a publishing workflow.
In the context of protecting the 3D models from scappers online, what is the best way for adding watermark to the 3D models to dissuade the scrappers?
The question is - what do you think adding a watermark to a textureless model is going to do for you? The texture can easily just be removed once downloaded. Even a watermark on a more complex texture can be fairly simple to remove if someone is inclined. All data can be grabbed in some way by the means that people have already mentioned.
The best you can do is just obfuscate the content to different degrees. Ie make a custom file format or load scrambled data and unscramble on the CPU or GPU. Of course this means more code for you to make, understand, and maintain. And at the end of the day this can just serve as a deterrent which may be good enough. But if someone really wants the models they can reverse engineer all of it.
Remote, off-client rendering will prevent someone from downloading the model data completely but this is at the further end of complicated and costly solutions, imo.
Does this work for models that don’t have UV maps? Also, would it be enough of a hassle for scrappers to remove the watermark to deter them from scrapping if the watermark is merged with the geometry?
It’s all about deterrence. For models with texture, a watermarked on the texture is going to be hard to remove (just like shutterstock). For models without texture, applying a watermark to the geometry would create a hassle that would be hard to automate.
Load times would be a nightmare. I was thinking of a solution similar to shutterstock. A watermark that would require time and manual effort to remove. Wouldn’t a merged watermark to the geometry be hard to remove?
In all fairness, if your models are that good I’d personally build a public profile showcasing the models on something like sketch fab which has its own (clean) obfu processing, this way people would know they’re your models once you build a following and most likely there’s a sketch fab api that’d let you retrieve assets with a private key from thier server… Just a thought
Nevertheless, as stated previously, the determined would always be able to scrape your models, down to the most hacky and robust way of creating a new photogrammetry from a screen recorded video of your model on sketch fab
I’d rather build my own solution than rely on Sketchfab’s api. Plus your example of photogrammetry could apply to the Sketchfab models as well.
I’m under the impression that merging a watermark to the geometry will be deterrence enough along as it requires someone to use a 3d modeling software to remove the watermark (since that would be pretty hard to automate). Similar to how Shutterstock relies on watermark to protect their assets.
Sure, again, if you’re models are good enough, someone will find a way to untangle something like this Reddit - Dive into anything
If you take an approach like @anidivr suggests above, I’d also use three-bvh-csg to completely bind the output geometry, loading only this output model to a user, storing the unaffected model on the server somewhere…
Thanks for the tip. I already use mesh bvh for raycasting but what’s the difference of using three-bvh-csg compared to just merging buffer geometries like in @anidivr example?
As in the reddit post, when merging geometries, someone may be able to determine geometry “islands” from the overall buffer geometry and automate the output of each island as a seperate mesh.
Using csg (constructive solid geometry) would allow you to perform boolean operations ( add, sub, intersection, difference ) on the original model and watermark as “brushes” which would yeild a single watertight geometry ( inclusive of both) as one (“inextricable”) geometry shell
Like gkjohnson already mentioned, a custom file format is the best you can do. Sure, people can download your files - since the browser already does that anyway - but if the files are in a custom format, then unless people build specific import tools for your models, they are useless without a viewer (your site).
I would suggest taking it one small step further: Create a container format that not only hosts the geometry, but textures as well. Similar to a zip file that you can “mount” as a virtual filesystem that your loaders can access. This is fairly trivial binary reading using some DataView’s once you have access to the ArrayBuffer (which you can grab using fetch().
To break down a simple container format:
The file should contain a header that consists of filenames, offsets and file-sizes. Maybe even a flag that denotes whether the data should be decompressed or not. Once you get to the end of the file header (mark at with a 0x00 byte), store the current offset. Then it’s just a simple matter of buffer-slicing starting with the stored offset + file offset until file length.
Let’s say the file header is a JSON string (for simplicity sake) and has 2 files. Each file is 10 bytes long. The first file contains only the letter A, while the second file contains letters B and C.
I wouldn’t recommend using this exact method, since the whole idea is that you have a proprietary format to protect your assets. Otherwise anyone reading this may as well build a reader for your assets Either way, I would recommend deflating the header and adding some XOR magic or something that only you know, so anyone opening the file with a text editor won’t see any JSON stuff that immediately explains the container format