.. Superquadric Tensor Glyphs in Teem documentation file .. highlight:: c .. _Teem: http://teem.sf.net/ .. _mailing list: https://lists.sourceforge.net/lists/listinfo/teem-users .. _OpenGL wiki: http://www.opengl.org/wiki/Vertex_Buffer_Object .. _OpenGL documentation: http://www.opengl.org/documentation/glsl/ Superquadric Glyphs for Symmetric Second-Order Tensors ====================================================== We currently do not provide an out-of-the-box demo program for our Vis 2010 paper on superquadric tensor glyphs [SK10]_, but the essential C code is available in the open source library Teem_. With the help of this tutorial, you should be able to integrate our code directly into your own favorite visualization framework. If you are new to Teem, you may find our `quick introduction `__ helpful. If you are unfamiliar with OpenGL and GLSL, the books by Shreiner [Shr09]_ and Rost et al. [RLC09]_ are standard references. If you should run into problems with this tutorial or if you find an error, Teem's `mailing list`_ is the most useful place for discussing that. Creating Your First Tensor Glyph -------------------------------- Teem represents symmetric tensors as arrays of seven floating point numbers. The first is a confidence value, which is expected to be in interval [0,1] and can be used to mask parts of a tensor field. It is not relevant to this tutorial. The remaining values specify the unique tensor components, in lexicographic order: xx, xy, xz, yy, yz, zz. To create a tensor glyph with Teem, you first need to :: #include near the top of your source file. Before geometry is created, we will perform a spectral decomposition of the tensor, map its eigenvalues to normalized (u,v) space (cf. Section 4.2 of the paper), and map (u,v) to the (alpha,beta) parameters of the superquadric (cf. Section 4.3). If the given tensor has very small norm, we will also blend (alpha,beta) with (1,1), so that a sphere will be used as the base glyph of the zero tensor. These steps are done by the following code: :: /* input variables */ double ten[7]={1.0,1.0,0.0,0.0,-0.5,0.0,-0.3}; /* tensor coefficients */ double eps=1e-4; /* small value >0; defines the smallest tensor * norm at which tensor orientation is still meaningful */ /* example code starts here */ double evals[3], evecs[9], uv[2], abc[3], norm; tenEigensolve_d(evals, evecs, ten); tenGlyphBqdUvEval(uv, evals); tenGlyphBqdAbcUv(abc, uv, 3.0); norm=ELL_3V_LEN(evals); if (norm1.0) abc[2]=abc[1]; else abc[2]=(1.0-dist)*2+dist*abc[1]; lpds[aIdx][bIdx] = limnPolyDataNew(); limnPolyDataSpiralBetterquadric(lpds[aIdx][bIdx], (1 << limnPolyDataInfoNorm), abc[0], abc[1], abc[2], 0.0, 2*glyphRes, glyphRes); limnPolyDataVertexNormals(lpds[aIdx][bIdx]); } } For simplicity, we sample a rectangular part of (alpha, beta) space. Our glyphs only make use of a subset of it, as illustrated in Figure 5(b) of our paper. As an exercise, you could make sure that you don't create shapes that will never be used. However, even without this additional optimization, a reasonably sized palette only uses a comparatively modest amount of memory (e.g., less than 13MB for the 15x45 palette created above), so our own implementation currently does not bother about this. Glyph rendering is most efficient when the palette is kept in video memory via vertex buffer objects. [Shr09]_ or the `OpenGL wiki`_ are good places to learn more about the use of vertex buffer objects. All base glyphs share the same index array, so there is no need for more than one index buffer object. Using Fragment Shaders to Color Code Sign ----------------------------------------- An efficient and comparatively simple way to achieve the glyph coloring shown in the paper is to evaluate the homogeneous form in a fragment shader. This is simplified by the fact that we transmit the vertex positions of a zero-centered and axis-aligned base geometry to the GPU, and any translation and rotation are done by the modelview matrix. Our implementation uses the OpenGL Shading Language (GLSL), which is described in the `OpenGL documentation`_. We are using the following GLSL code, which assumes a single directional light source and also does per-pixel Phong shading. Our vertex shader ``sign.vert`` is: :: varying vec4 diffuse,ambient; varying vec3 normal,lightDir,halfVector; void main() { /* transform the normal into eye space and * normalize the result */ normal = normalize(gl_NormalMatrix * gl_Normal); /* normalize the (directional) light * lights are already in eye space, no need to transform */ lightDir = normalize(vec3(gl_LightSource[0].position)); /* Normalize the halfVector (needed in the fragment shader) */ halfVector = normalize(gl_LightSource[0].halfVector.xyz); /* Compute the diffuse, ambient and globalAmbient terms */ diffuse = gl_Color * gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; ambient = gl_Color * gl_FrontMaterial.ambient * gl_LightSource[0].ambient; ambient += gl_Color * gl_LightModel.ambient * gl_FrontMaterial.ambient; gl_TexCoord[0] = gl_Vertex; /* pass on vertex pos as tex coordinates */ gl_Position = ftransform(); } The corresponding fragment shader ``sign.frag`` is: :: uniform vec4 posColor,negColor; uniform vec3 evals; varying vec4 diffuse,ambient; varying vec3 normal,lightDir,halfVector; void main() { vec3 n,halfV; float NdotL,NdotHV; vec3 v=gl_TexCoord[0].xyz; /* really the untransformed vertex position */ n = evals*v*v; /* abuse n for evaluating the homogeneous form */ vec4 signColor=posColor; if (n.x+n.y+n.z<0.0) signColor=negColor; /* The ambient term will always be present */ vec4 color = ambient*signColor; /* re-normalize the interpolated normal */ n = normalize(normal); /* compute the dot product between normal and ldir */ NdotL = max(dot(n,lightDir),0.0); if (NdotL > 0.0) { color += diffuse * signColor * NdotL; halfV = normalize(halfVector); NdotHV = max(dot(n,halfV),0.0); color += gl_FrontMaterial.specular * gl_LightSource[0].specular * pow(NdotHV, gl_FrontMaterial.shininess); } gl_FragColor = color; } Before rendering with these shaders, you will need to set the light source (using standard OpenGL calls, not shown below), specify two glyph colors (one for positive, one for negative), and provide the three eigenvalues of the tensor. Note that in the case where the modelview matrix included the "additional" rotation in our code above, the first and last eigenvalue switch places: :: /* setup shaders, once per run of your program: */ const char *vertSource = #include "sign.vert" ; const char *fragSource = #include "sign.frag" ; GLhandleARB frag, vert, prog; frag = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); glShaderSourceARB(frag, 1, &fragSource, NULL); glCompileShaderARB(frag); vert = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); glShaderSourceARB(vert, 1, &vertSource, NULL); glCompileShaderARB(vert); prog = glCreateProgramObjectARB(); glAttachObjectARB(prog, vert); glAttachObjectARB(prog, frag); glLinkProgramARB(prog); /* this can be done once per rendering pass: */ glUseProgramObjectARB(prog); float posColor[4] = {1.0, 0.38, 0.23,1.0}; float negColor[4] = {0.29, 0.33, 1.0,1.0}; GLint pointer = glGetUniformLocationARB(prog, "posColor"); glUniform4fvARB(pointer,1,posColor); pointer = glGetUniformLocationARB(prog, "negColor"); glUniform4fvARB(pointer,1,negColor); pointer = glGetUniformLocationARB(prog, "evals"); /* this needs to be done for each glyph: */ float glevals[3]; if (0==zone || 5==zone || 6==zone || 7==zone || 8==zone) { ELL_3V_SET(glevals, evals[2], evals[1], evals[0]); } else { ELL_3V_COPY(glevals, evals); } glUniform3fvARB(pointer,1,evals); To integrate the shader code in this way, you need to encode the above ``sign.vert`` and ``sign.frag`` as a single C string literal each. On a Unix-style command line, .. code-block:: sh sed -e 's/^/"/g' < sign.vert | sed -e 's/$/\\n"/g' should do the trick. If you haven't worked with shader programs before, we recommend that you consult [RLC09]_, the official OpenGL documentation and/or online tutorials to gain a better understanding of our sample code, and to find out how to do proper error checking and OpenGL resource management (omitted for brevity). .. figure:: superquad-color.png :alt: Color coded rendering of a superquadric glyph :align: center The same glyph geometry as above, but with color coded sign. References ---------- .. [RLC09] Randi J. Rost and Bill Licea-Kane. OpenGL Shading Language (3rd edition). Addison-Wesley, 2009 .. [Shr09] Dave Shreiner. OpenGL Programming Guide: The Official Guide to Learning OpenGL, Versions 3.0 and 3.1 (7th edition). Addison-Wesley, 2009. .. [SK10] Thomas Schultz and Gordon Kindlmann. Superquadric Glyphs for Symmetric Second-Order Tensors. IEEE Transactions on Visualization and Computer Graphics (Proc. IEEE Vis) 16(6):1595-1604, 2010.