Home Map Index Search News Archives Links About LF
[Top bar]
[Bottom bar]
[Photo of the Author]
Carlos Calzada Grau

作者简介:

计算机科学研究生,计算机图形迷。十分热心于Linux,它的发展哲学和 与微软无关的任何事物。与计算机无关的其他爱好是盆景和养鱼。

正文:

  1. 引言:
  2. 简单场景:
  3. 矩形直线运动:
  4. 抛物线运动:
  5. 参考文献:

着色人(Renderman)III

[Ilustration]

摘要:Abstract

这是Renderman系列的第三篇文章 ( Renderman I Renderman II ), 现在我们将讨论最主要的问题:使用“C”或“C++”语言 建立模型和产生一个场景的可能性。



 

引言

从前两篇文章中可很清晰地看出,直接通过键入一个文本文件 描述一个场景是可能的,但这毕竟太单调乏味。 比如,考虑写一个“.rib”文件来描述一个跳跃的球的运动! 更容易一些,我们可以书写建立场景模型和动画的“C语言”或“C++语言” 应用程序,并利用传送“.rib”到标准输出的用户自定义函数。 UNIX的管道允许我们直接传送生成的Renderman命令到其他进程 (如rendribrendribvrgl), 或者重定向到“.rib”文件。

我们以前安装的蓝月亮着色工具(Blue Moon Rendering Tools) 创建两个名为libinclude的新目录, 包含四个文件,我们只涉及到其中的两个:ri.h头文件和 libribout.a函数库。头文件应该拷贝到/use/local/include目录, libribout.a拷贝到/use/local/lib目录(有经验的读者可以 选择安装到其他位置)。安装库后,我们开始准备第一个例子程序。

 

简单场景:

我们的第一个例子演示十分基本的Renderman编程。像任何C代码一样, 我们使用库之前必须包含相应的头文件,这里就是ri.h。 此外,我们必须将程序和库这样连接:

gcc myprogram.c -o myprogram -lribout -lm
      

下面是一个将保存在控制台上键入的时间的例子 Makefile
LIBS = -lm -lribout
PROGNAME = primero
	 
all: $(PROGNAME)
	
$(PROGNAME).o: $(PROGNAME).c
	gcc -c $(PROGNAME).c
	
$(PROGNAME): $(PROGNAME).o 
	gcc -o $(PROGNAME) $(PROGNAME).o $(LIBS)

 

我们的第一个例子将在中央建立一些坐标轴,一个球体,文件名为 primero.c,以下是源代码:
 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <ri.h>
 4
 5 void main(void)
 6 {
 7  int i;
 8  int x,y,z;
 9  int nf;
10  float slopex,slopey,slopez;
11
12  RtColor Rojo={1,0,0};
13  RtColor Verde={0,1,0};
14  RtColor Azul={0,0,1};
15  RtColor Blanco={1,1,1};
16
17  RtPoint p1={30,0,10}; /* Posicicion inicial de la pelota */
18  RtPoint p2={0,20,10}; /*   Posicion final de la pelota   */
19		
20  RtPoint from={0,100,100}; /*   Direccion de la luz       */
21  RtPoint to={0,0,0};
22
23  char name[]="primero.tif";
24  RtFloat fov=45;
25  RtFloat intensity1=0.1;
26  RtFloat intensity2=1.5;
27  RtInt init=0,end=1;
28		
29  RiBegin(RI_NULL);
30    RiFormat(320,240,1);
31    RiPixelSamples(2,2);	
32    RiShutter(0,1);
33    RiFrameBegin(1);
34     RiDisplay(name,"file","rgb",RI_NULL);
35     name[7]++;
36     RiProjection("perspective","fov",&fov,RI_NULL);
37     RiTranslate(0,-5,60);
38     RiRotate(-120,1,0,0);
39     RiRotate(25,0,0,1);
40     RiWorldBegin();
41       RiLightSource("ambientlight","intensity",&intensity1,RI_NULL);
42       RiLightSource("distantlight","intensity",&intensity2,"from",from,"to",to,RI_NULL);
43       RiColor(Azul);
44       RiTransformBegin();
45         RiCylinder(1,0,20,360,RI_NULL);
46         RiTranslate(0,0,20);
47         RiCone(2,2,360,RI_NULL);
48       RiTransformEnd();
49       RiColor(Verde);
50       RiTransformBegin();
51         RiRotate(-90,1,0,0);
52 	   RiCylinder(1,0,20,360,RI_NULL);
53 	   RiTranslate(0,0,20);
54 	   RiCone(2,2,360,RI_NULL);
55       RiTransformEnd();
56       RiColor(Rojo);
57       RiTransformBegin();
58 	   RiRotate(90,0,1,0);
59 	   RiCylinder(1,0,20,360,RI_NULL);
60 	   RiTranslate(0,0,20);
61 	   RiCone(2,2,360,RI_NULL);
62       RiTransformEnd();
63       RiColor(Blanco);
64       RiSphere(5,-5,5,360,RI_NULL);
65     RiWorldEnd();
66    RiFrameEnd();
67  RiEnd();
68 };

 

