
Material 材质
在 OpenSceneGraph(OSG)三维渲染开发中,材质(Material) 是连接三维几何数据与真实视觉效果的核心桥梁。
它决定了物体如何与场景中的光线交互,直接影响物体的颜色、光泽、质感,是实现逼真 3D 渲染效果的必备知识点。
本文将从材质核心原理、类继承关系、材质与光源的协作机制讲起,结合完整可运行的四边形渲染代码,带你彻底吃透 OSG 材质系统,解决「为什么要设置光照属性」「材质和光源有什么区别」等新手高频疑问。
OSG 材质核心概念
1. 什么是材质?
OSG 中的 osg::Material 是物体表面的光学属性描述 ,它不产生光线,而是定义物体如何反射、吸收、散射场景中的光线 。
简单来说:光源是灯泡,材质是物体的「皮肤」,物体最终呈现的颜色、光泽,由两者共同计算得出。
2. 材质的核心作用
- 定义物体的基础颜色(漫反射)
- 定义物体的光泽感(镜面反射+高光指数)
- 定义物体的阴影底色(环境光)
- 定义物体的自发光效果(无需光照也能显示)
- 配合光照模型,让平面几何数据呈现3D 立体感
OSG 材质相关类继承关系
OSG 采用面向对象的继承架构,材质类的层级关系清晰,所有渲染状态都继承自统一的基类,保证了系统的统一性:
osg::Object (所有OSG对象的基类)
└── osg::StateAttribute (渲染状态属性基类:材质、剔除、纹理等)
├── osg::Material (材质类:核心,定义物体光学属性)
├── osg::CullFace (背面剔除:优化渲染)
├── osg::Texture (纹理:贴图渲染)
└── osg::Light (光源类:提供场景光线)
osg::StateSet (状态集:挂载所有渲染状态,绑定到节点)
osg::Node (场景节点)
└── osg::Geode (叶节点:挂载几何体)
└── osg::Geometry (几何体:存储顶点、法线、纹理坐标)
关键类说明
osg::StateAttribute:抽象基类,所有渲染状态(材质、光源、剔除、纹理)的父类,统一管理渲染属性;osg::Material:材质实现类,封装了冯氏光照模型的所有光学参数;osg::StateSet:状态容器,负责将材质、剔除等状态绑定到场景节点,让节点生效对应的渲染效果;osg::Light:光源类,与材质配合完成光照计算。
材质与光源的关系:缺一不可的协作机制
这是 OSG 新手最容易混淆的知识点,我们用最通俗的逻辑理清:
1. 核心分工
- 光源(
osg::Light) :发光者,负责提供光线,定义光的颜色、方向、亮度、位置; - 材质(
osg::Material) :反光者,负责接收光线,定义物体反射什么颜色、反光强度、表面光滑度。
2. 计算逻辑:最终颜色 = 光源 × 材质
GPU 遵循冯氏光照模型,计算公式:
最终像素颜色 = 环境光 + 漫反射 + 镜面反射 + 自发光
每一项都需要光源参数 × 材质参数才能计算。
3. 关键疑问:为什么代码没写光源,也能渲染?
OSG 的 osgViewer::Viewer 会自动创建一个默认白光光源 (位于相机位置),无需手动编写光源代码,场景就能被照亮。
但这并不代表光源不存在------没有光源,材质再精美,物体也会全黑。
4. 为什么材质要提供「光照相关 API」?
材质的 API(setDiffuse/setSpecular 等)不是设置光源,而是告诉 GPU:
当光线照射到我时,我该如何反射?
- 不设置材质:物体使用默认灰色材质,无光泽、无立体感;
- 设置材质:物体拥有自定义颜色、光泽,呈现逼真效果。
OSG 材质核心 API 详解
osg::Material 基于冯氏光照模型,提供 5 个核心光照属性,覆盖所有基础渲染需求:
| API 方法 | 作用 | 参数说明 |
|---|---|---|
setAmbient(面, 颜色) |
环境光:阴影处的底色 | 全局背景光,避免暗部全黑 |
setDiffuse(面, 颜色) |
漫反射:物体基础颜色 | 决定物体主色调,是立体感的核心 |
setSpecular(面, 颜色) |
镜面反射:高光颜色 | 光滑物体的亮斑颜色 |
setShininess(面, 值) |
高光指数:光滑度 | 0~128,值越大,高光越集中、表面越光滑 |
setEmission(面, 颜色) |
自发光:物体自身发光 | 不受光照影响,常用于灯、屏幕 |
通用参数:
osg::Material::FRONT:仅对物体正面生效(配合背面剔除使用);osg::Vec4(r,g,b,a):RGBA 颜色,取值 0.0~1.0,a 为透明度。
完整实战代码
以下代码整合了几何体创建、材质设置、背面剔除、场景渲染全流程,可直接编译运行,注释详尽:
cpp
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/StateSet>
#include <osg/Material>
#include <osg/CullFace>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgUtil/Optimizer>
#include <iostream>
// 创建一个四边形节点
osg::ref_ptr<osg::Node> createNode()
{
// 1. 创建叶节点Geode(用于承载可绘制对象Geometry)
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
// 2. 创建几何对象Geometry(用于存储顶点、纹理、法线等数据)
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
// --------------------------
// 3. 设置顶点坐标(定义四边形的4个顶点,z=0平面)
// --------------------------
osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array();
vc->push_back(osg::Vec3(0.0f, 0.0f, 0.0f)); // 左下角
vc->push_back(osg::Vec3(1.0f, 0.0f, 0.0f)); // 右下角
vc->push_back(osg::Vec3(1.0f, 0.0f, 1.0f)); // 右上角
vc->push_back(osg::Vec3(0.0f, 0.0f, 1.0f)); // 左上角
geom->setVertexArray(vc.get());
// --------------------------
// 4. 设置纹理坐标(对应4个顶点的UV,0~1范围)
// --------------------------
osg::ref_ptr<osg::Vec2Array> vt = new osg::Vec2Array();
vt->push_back(osg::Vec2(0.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 1.0f));
vt->push_back(osg::Vec2(0.0f, 1.0f));
geom->setTexCoordArray(0, vt.get()); // 0号纹理单元
// --------------------------
// 5. 设置法线(统一法向量,用于光照计算)
// --------------------------
osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();
nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f)); // 沿-y轴方向(面向观察者)
geom->setNormalArray(nc.get());
geom->setNormalBinding(osg::Geometry::BIND_OVERALL); // 整个几何体共享该法线
// --------------------------
// 6. 添加图元(指定绘制4个顶点组成的四边形QUADS)
// --------------------------
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
// 7. 将Geometry添加到Geode(OSG场景树的叶节点只能挂载Drawable)
geode->addDrawable(geom.get());
return geode.get();
}
int main()
{
// 1. 创建Viewer(OSG场景视口,负责渲染循环、交互控制)
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
// 2. 创建根节点Group(场景树的根,用于挂载子节点)
osg::ref_ptr<osg::Group> root = new osg::Group();
// 3. 创建自定义四边形节点
osg::ref_ptr<osg::Node> node = createNode();
// --------------------------
// 4. 获取/创建节点的状态集StateSet(用于设置材质、剔除等渲染状态)
// --------------------------
osg::ref_ptr<osg::StateSet> stateset = node->getOrCreateStateSet();
// --------------------------
// 5. 创建并设置材质(实现光照效果)
// --------------------------
osg::ref_ptr<osg::Material> mat = new osg::Material();
// 设置正面漫反射颜色(RGBA:红色,不透明)
mat->setDiffuse(osg::Material::FRONT, osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
// 设置正面镜面反射颜色(高光颜色,与漫反射一致)
mat->setSpecular(osg::Material::FRONT, osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
// 设置正面高光指数(90.0f:高光泽,数值越大高光越集中)
mat->setShininess(osg::Material::FRONT, 90.0f);
// 将材质属性绑定到状态集
stateset->setAttribute(mat.get());
// --------------------------
// 6. 设置背面剔除(优化渲染,只渲染正面,减少Draw Call)
// --------------------------
osg::ref_ptr<osg::CullFace> cullface = new osg::CullFace(osg::CullFace::BACK);
stateset->setAttribute(cullface.get());
// 启用背面剔除功能
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
// 7. 将四边形节点添加到根节点
root->addChild(node.get());
// --------------------------
// 8. 优化场景数据(合并状态、剔除冗余节点,提升渲染性能)
// --------------------------
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
// 9. 设置场景数据到Viewer
viewer->setSceneData(root.get());
// 10. 初始化Viewer(创建窗口、初始化OpenGL上下文)
viewer->realize();
// 11. 启动渲染循环(进入主循环,处理交互、渲染每一帧)
viewer->run();
return 0;
}

代码核心材质逻辑解析
-
材质绑定流程
创建
osg::Material→ 设置光照属性 → 挂载到StateSet→StateSet绑定到节点 → 节点生效材质效果。 -
材质参数设计
- 漫反射设为红色:让物体呈现红色基础色调;
- 镜面反射与漫反射一致:让高光呈现红色,统一质感;
- 高光指数 90.0:模拟光滑的塑料/金属质感,高光集中锐利。
-
为什么必须设置法线?
光照计算依赖法线方向判断光线入射角,没有法线,材质无法计算明暗,物体只会是平面纯色。
总结
- 材质是物体的光学属性,光源是光线提供者,二者协作才能实现 3D 渲染;
- OSG 材质继承自
osg::StateAttribute,通过StateSet绑定到节点生效; - 材质的光照 API 是定义反光规则,不是创建光源;
- 完整的渲染流程:几何数据 + 法线 + 材质 + 光源 = 逼真 3D 效果。
