其他

[ 源码分享 ] Test251- 有关树的生成

2017-10-30 Wenzy InsLab

分享一个先前练习的源码,使用的框架为 Openframeworks。下图是用该程序生成 3d 模型文件,再放到 KeyShot 中渲染的效果

程序中的生成过程

(打开程序后,枝干会自动开始生成,如希望产生其他形态,可按快捷键 ‘ C ’ 重置)

(待曲线生成完毕,按快捷键‘ R ’可将曲线转换为 3d 模型文件,并储存到 data 文件夹中)


具体代码

ofApp.h 部分

#include "ofMain.h" #include "WenzyGrowingCurve.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ......    ofPoint crossProduct(ofPoint a,ofPoint b);    void generate();    void drawMeshLine(ofPolyline myLine,int divideNum,int circleNum,float Rmin,float Rmax);    ofMesh mesh;    bool addingMesh;    int startStepNum; // 起始步长    int bloomNum; // 每个节点生长的枝干数量    int maxRecursionNum;    ofEasyCam cam;    vector<WenzyGrowingCurve> curve; // 储存所有曲线 };

ofApp.cpp 部分

void ofApp::setup(){    cam.setDistance(2000);    startStepNum = 200;    maxRecursionNum = 3; // 递归次数,决定枝干复杂度    bloomNum = 5;    generate(); } void ofApp::generate(){    // 样式一:    for(int i = 0;i < 1;i++){        WenzyGrowingCurve temp;        temp.setup(ofPoint(0,-300,0),ofPoint(0,1,0),0,startStepNum,0.01);        curve.push_back(temp);    }    // 样式二 //    for(int i = 0;i < 6;i++){ //        WenzyGrowingCurve temp; //        temp.setup(ofPoint(0,0,0),ofPoint(0,0,0),0,startStepNum,0.02); //        curve.push_back(temp); //    }    mesh.setMode(OF_PRIMITIVE_TRIANGLES); } //-------------------------------------------------------------- void ofApp::update(){    for(int i = 0;i < curve.size();i++){        curve[i].update();        // 长出新枝干        if(curve[i].generation < maxRecursionNum && curve[i].moving == false && !curve[i].hasBloomed){            curve[i].hasBloomed = true;            for(int j = 0;j < bloomNum;j++){                WenzyGrowingCurve temp;                int nextStepNum;                if(curve[i].generation < maxRecursionNum - 1){                    float ratio = 0.72; // 决定每个枝干缩小的比例                    nextStepNum = pow(ratio,curve[i].generation) * startStepNum * ofRandom(0.8,1.1);                }else{                    // 末端枝干更短                    nextStepNum = startStepNum * 0.2 * ofRandom(0.5,1.5);                }                temp.setup(curve[i].endPos,curve[i].curveLineDir,curve[i].generation + 1,nextStepNum,0.06);                curve.push_back(temp);            }        }    }    ofSetWindowTitle(ofToString(ofGetFrameRate())); } // 求叉积 ofPoint ofApp::crossProduct(ofPoint a, ofPoint b){    ofPoint c;    c.x = a.y*b.z - a.z*b.y;    c.y = a.z*b.x - a.x*b.z;    c.z = a.x*b.y - a.y*b.x;    return c; } //-------------------------------------------------------------- void ofApp::draw(){    ofBackground(0);    cam.begin();    ofRotateY(ofGetFrameNum() * 0.5);    ofSetLineWidth(2.5);    ofSetColor(255,200);    for(int i = 0;i < curve.size();i++){        curve[i].draw();    }    if(addingMesh){        for(int i = 0;i < curve.size();i++){            float minR = 50 * pow(0.5,curve[i].generation + 1);            float maxR = 50 * pow(0.5,curve[i].generation);            if(curve[i].generation == 0){                drawMeshLine(curve[i].curveLine,20,20,maxR,minR);            }else if(curve[i].generation == maxRecursionNum){                // 末端枝干用更少的面数可节省资源                drawMeshLine(curve[i].curveLine,6,10,maxR,minR);            }else{                drawMeshLine(curve[i].curveLine,20,20,maxR,minR);            }        }        addingMesh = false;        // 保存文件        mesh.save("1.ply");    }    if(!addingMesh){        ofSetLineWidth(1);        ofSetColor(255,100);        mesh.drawWireframe();    }    cam.end(); } void ofApp::drawMeshLine(ofPolyline myLine,int divideNum,int circleNum,float startR,float endR){    vector<vector<ofPoint>> myPos;    // 从 -1 开始是为了能绘制底面    for(int i = -1;i < divideNum;i++){        float ratio1,ratio2;        ofPoint A,B; // 根据 A,B 求圆环        if(i != -1){            ratio1 = i/(float)divideNum;            A = myLine.getPointAtPercent(ratio1);            ratio2 = (i + 1)/(float)divideNum;            B = myLine.getPointAtPercent(ratio2);        }else{            ratio1 = 0;            A = myLine.getPointAtPercent(ratio1);            ratio2 = 0.00001;            B = myLine.getPointAtPercent(ratio2);        }        // dir 为基向量        ofPoint ab;        ab = B - A;        ab.normalize();        // ab 与 X 轴的叉积        ofPoint M;        M = crossProduct(ab,ofPoint(1,0,0));        // ab 与 M 的叉积,N        ofPoint N;        N = crossProduct(ab,M);        // 求基向量        ofPoint n,m;        n = N.normalize();        m = M.normalize();        // 设 theta        float newRatio = (i + 1)/(float)(divideNum + 1);        float R = ofLerp(startR,endR,newRatio);        vector<ofPoint> tempPos;        for(int i = 0;i < circleNum;i++){            float theta = 2 * PI /circleNum * i;            float ratio = 1;            ofPoint C;            C = B + ratio * R * (m * cos(theta) + n * sin(theta));            ofSetColor(255,0,0);            //ofDrawSphere(C,3);            tempPos.push_back(C);        }        myPos.push_back(tempPos);    }    ofSetColor(255,150);    for(int i = 0;i < myPos.size()-1;i++){        int indexA = i;        int indexB = i + 1;        for(int j = 0;j < circleNum;j++){            if(addingMesh){                mesh.addVertex(myPos[indexA][j]);                mesh.addVertex(myPos[indexA][(j + 1) % circleNum]);                mesh.addVertex(myPos[indexB][j]);                mesh.addVertex(myPos[indexA][(j + 1) % circleNum]);                mesh.addVertex(myPos[indexB][(j + 1) % circleNum]);                mesh.addVertex(myPos[indexB][j]);                if(i == 0){                    ofPoint center;                    center.set(0,0,0);                    for(int k = 0;k < circleNum;k++){                        center += myPos[indexA][k];                    }                    center /= circleNum;                    for(int k = 0;k < circleNum;k++){                        mesh.addVertex(center);                        mesh.addVertex(myPos[indexA][(k+1) % circleNum]);                        mesh.addVertex(myPos[indexA][k]);                    }                }                if(i == myPos.size() - 2){                    ofPoint center;                    center.set(0,0,0);                    for(int k = 0;k < circleNum;k++){                        center += myPos[indexB][k];                    }                    center /= circleNum;                    for(int k = 0;k < circleNum;k++){                        mesh.addVertex(myPos[indexB][k]);                        mesh.addVertex(myPos[indexB][(k+1) % circleNum]);                        mesh.addVertex(center);                    }                }            }        }    } } //-------------------------------------------------------------- void ofApp::keyPressed(int key){    if(key == 'r'){        addingMesh = !addingMesh;    }    //  重新生成    if(key == 'c'){        curve.clear();        mesh.clear();        generate();    } }

自定义类 WenzyGrowingCurve.h 

class WenzyGrowingCurve{ public:    ofPolyline curveLine; // 表示每条基础曲线    ofVec3f curveLineDir;  // 线条方向    ofVec3f curvePos; // 用于加入的点,方便累加    ofVec3f noiseSeed;    ofVec3f startPos,endPos; // 每条曲线的起始结束点    // 动画相关    bool moving; // 是否开始运动(添加点)    int curStep;  // 当前已运行步数    int stepNum; // 运行总步数    float stepLength; // 每次步进的长度    int generation; // 代数,方便外部初始化    float dirRange; // 扩散角度,数值越大扭曲程度越高    bool hasBloomed; // 是否已经生长过    void setup(ofVec3f startPos_,ofVec3f startDir_,int generation_,int stepNum_,float dirRange_){        noiseSeed = ofVec3f(ofRandom(100),ofRandom(100),ofRandom(100));        stepLength = 2;        moving = true;        curStep = 0;        stepNum = stepNum_;        generation = generation_;        curveLineDir = startDir_; // 继承上条曲线的方向        startPos = startPos_;        curvePos = startPos;        hasBloomed = false;        dirRange = dirRange_;        curveLine.curveTo(startPos);        curveLine.curveTo(startPos);    }    void update(){        if(moving && curStep < stepNum){            float noiseRange = 0.01; // 控制 noise 的输入            curveLineDir += ofVec3f(ofSignedNoise(noiseSeed.x + ofGetFrameNum() * noiseRange),                                    ofSignedNoise(noiseSeed.y + ofGetFrameNum() * noiseRange),                                    ofSignedNoise(noiseSeed.z + ofGetFrameNum() * noiseRange))                                    * dirRange;            curveLineDir.normalize();            curvePos += curveLineDir * stepLength;            if(curStep % 3 == 0){                curveLine.curveTo(curvePos);            }            curStep++;            if(curStep == stepNum){                endPos = curvePos;                curveLine.curveTo(curvePos);                curveLine.curveTo(curvePos);            } &nb 44 26413 44 11866 0 0 3790 0 0:00:06 0:00:03 0:00:03 3791 44 26413 44 11866 0 0 2872 0 0:00:09 0:00:04 0:00:05 2873 44 26413 44 11866 0 0 2312 0 0:00:11 0:00:05 0:00:06 2312 44 26413 44 11866 0 0 1934 0 0:00:13 0:00:06 0:00:07 2405 44 26413 44 11866 0 0 1663 0 0:00:15 0:00:07 0:00:08 2301sp;      }else{            moving = false;        }    }    void draw(){        curveLine.draw();    } };

简要说明:

  • 枝干的生成主要通过 WenzyGrowingCurve.h 类实现。代码不足 100 行,尽管结构简单,但生成的效果还是比较贴近植物的自然形态。通过该类生成的对象对应每根独立的枝干。调整相关参数,可以产生截然不同的形态。

  • 从曲线转化为立体,主要通过自定义函数 drawMeshLine 实现。之前某篇文章有介绍过具体原理。

  • 程序导出的 3d 文件格式为 ply,可以使用 Meshlab 等软件转换为常用的 obj 格式放到 keyshot 中渲染

End

以上代码仅仅是一个基础版本,还有很多内容可以继续深挖。例如使用 shader 渲染,添加树叶,让它产生更丰富自然的光影效果。

(使用 Shader 实时渲染)

完整工程文件下载:

(链接: https://pan.baidu.com/s/1sldpISP 密码: xs2v



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存