-
简介
这节课NeHe教我们实现了卡通纹理。卡通渲染(英语:Cel-shading或者Toon Shading)是一种去真实感的渲染方法,旨在使电脑生成的图像呈现出手绘般的效果。为了使图像可以与漫画或者卡通达到形似的效果,专业人员通常使用卡通渲染着色器进行处理。卡通渲染是在大约21世纪初期,作为计算机图形学的副产物出现的新技术,并且主要应用于电子游戏中;然而,它可以呈现出如手绘动画一样简洁明了的效果。更多卡通渲染的内容可以参考Wiki:卡通渲染
首先我们需要创建模型,模型来自文件model.txt,读取该模型并生成节点:
- BOOL ReadMesh () // Reads The Contents Of The "model.txt" File ( NEW )
- {
- FILE *In = fopen ("Data\\model.txt", "rb"); // Open The File ( NEW )
- if (!In)
- return FALSE; // Return FALSE If File Not Opened ( NEW )
- fread (&polyNum, sizeof (int), 1, In); // Read The Header (i.e. Number Of Polygons) ( NEW )
- polyData = new POLYGON [polyNum]; // Allocate The Memory ( NEW )
- fread (&polyData[0], sizeof (POLYGON) * polyNum, 1, In); // Read In All Polygon Data ( NEW )
- fclose (In); // Close The File ( NEW )
- return TRUE; // It Worked ( NEW )
- }
1.卡通渲染着色:
首先创建一维纹理:
- osg::Image *image = new osg::Image;
- image->allocateImage(32, 1, 1, GL_RGB, GL_FLOAT);
- osg::Vec3 *dataPtr = (osg::Vec3*)(image->data());
- for (int i = 0; i < 32; ++i)
- {
- *dataPtr++ = osg::Vec3(shaderData[i][0], shaderData[i][1], shaderData[i][2]);
- }
- g_Texture1D = new osg::Texture1D;
- g_Texture1D->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- g_Texture1D->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- g_Texture1D->setImage(image);
在节点的更新回调中计算纹理坐标(纹理坐标计算需要法线方向和光线方向):光线的方向一直不变是(0,0,1),而法线方向随着旋转会改变,我们通过获取旋转量来计算法线的实时方向,然后通过二者点乘得到纹理坐标,实现如下:
- osg::FloatArray *textureArray = new osg::FloatArray;
- for (int i = 0; i < polyNum; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- osg::Vec3 nor;
- nor.x() = polyData[i].Verts[j].Nor.X;
- nor.y() = polyData[i].Verts[j].Nor.Y;
- nor.z() = polyData[i].Verts[j].Nor.Z;
- RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_RotMT->getUpdateCallback());
- double angle = rotCallback->getCurrentAngle();
- nor = nor * osg::Matrix::rotate(angle, osg::Y_AXIS);
- nor.normalize();
- osg::Vec3 lightDir = osg::Vec3(lightAngle.X, lightAngle.Y, lightAngle.Z);
- float TmpShade = nor * lightDir;
- if (TmpShade < 0.0f)
- TmpShade = 0.0f;
- textureArray->push_back(TmpShade);
- }
- }
之后把节点加到场景中就完成卡通着色的效果。
2.Outline外框
实质上是通过一系列状态的组合来得到这种效果:
- osg::Geode *outlineModel = createModelGeode();
- g_OutlineGeode = outlineModel;
- osg::Geometry *d = dynamic_cast<osg::Geometry*>(outlineModel->getDrawable(0));
- if(d)
- {
- osg::Vec3Array *vecArray = dynamic_cast<osg::Vec3Array*>(d->getColorArray());
- vecArray->at(0).set(osg::Vec3(0,0,0));
- d->setColorArray(vecArray, osg::Array::BIND_OVERALL);
- }
- osg::BlendFunc *blendFunc = new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
- osg::PolygonMode *polygonMode = new osg::PolygonMode(osg::PolygonMode::BACK, osg::PolygonMode::LINE);
- osg::LineWidth *lineWidth = new osg::LineWidth(3.0f);
- osg::CullFace *cullFace = new osg::CullFace(osg::CullFace::FRONT);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(polygonMode);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(lineWidth);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(cullFace);
在与键盘的交互代码中完成旋转和变换边框线的宽度:
- case(osgGA::GUIEventAdapter::KEYDOWN):
- {
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space)
- {
- static bool flag = true;
- if (!g_RotMT)
- return false;
- RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_RotMT->getUpdateCallback());
- if (!rotCallback)
- return false;
- if(flag)
- {
- double speed = rotCallback->getRotateSpeed();
- speed -= 0.02;
- rotCallback->setRotateSpeed(speed);
- }
- else
- {
- rotCallback->setRotateSpeed(0);
- }
- flag = !flag;
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
- {
- if (!g_OutlineGeode)
- return false;
- osg::LineWidth *lineWidth = dynamic_cast<osg::LineWidth*>( g_OutlineGeode->getOrCreateStateSet()->getAttribute(osg::StateAttribute::LINEWIDTH));
- float w = lineWidth->getWidth();
- w += 1.0f;
- lineWidth->setWidth(w);
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)
- {
- if (!g_OutlineGeode)
- return false;
- osg::LineWidth *lineWidth = dynamic_cast<osg::LineWidth*>(g_OutlineGeode->getOrCreateStateSet()->getAttribute(osg::StateAttribute::LINEWIDTH));
- float w = lineWidth->getWidth();
- w -= 1.0f;
- lineWidth->setWidth(w);
- }
附:本课源码(源码中可能存在错误和不足,仅供参考)
- #include "../osgNeHe.h"
- #include <QtCore/QTimer>
- #include <QtGui/QApplication>
- #include <QtGui/QVBoxLayout>
- #include <osgViewer/Viewer>
- #include <osgDB/ReadFile>
- #include <osgQt/GraphicsWindowQt>
- #include <osg/MatrixTransform>
- #include <osg/Texture1D>
- #include <osg/BlendFunc>
- #include <osg/CullFace>
- #include <osg/PolygonMode>
- #include <osg/LineWidth>
- // User Defined Structures
- typedef struct tagMATRIX // A Structure To Hold An OpenGL Matrix ( NEW )
- {
- float Data[16]; // We Use [16] Due To OpenGL's Matrix Format ( NEW )
- }
- MATRIX;
- typedef struct tagVECTOR // A Structure To Hold A Single Vector ( NEW )
- {
- float X, Y, Z; // The Components Of The Vector ( NEW )
- }
- VECTOR;
- typedef struct tagVERTEX // A Structure To Hold A Single Vertex ( NEW )
- {
- VECTOR Nor; // Vertex Normal ( NEW )
- VECTOR Pos; // Vertex Position ( NEW )
- }
- VERTEX;
- typedef struct tagPOLYGON // A Structure To Hold A Single Polygon ( NEW )
- {
- VERTEX Verts[3]; // Array Of 3 VERTEX Structures ( NEW )
- }
- POLYGON;
- // User Defined Variables
- bool outlineDraw = true; // Flag To Draw The Outline ( NEW )
- bool outlineSmooth = false; // Flag To Anti-Alias The Lines ( NEW )
- float outlineColor[3] = { 0.0f, 0.0f, 0.0f }; // Color Of The Lines ( NEW )
- float outlineWidth = 3.0f; // Width Of The Lines ( NEW )
- VECTOR lightAngle; // The Direction Of The Light ( NEW )
- bool lightRotate = false; // Flag To See If We Rotate The Light ( NEW )
- float modelAngle = 0.0f; // Y-Axis Angle Of The Model ( NEW )
- bool modelRotate = false; // Flag To Rotate The Model ( NEW )
- POLYGON *polyData = NULL; // Polygon Data ( NEW )
- int polyNum = 0; // Number Of Polygons ( NEW )
- //GLuint shaderTexture[1]; // Storage For One Texture ( NEW )
- osg::Texture1D *g_Texture1D;
- osg::MatrixTransform *g_RotMT = NULL;
- osg::Geode *g_OutlineGeode = NULL;
- // File Functions
- BOOL ReadMesh () // Reads The Contents Of The "model.txt" File ( NEW )
- {
- FILE *In = fopen ("Data\\model.txt", "rb"); // Open The File ( NEW )
- if (!In)
- return FALSE; // Return FALSE If File Not Opened ( NEW )
- fread (&polyNum, sizeof (int), 1, In); // Read The Header (i.e. Number Of Polygons) ( NEW )
- polyData = new POLYGON [polyNum]; // Allocate The Memory ( NEW )
- fread (&polyData[0], sizeof (POLYGON) * polyNum, 1, In); // Read In All Polygon Data ( NEW )
- fclose (In); // Close The File ( NEW )
- return TRUE; // It Worked ( NEW )
- }
- // Math Functions
- inline float DotProduct (VECTOR &V1, VECTOR &V2) // Calculate The Angle Between The 2 Vectors ( NEW )
- {
- return V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z; // Return The Angle ( NEW )
- }
- inline float Magnitude (VECTOR &V) // Calculate The Length Of The Vector ( NEW )
- {
- return sqrtf (V.X * V.X + V.Y * V.Y + V.Z * V.Z); // Return The Length Of The Vector ( NEW )
- }
- void Normalize (VECTOR &V) // Creates A Vector With A Unit Length Of 1 ( NEW )
- {
- float M = Magnitude (V); // Calculate The Length Of The Vector ( NEW )
- if (M != 0.0f) // Make Sure We Don't Divide By 0 ( NEW )
- {
- V.X /= M; // Normalize The 3 Components ( NEW )
- V.Y /= M;
- V.Z /= M;
- }
- }
- void RotateVector (MATRIX &M, VECTOR &V, VECTOR &D) // Rotate A Vector Using The Supplied Matrix ( NEW )
- {
- D.X = (M.Data[0] * V.X) + (M.Data[4] * V.Y) + (M.Data[8] * V.Z); // Rotate Around The X Axis ( NEW )
- D.Y = (M.Data[1] * V.X) + (M.Data[5] * V.Y) + (M.Data[9] * V.Z); // Rotate Around The Y Axis ( NEW )
- D.Z = (M.Data[2] * V.X) + (M.Data[6] * V.Y) + (M.Data[10] * V.Z); // Rotate Around The Z Axis ( NEW )
- }
- bool initialize()
- {
- int i;
- char Line[255];
- float shaderData[32][3];
- FILE *In = NULL;
- In = fopen ("Data\\shader.txt", "r");
- if (In)
- {
- for (i = 0; i < 32; i++)
- {
- if (feof (In))
- break;
- fgets (Line, 255, In);
- shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = float(atof (Line));
- }
- fclose (In);
- }
- else
- return false;
- osg::Image *image = new osg::Image;
- image->allocateImage(32, 1, 1, GL_RGB, GL_FLOAT);
- osg::Vec3 *dataPtr = (osg::Vec3*)(image->data());
- for (int i = 0; i < 32; ++i)
- {
- *dataPtr++ = osg::Vec3(shaderData[i][0], shaderData[i][1], shaderData[i][2]);
- }
- g_Texture1D = new osg::Texture1D;
- g_Texture1D->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
- g_Texture1D->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
- g_Texture1D->setImage(image);
- lightAngle.X = 0.0f;
- lightAngle.Y = 0.0f;
- lightAngle.Z = 1.0f;
- Normalize (lightAngle);
- return ReadMesh ();
- }
- osg::Geode* createModelGeode()
- {
- osg::Geode *geode = new osg::Geode;
- osg::Geometry *geometry = new osg::Geometry;
- osg::Vec3Array *vertexArray = new osg::Vec3Array;
- osg::Vec3Array *colorArray = new osg::Vec3Array;
- colorArray->push_back(osg::Vec3(1.0, 1.0, 1.0));
- geometry->setColorArray(colorArray, osg::Array::BIND_OVERALL);
- for (int i = 0; i < polyNum; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- vertexArray->push_back(osg::Vec3(polyData[i].Verts[j].Pos.X,polyData[i].Verts[j].Pos.Y,polyData[i].Verts[j].Pos.Z));
- }
- }
- geometry->setVertexArray(vertexArray);
- geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
- geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, vertexArray->size()));
- geode->addDrawable(geometry);
- return geode;
- }
- class RotAxisCallback : public osg::NodeCallback
- {
- public:
- RotAxisCallback(const osg::Vec3& axis, double rotSpeed = 0.0, double currentAngle = 0.0)
- : _rotAxis(axis), _rotSpeed(rotSpeed), _currentAngle(currentAngle){ }
- virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
- {
- osg::MatrixTransform *rotMT = dynamic_cast<osg::MatrixTransform*>(node);
- if (!rotMT)
- return;
- rotMT->setMatrix(osg::Matrix::rotate(_currentAngle, _rotAxis));
- _currentAngle += _rotSpeed;
- traverse(node, nv);
- }
- void setRotateSpeed(double speed)
- {
- _rotSpeed = speed;
- }
- double getRotateSpeed() const
- {
- return _rotSpeed;
- }
- double getCurrentAngle() const
- {
- return _currentAngle;
- }
- private:
- osg::Vec3 _rotAxis;
- double _currentAngle;
- double _rotSpeed;
- };
- //
- class ManipulatorSceneHandler : public osgGA::GUIEventHandler
- {
- public:
- ManipulatorSceneHandler()
- {
- }
- virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
- {
- osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
- if (!viewer)
- return false;
- if (!viewer->getSceneData())
- return false;
- if (ea.getHandled())
- return false;
- osg::Group *root = viewer->getSceneData()->asGroup();
- switch(ea.getEventType())
- {
- case(osgGA::GUIEventAdapter::KEYDOWN):
- {
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space)
- {
- static bool flag = true;
- if (!g_RotMT)
- return false;
- RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_RotMT->getUpdateCallback());
- if (!rotCallback)
- return false;
- if(flag)
- {
- double speed = rotCallback->getRotateSpeed();
- speed -= 0.02;
- rotCallback->setRotateSpeed(speed);
- }
- else
- {
- rotCallback->setRotateSpeed(0);
- }
- flag = !flag;
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
- {
- if (!g_OutlineGeode)
- return false;
- osg::LineWidth *lineWidth = dynamic_cast<osg::LineWidth*>( g_OutlineGeode->getOrCreateStateSet()->getAttribute(osg::StateAttribute::LINEWIDTH));
- float w = lineWidth->getWidth();
- w += 1.0f;
- lineWidth->setWidth(w);
- }
- if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)
- {
- if (!g_OutlineGeode)
- return false;
- osg::LineWidth *lineWidth = dynamic_cast<osg::LineWidth*>(g_OutlineGeode->getOrCreateStateSet()->getAttribute(osg::StateAttribute::LINEWIDTH));
- float w = lineWidth->getWidth();
- w -= 1.0f;
- lineWidth->setWidth(w);
- }
- }
- default: break;
- }
- return false;
- }
- };
- class ToonShadingCallback : public osg::NodeCallback
- {
- public:
- virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
- {
- osg::Geode *geode = dynamic_cast<osg::Geode*>(node);
- if(!geode)
- return;
- osg::Geometry *model = dynamic_cast<osg::Geometry*>(geode->getDrawable(0));
- if (!model)
- return;
- osg::FloatArray *textureArray = new osg::FloatArray;
- for (int i = 0; i < polyNum; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- osg::Vec3 nor;
- nor.x() = polyData[i].Verts[j].Nor.X;
- nor.y() = polyData[i].Verts[j].Nor.Y;
- nor.z() = polyData[i].Verts[j].Nor.Z;
- RotAxisCallback *rotCallback = dynamic_cast<RotAxisCallback*>(g_RotMT->getUpdateCallback());
- double angle = rotCallback->getCurrentAngle();
- nor = nor * osg::Matrix::rotate(angle, osg::Y_AXIS);
- nor.normalize();
- osg::Vec3 lightDir = osg::Vec3(lightAngle.X, lightAngle.Y, lightAngle.Z);
- float TmpShade = nor * lightDir;
- if (TmpShade < 0.0f)
- TmpShade = 0.0f;
- textureArray->push_back(TmpShade);
- }
- }
- model->setTexCoordArray(0, textureArray, osg::Array::BIND_PER_VERTEX);
- model->getOrCreateStateSet()->setTextureAttributeAndModes(0, g_Texture1D);
- traverse(node, nv);
- }
- };
- //
- //osgNeHe
- //
- class ViewerWidget : public QWidget, public osgViewer::Viewer
- {
- public:
- ViewerWidget(osg::Node *scene = NULL)
- {
- QWidget* renderWidget = getRenderWidget( createGraphicsWindow(0,0,100,100), scene);
- QVBoxLayout* layout = new QVBoxLayout;
- layout->addWidget(renderWidget);
- layout->setContentsMargins(0, 0, 0, 1);
- setLayout( layout );
- connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) );
- _timer.start( 10 );
- }
- QWidget* getRenderWidget( osgQt::GraphicsWindowQt* gw, osg::Node* scene )
- {
- osg::Camera* camera = this->getCamera();
- camera->setGraphicsContext( gw );
- const osg::GraphicsContext::Traits* traits = gw->getTraits();
- camera->setClearColor( osg::Vec4(0.7f, 0.7f, 0.7f, 0.0f) );
- camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
- camera->setProjectionMatrixAsPerspective(45.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 0.1f, 100.0f );
- camera->setViewMatrixAsLookAt(osg::Vec3d(0, 0, 0), osg::Vec3d(0, 0, -1), osg::Vec3d(0, 1, 0));
- this->setSceneData( scene );
- this->addEventHandler(new ManipulatorSceneHandler);
- return gw->getGLWidget();
- }
- osgQt::GraphicsWindowQt* createGraphicsWindow( int x, int y, int w, int h, const std::string& name="", bool windowDecoration=false )
- {
- osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
- osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
- traits->windowName = name;
- traits->windowDecoration = windowDecoration;
- traits->x = x;
- traits->y = y;
- traits->width = w;
- traits->height = h;
- traits->doubleBuffer = true;
- traits->alpha = ds->getMinimumNumAlphaBits();
- traits->stencil = ds->getMinimumNumStencilBits();
- traits->sampleBuffers = ds->getMultiSamples();
- traits->samples = ds->getNumMultiSamples();
- return new osgQt::GraphicsWindowQt(traits.get());
- }
- virtual void paintEvent( QPaintEvent* event )
- {
- frame();
- }
- protected:
- QTimer _timer;
- };
- osg::Node* buildScene()
- {
- initialize();
- osg::Group *root = new osg::Group;
- osg::MatrixTransform *zoomMT = new osg::MatrixTransform;
- zoomMT->setMatrix(osg::Matrix::translate(0,0,-2));
- osg::MatrixTransform *rotMT = new osg::MatrixTransform;
- g_RotMT = rotMT;
- rotMT->addUpdateCallback(new RotAxisCallback(osg::Y_AXIS));
- root->addChild(zoomMT);
- zoomMT->addChild(rotMT);
- //
- //Cell Shading Model
- osg::Geode *cellModel = createModelGeode();
- cellModel->addUpdateCallback(new ToonShadingCallback);
- rotMT->addChild(cellModel);
- //
- //Outline Model
- osg::Geode *outlineModel = createModelGeode();
- g_OutlineGeode = outlineModel;
- osg::Geometry *d = dynamic_cast<osg::Geometry*>(outlineModel->getDrawable(0));
- if(d)
- {
- osg::Vec3Array *vecArray = dynamic_cast<osg::Vec3Array*>(d->getColorArray());
- vecArray->at(0).set(osg::Vec3(0,0,0));
- d->setColorArray(vecArray, osg::Array::BIND_OVERALL);
- }
- osg::BlendFunc *blendFunc = new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
- osg::PolygonMode *polygonMode = new osg::PolygonMode(osg::PolygonMode::BACK, osg::PolygonMode::LINE);
- osg::LineWidth *lineWidth = new osg::LineWidth(3.0f);
- osg::CullFace *cullFace = new osg::CullFace(osg::CullFace::FRONT);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(polygonMode);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(lineWidth);
- outlineModel->getOrCreateStateSet()->setAttributeAndModes(cullFace);
- rotMT->addChild(outlineModel);
- return root;
- }
- int main( int argc, char** argv )
- {
- QApplication app(argc, argv);
- ViewerWidget* viewWidget = new ViewerWidget(buildScene());
- viewWidget->setGeometry( 100, 100, 640, 480 );
- viewWidget->show();
- return app.exec();
- }