文档章节

GPU程序在GameByro中的使用

rise-worlds
 rise-worlds
发布于 2016/06/20 13:37
字数 2651
阅读 0
收藏 0

引言:

GameBryo拥有一套复杂的材质系统,这套材质系统可以根据渲染对象的状态和属性生成不同的shader代码,提高了渲染流程的适应性,可以使你定义一套材质能适应多种渲染对象。同时,GameByro将shader的初始化和使用插件化,方便与美术工具集成,并且实现了平台无关性。为了实现这些目的,GameByro使用了一套复杂的机制,本文主要解析GameByro如何生成、编译并使用shader代码。

Shader

GameBryo的shader的接口封装在NiShader中,顶点数据流声明,常量表的访问,渲染状态的设置都是通过这个类(有点类似于D3Deffect)。在程序运行NiShader是由NiShaderFactory负责管理的,NiShaderFactory通过NiShaderLibrary从文件中创建shader,用全局性的map管理起来。NiShaderLibrary通过解析shader文本创建NiShader对象,并调用3D图形接口编译shader代码,将这个类以dll的形式封装,就可以作为插件来使用。NiShader类的创建可以通过解析文件来进行,也可以通过C++的类来定制,只需从NiShader上继承即可。GameByro为PC平台提供了一个NiD3DXEffectShaderLib库,这个库提供了解析shader文件和初始化shader对象的功能。用户只需按GameByro定义的格式编写shader代码的语意和注释,NiD3DXEffectShaderLibrary就会根据文本来创建NiD3Dshader对象,在应用程序中就可以通过Techinqe的名称来访问这个对象。通过这种机制,我们将shader文本文件放在相关美术工具指定的目录下,在工具中就可以使用这些shader,并且能够通过shader的语意和注释为相关参数和变量生成UI,方便美术调试。

WIN平台上的整个流程如下:

1. 应用程序在启动时会先初始化整个shader系统,接下来导入Shader解析库和加载库(dll的形式)。

2. 接下来应用程序将NiD3DShader的初始化工作委托给NiShaderLibrary来处理,NiShaderLibrary首先通过NiD3DXEffectLoader载入所有的shader文本文件,并通过NiD3DXEffectParser解析文本生成NiD3DXEffectFile对象,同时NiD3DXEffectLoader还负责将shader代码编译成二进制形式的GPU程序。

3. 最后由NiD3DXEffectTechnique负责通过NiD3DXEffectFile上的信息生成NiD3Dshader对象。

4. 所有的shader对象创建后,NiShaderLibrary的初始化就结束了,最后由NiShaderFactory负责统一管理。

材质:

NiMaterial为渲染对象生成和定义Shader,NiMaterialInstance为渲染对象分配 和Cach Shader。NiFragmentMaterial提供了一个Shader Tree框架,在它的继承类中可以使用这个框架搭建shader tree。这个机制允许NiFragmentMaterial根据对象不同的渲染状态生成不同的shader代码,Cach在内存中,并保存到磁盘文件。GameByro描述符的概念大量使用,包括前面提到的Shader解析过程也是通过描述符来传递信息。在材质系统中主要使用了NiMaterialDescriptor和NiGPUProgramDescriptor这个两个类做描述符,这两个类中保存的信息是兼容的,都是为了描述某种材质在渲染对象的某一特定渲染状态下所对应的GPU程序的特征。NiFragmentMaterial通过渲染目标的状态和属性生成NiMaterialDescriptor,并通过NiMaterialDescriptor查找匹配的shader,如果找不到,则通过shader tree生成相应的shader程序,并保存到磁盘文件中。当下一次应用程序启动时就可以通过这个文件直接创建NiShader对象。可以说通过NiFragmentMaterial生成的shader代码是为特定的渲染对象在特定的情况下量身打造的。

整个过程的详细流程如下:

1. 在每次渲染一个物体之前,NiMaterialInstance会先判断这个物体的shader程序是否需要更新,如果不需要更新,就直接返回当前Cach的NiShader;如果需要更新, NiMaterialInstance首先会根据物体的渲染状态为其生成一个NiMaterialDescriptor,然后将这个NiMaterialDescriptor和当前Cach住的NiShader进行比较,如果匹配仍然返回当前Cach的NiShader,如果不匹配,将获得shader的工作转交给NiMaterial进行。

2. NiMaterial首先通过这个NiShaderFactory 查询匹配这个NiMaterialDescriptor的NiShader,如果找不到,就通过NiMaterialDescriptor生成NiShader,同时生成一段Shader代码,并保存到以shader描述符中的特征码来命名对应的shader文件。

3. 当获得相应的NiShader对象后,NiMaterialInstance会调用NiShader的SetupGeometry接口,在这个接口中会进行顶点声明。

以下是NiMaterialInstance为Geometry选择shader的代码:

NiShader* NiMaterialInstance::GetCurrentShader(NiRenderObject* pkGeometry,

const NiPropertyState* pkState,

const NiDynamicEffectState* pkEffects)

