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

dynamic clipping box #1330

Closed
landonsilla opened this issue Feb 15, 2012 · 11 comments
Closed

dynamic clipping box #1330

landonsilla opened this issue Feb 15, 2012 · 11 comments
Labels

Comments

@landonsilla
Copy link

I have a scene that I render as a ParticleSystem or a mesh. Here's the ParticleSystem one:

http://archive.cyark.org/3d/index11.php?chunks=3

I want to put in a bounding box and have the user control the extents of the bounding box. And this bounding box would double as a clipping box, meaning anything outside of this box would not be visible. I'm new to 3d rendering, but I think this concept is pretty common. My question: How do I do this?

I figure, on every render() loop, I can go through all my points and test with simple math equations if they are out of the box or not. This concept is simple, but would probably kill my computer. My second thought would be to render massive black rectangles with outward culling which would just, essentially, block things outside of the box. This would require the background to be a flat color matching my blocking rectangles, which I don't want. I'd prefer a non flat background, like the one in my example above.

Is there an element in the api to do this? I found issue #647 which was rather brief, but implies that it can be done. Can you kindly provide some more information as to how to implement this?

I gather this is a feature in webgl that doesn't really have a three.js layer to support it. Is that correct? I don't know webgl, but I can learn. How do I incorporate webgl into my three.js application?

@mrdoob
Copy link
Owner

mrdoob commented Feb 15, 2012

I think you'll have to write your custom vertex shader and send some uniforms that define the limits.

This example should be a good reference (take a look at the custom vertex/fragment shader):
http://mrdoob.github.com/three.js/examples/webgl_kinect.html

However, you'll have to figure out how to get the pieces of shader code that you currently rely on (for coloring the points).

I think you should be able to figure out the code you rely on by doing

console.log( material.vertexShader, material.fragmentShader )

and pick the pieces of code from there and compose your own shader.

@landonsilla
Copy link
Author

Thank you mrdoob. I think I have some test code running that I can polish up into a final product to get the effect I want for a ParticleSystem. I will gladly post my solution here once it's done for others to use/borrow. However, half of my application will rely on meshes. How can I do this with meshes considering meshes don't use shaders (I think). Here's a test function that I'm working on:

<a href="#" onclick="setXClippingPlane();return false;">move plane</a>
var xclip=0;
function setXClippingPlane(){
    xclip++;
    uniforms['xClippingPlane'].value=xclip;//this is used to send to the vertex shader that filters points out of the box

    for(var i=0;i<group_model.children.length;i++){//go through all the items in the group_model
        for (var j = 0; j<group_model.children[i].geometry.faces.length; j++){//evaluate all the faces in that given geometry
            var firstcoordinateinface = group_model.children[i].geometry.faces[j].a;
            if(group_model.children[i].geometry.vertices[firstcoordinateinface].position.x > xclip)//is the x val greater than the limit?
                group_model.children[i].geometry.faces[j].visible = false;//hide a specific face
            else
                group_model.children[i].geometry.faces[j].visible = true;
        }
    }
}

I what it's pretty easy to see what I'm trying to do. Loop through the geometries in my group_model object. Loop through the faces in that geometry. Is the x value of the first vertex in that face greater than some value? If so, hide it, otherwise show it.

I've tried a bunch of stuff here including playing with the "dirty" flags on both the geometries and the faces. The only thing I'm able to do is hide the whole geometry. I'm looking for a way to hide various faces within a given geometry. How do I do this?

For bonus points, I have a harder question. Suppose I have a triangle, and my limit box goes through that triangle. I can adjust my logic above to say "is the centroid inside/outside the limit box" or "are all of the vertices inside/outside the limit box" or any other similar question. In the event of a limit box intersecting a triangle, is it possible to show the portion of the triangle that is inside the box and hide the other part that's outside the box? That would be the best solution here, but I'm not sure that's possible.

@WestLangley
Copy link
Collaborator

@landonsilla
Copy link
Author