前三行是基本的头文件,其中ri.h是Renderman库的头定义。 每个Renderman调用在ri.h中有其等价的C类调用,例如 TransformBegin符合于函数RiTransformBegin()等等。 运行make创建可执行的primero。我们的例子 执行时可以通过重定向(primero > primero.rib)或 直接传送输出到其他进程(primero | rendrib)来创建输入文件。 在后一种情况中,rendrib负责生成一个着色后的文件primero.tif

从库里调用的函数必须处于RiBegin(RI_NULL)RiEnd()调用之间。 传递给RiBegin的参数通常是RI_NULL。 为了防止RIB输出到标准输出,我们可以传递输出文件名 * ("myfile.rib"), 甚至一个进程名(如rendrib),它的执行将传送Renderman命令给着色者 而无须创建一个中间RIB文件。

我们第一个例子的源代码包含典型的C指令,加上Renderman接口固有的类型和函数; 类型RtColor是含三个实数部分的向量,分别代表红色、绿色和蓝色 (范围由0.0 到1.0 ),类型RtPoint保存空间位置,RtFloatRtInt分别是实数和整数类型。

第29行包含一个到RiBegin(RI_NULL)的调用,正像我们先前描述的 是Renderman接口的初始化调用。从这里开始在通常的命令函数后, 应该输入一个典型的RIB文件。试着运行代码,并将输出重定向到一个文件 (./primero > primero.rib),输出结果应该这样:
##RenderMan RIB-Structure 1.0
version 3.03
Format 320 240 1
PixelSamples 2 2
Shutter 0 1
FrameBegin 1
Display "camara.tif" "file" "rgb"
Projection "perspective" "fov" [45 ]
Translate 0 -5 60 
Rotate -120 1 0 0 
Rotate 25 0 0 1 
WorldBegin
LightSource "ambientlight" 1 "intensity" [0.1 ]
LightSource "distantlight" 2 "intensity" [1.5 ] "from" [0 100 100] "to" [0 0 0]
Color [0 0 1]
TransformBegin
Cylinder 1 0 20 360
Translate 0 0 20 
Cone 2 2 360
TransformEnd
Color [0 1 0]
TransformBegin
Rotate -90 1 0 0 
Cylinder 1 0 20 360
Translate 0 0 20 
Cone 2 2 360
TransformEnd
Color [1 0 0]
TransformBegin
Rotate 90 0 1 0 
Cylinder 1 0 20 360
Translate 0 0 20 
Cone 2 2 360
TransformEnd
Color [1 1 1]
Sphere 5 -5 5 360
WorldEnd
FrameEnd

我们的第一个例子并不十分有用。为了生成另一个场景,我们必须写一个 新的程序,完成类似的操作。库的性能真正体现在动画的生成上。 第一个例子中只有一幅画面生成,接下来,我们要使球体移动。

 

矩形线性运动:

