3Delight NSI: A Streamable Render API
3Delight是應用於高階電影級別渲染的軟體渲染器,迄今為止已經參與了無數的電影製作,具體可以參見連結。
如果你對3Delight的印象就依然是RenderMan的替代品,那就顯然已經和時代發展脫節了。現在的3Delight是一個完全PBR Unbiased的渲染器,而且完全為了互動式渲染以及雲端渲染設計,所以你對它的固有印象可以從看到這篇文章開始徹底改變了。
渲染=資料操作
其實“渲染”這個動作的本身,就是資料處理,你可以用任何流行的思路來對照,比如MapReduce。但是歸根結底,可以認為只有3個概念。
- 資料填充
- 資料修改
- 資料計算
這3個概念可以直接展開,把你所知道所有的計算機圖形學相關的概念和技術都丟入,但是這裡不展開。
本文會結合這3個概念,來仔細的闡述3Delight NSI的優點和思路,以及解決的問題。
一切從過程開始
計算機,其實是過程性裝置。所謂面向物件,只是軟體設計領域的一個對過程和資料的合併抽象而已,本質上,最後的“執行”這個本身依然是個過程。
那麼回顧一下RenderMan API(以下簡稱RI)的設計。
RenderMan
一個完整RI可渲染的場景一般結構如下,來自ofollow,noindex">這裡 。
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 20Transform[.707107-.408248-.57735 0 210.816497-.577350 22-.707107-.408248-.577350 230017.32051 ] 24WorldBegin 25AttributeBegin 26Attribute "identifier" "name" "myball" 27Displacement "MyUserShader" "squish" 5 28AttributeBegin 29Attribute "identifier" "shadinggroup" ["tophalf"] 30Surface "PIXARmarble" 31Sphere .5 0 .5 360 32AttributeEnd 33AttributeBegin 34Attribute "identifier" "shadinggroup" ["bothalf"] 35Surface "plastic" 36Sphere .5 -.5 0. 360 37AttributeEnd 38AttributeEnd 39AttributeBegin 40Attribute "identifier" "name" ["floor"] 41Surface "PIXARwood" "roughness" [.3] "d" [1] 42# geometry for floor 43Polygon "P" [-100. 0. -100.-100. 0. 100.100. 0. 100.10.0 0. -100.] 44AttributeEnd 45WorldEnd 46 FrameEnd 47 FrameBegin 2 48##Shaders PIXARwood, PIXARmarble 49##CameraOrientation 10.0 20.0 10.0 0.0 0.0 0.0 50Transform [.707107-.57735-.4082480 510.57735 52-.815447 0 53-.707107-.57735-.4082480 540024.4949 1 ] 55WorldBegin 56AttributeBegin 57Attribute "identifier" "name" ["myball"] 58AttributeBegin 59Attribute "identifier" "shadinggroup" ["tophalf"] 60Surface "PIXARmarble" 61ShadingRate .1 62Sphere .5 0 .5 360 63AttributeEnd 64AttributeBegin 65Attribute "identifier" "shadinggroup" ["bothalf"] 66Surface "plastic" 67Sphere .5 -.5 0 360 68AttributeEnd 69AttributeEnd 70AttributeBegin 71Attribute "identifier" "name" ["floor"] 72Surface "PIXARwood" "roughness" [.3] "d" [1] 73# geometry for floor 74AttributeEnd 75WorldEnd 76 FrameEnd View Code
聰明的你告訴我,你覺得這個場景描述有什麼限制?這個問題可能很難回答,但是我們先來提幾個看似簡單的需求。
- 流式更新
- 幾何體資料的修改
- 幾何體屬性的修改
- 材質資料的修改
- 材質和幾何體關係的修改
- 多螢幕計算
- 多螢幕不同解析度的計算
- 多螢幕不同解析度不同資料的計算
但是告訴我,如果你想修改這個Mesh的幾何資料,你會如何做?這個答案在RI內,使用負責場景資料,範例如下。
1 RiEditBegin("attribute", "string editlights", "light1", RI_NULL); 2// specify the coordinate system for light1 3RiTransform( ... ); 4RiLightsource( "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 { 84 9 }; 10 11 int plane_shape_indices_data[4] = 12 { 130, 1, 3, 2 14 }; 15 16 float plane_shape_P_data[12] = // 3 * 4 17 { 18-50, 0, 50, 1950, 0, 50, 20- 50, 0, - 50, 2150, 0, - 50 22 }; 23 24 int plane_shape_N_data[12] = // 3 * 4 25 { 260, 1, 0, 270, 1, 0, 280, 1, 0, 290, 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來說,它具備如下幾個內建的屬性
- 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 { 71, 0, 0, 0, 80, 1, 0, 0, 90, 0, 1, 0, 100, 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), 18NSI::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,體驗其卓越的效能和所有功能特色。