Cylinder Pretender in GLSL

I am developing a small tool for three-dimensional visualization of molecules. For my project, I decided to do something like what Mr. Brad Larson did with his Apple Molecule software. The link where you can find a small presentation of the equipment used: presentation of the Brad Larsson software

To do my job, I have to calculate the sensation of a spherical and self-styled cylinder .

At the moment, I managed to make "Sphere Impostor" with the help of another textbook, "Lies and Impostors"

to summarize the calculations of the burst of the sphere: first we send the “sphere position” and “radius of the sphere” to the “vertex shader”, which will create a square in the camera space that is always facing the camera, after which we send our square to the fragment shader, where we we use a simple ray tracing to find which fragment of the square is included in the sphere, and finally, we calculate the normal position and position of the fragment to calculate the lighting. (we also write gl_fragdepth in order to give good depth to our self-esteem!)

But now I’m locked in calculating the cylinder’s impostor, I’m trying to draw a parallel between the self-styled sphere and the self-styled cylinder, but I don’t find anything, my problem is that it was easy for a sphere because the sphere is always the same no matter how we make it see, we will always see the same thing: the "circle", and the other thing is that the sphere was completely defined by mathematics, then we can easily find the position and normals for calculating the lighting and create our impostor.

This is not the same for a cylinder, and I did not find hints for modeling a shape that can be used as a “cylinder impostor,” because the cylinder shows many different shapes depending on the angle we see!

therefore, my request is to ask you about a solution or an indication of my “impostor cylinder” problem.

+6
source share
4 answers

I know this question is more than one year old, but I would still like to give 2 cents.

I was able to make cylinder implants with a different technique, I took inspiration from the pymol code. Here is the basic strategy:

1) You want to draw a bounding box (a cuboid ) for the cylinder. To do this, you need 6 faces that translate into 18 triangles that translate into 36 triangular vertices. Assuming that you do not have access to the geometric shaders, you go to the vertex shader 36 times at the starting point of the cylinder, 36 times in the direction of the cylinder, and for each of these vertices you pass the corresponding point of the bounding box, For example, the vertex associated with point (0, 0, 0), means that it will be transformed in the lower left corner of the corner of the bounding box, (1,1,1) means the diagonally opposite point, etc.

2) In the vertex shader, you can build the points of the cylinder by moving each vertex (you have passed 36 equal vertices) in accordance with the corresponding points that you have passed. At the end of this step, you should have a bounding box for the cylinder.

3) Here you must restore the points on the visible surface of the bounding box. From the point of view that you get, you must complete the intersection of the rays-cylinders.

4) From the intersection point, you can restore depth and normal. You should also discard the intersection points that are outside the bounding box (this can happen when you look at the cylinder along its axis, the intersection point will be infinitely far).

By the way, this is a very difficult task if someone is interested in the source code here:

https://github.com/chemlab/chemlab/blob/master/chemlab/graphics/renderers/shaders/cylinderimp.frag

https://github.com/chemlab/chemlab/blob/master/chemlab/graphics/renderers/shaders/cylinderimp.vert

+3
source

From what I can understand from the article, I would interpret it as follows.

A homing cylinder, looking at any angle, has the following characteristics.

  • Above is a circle. Therefore, given that you will never need to look down the cylinder, you do not need to do anything.
  • From the side it is a rectangle. For a pixel shader, only the lighting count is required as usual.
  • From any other angle, it is a rectangle (the same one that was calculated in step 2), which curves. Its curvature can be modeled inside the pixel shader as the curvature of the upper ellipse. This curvature can be considered as simply the displacement of each “column” in the texture space depending on the viewing angle. The minor axis of this ellipse can be calculated by multiplying the major axis (cylinder thickness) with the coefficient of the current viewing angle (angle / 90), assuming that 0 means that you are viewing the side cylinder.

Fig 1. Viewing angles. I only considered case 0-90 in the math below, but other cases are trivially different.

Fig 2. Given the viewing angle (phi) and cylinder diameter (a), here, as a shader, it is necessary to deform the Y axis in the texture space Y = b 'sin (phi). And b '= a * (phi / 90). Cases phi = 0 and phi = 90 should never be displayed.

Of course, I did not take into account the length of this cylinder, which will depend on your specific projection and is not an image space problem.

+2
source

In addition to the answers to pygabriels, I want to share a standalone implementation using the specified shader code from Blaine Bell (PyMOL, Schrödinger, Inc.).

The approach explained by pygabriel can also be improved. The border can be aligned so that it always looks at the viewer. Only two faces are most visible. Consequently, only 6 vertices are needed (i.e., Two faces consisting of 4 triangles).

See the image here, the box (direction vector) is always facing the viewer:
Image: Limited Frame

For source code, download: cylinder gem source code

The code does not cover round caps and spelling projections. It uses a geometric shader to generate vertices. You can use the shader code in accordance with the PyMOL license agreement.

+2
source

A cylindrical impostor can actually be made just like a sphere, as Nicholas Bolas did in his lesson. You can make a square facing the camera and colorize it so that it looks like a cylinder, just like Nicole did for the spheres. And it’s not so difficult.

