• 3Delight NSI: A Streamable Render API


    3Delight是应用于高端电影级别渲染的软件渲染器,迄今为止已经参与了无数的电影制作,具体可以参见链接。

    如果你对3Delight的印象就依然是RenderMan的替代品,那就显然已经和时代发展脱节了。现在的3Delight是一个完全PBR Unbiased的渲染器,而且完全为了交互式渲染以及云端渲染设计,所以你对它的固有印象可以从看到这篇文章开始彻底改变了。

    渲染=数据操作

    其实“渲染”这个动作的本身,就是数据处理,你可以用任何流行的思路来对照,比如MapReduce。但是归根结底,可以认为只有3个概念。

    • 数据填充
    • 数据修改
    • 数据计算

    这3个概念可以直接展开,把你所知道所有的计算机图形学相关的概念和技术都丢入,但是这里不展开。

    本文会结合这3个概念,来仔细的阐述3Delight NSI的优点和思路,以及解决的问题。

    一切从过程开始

    计算机,其实是过程性设备。所谓面向对象,只是软件设计领域的一个对过程和数据的合并抽象而已,本质上,最后的“执行”这个本身依然是个过程。

    那么回顾一下RenderMan API(以下简称RI)的设计。

    RenderMan

    一个完整RI可渲染的场景一般结构如下,来自这里

     1 ##RenderMan RIB-Structure 1.1
     2 ##Scene Bouncing Ball
     3 ##Creator /usr/ucb/vi
     4 ##CreationDate 12:30pm 8/24/89
     5 ##For RenderMan Jones
     6 ##Frames 2
     7 ##Shaders PIXARmarble, PIXARwood, MyUserShader
     8 ##CapabilitiesNeeded ShadingLanguage Displacements
     9 version 3.03
    10 Declare "d" "uniform point"
    11 Declare "squish" "uniform float"
    12 Option "limits" "bucketsize" [6 6]  #renderer specific
    13 Option "limits" "gridsize" [18]  #renderer specific
    14 Format 1024 768 1  #mandatory resolution
    15 Projection "perspective"
    16 Clipping 10 1000.0
    17 FrameBegin 1
    18     ##Shaders MyUserShader, PIXARmarble, PIXARwood
    19     ##CameraOrientation 10.0 10.0 10.0 0.0 0.0 0.0
    20     Transform  [.707107  -.408248  -.57735 0
    21                 0  .816497  -.57735  0
    22                 -.707107  -.408248  -.57735  0
    23                 0  0  17.3205  1 ]
    24     WorldBegin
    25         AttributeBegin
    26             Attribute "identifier" "name" "myball"
    27             Displacement "MyUserShader" "squish" 5
    28             AttributeBegin
    29                 Attribute "identifier" "shadinggroup" ["tophalf"]
    30                 Surface "PIXARmarble"
    31                 Sphere .5 0 .5 360
    32             AttributeEnd
    33             AttributeBegin
    34             Attribute "identifier" "shadinggroup" ["bothalf"]
    35                 Surface "plastic"
    36                 Sphere .5 -.5 0. 360
    37             AttributeEnd
    38         AttributeEnd
    39         AttributeBegin
    40             Attribute "identifier" "name" ["floor"]
    41                 Surface "PIXARwood" "roughness" [.3] "d" [1]
    42                 # geometry for floor
    43                 Polygon "P" [-100. 0. -100.  -100. 0. 100.  100. 0. 100.  10.0 0. -100.]
    44         AttributeEnd
    45     WorldEnd
    46 FrameEnd
    47 FrameBegin 2
    48     ##Shaders PIXARwood, PIXARmarble
    49     ##CameraOrientation 10.0 20.0 10.0 0.0 0.0 0.0
    50     Transform [.707107  -.57735  -.408248  0
    51                0   .57735
    52                -.815447 0
    53                -.707107  -.57735  -.408248  0
    54                0  0  24.4949 1 ]
    55     WorldBegin
    56         AttributeBegin
    57             Attribute "identifier" "name" ["myball"]
    58             AttributeBegin
    59                 Attribute "identifier" "shadinggroup" ["tophalf"]
    60                     Surface "PIXARmarble"
    61                     ShadingRate .1
    62                     Sphere .5 0 .5 360
    63                 AttributeEnd
    64             AttributeBegin
    65             Attribute "identifier" "shadinggroup" ["bothalf"]
    66                 Surface "plastic"
    67                 Sphere .5 -.5 0 360
    68             AttributeEnd
    69         AttributeEnd
    70         AttributeBegin
    71             Attribute "identifier" "name" ["floor"]
    72             Surface "PIXARwood" "roughness" [.3] "d" [1]
    73             # geometry for floor
    74         AttributeEnd
    75     WorldEnd
    76 FrameEnd
    View Code

    聪明的你告诉我,你觉得这个场景描述有什么限制?这个问题可能很难回答,但是我们先来提几个看似简单的需求。

    • 流式更新
    • 几何体数据的修改
    • 几何体属性的修改
    • 材质数据的修改
    • 材质和几何体关系的修改
    • 多屏幕计算
    • 多屏幕不同分辨率的计算
    • 多屏幕不同分辨率不同数据的计算

    但是告诉我,如果你想修改这个Mesh的几何数据,你会如何做?这个答案在RI内,使用负责场景数据,范例如下。

    1 RiEditBegin("attribute", "string editlights", "light1", RI_NULL);
    2   // specify the coordinate system for light1
    3   RiTransform( ... );
    4   RiLightsource( "spotlight", RI_HANDLEID, "light1", "color lightcolor", (RtPointer)&color );
    5 RiEditEnd();
    View Code

    这套系统只支持非常有限的场景元素的修改,也就是你只能改改Shader参数,移动一下位置如此,也就是我们现在看到常见IPR的所有的操作。

    当然这一套系统的限制呢,也是写的明明白白。

    Restrictions, Constraints, and Known Issues
    Each re-rendering mode has certain restrictions and limitations that should be considered before being incorporated in a production pipeline. It is our intent to address these in future releases. Below is the current list of restrictions, constraints, and known issues:

    • Hider restrictions The only hiders supported are stochastic and raytrace. Sigma buffer and stitching are not supported.
    • Camera restrictions Multi-camera rendering is not supported.
    • Graphics primitives CSG is not supported.
    • Display Progressive refinement is critical to making editing interactive. We have provided a new display driver, multires, that can quickly display the multi-resolution images produced by re-rendering. However, existing display drivers can't display multi-resolution images and will cause the re-renderer to disable progressive refinement, rendering only at the highest resolution.
    • Resizable Arrays Traditional shaders with resizeable arrays will not be baked properly, leading to a crash during re-rendering. However, shader object-based shaders do support the use of resizeable arrays.

    限制有

    • 仅仅是支持stochastic和raytrace 2种Hider。
    • 不支持多摄影机渲染。
    • 不支持CSG几何体。
    • 需要新的Display Driver支持。
    • 不支持变长的Shader数组参数。

    那么显然,这一套系统的缺陷是

    • 先后顺序存在依赖
    • API太多太琐碎每次都得学新的函数
    • 可操作的对象和数据类型受限
    • 不支持复杂操作,比如删除几何体
    • 不支持修改分辨率、摄影机参数等必须参数

    来到Nodel Scene API

    显然到了如今,再遵循RenderMan标准,显然已经没有意义。如今RenderMan渲染器本身就没有丝毫优势,大家的渲染已经更多,已经不是当年那个缺少靠谱的解决方案的时代了。所以,为了克服RenderMan的所有缺点和限制,3Delight重新引入了NSI这么一套API。下面是所有函数列表,对,你没有看错,所有的函数。

    NSIContext_t NSIBegin(int nparams, const struct NSIParam_t *params );
    
    void NSIEnd( NSIContext_t ctx );
    
    void NSICreate(NSIContext_t ctx, NSIHandle_t handle, const char *type, int nparams, const struct NSIParam_t *params );
    
    void NSIDelete(NSIContext_t ctx, NSIHandle_t handle, int nparams, const struct NSIParam_t *params);
    
    void NSISetAttribute(NSIContext_t ctx, NSIHandle_t object, int nparams, const struct NSIParam_t *params );
    
    void NSISetAttributeAtTime(NSIContext_t ctx, NSIHandle_t object, double time, int nparams, const struct NSIParam_t *params );
    
    void NSIDeleteAttribute(NSIContext_t ctx, NSIHandle_t object, const char *name );
    
    void NSIConnect(NSIContext_t ctx, NSIHandle_t from, const char *from_attr, NSIHandle_t to, const char *to_attr, int nparams, const struct NSIParam_t *params );
    
    void NSIDisconnect(NSIContext_t ctx, NSIHandle_t from, const char *from_attr, NSIHandle_t to, const char *to_attr);
    
    void NSIEvaluate(NSIContext_t ctx, int nparams, const struct NSIParam_t *params);
    
    void NSIRenderControl(NSIContext_t ctx, int nparams, const struct NSIParam_t *params);

    以上就是所有的函数。

    其实从函数名字就可以看到背后的设计思路,虽然还是填充场景对象的数据,但是由于这个不存在任何的依赖关系,所以克服了RI的那几个重要的缺点,一切的一切只要在调用NSIRenderControl之前即可。用户可以用这一套API以自己喜欢的顺序组织场景,构造节点和节点之间的连接即可。下面来具体用例子解释如何构造场景。

    一个NSI场景

    首先从构造一个Plane的片段开始。

     1 #include <nsi.hpp>
     2 
     3 
     4 // Set mesh data.
     5 //
     6 int plane_shape_nvertices_data[1] =
     7 {
     8     4
     9 };
    10 
    11 int plane_shape_indices_data[4] =
    12 {
    13     0, 1, 3, 2
    14 };
    15 
    16 float plane_shape_P_data[12] = // 3 * 4
    17 {
    18     -50, 0, 50,
    19     50, 0, 50,
    20     - 50, 0, - 50,
    21     50, 0, - 50
    22 };
    23 
    24 int plane_shape_N_data[12] = // 3 * 4
    25 {
    26     0, 1, 0,
    27     0, 1, 0,
    28     0, 1, 0,
    29     0, 1, 0
    30 };
    31 
    32 NSI::ArgumentList plane_shape_attrs;
    33 
    34 plane_shape_attrs.push(NSI::Argument::New("nvertices")
    35     ->SetType(NSITypeInteger)
    36     ->SetCount(1)
    37     ->SetValuePointer(plane_shape_nvertices_data));
    38 
    39 plane_shape_attrs.push(NSI::Argument::New("P")
    40     ->SetType(NSITypePoint)
    41     ->SetCount(4)
    42     ->SetFlags(NSIParamInterpolateLinear)
    43     ->SetValuePointer(plane_shape_P_data));
    44 
    45 plane_shape_attrs.push(NSI::Argument::New("P.indices")
    46     ->SetType(NSITypeInteger)
    47     ->SetCount(4)
    48     ->SetValuePointer(plane_shape_indices_data));
    49 
    50 plane_shape_attrs.push(NSI::Argument::New("N")
    51     ->SetType(NSITypeNormal)
    52     ->SetCount(4)
    53     ->SetFlags(NSIParamInterpolateLinear)
    54     ->SetValuePointer(plane_shape_N_data));
    55 
    56 plane_shape_attrs.push(NSI::Argument::New("N.indices")
    57     ->SetType(NSITypeInteger)
    58     ->SetCount(4)
    59     ->SetValuePointer(plane_shape_indices_data));
    60 
    61 nsi.SetAttribute(plane_shape_handle, plane_shape_attrs);

    对于一个mesh来说,它具备如下几个内置的属性

    • P
    • nvertices
    • nholes
    • clockwisewinding
    • subdivision.scheme
    • subdivision.cornervertices
    • subdivision.cornersharpness
    • subdivision.creasevertices
    • subdivision.creasesharpness

    顾名思义,这些属性定义了这个mesh的所有几何数据,每一个属性的数据就是一个数组,如同范例C++代码所展示的一样。

    光有mesh当然不行,还需要transform

     1 #include <nsi.hpp>
     2 
     3 // Set transform data, which is identity.
     4 //
     5 double plane_xform_matrix_data[16] =
     6 {
     7     1, 0, 0, 0,
     8     0, 1, 0, 0,
     9     0, 0, 1, 0,
    10     0, 0, 0, 1
    11 };
    12 
    13 NSI::ArgumentList plane_xform_attrs;
    14 plane_xform_attrs.push(NSI::Argument::New("transformationmatrix")
    15     ->SetType(NSITypeDoubleMatrix)
    16     ->SetCount(1)
    17     ->SetValuePointer(plane_xform_matrix_data));
    18 
    19 nsi.SetAttributeAtTime(plane_xform_handle, 0.0, plane_xform_attrs);
    20 
    21 // Create plane's mesh and connect it to the last transform.
    22 //
    23 const std::string plane_shape_handle("planeShape1");
    24 
    25 nsi.Create(plane_shape_handle, "mesh");
    26 nsi.Connect(plane_shape_handle, "", plane_xform_handle, "objects");

    其实非常简单,这里使用了SetAttributeAtTime,用来定义多个matrix实现运动模糊。末了,直接调用Connect,这样就把先前构造的mesh放入了transform的objects这个属性之下,从此这个mesh可以被transform所变换。当然transform是可以包含transform,构造成了层次化的变换。

    下面当然是需要附上材质了,我们就用最简单的lambert。

     1 #include <nsi.hpp>
     2 
     3 // Assign lambert shader to the plane.
     4 //
     5 const std::string plane_xform_attrs_handle = plane_xform_handle + "Attrs";
     6 
     7 nsi.Create(plane_xform_attrs_handle, "attributes");
     8 nsi.Connect(plane_xform_attrs_handle, "", plane_xform_handle, "geometryattributes");
     9 
    10 const std::string lambert_shader_handle("lambert1");
    11 
    12 nsi.Create(lambert_shader_handle, "shader");
    13 
    14 char lambert_shader_name[256];
    15 sprintf(lambert_shader_name, "%s/maya/osl/lambert", delight_dir);
    16 
    17 nsi.SetAttribute(lambert_shader_handle, (NSI::StringArg("shaderfilename", lambert_shader_name),
    18     NSI::FloatArg("i_diffuse", 0.8)));
    19 
    20 nsi.Connect(lambert_shader_handle, "", plane_xform_attrs_handle, "surfaceshader");

    这里需要先构造attributes,然后把这个attributes和之前创造的transform节点的geometryattributes连接,这样所有attributes都会被所有transform的objects所继承,从此那个mesh就会附上了这个lambert材质。当然此shader实例可以用同样的方式共享给其他的几何体。

    还有更多的代码可以从nsi-example这个开源项目看到完整的源代码。

    感兴趣的用户可以直接到3Delight Download下载试用版体验最新3Delight,体验其卓越的性能和所有功能特色。

  • 相关阅读:
    群发邮件2
    谈谈C#中的三个关键词new , virtual , override
    一个简单的jQuery插件ajaxfileupload实现ajax上传文件例子
    网站静态化结构
    第四十七章 天神的邀请
    asp.net 异步群发邮件时遭遇到的问题 ddddddddd
    第四十章 远方的消息
    商用群发p2p网络
    第四十八章 三大客卿
    第四十五章 你没让我失望
  • 原文地址:https://www.cnblogs.com/Jedimaster/p/9249883.html
Copyright © 2020-2023  润新知