{"id":186,"date":"2016-08-31T03:11:00","date_gmt":"2016-08-30T18:11:00","guid":{"rendered":"https:\/\/posuer000.wordpress.com\/?p=186"},"modified":"2018-12-28T12:54:10","modified_gmt":"2018-12-28T03:54:10","slug":"b-spline-surface-implementation-with-opengl","status":"publish","type":"post","link":"https:\/\/www.wanggengyu.com\/?p=186","title":{"rendered":"B-Spline Surface Implementation with OpenGL"},"content":{"rendered":"<p>In this implementation, instead of using any B-Spline built-in functions of OpenGL, I made my own functions calculate points on surface base on given knots and control points.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-500\" src=\"https:\/\/posuer000.files.wordpress.com\/2016\/08\/capture.jpg\" alt=\"capture\" width=\"1303\" height=\"768\" \/><\/p>\n<p>OK, let&#8217;s start!<\/p>\n<h3>Basic Information<\/h3>\n<p>First of all, I want to specify some notations in this implementation.<\/p>\n<p>Here are two core expression of B-Spline <strong>curve<\/strong>.<br \/>\n<!--more Read More--><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-212\" src=\"https:\/\/posuer000.files.wordpress.com\/2016\/06\/e59bbee789871.jpg\" alt=\"\u56fe\u72471\" width=\"530\" height=\"155\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-213\" src=\"https:\/\/posuer000.files.wordpress.com\/2016\/06\/e59bbee789872.jpg\" alt=\"\u56fe\u72472\" width=\"213\" height=\"66\" \/><\/p>\n<p>and here is the\u00a0linear combination of B-spline <strong>surface<\/strong>. They use the same expressions for the polynomial pieces ( N() function).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-214\" src=\"https:\/\/posuer000.files.wordpress.com\/2016\/06\/e59bbee789873.jpg\" alt=\"\u56fe\u72473\" width=\"300\" height=\"61\" \/><\/p>\n<p>If you do not understand these expressions clearly yet, I\u00a0recommend you read this tutorial\u00a0below before going on. Knowledge\u00a0in Unit 6 and 8 is enough for this implementation.<\/p>\n<p><a href=\"http:\/\/www.cs.mtu.edu\/~shene\/COURSES\/cs3621\/NOTES\/\">&lt;CS3621 Introduction to Computing with Geometry Notes&gt;<\/a><\/p>\n<p>&lt;You can download the complete runnable code and input file at the end of this page.&gt;<\/p>\n<h3><strong>Input Data<\/strong> (knots vector and control points)<\/h3>\n<p>Here is an example to help you have intuition view on input data. Since we are making a\u00a0surface, we have U and V two directions.<\/p>\n<p><code><br \/>\n6 6 \/\/the number of control points in U and V directions.<br \/>\n2 2 \/\/the degree of U and V direction<br \/>\n0 0 0 0.2 0.5 0.7 1 1 1 \/\/knots in U direction<br \/>\n0 0 0 0.2 0.5 0.7 1 1 1 \/\/knots in V direction<br \/>\n-30 -36 -30 \/\/first control point\u2019s 3D coordinates in U and V direction<br \/>\n-30 -26 -20 \/\/ secondcontrol point<br \/>\n....... \/\/Other 36 control points in this example. control points\u2019 coordinates in row-major order. The row is V direction, the column is U direction<br \/>\n<\/code><br \/>\nFrom this example, we have these variables.<\/p>\n<p><code><br \/>\nnumContrU = 6, numContrV = 6 \/\/numbers of control points<\/p>\n<p>degreeU = 2, degreeV = 2 \/\/ degree<br \/>\nknotU[numContrU + degreeU + 1] = 0 0 0 0.2 0.5 0.7 1 1 1<br \/>\nknotV[numContrV + degreeV + 1] = 0 0 0 0.2 0.5 0.7 1 1 1<br \/>\n<\/code><\/p>\n<p>The introduction\u00a0of the code for reading input data will be omitted here.<\/p>\n<h3>B-Spline Function (core code)<\/h3>\n<p>First, for the B-Spline basic functions(<i>Cox-de Boor recursion formula)<\/i><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-212\" src=\"https:\/\/posuer000.files.wordpress.com\/2016\/06\/e59bbee789871.jpg\" alt=\"\u56fe\u72471\" width=\"530\" height=\"155\" \/><\/p>\n<p>I wrote a recursive function deBoor()<\/p>\n<p><code><br \/>\nfloat deBoor(int i, int p, float u, float *knots){<br \/>\nfloat equationR, equationL;\/\/right coefficient and left coefficient<\/p>\n<p>if (p == 0){<br \/>\nif (u &amp;amp;gt;= knots[i] &amp;amp;amp;&amp;amp;amp; (u &amp;amp;lt; knots[i + 1]))<br \/>\nreturn 1;<br \/>\nelse return 0;<br \/>\n}<br \/>\nelse{<br \/>\nif ((knots[i + p] - knots[i]) == 0) equationR = 0; \/\/Prevent divide by zero<br \/>\nelse equationR = ((u - knots[i]) \/ (knots[i + p] - knots[i]));<br \/>\nif ((knots[i + p + 1] - knots[i + 1]) == 0) equationL = 0; \/\/ Prevent divide by zero<br \/>\nelse equationL = ((knots[i + p + 1] - u) \/ (knots[i + p + 1] - knots[i + 1]));<br \/>\nreturn equationR * deBoor(i, p - 1, u, knots) + equationL * deBoor(i + 1, p - 1, u, knots);<br \/>\n}<br \/>\n}<br \/>\n<\/code><\/p>\n<p>After calculating the\u00a0deBoor(), it&#8217;s the time to get the points on the surface\u00a0by Control Points. I wrote a function to do this job.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-214\" src=\"https:\/\/posuer000.files.wordpress.com\/2016\/06\/e59bbee789873.jpg\" alt=\"\u56fe\u72473\" width=\"300\" height=\"61\" \/><\/p>\n<p><code><br \/>\nvoid bSpline(){<\/p>\n<p>for (int u = 0; u &amp;amp;lt; numSegment; u++) \/\/for each point on the surface, 20*20 points for this surface<br \/>\nfor (int v = 0; v &amp;amp;lt; numSegment; v++){<br \/>\nfor (int i = 0; i &amp;amp;lt;= numContrU; i++)\/\/for each control points<br \/>\nfor (int j = 0; j &amp;amp;lt;= numContrV; j++){<br \/>\nP[u][v] = P[u][v] + ctlPoints[i][j] * double(deBoor(i, degreeU, u*piece, knotU) * deBoor(j, degreeV, v*piece, knotV));<br \/>\n}<br \/>\n}<br \/>\n}<br \/>\n<\/code><\/p>\n<h3>Normal Vectors<\/h3>\n<p>I wrote a function(vertexNormal) calculate the normal vector of each vertex(smooth shading) and face (flat shading) on the surface.<\/p>\n<p><code><br \/>\nVec3 faceNormal(Vec3 a, Vec3 b, Vec3 c){<br \/>\nVec3 ab = a - b;<br \/>\nVec3 ac = a - c;<br \/>\nVec3 cross = ab.crossProduct(ac);<br \/>\nreturn cross.normalize();<br \/>\n}<\/p>\n<p>void vertexNormal(){<br \/>\nfor (int i = 0; i &amp;amp;lt; numSegment-1; i++)<br \/>\nfor (int j = 0; j &amp;amp;lt; numSegment-1; j++){<br \/>\nfacenormal[i][j] = faceNormal(P[i][j + 1], P[i + 1][j], P[i][j]);<br \/>\n}<br \/>\n\/\/four corners<br \/>\nvertexnormal[0][0] = facenormal[0][0];<br \/>\nvertexnormal[numSegment][numSegment] = facenormal[numSegment - 1][numSegment - 1];<br \/>\nvertexnormal[0][numSegment] = facenormal[0][numSegment - 1];<br \/>\nvertexnormal[numSegment][0] = facenormal[numSegment - 1][0];<br \/>\n\/\/four edge<br \/>\nfor (int i = 1; i &amp;amp;lt; numSegment; i++){<br \/>\nvertexnormal[i][0] = (facenormal[i - 1][0] + facenormal[i][0]) \/ 2;<br \/>\nvertexnormal[0][i] = (facenormal[0][i-1] + facenormal[0][i]) \/ 2;<br \/>\nvertexnormal[i][19] = (facenormal[i - 1][18] + facenormal[i][18]) \/ 2;<br \/>\nvertexnormal[19][i] = (facenormal[18][i-1] + facenormal[18][i]) \/ 2;<br \/>\n}<br \/>\n\/\/others<br \/>\nfor (int i = 1; i &amp;amp;lt; numSegment-1; i++)<br \/>\nfor (int j = 1; j &amp;amp;lt; numSegment-1; j++){<br \/>\nvertexnormal[i][j] = (facenormal[i - 1][j - 1] + facenormal[i - 1][j] + facenormal[i][j - 1] + facenormal[i][j]) \/ 4;<br \/>\n}<br \/>\n}<br \/>\n<\/code><\/p>\n<h3>Render the Sence<\/h3>\n<p>I used the\u00a0GL_TRIANGLES to generate the surface in two models(smooth shading and flat shading).<\/p>\n<p>If you use the GL_LIGHT_MODEL_TWO_SIDE model \u00a0for lighting, you have to be careful to arrange the order of three points of each triangle and their normal vectors.<\/p>\n<h4>Smooth Shading<\/h4>\n<p>Each loop generates two triangles, all these triangles will be connected together.<\/p>\n<p><code><br \/>\nglShadeModel(GL_SMOOTH);<br \/>\nglBegin(GL_TRIANGLES);<br \/>\nfor (int i = 0; i &amp;amp;amp;amp;lt; numSegment-1; i++){<br \/>\nfor (int j = 0; j &amp;amp;amp;amp;lt; numSegment-1; j++){<br \/>\nglNormal3f(vertexnormal[i][j + 1].x, vertexnormal[i][j + 1].y, vertexnormal[i][j + 1].z);<br \/>\nglVertex3f(P[i][j + 1].x, P[i][j + 1].y, P[i][j + 1].z);<br \/>\nglNormal3f(vertexnormal[i + 1][j].x, vertexnormal[i + 1][j].y, vertexnormal[i + 1][j].z);<br \/>\nglVertex3f(P[i + 1][j].x, P[i + 1][j].y, P[i + 1][j].z);<br \/>\nglNormal3f(vertexnormal[i][j].x, vertexnormal[i][j].y, vertexnormal[i][j].z);<br \/>\nglVertex3f(P[i][j].x, P[i][j].y, P[i][j].z);<\/p>\n<p>glNormal3f(vertexnormal[i + 1][j].x, vertexnormal[i + 1][j].y, vertexnormal[i + 1][j].z);<br \/>\nglVertex3f(P[i + 1][j].x, P[i + 1][j].y, P[i + 1][j].z);<br \/>\nglNormal3f(vertexnormal[i][j + 1].x, vertexnormal[i][j + 1].y, vertexnormal[i][j + 1].z);<br \/>\nglVertex3f(P[i][j + 1].x, P[i][j + 1].y, P[i][j + 1].z);<br \/>\nglNormal3f(vertexnormal[i + 1][j + 1].x, vertexnormal[i + 1][j + 1].y, vertexnormal[i + 1][j + 1].z);<br \/>\nglVertex3f(P[i + 1][j + 1].x, P[i + 1][j + 1].y, P[i + 1][j + 1].z);<br \/>\n}<br \/>\n}<br \/>\nglEnd();<br \/>\n<\/code><\/p>\n<h4>Flat Shading<\/h4>\n<p>Different from the smooth shading, there is only one normal vector for each triangle.<\/p>\n<p><code><br \/>\nVec3 facenormal1, facenormal2;<br \/>\nglShadeModel(GL_FLAT);<br \/>\nglBegin(GL_TRIANGLES);<br \/>\nfor (int i = 0; i &amp;amp;amp;amp;lt; numSegment-1; i++){<br \/>\nfor (int j = 0; j &amp;amp;amp;amp;lt; numSegment-1; j++){<br \/>\nfacenormal1 = faceNormal(P[i][j + 1], P[i + 1][j], P[i][j]);<br \/>\nfacenormal2 = faceNormal(P[i + 1][j + 1], P[i + 1][j], P[i][j + 1]);<\/p>\n<p>glNormal3f(facenormal1.x, facenormal1.y, facenormal1.z);<br \/>\nglVertex3f(P[i][j + 1].x, P[i][j + 1].y, P[i][j + 1].z);<br \/>\nglVertex3f(P[i + 1][j].x, P[i + 1][j].y, P[i + 1][j].z);<br \/>\nglVertex3f(P[i][j].x, P[i][j].y, P[i][j].z);<\/p>\n<p>glNormal3f(facenormal2.x, facenormal2.y, facenormal2.z);<br \/>\nglVertex3f(P[i + 1][j].x, P[i + 1][j].y, P[i + 1][j].z);<br \/>\nglVertex3f(P[i][j + 1].x, P[i][j + 1].y, P[i][j + 1].z);<br \/>\nglVertex3f(P[i+1][j+1].x, P[i+1][j+1].y, P[i+1][j+1].z);<br \/>\n}<br \/>\n}<br \/>\nglEnd()<br \/>\n<\/code><\/p>\n<p>Now, you have a B-Spline surface!<\/p>\n<p>I added some small features for this surface.<\/p>\n<h3>Other Features<\/h3>\n<h4>Keyboard Contol<\/h4>\n<p><code><br \/>\nvoid processSpecialKeys(int key, int x, int y)<br \/>\n{<br \/>\nswitch (key) {<br \/>\ncase GLUT_KEY_F2: \/\/Switch flat and smooth shading<br \/>\nflatSmooth = !flatSmooth;<br \/>\nbreak;<br \/>\ncase GLUT_KEY_F3: \/\/turn on\/off the control polygon<br \/>\nshowControlPolygon = !showControlPolygon;<br \/>\nbreak;<br \/>\ncase GLUT_KEY_F4: \/\/ture on\/off the wire frame<br \/>\nshowWireframe = !showWireframe;<br \/>\nglutPostRedisplay();<br \/>\nbreak;<br \/>\n\/\/change the visualization view, there is some code works together in the renderScenc function.<br \/>\ncase GLUT_KEY_LEFT:<br \/>\nyAngle += 5;<br \/>\nif (yAngle == 360) yAngle = 0;<br \/>\nbreak;<br \/>\ncase GLUT_KEY_RIGHT:<br \/>\nyAngle -= 5;<br \/>\nif (yAngle == -360) yAngle = 0;<br \/>\nbreak;<br \/>\ncase GLUT_KEY_UP:<br \/>\nxAngle += 5;<br \/>\nif (xAngle == 360) xAngle = 0;<br \/>\nbreak;<br \/>\ncase GLUT_KEY_DOWN:<br \/>\nxAngle -= 5;<br \/>\nif (xAngle == -360) xAngle = 0;<br \/>\nbreak;<br \/>\n}<br \/>\nglutPostRedisplay();<br \/>\n}<br \/>\n<\/code><\/p>\n<h4>Flat and Smooth Shading<\/h4>\n<p><code><br \/>\nif (flatSmooth){<br \/>\n\/************ Smooth shading ****************\/<br \/>\n\/\/you can find this part of code above<br \/>\n}<br \/>\nelse {<br \/>\n\/**************Flat shading****************<br \/>\n\/\/you can find this part of code above<br \/>\n}<br \/>\n<\/code><\/p>\n<h4>Control Polygon<\/h4>\n<p><code><br \/>\nif (showControlPolygon) {<br \/>\nglDisable(GL_LIGHTING);<br \/>\nglColor3f(1.0, 1.0, 0.0);<br \/>\nfor (int i = 0; i &amp;amp;lt;= numContrU; i++) {<br \/>\nglBegin(GL_LINE_STRIP);<br \/>\nfor (int j = 0; j &amp;amp;lt;= numContrV; j++) {<br \/>\nglVertex3f(ctlPoints[i][j].x, ctlPoints[i][j].y, ctlPoints[i][j].z);<br \/>\n}<br \/>\nglEnd();<br \/>\n}<br \/>\nfor (int i = 0; i &amp;amp;lt;= numContrV; i++) {<br \/>\nglBegin(GL_LINE_STRIP);<br \/>\nfor (int j = 0; j &amp;amp;lt;= numContrU; j++) {<br \/>\nglVertex3f(ctlPoints[j][i].x, ctlPoints[j][i].y, ctlPoints[j][i].z);<br \/>\n}<br \/>\nglEnd();<br \/>\n}<br \/>\nglEnable(GL_LIGHTING);<br \/>\n}<br \/>\n<\/code><\/p>\n<h4>Control Polygon<\/h4>\n<p><code><br \/>\nif (showWireframe) {<br \/>\nglDisable(GL_LIGHTING);<br \/>\nglColor3f(1.0, 1.0, 1.0);<br \/>\nfor (int i = 0; i &amp;amp;lt; numSegment; i++) {<br \/>\nglBegin(GL_LINE_STRIP);<br \/>\nfor (int j = 0; j &amp;amp;lt; numSegment; j++) {<br \/>\nglVertex3f(P[i][j].x, P[i][j].y, P[i][j].z);<br \/>\n}<br \/>\nglEnd();<br \/>\nglBegin(GL_LINE_STRIP);<br \/>\nfor (int j = 0; j &amp;amp;lt; numSegment; j++) {<br \/>\nglVertex3f(P[j][i].x, P[j][i].y, P[j][i].z);<br \/>\n}<br \/>\nglEnd();<br \/>\n}<br \/>\nglEnable(GL_LIGHTING);<br \/>\n}<br \/>\n<\/code><\/p>\n<h4>Control Polygon<\/h4>\n<p><code><br \/>\n\/\/Global declaration<br \/>\nfloat xAngle = 0, yAngle = 0;<br \/>\n\/\/In the renderScence function<br \/>\nglRotatef(xAngle, 1.0f, 0.0f, 0.0f);<br \/>\nglRotatef(yAngle, 0.0f, 1.0f, 0.0f);<br \/>\n\/\/Kyeboard contol<br \/>\n\/\/In the kyeboard control function<br \/>\n<\/code><\/p>\n<p>Ok! That&#8217;s all!<\/p>\n<p>Code and input file <a href=\"https:\/\/drive.google.com\/file\/d\/0B8T766Ym3SrvYk5lTXlMSVBHWjQ\/view?usp=sharing\" target=\"_blank\">download<\/a><\/p>\n<p>Thanks for reading. If you have any questions, please comment this articles.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this implementation, instead of using any B-Spline built-in functions of OpenGL, I made my own functions calculate points on surface base on given knots and control points. OK, let&#8217;s start! Basic Information First of all, I want to specify some notations in this implementation. Here are two core expression&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-186","post","type-post","status-publish","format-standard","hentry","category-techniques"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=\/wp\/v2\/posts\/186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=186"}],"version-history":[{"count":1,"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=\/wp\/v2\/posts\/186\/revisions"}],"predecessor-version":[{"id":683,"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=\/wp\/v2\/posts\/186\/revisions\/683"}],"wp:attachment":[{"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wanggengyu.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}