28 1月

Vex 案例

作者:will 网址:http://ymuhua.com/2016/01/28/vex-104/

用volume做mask选点。(volumesample)

现在我们有一个河床,和一个用box塞满点。我想做个一条小溪流。

comb

但是,我想把在河床外面的点去掉,只留下河床里面的点来作为水。

bedvolume

那么,首先我们需要将河床转换成有体积的模型后再转换成volume。

然后,我通过volumesample来选择我需要的点。

v1node

结果:

wkresult

用volume来选模型的好处就是,速度非常的快,比用一个封闭的几何体去选择快很多,这个在生产中是非常实用的。

 

vex选出稀疏(落单)的点(point cloud)

我们做水或者水花的时候,可能会发现某些点飞得非常的远,为了处理这些落单的点,可能通常会用到速度来判断删除,但是用速度有个问题,当它速度减小的时候这些点又会重新出现。

所以,我们需要用点云pointcloud来判断,当某个点周围没有点,或者点很少的时候,我们可以删除这个点。

pointxxx density

 

 

更具一个轴来旋转方向向量

现在如图有一个法线N,它 的初始方向是{1,0,0}, 现在我需要让法线N绕y轴{0,1,0}来旋转。rot

rot2

转载请注明作者和出处,谢谢。http://ymuhua.com/2016/01/28/vex-104/
27 1月

Vex 语法

作者:will 网址:http://ymuhua.com/2016/01/27/vex-102/

这部分我们会简单介绍一下,判断和循环。为什么简单介绍一下呢,因为几乎和c是一样的。

{}

和C一样,大括号在逻辑语句中表示开始和结束的标记

If

结果:

cd

while loop

For

结果:增加10个点

foreach

这个循环旨在帮助你循环数组中的元素。
语法

举例

return

函数返回值

跳出当前循环,进行下次循环

Break

结束循环

 

调用使用用户自定义函数

转载请注明作者和出处,谢谢。http://ymuhua.com/2016/01/27/vex-102/
27 1月

Vex Expression 用法

作者:will 网址:http://ymuhua.com/2016/01/27/vex-103/

自从有了wrangle这些节点让我能够写vex表达式之后,houdini原有哪些很常用的节点都被我抛弃了。下面来介绍一下它可以代替哪些节点。

Point

point

如果用过houdini15之前的版本的同学,我们发现houdini15之后这个节点里面填写的内容发生改变了。说明sidefx也更倾向于用vex表达式了。

point2

以上的这些参数,我们用vex可以这么写:

Attribute Create

pscale

vex可以这么写:

很简洁~

将属性映射为local varible.

group

可以替代group节点

创建组:用group_name的前缀就能很方便创建一个组

判断组是否存在

根据条件进组

组合并

group2

其实我们在Spreadsheet中查看,其实就相当于添加了一个组的属性。

group

除了以上这些我比较常用的操作,它还能完成以下

Creating geometry修改创建模型

常用到一下函数:

addpoint

addprim

addvertex

removepoint

removeprim

setprimintrinsic

setattrib   用@name=val这种方式直接赋值属性会比较快,效率会更高,但是当你需要创 建不同类型的属性的时候可以用这个。

还有一点我们需要知道就是上面这些函数都是并发的,也就是说支持cpu多核并发处理。所以vex还是非常强大的。

创建primitive需要注意的是,要为每个point再创建一个相对应的vertex,否则houdini会崩溃。

转载请注明作者和出处,谢谢。http://ymuhua.com/2016/01/25/vex-103/
25 1月

VEX Expression 基础

作者:will  网址:http://ymuhua.com/2016/01/25/vex-101/

Houdini中的Vex语言是什么?

VEX在houdini中是一种执行效率很高的语言。简而言之它执行速度非常的快,速度接近C/C++的执行速度,而且编译houdini会帮你完成。这是他的优势所在了。它的目的是方便提供给用户自定义节点,自定义材质的一种语言。
当然了,日常中我用vex语言很容易搞混,它语法和C挺相似但是有不完全像C,所以我自己有时用的时候也经常犯一些愚蠢的语法问题。如下图。

cclose

这里是sidefx官网关于Vex的解释:

http://www.sidefx.com/docs/houdini15.0/vex/_index

Vex Expression是什么?

俗称vex的表达式,它是基于vex语法,大概是houdini12可以写在以下节点里面的语言,比如 Point Wrangle, Attrib Wrangle ; Geometry Wrangle, Gas Field Wrangleparticle dynamics nodes.我们可以直接将vex写在这些节点里面。