{

if (m_spMaterial)

{

bool bGetNewShader = m_eNeedsUpdate == DIRTY;

if (m_eNeedsUpdate == UNKNOWN)

bGetNewShader = pkGeometry->GetMaterialNeedsUpdateDefault();

// Check if shader is still current

if (bGetNewShader && m_spCachedShader)

{

bGetNewShader = !m_spMaterial->IsShaderCurrent(m_spCachedShader,

pkGeometry, pkState, pkEffects, m_uiMaterialExtraData);

}

// Get a new shader

if (bGetNewShader)

{

NiShader* pkNewShader = m_spMaterial->GetCurrentShader(

pkGeometry, pkState, pkEffects, m_uiMaterialExtraData);

if (pkNewShader)

{

NIASSERT(m_spCachedShader != pkNewShader);

ClearCachedShader();

m_spCachedShader = pkNewShader;

if (!pkNewShader->SetupGeometry(pkGeometry, this))

ClearCachedShader();

}

else

{

ClearCachedShader();

}

}

m_eNeedsUpdate = UNKNOWN;

}

return m_spCachedShader;

}

如果想通过NiFragmentMaterial实现自己的shader tree就需要在NiFragmentMaterial提供的接口中实现自己拼装代码的逻辑,代码块由NiMaterialLibraryNode封装,NiMaterialLibraryNode既可以直接写C++代码来定义,也可以先写成XML脚本,再由专门的解析工具转换成C++代码。

由NiStandardMaterial生成的shader代码文件如下图所示:

clip_image002

文件名就是NiMaterialDescriptor的掩码,用来标识的shader代码的行为。

Shader代码的行为描述如下:

Shader description:

APPLYMODE = 1

WORLDPOSITION = 0

WORLDNORMAL = 0

WORLDNBT = 0

WORLDVIEW = 0

NORMALMAPTYPE = 0

PARALLAXMAPCOUNT = 0

BASEMAPCOUNT = 1

NORMALMAPCOUNT = 0

DARKMAPCOUNT = 0

DETAILMAPCOUNT = 0

BUMPMAPCOUNT = 0

GLOSSMAPCOUNT = 0

GLOWMAPCOUNT = 0

CUSTOMMAP00COUNT = 0

CUSTOMMAP01COUNT = 0

CUSTOMMAP02COUNT = 0

CUSTOMMAP03COUNT = 0

CUSTOMMAP04COUNT = 0

DECALMAPCOUNT = 0

FOGENABLED = 0

ENVMAPTYPE = 0

PROJLIGHTMAPCOUNT = 0

PROJLIGHTMAPTYPES = 0

PROJLIGHTMAPCLIPPED = 0

PROJSHADOWMAPCOUNT = 0

PROJSHADOWMAPTYPES = 0

PROJSHADOWMAPCLIPPED = 0

PERVERTEXLIGHTING = 1

UVSETFORMAP00 = 0

UVSETFORMAP01 = 0

UVSETFORMAP02 = 0

UVSETFORMAP03 = 0

UVSETFORMAP04 = 0

UVSETFORMAP05 = 0

UVSETFORMAP06 = 0

UVSETFORMAP07 = 0

UVSETFORMAP08 = 0

UVSETFORMAP09 = 0

UVSETFORMAP10 = 0

UVSETFORMAP11 = 0

POINTLIGHTCOUNT = 0

SPOTLIGHTCOUNT = 0

DIRLIGHTCOUNT = 0

SHADOWMAPFORLIGHT = 0

SPECULAR = 1

AMBDIFFEMISSIVE = 0

LIGHTINGMODE = 1

APPLYAMBIENT = 0

BASEMAPALPHAONLY = 0

APPLYEMISSIVE = 0

SHADOWTECHNIQUE = 0

ALPHATEST = 0

NiStanderMaterial就是根据这些掩码的数据来生成shader代码,用户可以通过重载GenerateVertexShadeTree、GeneratePixelShadeTree、CreateShader这些接口来定义自己的shader生成规则。

增加自己的渲染效果:

通过前几节我们可以了解到,想定义自己的材质,一是通过编写shader代码完成。在应用程序初始化的时候,这些shader代码会被初始化成NiShader对象,进一步的通过NiShader对象来初始化NiSingleShaderMaterial对象,并分配给渲染对象。在GameByro默认的渲染流程中,这些步骤都是自动进行的,美术只需在3DMAX插件中为几何体的材质指定Shader程序,导出到nif文件,应用程序就能正确加载并渲染;二是定义自己的NiMaterialFragment类,在类中定义如何生成shader,在应用程序运行时只要将这个类的实例指派给几何体,这个类就会自动为几何体生成shader。这两种方式对于美术人员来说,主要区别在于,采用第一种方法定义的材质,其渲染数据的设置必须严格符合shader代码中所需的数据,否则就会报错。(比如说,顶点数据流必须严格符合shader程序的定义,必须为shader中每个采样器提供格式正确的纹理);而采用第二种方法定义的材质,就有很高的容错和适应性,但是这种容错性和适应性需要自己写代码来完成,GameByro提供的NiStanderMaterial就提供了这套完整的机制。每个贴图槽内的贴图如果你设置就会生成相应的贴图处理流程,如果不设置,就没有这张贴图的处理流程。

