【OSG学习笔记】Day 35: Material(材质)

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 (几何体:存储顶点、法线、纹理坐标)

关键类说明

  1. osg::StateAttribute:抽象基类,所有渲染状态(材质、光源、剔除、纹理)的父类,统一管理渲染属性;
  2. osg::Material:材质实现类,封装了冯氏光照模型的所有光学参数;
  3. osg::StateSet :状态容器,负责将材质、剔除等状态绑定到场景节点,让节点生效对应的渲染效果;
  4. 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;
}

代码核心材质逻辑解析

  1. 材质绑定流程

    创建 osg::Material → 设置光照属性 → 挂载到 StateSetStateSet 绑定到节点 → 节点生效材质效果。

  2. 材质参数设计

    • 漫反射设为红色:让物体呈现红色基础色调;
    • 镜面反射与漫反射一致:让高光呈现红色,统一质感;
    • 高光指数 90.0:模拟光滑的塑料/金属质感,高光集中锐利。
  3. 为什么必须设置法线?

    光照计算依赖法线方向判断光线入射角,没有法线,材质无法计算明暗,物体只会是平面纯色。

总结

  1. 材质是物体的光学属性,光源是光线提供者,二者协作才能实现 3D 渲染;
  2. OSG 材质继承自 osg::StateAttribute ,通过 StateSet 绑定到节点生效;
  3. 材质的光照 API 是定义反光规则,不是创建光源;
  4. 完整的渲染流程:几何数据 + 法线 + 材质 + 光源 = 逼真 3D 效果。
相关推荐
ZhiqianXia2 小时前
Pytorch 学习笔记(21) : PyTorch Profiler
pytorch·笔记·学习
iiiiii112 小时前
【论文阅读笔记】ReVal:让大模型强化学习真正支持离策略(off-policy)数据复用
论文阅读·笔记·语言模型·大模型·llm
炽烈小老头2 小时前
【每天学习一点算法 2026/04/10】Excel表列序号
学习·算法
渡我白衣2 小时前
运筹帷幄——在线学习与实时预测系统
人工智能·深度学习·神经网络·学习·算法·机器学习·caffe
ZhiqianXia2 小时前
PyTorch学习笔记(16):scheduler.py
pytorch·笔记·学习
半壶清水2 小时前
[软考网规考点笔记]-局域网之高速以太网
网络·笔记·网络协议·考试
一定要AK10 小时前
Spring 入门核心笔记
java·笔记·spring
AI成长日志10 小时前
【Agentic RL】1.1 什么是Agentic RL:从传统RL到智能体学习
人工智能·学习·算法
_李小白11 小时前
【OSG学习笔记】Day 38: TextureVisitor(纹理访问器)
android·笔记·学习