vex表达式运行是基于物体的((point, particle, edge, primitive, voxel),通过vex表达式可以很方便访问物体最底层的属性,并直接修改赋值。它们的出现大大方便了我们写vex表达式,同时也可以替代一些节点,如point节点,自从wrangle出现之后,我基本就不用这个节点了。
wrange

数据类型

数据类型的定义和c语言有点像又有点不像,相似的地方是声明一个变量需要定义他的类型。

数据类型
Type Definition Example
int Integer values 21, -3, 0x31, 0b1001, 0212, 1_000_000
float Floating point scalar values 21.3, -3.2, 1.0, 0.000_000_1
vector2 '二维向量',float类型 {0,0}, {0.3,0.5}
vector '三维变量' positions, directions, normals or colors (RGB or HSV) {0,0,0}, {0.3,0.5,-0.5}
array '数组' { 1, 2, 3, 4, 5, 6, 7, 8 }
struct '结构' c
matrix3 '矩阵' { {1,0,0}, {0,1,0}, {0,0,1} }
matrix '矩阵' { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} }
string '字符' hello world
bsdf PBR shaders for information on BSDFs.

更多关于数组用法:http://www.sidefx.com/docs/houdini15.0/vex/arrays

定义属性

和c的变量定义很相似,那么下面是不同的地方。它不单可以定义变量,它还能定义属性attributes。这是vex表达式特有的。
何为属性,大家应该看着个教程之前都有一定houdini的基础了。就是基于模型元素的一些属性来表示元素性质,如质量,速度。
属性名前面必须要加@符号。

属性类型
type Syntax Example
float f@name 21.3,-3.2
vector2 (2 floats) u@name {0,0}{0.3,0.5}
vector (3 floats) v@name {0.3,0.5,-0.5}
vector4 (4 floats) p@name {0,0,0,1}
int i@name 21,-3,0x31
matrix2 (2×2 floats) 2@name
matrix3 (3×3 floats) 3@name { {1,0,0},{0,1,0},{0,0,1}}
matrix (4×4 floats) 4@name
string s@name hello world

注意:在声明属性的时候,等号的右边不能函数,变量、运算符(个人认为很不解的地方)

attributes

访问和修改属性属性:

注意:在赋值vector的时候如果右边有运算符或者调用函数,不能用{}。要用set()

houdini本身会自定义一些预设属性,所以我们在定义自己的属性的时候最好避开这些命名,如果你想更改预设属性也可以,名字要匹配(区分大小写),类型也一定要对上才能完成修改。

houdini预设属性
VEX type Attribute names
vector (3 floats) @P,@accel,@center,@dPdx,@dPdy,@dPdz
@Cd,@N,@scale,@force,@rest,@torque,@up,@uv,@v
vector4 (4 floats) @backtrack,@orient,@rot
int @id,@ix,@iy,@iz,@nextid,@pstate,@resx,@resy,@resz
@ptnum,@vtxnum,@primnum,@numpt,@numvtx,@numprim,@group_*
string @name,@instance,@OpInput1,@OpInput2,@OpInput3,@OpInput4

注意,这里某些预设的属性他们作用域不是全局的,是局部的,只针对当前节点,如果这个节点支持这个属性那么,我们才可以调用。

全局变量

和HScript的表达式不同, 你不能在vex中使用$F(我经常搞混这里).

要改成这些命名了。这些是全局的,你可以在所有类型的wrangle都能用。

@Time Float time ($T)
@Frame Float frame ($FF)
@SimTime Float simulation time ($ST), only present in DOP contexts.
@SimFrame Float simulation frame ($SF), only present in DOP contexts.
@TimeInc Float time step (1/$FPS)

操作符

Vex操作符和c相似,下面主要是区别一些和c不同的地方。

vector相乘的结果是vector每个元素之间的相乘

当两个不同类型的变量相加时,结果数据类型和第二个数据类型相匹配 for example

点符号Dot operator

可以用来表示一个向量每个元素。

  • .x or .u 表示vector2的第一个元素

  • .x or .r 表示vector and vector4的第一个元素

  • .y or .v 表示vector2的第二个元素

  • .y or .g 表示vector and vector4的第二个元素

  • .z or .b 表示vector and vector4的第三个元素

  • .w or .a 表示vector4的第四个元素

对于 matrices, 你可以使用以下:

  • .xx 表示 [0][0]元素

  • .zz 表示 [2][2]元素

  • .ax 表示 [3][0] 元素

保留关键字

break, bsdf, char, color, const, continue, do, else, export, false, float, for, forpoints, foreach, gather, hpoint, if, illuminance, import, int, integer, matrix, matrix2, matrix3, normal, point, return, string, struct, true, typedef, union, vector, vector2, vector4, void, while

 

转载请注明作者和出处,谢谢。http://ymuhua.com/2016/01/25/vex-101/
19 1月

Jetter Sampling Antialiasing 随机采样抗锯齿(二)

在抗锯齿第一章我们讲了均匀采样。如,将一个像素点分成5*5的子格子分别进行光线采样。

organiti

他的缺点就是太平均了,可能会出现一些奇怪的波纹。大自然都是无规律的形状,所以我们也要随机话。

jettersample

a图,在一个像素点随机取25个点进行光线采样,缺点是点很不平均。

b图,更均匀的分布,原理如c图将一个像素分割成5*5的格子,然后在每个子格子中采样点进行光线采样。即随机,又均匀。

代码修改很简单。只要将前一章的

q+0.5   p+0.5 替换成 q+rand_float()  p+rand_float(),rand_float()范围是0到1

 

15 1月

抗锯齿 Antialiasing(一)

抗锯齿,也译为抗锯齿或边缘柔化、消除混叠、抗图像折叠有损等。它是一种消除显示器输出的画面中图物边缘出现凹凸锯齿的技术。

这个技术是为了在保证低分辨的情况下,把图形的边缘变得更平滑,过渡更自然。

其实我最先遇到这个技术还是在小时候玩游戏的时候遇到的。尤其是那些大型三维游戏。那时候家里穷,买不起牛逼的显卡,但是又想玩,所以只能把游戏的分辨率调到最低,然后在显示选项里面把抗锯齿给关了,然后忍受的满屏的狗牙还玩的乐此不疲。最佩服自己的还是在一台只有两兆显存的电脑上,把生化危机3给通关了,全程帧速率不足10帧,就像在玩幻灯片一样,有个好处是你有足够的反映时间,里面那个追击者很难追上你,很怀念小时候的时光啊。

言归正转,以下三张图,最左边是没有抗锯齿,右边两张图是经过抗锯齿的,我们可以看到右边两张图是经过虚化处理。看起来边缘的过渡更平滑。

antitri

我先来介绍实现抗锯齿最简单的一个方法。

我们知道光线追踪是根据点采样产生的,我们每采样一个点,就在那个点上射出一根光线,之前我们介绍的是我们从每个像素的中心点作为一个光线的起点射出,虽然点在空间中是可以无限小的点,但是他在空间中也必须代表一块有限大小的面积。我们上一章一个中心点就代表这整个像素的结果,如果它射出去遇到红色的球,那么一整块像素就是红色的,但是这么做是很不精确的,尤其是在你像素点很大的情况下。

aniti1 anti2

上面两张图左图是理想状态下,右图是根据实际像素所采样的结果,图a的A像素,我们看到他2/3的面积是被黑色的背景覆盖,而1/3的面积是被黄色所覆盖,但是图b的A像素的中心点是在背景中,所以沿着中心点射出,我们必定遇不到黄色的模型。所以是黑色。

现在我们想,如果让这个A像素显示一个1/3的黄色的话不是更能够代表这个像素的真实情况吗。就像下图。

anti

那我们就需要将一个像素在分割成更小的像素。

aniti1G

如上图,像素A又被分割成9个小像素,然后我们再从这9个像素射出9个光线,然后,我们将这9个像素射出返回的值求他们的一个平均值给到像素A。我们就能得到一个光滑的边缘了。

anti

然后,我们来改一下代码。
假设我们采样3*3,也就是将每个像素点再划分成9小格,横向3个,纵向3个,定义n=3,n*n=9,也就是说我们在每次循环再增加9次光线碰撞计算,

Done

resultanit

当然了,这是最最基础抗锯齿的算法,速度也是最慢的,但是不关怎么着,我们有了第一步的图像优化!

接下俩又是思考题,如果多个物体的抗锯齿,尤其是叠加在一起的物体怎么算?

multanit

留给大家思考,有问题欢迎联系我

09 1月

渲染第一张图片

在这一章节,我来教大家怎么来用光线追踪来渲染第一张图片,把场景中的一个球渲染出来,我们假设这个球的颜色是红色。

sphere

我们要渲染一张图片,需要将三维的模型投射到一个平面上,我们先称它为画布,画布就是观察者的屏幕,实体化的画布就是电脑的显示屏,我们需要将画布在三维场景里面表示出来。所以我们定义:

画布的位置,朝向: 我们先定义画布在世界坐标的原点pos(0,0,0),面朝Z轴方向dir(0,0,1)。

画布的大小:这个画布都宽高多少,我们先定义画布宽(Aperture):41.4214m(houdini默认的摄像机的宽度)。画布高度这里是根据像素的宽高比来算出来。

注意,宽高不是指的是像素,这不是一个概念,像素指的是画布宽高在一个指定长度里面被划分成多少格数,一个格子代表一个像素。

像素:Hres8宽,Vres6高(如下图)

像素大小:因为像素一般都是一个正方形,所以我们只要求出像素宽就行了。

pixel_size=Aperture/Hres=41.4214/8(m)

viewplane

好了,现在每个有8*6个像素点,每个像素点我要射出去一根光线来判断我这个像素到底显示什么内容,如果这个线射出去碰到了场景中的红球,那么对应的像素点就填上红色。如果没有碰到红球,相对应的就显示黑色(背景)。

那么问题来了,我现在有48个像素对应要射出48根光线,那么这48根光线从哪里?方向?射出呢?

我们先不考虑透视的关系,现在我们假定每根线光线的方向都是沿着z轴的方向,定义ray.dir=(0,0,1);

下面,我们来确定他们的位置,如何确定光线的位置就相当于确定每个像素点的位置。我们来规定画布的中心正好在世界坐标的原点(0,0,0)。

画布的左下角等于:(0-8/2, 0-6/2)=(-4,-3) 单位是像素格。

画布的右上角等于: (3,2)   左上角:(-4,2) 右下角:(3,-3) 单位是像素格。

注意,这些数字现在都是以每个像素点的左下角为起点的。我们现在想让光想从每个像素的中心点出发。那么我们分别要向右和向上移动半格像素:

左下角(-3.5,-2.5) 右上角等于: (3.5,2.5)   左上角:(-3.5,2.5) 右下角:(3.5,-2.5)  单位是像素格。

既然我们在之前求出了像素的大小(pixel_size)那么我们可以知道每个像素的中心点的坐标了。

例:左下角:(-3.5,-2.5)*pixel_size=(-18.13,-12.95) 单位(m)

那么每个光线的位置ray.pos和方向ray.dir=(0,0,1)我们都已经确定了。遍历所有像素的

输出ppm:

结果如果下,球的位置坐标为(0,0,10),半径为1:

first1

大家感到很奇怪,为什会是个正方形?下面我们增加像素再来渲染看看。

middlefirsthigh

通过上面三张图比较,我们知道。像素越高,这个圆的还原度就越好。如果像素太低,那么图像的细节就会丢失很多,当然像素越高图像所花的渲染时间也就月多。

8*6:48根光线

80*60:4800根光线

800*600:4800000根光线

这是处理数据量的对比。

 

好了。这章我们讲完了。下面留下课后思考:如果场景中有多个球怎么办?并且他们从摄像机看去还有重叠关系。如何区分前后关系?留给大家思考吧

think

有任何问题,欢迎来信或者留言,我很乐意提供解答和帮助。

谢谢

 

08 1月

ppm 图片格式文件

好了,经过前面两章的介绍,我们知道了,如何来判断从观察者射出的光线是否能和圆碰到。接下来我们要继续讲下去去之前,要先介绍用什么图片格式来保存我们的数据。

我们平时用到最多的格式恐怕就是jpg图了,当然搞cg的同学平时渲染会输出现在的主流格式exr,当然用maya还会有tiff。不管什么格式的图片格式他们做的事情都是保存图片信息,不同是压缩算法的不同,看哪家的格式占用空间最少,图片信息丢失最少。这些都不是我们现在要关心的,我们现在关心的哪个图片格式最容易入门,那当然是ppm格式。

为什么说他容易,因为你可以用电脑自带的记事本做一个图片,对,就是那么的容易。

现在大家打开记事本贴写以下几行。

然后另存为test.ppm。好了,一张图片创建好了。

pixelppm

ppm像素的排列顺序是从左到右,从上到下。

对了。因为我是在Linux系统下工作环境,所以linux是直接支持ppm的格式图片。

如果大家是在windows平台下面,那么要看到ppm就需要安装看图软件ACDSEE

下面我们通过一个Rgb类来输出Rgb这三个颜色通道。

注意,我在内部进行数据操作的时候R,G,B的范围是0-1,理论上是可以大于1的,但是大于1之后,人眼分辨不出,但是可能以后后期较色需要这些信息,这里我们假定是[0,1]范围
还有,我们在写入到ppm文件是,需要将R,G,B值域范围映射到[0,255]的范围。如果值大于1了,限定等于255(clamp)。