第二个例子中,我们的场景仍是由三个坐标轴和一个球体组成, 但球体将由坐标(20,0,10)移动到(0,20,10),即 从计算机屏幕的右端移动到左端。位置使用RtPoint结构定义(18、19行)。 动画中画面或图像的数量定义在变量nf中。 使用这个数、初始和终止位置,可以计算出每个画面三个方向上的步长 (slopex,slopeyslopez)。这些就是用画面 数的函数来修改球体位置所需的所有信息。在第75到78行之间, TransformBegin/TransformEnd负责定义球体的位置。 每一步新的位置将在第76行中进行简单地计算。

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <ri.h>
 4 #include "filename.h"
 5
 6 void main(void)
 7 {
 8  int i;
 9  int x,y,z;
10  int nf;
11  float slopex,slopey,slopez;  
12
13  RtColor Rojo={1,0,0};
14  RtColor Verde={0,1,0};
15  RtColor Azul={0,0,1};
16  RtColor Blanco={1,1,1};
17
18  RtPoint p1={30,0,10}; /* Posicicion inicial de la pelota */
19  RtPoint p2={0,20,10}; /*   Posicion final de la pelota   */
20	
21  RtPoint from={0,100,100}; /*      Direccion de la luz        */
22  RtPoint to={0,0,0};
23
24  char base[]="camara_";
25  char ext[]="tif";
26  char name[50];
27  RtFloat fov=45;
28  RtFloat intensity1=0.1;
29  RtFloat intensity2=1.5;
30  RtInt init=0,end=1;
31	
32  nf=100; /*        Numero de frames         */
33  slopex=(p2[0]-p1[0])/nf;
34  slopey=(p2[1]-p1[1])/nf;
35  slopez=(p2[2]-p1[2])/nf;
36
37  RiBegin(RI_NULL);
38    RiFormat(320,240,1);
39    RiPixelSamples(2,2);	
40    RiShutter(0,1);
41    for (i=1;i <= nf;i++)
42 	{
43 	RiFrameBegin(i);
44	  filename(base,ext,sizeof(base)+4,i-1,name);
45	  RiDisplay(name,"file","rgb",RI_NULL);
46	  name[7]++;
47	  RiProjection("perspective","fov",&fov,RI_NULL);
48	  RiTranslate(0,-5,60);
49	  RiRotate(-120,1,0,0);
50	  RiRotate(25,0,0,1);
51	  RiWorldBegin();
52	    RiLightSource("ambientlight","intensity",&intensity1,RI_NULL);
53	    RiLightSource("distantlight","intensity",&intensity2,"from",from,"to",to,RI_NULL);
54	    RiColor(Azul);
55	    RiTransformBegin();
56	    	RiCylinder(1,0,20,360,RI_NULL);
57	  	RiTranslate(0,0,20);
58	  	RiCone(2,2,360,RI_NULL);
59	    RiTransformEnd();
60	    RiColor(Verde);
61	    RiTransformBegin();
62		RiRotate(-90,1,0,0);
63		RiCylinder(1,0,20,360,RI_NULL);
64		RiTranslate(0,0,20);
65		RiCone(2,2,360,RI_NULL);
66	    RiTransformEnd();
67	    RiColor(Rojo);
68	    RiTransformBegin();
69		RiRotate(90,0,1,0);
70		RiCylinder(1,0,20,360,RI_NULL);
71		RiTranslate(0,0,20);
72		RiCone(2,2,360,RI_NULL);
73	    RiTransformEnd();
74	    RiColor(Blanco);
75	    RiTransformBegin();
76		RiTranslate(p1[0]+slopex*(i-1),p1[1]+slopey*(i-1),p1[2]+slopez*(i-1));
77		RiSphere(5,-5,5,360,RI_NULL);
78	    RiTransformEnd();
79	  RiWorldEnd();
80	RiFrameEnd();
81	}
82  RiEnd();
83 };

接下来,让我们像以前一样试验第二个例子:编译并执行,作为例子 重定向输出到rendribv。这是以我们可接受的速率快速 预览我们的动画的一个简易方法。为检查rib输出文件,传送标准输出到一个新文件。 如这可以发现生成的文件是十分庞大的 (segundo.rib占70kb) ,这是因为同一个场景定义了100次(每个画面一次)

下面的图例给出了一些动画的中间画面:

当然,可以使我们希望的任何事物动起来:物体的位置, 大小,光强度,摄像机,使物体忽隐忽显……

 

抛物线运动:

在最后一个例子中,让我们来看看如何使球体从地上反弹起来。 我们首先定义函数rebote()(反弹的意思),有三个参数: 当前画面的数量,每次反弹的画面数,球体能达到的最大高度。 以下是其实现:

float rebote (int i, int nframes, int max)
{
  float min, z;

  while (i > nframes) i-=nframes;

  min=sqrt(max);

  z=i-((float)nframes/2.0);
  z=(z*min)/((float)nframes/2.0);
  z=(float)max - (z*z);
  return(z);
}

利用一些简单的计算,可以映射典型的抛物曲线(y=x^2)画面数和希望的最大高度。下面的图例给出一些由程序生成的 每次反弹的中间图像。 tercero.c:

我提供一些动画GIF文件来形象显示动画过程,虽然他们运行缓慢 (至少在Netscape下是这样),但通过xanim你应该能够 正常地看到。

矩形线性运动:segundo_anim.gif

抛物线运动: tercero_anim.gif

这样我们关于Renderman的C语言接口基础的论述及编程告一段落。 最高级、最壮观的编程主题是阴影(shaders)。 它提供了场景最终着色的根本控制,因为允许我们控制纹理,照明等等。

 

参考文献



翻译:Miguel A Sepulveda


网页由LinuxFocus编辑组维护
© Carlos Calzada Grau
LinuxFocus 1999