为了验证这个过程,笔者尝试增加了一个自己的shader特效——SubSurfaceScattering,简称3s,其原理是模拟光在半透明物体中散射的效果。由于该效果无须预处理过程,所有的贴图均来自磁盘文件,所以比较容易融合到GameByro工作流中。

笔者将在FX COMPOSER中调试通过的fx文件放入SDK中的SDK\Win32\Shaders\Data目录下,在3DMAX的材质面板选择GameByroShader,然后就可在显示shader的组合框中看到文件中定义的Techinqe,选择点击apply按钮,就会出现自定义的参数调整界面。

clip_image004

clip_image006

通过调整参数,最终得到皮肤和玉器的渲染效果如下:

clip_image007

皮肤

clip_image009

玉器

总结

GameByro的这套开发流程非常方便直观,但是美术仅能为shader程序分配静态的数据源,比如说光照图等,CubeMap等;而一些在程序中实时生成的纹理数据则无法整合到美术工具中,比如说阴影图、折射图、反射图等,这些都需要程序写代码来实现。调试起来就不大方便了。大部分情况下,我们只需要使用GameByro提供的NiStanderMaterial就可以完成大部分材质的需求,特殊的效果可以自己写shader或者通过引擎提供的shader库来完成,只有当我们需要即根据复杂的情况做很多不同的处理时,我们才需要重载NiFragmentMaterial搭建自己的shader tree。不过搭建shader tree的程序一般比较复杂,编写难度大,虽然引擎允许通过XML文件来编写材质节点,但是使用起来仍然不方便。GameByro并没有提供相关的后期处理的开发工具,后期处理的特效并不能所见即所得,这方面还需完善。

GameByro为几何体在特定的环境下生成专用的shader代码,具有一定的灵活性,但是也付出了以下代价:

l 分析几何体的属性和当前状态,为其生成shader代码的过程有性能损耗。

l Shader代码生成后会保存到磁盘文件中,这个过程如果不使用异步,可能会引起阻塞。

l 生成的NiShader对象会有内存消耗。由于GameByro默认的实现是将所有的shader文件初始化成NiShader对象,所以当游戏运行的时间久了以后会生成大量的shader文件,这时候内存的消耗可能会很可观,同时加载的时间也会增加。不过可以自己控制加载的流程,在这里进行性能优化。

作者:叶起涟漪

本文转载自:http://www.cnblogs.com/flying_bat/archive/2009/04/22/1441175.html

rise-worlds

rise-worlds

粉丝 3
博文 1755
码字总数 0
作品 0
深圳
程序员
私信 提问
教程 如何使用深度学习硬件的空余算力自动挖矿

     人工智能和虚拟货币是 2017 年的两大重要名词,作为一名开发者,如何充分利用自己昂贵的硬件设备呢?本文将给你一种有趣的解决方案。         如果没有 GPU,现代深度学习是不...

深度学习
2017/12/26
0
0
GPU高级调试与优化(2018SH)

[纠正上一版本小错误,重新发送,如有打扰,请原谅] GPU的历史很短,只有十几年。但它发展迅猛,凭借强大的并行计算能力和高效率的固定硬件单元,在人工智能、虚拟和增强现实(VR/AR)、3D游...

pcb4jr
2017/12/27
0
0
教程 | 如何使用深度学习硬件的空余算力自动挖矿

  选自Medium   作者:Max Lapan   机器之心编译   参与:李泽南      人工智能和虚拟货币是 2017 年的两大重要名词,作为一名开发者,如何充分利用自己昂贵的硬件设备呢?本文将...

机器之心
2017/12/25
0
0
别为Docker本地实现不支持GPU发愁,解决方案在此!

导读 通过提供独立的执行环境而不需要整个虚拟机的开销,容器已经成为大规模部署应用程序的很有吸引力的选择。 Docker让容器变得易于使用,因此受到欢迎。通过使多个工程团队能够利用自己的配...

lq1ns259ej3okyvk4jf
2017/12/08
0
0
CUDA并行计算框架(一)概念相关、内容比较抽象。

一. 概念。 1. 相关关键字。 CUDA(Compute Unified Device Architecture)。 GPU英文全称Graphic Processing Unit,中文翻译为“图形处理器”。 2. CUDA是一种由NVIDIA推出的通用并行计算架构...

wbf961127
2017/11/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周六乱弹 —— 如果是个帅小伙你愿意和他出去吗

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 小小编辑推荐:《Ghost 》游戏《死亡搁浅》原声 《Ghost 》游戏(《死亡搁浅》原声) - Au/Ra / Alan Walker 手机党少年们想听歌,请使劲儿戳...

小小编辑
54分钟前
97
5
java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
16
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
19
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部