@WestLangley thank you for your response. That's a very interesting library. I tried too hook that up and run a test and the browser just locked up on me. My geometries are very complex being in the 100,000s of polygons. I imagine this library is evaluating each of those faces with the basic cube box i'm doing with the boolean logic. This sounds expensive. I did some time testing and found the conversion from three.js format to csg format took a blink of the eye, creating the cube was instant. Then performing the union was very very expensive. I couldn't get through it in my chrome browser.

Any other ideas? How can I create a limit box for meshes in three.js?

@WestLangley
Copy link
Collaborator

@landonsilla Hmm... I'd love to see one of your demos using CSG with, say, a few thousand polys -- even if only to show others exactly what you are trying to achieve. Once you have it working as you envision it, then worrry about the speed.

@alteredq
Copy link
Contributor

Everything uses shaders.

For meshes you could use custom ShaderMaterial based on whichever mesh material you use now plus extra custom attribute which would be face centroid.

Then you would do the same magic like you do for culling particles (all vertices from the face would get the same centroid, so whole face would be on/off, depending on whether centroid would be inside clipping region).

Or maybe you could just deal with world positions in the shader directly, then it should be possible to clip just parts of faces.

Something like this:

// vertex shader

varying vec4 worldPosition;

worldPosition = objectMatrix * vec4( position, 1.0 );
// fragment shader

uniform float xClippingPlaneLo;
uniform float xClippingPlaneHi;

varying vec4 worldPosition;

if ( worldPosition.x < xClippingPlaneLo || worldPosition.x > xClippingPlaneHi ) discard;

@landonsilla
Copy link
Author

I'm pretty new to the 3d/webgl world. A lot of these concepts are unfamiliar to me. I found and read this article by Paul Lewis:

http://www.html5rocks.com/en/tutorials/webgl/shaders/

To any future novice readers, it's a GREAT introduction to what shaders can do and what power they have. The ability to move a million vertices without absolutely killing your frame rate is very impressive.

After reading that and coming back to this thread, @alteredq, your code was pretty close to what I want and works very well. But now, I'm having problems with the material. My meshes are supposed to be textured and I only found one material that supports that MeshFaceMaterial. On my json obj loader, I had:

var mesh = new THREE.Mesh( geometry,  new THREE.MeshFaceMaterial({}) );

But in order to get my shader logic to work, I had to change the type of material I was using from MeshFaceMaterial to ShaderMaterial:

var material =  new THREE.MeshFaceMaterial({
    uniforms:       uniformsmesh,
    vertexShader:   document.getElementById( 'vertexshader-mesh' ).textContent,
    fragmentShader: document.getElementById( 'fragmentshader-mesh' ).textContent
});

var mesh = new THREE.Mesh( geometry, material );

So how do I fix this? I guess I don't 100% know what materials are or how they work. I just plugged a few different ones in until I got one that I liked. Can you send vertex/fragment shaders to any material other than ShaderMaterial?

@alteredq
Copy link
Contributor

alteredq commented Mar 6, 2012

MeshFaceMaterial is not "proper" material, it's just dummy pass-through material for using real materials defined in geometry.materials (so that single mesh can have faces with different materials).

All standard materials (e.g MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial) internally use the same machinery as ShaderMaterial, they are basically just shortcuts for commonly used features.

You can check how they are implemented here:

https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLShaders.js

If you already have something working without texture, it's just few lines to add to get texture to your ShaderMaterial.

See this example:

https://github.com/mrdoob/three.js/blob/master/examples/webgl_custom_attributes.html

@landonsilla
Copy link
Author

Thank you for your response, @alteredq , however I'm still stuck. I adjusted my code to use ShaderMaterial for my material:

    var material =  new THREE.ShaderMaterial({
        uniforms:       uniformsmesh,
        vertexShader:   document.getElementById( 'vertexshader-mesh' ).textContent,
        fragmentShader: document.getElementById( 'fragmentshader-mesh' ).textContent
    });

I read through the shader definition javascript file. And I checked out the demo you sent; that demo doesn't seem to help. It shows how you can use attributes and uniforms to send data to the shader. That's something that I can already do in my application with the use of ShaderMaterial. All I need to do is to have ShaderMaterial use the associated jpg's to texture the mesh as defined in the obj/mtl files (that was converted into json format).