The way this is done is, of course, ray tracing. Note that a cylinder facing up in the chamber space is quite easy to implement. For example, an intersection with a side can be projected onto the xz plane, this is a two-dimensional problem of a line intersecting a circle. Getting the top and bottom is also not difficult, given the z coordinate of the intersection, so that you actually know the point of intersection of the ray and the circular plane, all you need to do is check if it is inside the circle. And basically, this, you get two points and return a closer one (the norm is also quite trivial).

And when it comes to an arbitrary axis, it turns out to be almost the same problem. When you solve equations in a cylinder with a fixed axis, you solve them for a parameter that describes how long you need to go from a given point in a given direction to reach the cylinder. From the “definition” of this, you should note that this parameter does not change if you rotate the world. Thus, you can rotate an arbitrary axis to become the y axis, solve the problem in space where the equations are simpler, get the parameter for the equation of the line in this space, but return the result in the camera space.

You can download shader files from here . Just a picture of this in action: screenshot

The code in which the magic happens (it is long because it is full of comments, but the code itself contains no more than 50 lines):

void CylinderImpostor(out vec3 cameraPos, out vec3 cameraNormal) { // First get the camera space direction of the ray. vec3 cameraPlanePos = vec3(mapping * max(cylRadius, cylHeight), 0.0) + cameraCylCenter; vec3 cameraRayDirection = normalize(cameraPlanePos); // Now transform data into Cylinder space wherethe cyl symetry axis is up. vec3 cylCenter = cameraToCylinder * cameraCylCenter; vec3 rayDirection = normalize(cameraToCylinder * cameraPlanePos); // We will have to return the one from the intersection of the ray and circles, // and the ray and the side, that is closer to the camera. For that, we need to // store the results of the computations. vec3 circlePos, sidePos; vec3 circleNormal, sideNormal; bool circleIntersection = false, sideIntersection = false; // First check if the ray intersects with the top or bottom circle // Note that if the ray is parallel with the circles then we // definitely won't get any intersection (but we would divide with 0). if(rayDirection.y != 0.0){ // What we know here is that the distance of the point y coord // and the cylCenter is cylHeight, and the distance from the // y axis is less than cylRadius. So we have to find a point // which is on the line, and match these conditions. // The equation for the y axis distances: // rayDirection.y * t - cylCenter.y = +- cylHeight // So t = (+-cylHeight + cylCenter.y) / rayDirection.y // About selecting the one we need: // - Both has to be positive, or no intersection is visible. // - If both are positive, we need the smaller one. float topT = (+cylHeight + cylCenter.y) / rayDirection.y; float bottomT = (-cylHeight + cylCenter.y) / rayDirection.y; if(topT > 0.0 && bottomT > 0.0){ float t = min(topT,bottomT); // Now check for the x and z axis: // If the intersection is inside the circle (so the distance on the xz plain of the point, // and the center of circle is less than the radius), then its a point of the cylinder. // But we can't yet return because we might get a point from the the cylinder side // intersection that is closer to the camera. vec3 intersection = rayDirection * t; if( length(intersection.xz - cylCenter.xz) <= cylRadius ) { // The value we will (optianally) return is in camera space. circlePos = cameraRayDirection * t; // This one is ugly, but i didn't have better idea. circleNormal = length(circlePos - cameraCylCenter) < length((circlePos - cameraCylCenter) + cylAxis) ? cylAxis : -cylAxis; circleIntersection = true; } } } // Find the intersection of the ray and the cylinder side // The distance of the point and the y axis is sqrt(x^2 + z^2), which has to be equal to cylradius // (rayDirection.x*t - cylCenter.x)^2 + (rayDirection.z*t - cylCenter.z)^2 = cylRadius^2 // So its a quadratic for t (A*t^2 + B*t + C = 0) where: // A = rayDirection.x^2 + rayDirection.z^2 - if this is 0, we won't get any intersection // B = -2*rayDirection.x*cylCenter.x - 2*rayDirection.z*cylCenter.z // C = cylCenter.x^2 + cylCenter.z^2 - cylRadius^2 // It will give two results, we need the smaller one float A = rayDirection.x*rayDirection.x + rayDirection.z*rayDirection.z; if(A != 0.0) { float B = -2*(rayDirection.x*cylCenter.x + rayDirection.z*cylCenter.z); float C = cylCenter.x*cylCenter.x + cylCenter.z*cylCenter.z - cylRadius*cylRadius; float det = (B * B) - (4 * A * C); if(det >= 0.0){ float sqrtDet = sqrt(det); float posT = (-B + sqrtDet)/(2*A); float negT = (-B - sqrtDet)/(2*A); float IntersectionT = min(posT, negT); vec3 Intersect = rayDirection * IntersectionT; if(abs(Intersect.y - cylCenter.y) < cylHeight){ // Again it in camera space sidePos = cameraRayDirection * IntersectionT; sideNormal = normalize(sidePos - cameraCylCenter); sideIntersection = true; } } } // Now get the results together: if(sideIntersection && circleIntersection){ bool circle = length(circlePos) < length(sidePos); cameraPos = circle ? circlePos : sidePos; cameraNormal = circle ? circleNormal : sideNormal; } else if(sideIntersection){ cameraPos = sidePos; cameraNormal = sideNormal; } else if(circleIntersection){ cameraPos = circlePos; cameraNormal = circleNormal; } else discard; } 
+2
source

Source: https://habr.com/ru/post/910131/


All Articles