写入ppm文件

 

07 1月

你可能说要知道的知识

光 颜色 Light and Color

 

我们知道,计算机中三维模型都需要投射到二维屏幕上。那么投射到二维屏幕上的每个像素点的颜色是由谁来决定的呢。

一个物体的颜色、明暗是由场景中灯光照射到物体上和这个物体本身的材质有关。

光——是由千千万万个光子(传递电磁相互作用的基本粒子)所组成,也就是说这些粒子他们带有能量,他们会震荡,就好比声波在空气中传播一样,他们是直线传播的。.光子是从光源中发射的,太阳,就是我们身边最大的一个光源。如果一组光子打到一个物体上。那么会发生以下三种情况:被吸收,反弹,穿过。不同的材质这三个情况发生的概率是不一样的。所以,每个不同材质的物体在我们眼里他们表现的质感也是不同的。但是,有一个规则是所有材质都相同的。那就是他们遵循能量守恒的原则,设入的光子总量=吸收+反弹+穿过的光子总和。

在科学界上,我们将材料总归为两类,导体和非导体。导体:金属类。非导体:玻璃,塑料,木头。非导体,可以是透明或不透明的(如下图塑料球和玻璃球)。

同样的,材质是可以合成在一起的,比如下图的彩色球,和颜色偏深的玻璃球一样

下面我们来考虑一下漫反射,一束光照射到一个球上,为什么他会显示红色?我们简单假设一束白光是由,红,绿,蓝三种颜色光子组成,现在我们照射一个红球上,现在蓝色和绿色光被这个球所吸收(过滤)了。而红光(光子)反射到我们眼睛里面,所以我们看到的是红色。为什么我们能看到五颜六色的物体,就是因为大自然界中的光源发射能量不同(颜色不同)的光子,然后这些光子在空间中来回穿梭,有的被吸收了,有的被反弹,有的穿过了,这也就是为什么天有时候是蓝的,有时候是橘红色,太阳光被大气过滤。然后不同光子从不同物体上反射到不同角度上,到达我们眼睛就是我们所看到不同的颜色。所以说,人眼配合上人脑是非常非常牛逼的渲染器,他要处理无数个光子是我们现在电脑处理器所远远达不到的运算速度。

06 1月

从一个球开始

好了,我们定义好光线了,那么接下来我们需要拿些物体来检测了光线碰撞了,我们会介绍光线和平面交互,光线和box,光线和三角平面,光线和圆。

很多人会问,为什么不介绍复杂模型碰撞检测呢,比如说茶壶,龙。因为在三维中所有的三维模型都能用三角面组成,所以只要讲光线和三角面碰撞检测,我们就能融会贯通了。

首先我们使用圆来进行。为什么用圆来做碰撞检测呢。因为圆最简单。(为什么最简单,等以后你就知道了)

我们在初中学过圆的方程:

(x-c)²=r²

光线的方程:

p=o+t*d;  (  t>0  )

当光线和圆交互也就是有交点的时候,点p是在圆上面。得到

(p-c)²=r²;

(p-c)²-r²=0;

将光线代入:

(o+t*d-c)²-r²=0;

现在我们唯一不知道的是未知数t:

所以就变成解二元一次方程解:

d²t²+[2(o-c)d]t+(o-c)(o-c)-r²=0;

简化方程:

at²+bt+c=0;

a=d²

b=2(o-c)d

c=(o-c)(o-c)-r²

那么得到t的解是:

b_4abc

我们确定有没有解,就要判断开根号里面的值是否>0

所以,我们要判断试着个等式

d=b²-4ac;

如果,d>0,那么t有两个解,如果d=0,那么t有一个解。如果d<0则无解。我们用一张图来表示

sphere

这是第一步。因为如果我求出d>0,不等于就一定就有交互。我们还要看t的值。t值必须大于0,因为光线只会向前进,不会倒着走。

ok,求出了p的值之后,那么再将t代入p=o+t*d,我们就能知道撞击点的位置了。

Hitpos=0+t*d

现在我们来看看圆的类

以下是碰撞检测函数,

*这里来解释一下为什么t要大于0.0001而不是0,以后章节会提到,我这里简单说一下,之后为了判断这个点在不在阴影里面还要从这个点再继续射出一根shadow_ray。为了避免碰到自己本身=0的极端值,用着个方法把碰撞点往外推了一点点。