One puzzling thing was that I looked in MeshFaceMaterial.js to see how it works for the phototexturing, and it's just a blank function. Where is the code that performs the phototexturing associated with MeshFaceMaterial?

In any event, you said it will just take a few lines to get phototexturing working with ShaderMaterial. I'm afraid I need a little more help. I tried putting more attributes in my ShaderMaterial like skinning:true. I also tried altering my shaders. Where does the code go?

As I said, this should be the last step in this bounding box and afterwards, I will gladly share my code for others to use.

@alteredq
Copy link
Contributor

alteredq commented Mar 7, 2012

In webgl_custom_attributes.html relevant lines for using texture in ShaderMaterial are:

Uniform that holds texture:

texture:   { type: "t", value: 0, texture: THREE.ImageUtils.loadTexture( "textures/water.jpg" ) },

https://github.com/mrdoob/three.js/blob/master/examples/webgl_custom_attributes.html#L120

Declaration of this uniform in fragment shader:

uniform sampler2D texture;

https://github.com/mrdoob/three.js/blob/master/examples/webgl_custom_attributes.html#L66

Getting color out of texture:

vec4 tcolor = texture2D( texture, vUv );

https://github.com/mrdoob/three.js/blob/master/examples/webgl_custom_attributes.html#L75

Declaration of texture coordinates varying in vertex and fragment shaders:

varying vec2 vUv;

https://github.com/mrdoob/three.js/blob/master/examples/webgl_custom_attributes.html#L46
https://github.com/mrdoob/three.js/blob/master/examples/webgl_custom_attributes.html#L63

Passing of texture coordinate attribute into varying in vertex shader (you don't need to mess with them, it was just in that example to have "floating" texture effect, just pass them unchanged):

vUv = uv;

https://github.com/mrdoob/three.js/blob/master/examples/webgl_custom_attributes.html#L51

Now if you just want to reuse texture that is already defined in some material in JSON, it should be enough to do something like this:

uniforms[ "texture" ].texture = geometry.materials[ 0 ].map;

@landonsilla
Copy link
Author

Ok, great! Thank you @alteredq . I slapped some of that code for the shader in there and I got it working! I started stripping the lines that didn't apply to my needs and it actually dwindled to something very brief and concise. The worldPosition and clippingPlane stuff is for the clipping box, the rest is for the texturing:

<script type="x-shader/x-vertex" id="vertexshader2">
    varying vec2 vUv;
    varying vec4 worldPosition;
    void main() {
        worldPosition = objectMatrix * vec4( position, 1.0 );
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
</script>

<script type="x-shader/x-fragment" id="fragmentshader2">
    varying vec2 vUv;
    uniform sampler2D texture;

    varying vec4 worldPosition;
    uniform float xClippingPlaneMax;
    uniform float xClippingPlaneMin;
    uniform float yClippingPlaneMax;
    uniform float yClippingPlaneMin;
    uniform float zClippingPlaneMax;
    uniform float zClippingPlaneMin;

    void main() {
        vec4 tcolor = texture2D( texture, vUv );
        gl_FragColor = tcolor;

        if(worldPosition.x<xClippingPlaneMin) discard;
        if(worldPosition.x>xClippingPlaneMax) discard;
        if(worldPosition.z<zClippingPlaneMin) discard;
        if(worldPosition.z>zClippingPlaneMax) discard;
        if(worldPosition.y<yClippingPlaneMin) discard;
        if(worldPosition.y>yClippingPlaneMax) discard;
    }
</script>

I do have one other small problem. In my uniform, in js, I have:

texture: THREE.ImageUtils.loadTexture( "models/test25percentImage1.png" ) 

That's no good. I can't have the image hardcoded in there. And, worse, my json mesh file could point to multiple images (an obj/mtl meshes can and should have multiple images to make it look better, right?). How can I dynamically do this with multiple images without hardcoding the texture attribute in the uniform? I feel like I could read through the json file, find the different textures, and then split up all my geometries, but that sounds hard on the processor for 100's of thousands of faces. Is there a better way to do this with either three.js or webgl? Maybe some conditional logic in the shaders to point to a different texture file based on a certain attribute?

@mrdoob mrdoob closed this as completed Dec 13, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants