1-4期
195人加入学习
(33人评价)
Houdini 影视特效实验班

6个月零基础到入职

价格 ¥ 6198.00
教学计划
承诺服务

gasmatchfield微解算器节点:将一个场拷贝到另一个场

此例中,将vel场拷贝,新场名称为disturb,记住vel场是一个矢量场,下面要选择vector

但是disturb场内没有任何东西,需要添加进去

 

 

!!!!发现这里一个问题!!!!

8分30秒的时候,wrangle代码中v@v必须改成v@vel,不然速度场将不会受到disturb场的影响!!!

 

!!本节中gasdisturb节点,houdini16和18版本中有巨大不同,课程中的数字资产新版本内没有,但是好像直接调整这个阈值,就可以达到老胡课程中希望达到的效果

!!并不能达到效果!!问题已经交给胡老师了

[展开全文]

在本课中(22 02-07添加石头中),在22分钟40秒时,需要添加remesh节点细分,我之前用的是huodini17版本做的没遇到什么问题,但是后来用18版本之后发现这个节点不起作用,是因为在之前破碎石头的时候添加了assemble打包节点,17以下的版本remesh节点有自动解包的功能,到18之后这功能就没有了,所以要细分的话,必须在之前添加一个unpack解包节点,然后再进行细分。

[展开全文]

这节课新版本Houdini要用个vdbvectormerge节点,因为老版本的sourcevolume识别速度是XYZ分开三路的,新版本的volumesource节点识别的是XYZ路合在一起的。加个vdbvectormerge节点三路合并起来就行了。

还有要把volumesource节点读入的v改成vel。

[展开全文]

!!这里复制左边copy节点后,skin2节点会出错,本节视频中没有显示也没有解决,在下一节课中会解决!!

[展开全文]

其实streak和cell两个force都是一个排斥力,一模一样的设置,只是排斥力的大小不一样而已,这两个力的大小由mass这个属性值来决定,上面的streak因为mass属性值小,往外排斥的力小,看上去有条纹,下面的cell因为mass属性值大,往外排斥的力大,能清晰的看到蜂窝状

 

这两个设置里面唯一的区别就是pcfind radius节点的radius scale参数不同,这个决定目标点对周边多大范围的粒子产生条纹或者产生蜂窝

popsource节点发射的目标粒子决定有多少条纹和多少蜂窝

 

 

[展开全文]

float id_seed竟然是=cur_P *@Frame

cur_P是个矢量,乘以帧号得出了一个浮点?(这里F一定要大写,小写不行)

 

保持代码的轻量化,不浪费内存,开启点云后要关闭点云pcclose(handle)

[展开全文]

!!houdini18版本需要注意,在3个name节点之后,使用merge节点合并之后,需要一个节点vdbvectormerge,来将xyz三个合并成一个vel场,不然将无法像课程里一样运行!

 

另在dopnet中,volume source节点有些改变,

点击operation旁边的+号,就可以添加field了,具体可以参考老胡volume II这套课程!

[展开全文]

主体闪电上自己创建的四个初始属性,第一个i@branch_time属性用来规定分支闪电的存在时间,初始值是8帧到15帧之间;   

 

i@branch_count属性用来规定分支闪电的数量,初始值是-1,表示现在还没有任何分支闪电。主体闪电上的点每找到一个分支闪电的目标点,那么就会形成一个分支闪电,这个属性的值就加1;   

 

并且可以拿i@branch_time属性的值和i@branch_count的值做一个比较,当i@branch_count的值大于i@branch_time属性的最大值的时候,就可以让大于15的分支闪电清除掉;         

 

i@branch_point属性是用来记录主体闪电上的点找到了哪些分支闪电的目标点,初始值是-1,表示主体闪电上的点还没有找到任何一个分支闪电的目标点;

 

v@Cd属性是用来做debug的,初始值全部设置成黑色

 

i[]@already_taken属性是一个数组,同样也是用来存储被找到的分支闪电的目标点的序号,初始值只是一个{},代表是一个空数组,还没有写进任何点序号,这个属性的作用是这样的,比如分支闪电的目标点上有一个序号是147的点,主体闪电上有一个序号为444的点,主体闪电上序号为444的点找到了分支闪电的目标点上序号为147的点,由此创建了一根分支闪电,那么主体闪电上别的点也有可能找到分支闪电的目标点上序号为147的点,也由此创建了一根分支闪电,这样形成的分支闪电就很奇怪,为了避免这种情况,分支闪电的目标点每被找到一个通过append函数被储存进i[]@already_taken属性这个数组中,再通过find函数,确认被找到的分支闪电的目标点的唯一性,那么主体闪电上别的点再去找分支闪电的目标点便会跳过这个已经被找到了的点

[展开全文]

之所以vex表达式要在detail层级下运行,是因为要保持数据的轻量化,同一行表达式,如果在point层级下运行,那么有多少个点,这行表达式就会运行多少次,但是在detail层级下运行,这行表达式就只会运行一次。  在这里因为要写For循环语句,For循环语句运行一次就会循环五百零一遍,如果在point层级下写For循环语句,计算量会非常非常巨大

 

for(init; condition;change){ statement }循环语句格式,先运行init,再进行条件判断,判断为真,便执行大括号里边的statement,再执行change,进行条件判断,判断为假便退出循环;

for(int i = 0; i < npoints(0); i++){

      setpointattrib(0, "aaa", i, i, "set");

}     这个是for循环的标准写法;    先定义一个临时的int形式的变量,名字随便起,在这里起名为 i ; 数值是0,然后给一个条件,这个条件就是 i < npoints(0),当这个条件满足,即代表是真,那么就执行下边大括号里面的操作。

setpointattrib函数是给点设置一个属性,因为这是在Detail层级下运行,如果直接创建属性,这个属性会写入Detail层级下,所以需要用到这个函数,才能把属性写入point层级。 第一空填要把属性设置到哪一个对象,0即是代表自身;第二空是要设置的属性的名称;第三空是要设置属性的点的序号;第四空是要将属性设置为多少的值;第五空是用哪一种方式来设置属性。在这里,因为这是第一次循环,i 已经在上边设置为0,那么要设置属性的点的序号是0号,设置的属性的值是0。

完成第一次循环,进入第二次循环 i 的值会加1,此时 i 的值是0+1,等于1,进行条件判断,条件依然满足,输出依然为真,再执行一遍大括号里的操作,此时要设置属性的点的序号是1号,设置的属性的值是1。完成第二次循环,剩下的循环都是以此类推,当条件不再满足,那么便会退出循环

 

 

int temp_array[] = point(0, "taken_already", 0);     这句话是创建一个临时的整数型变量数组,读取的是0号点的i@already_taken[]属性的数值

[展开全文]


for(int i = 0; i < npoints(0); i++){
    
    vector current_P = point(0, "P", i);
    int current_id = point(0, "id", i);
    int branch_count_temp = point(0, "branch_count", i);
    int branch_time_temp = point(0, "branch_time", i);
    
    float seach_radius = chf("seach_radius");
    int max_points = chi("max_points");
    
    int found_ptnum;
    
    int handle = pcopen(1, "P", current_P, seach_radius, max_points);
    while(pciterate(handle)){
        pcimport(handle, "point.number", found_ptnum);
        
        int bool_A = branch_count_temp == -1 ? 1 : 0;
        int array_check = find(temp_array, found_ptnum);
        int bool_B = array_check < 0 ? 1 : 0;
        
        if(bool_A && bool_B){
            setpointattrib(0, "branch_point", i, found_ptnum, "set");
            
            append(temp_array, found_ptnum);
            temp_array = sort(temp_array);
            
            setpointattrib(0, "Cd", i, {1,0,0}, "set");
        }
    }
    pcclose(handle);
}
setpointattrib(0, "already_taken", 0, temp_array, "set");

 

这整段代码是用来确定被找到的分支闪电的目标点的唯一性,其原理是来自于SOPsolve这个节点在DOP网络里粒子系统中的循环,我不知道该怎么来形容,不过就是这么个意思,关于这种分支闪电是非常高级的应用技巧了,在实际镜头运用中我想不到除了这个效果,其他有什么作用,关于这种效果可以直接拿过来使用,唯一要改的地方只有bool_C和bool_D两个条件设置的参数,用来确定闪电分支的多少

[展开全文]

int temp_array[] = point(0, "already_taken", 0);

for(int i = 0; i < npoints(0); i++){
    
    vector current_P = point(0, "P", i);
    int current_id = point(0, "id", i);
    int branch_count_temp = point(0, "branch_count", i);
    int branch_time_temp = point(0, "branch_time", i);
    
    if(branch_count_temp > -1 && branch_count_temp <= branch_time_temp){
        branch_count_temp += 1;
        setpointattrib(0, "branch_count", i, branch_count_temp, "set");
    }else if(branch_count_temp > -1 && branch_count_temp > branch_time_temp){
        branch_count_temp = -1;
        setpointattrib(0, "branch_count", i, branch_count_temp, "set");
        
        setpointattrib(0, "Cd", i, {0,0,0}, "set");
    }
    
    
    float seach_radius = chf("seach_radius");
    int max_points = chi("max_points");
    
    int found_ptnum;
    
    float id_seed = rand(current_P * @Frame);
    
    int handle = pcopen(1, "P", current_P, seach_radius, max_points);
    while(pciterate(handle)){
        pcimport(handle, "point.number", found_ptnum);
        
        int bool_A = branch_count_temp == -1 ? 1 : 0;
        int array_check = find(temp_array, found_ptnum);
        int bool_B = array_check < 0 ? 1 : 0;
        int bool_C = id_seed > chf("id_shreshold") ? 1 : 0;
        int bool_D = rand(found_ptnum + 343 * @Frame) > chf("found_ptnum_threshold") ? 1 : 0;
        
        if(bool_A && bool_B && bool_C && bool_D){
            setpointattrib(0, "branch_point", i, found_ptnum, "set");
            setpointattrib(0, "branch_count", i, branch_count_temp + 1, "set");
            
            append(temp_array, found_ptnum);
            temp_array = sort(temp_array);
            
            setpointattrib(0, "Cd", i, {1,0,0}, "set");
            
            break;
        }
    }
    pcclose(handle);
}
setpointattrib(0, "already_taken", 0, temp_array, "set");

 

我终于弄明白了这些VEX代码的意思,并且简单的进行了一个排序,让它看起来更加符合逻辑,整个学下来,这么说吧,这套VEX代码是专门为这种分支闪电开发出来的,我实在是想不到在别的什么地方这一套代码有什么样的应用,而且,其实可以完全手动的进行添加分支线,具体方法是这样的,通过blast节点选出想要进行分支的点,然后把这个点投射到目标物体上,把两个点进行连接,重采样,进行扰乱,形成闪电的形状,甚至可以再添加polywire形成一个mesh,mesh的粗细可以通过重采样节点的curveu属性进行控制(因为有些公司不使用houdini来渲染,需要导出一个polygon给到材质组渲染,如果只是在houdini里面渲染,可以直接渲染线条,添加一个@width属性就可以控制线条的粗细),主体闪电和分支闪电都可以用group节点,使用一个object进行打组,再进行删除key帧,这样就有闪电从无到有再从有到无的一个变化,然后通过trail节点来进行速度计算(或者可以在线条还没有进行扰乱的时候添加一个同一方向的速度),渲染运动模糊,不过闪电一般都是高亮渲染,即使有运动模糊也不是很看得出来,这样级别的运动模糊也完全可以后期加。我说的这种办法虽然操作起来很麻烦,也不够那么程序化,用到的节点会比较多,但是控制更加灵活,无论导演对哪一根闪电有什么样的要求都可以进行修改,而这一套VEX代码想要修改具体的哪一根闪电,实现起来难上加难

[展开全文]

vop中 relative to bounding  box节点, 是让点的位置相对于bounding box的一个值,输出的值是0-1之间

 

做完ramp之后,输出pintoanimation属性,这是个固定写法,如果值为1, 则表示完全定住,跟随target animation, 如果值为0, 则完全不受约束

[展开全文]

之所以把模型放回远点是因为在远点模拟烟雾相对来说会比较方便,也比较准确

 

在这里终于用到破碎刚开始就设置的pscale属性了,老胡的教程真棒,这才是一个整体的制作思路,一开始就要有,从头到尾,每一个环节都要考虑到

[展开全文]

solve节点能够记录并累积上一帧的结果,使用的时候最好还是不要开启cache simulation选项,不然修改了一些地方之后其实是不能够即时的进行调整的,需要退回起始帧才能重新计算被修改的地方

[展开全文]

为什么要做点的插补,因为直接拿这些被找到的红色的点做发射源,这些点运动速度太快,会导致烟雾都是一小截一小截的,烟雾有明显的断开,这个问题可以在DOP里解决,提高解算步幅就行,但是这样会带来非常大的计算量,所以还是在进行DOP模拟之前就给点进行插补

 

求点在上一秒的位置,就是当前的位置减去速度,如果求点在上一帧的位置,就是当前的位置减去速度的二十四分之一,这样生成的点只是出现在上一帧的位置,还要再乘一个0.1到1之间的值,这样才能形成一个正确的点插补

 

int adaptive_points = int(fit(length(v@v), 0, chf("max_v_len"), 0, max_point));设置一个这样的变量是根据点的速度来生成点,这样的话运动的快的点生成的点数就多,运动的慢的点生成的点数就少

 

整数与整数相除,得到的还是一个整数,所以需要强制其中一个数为浮点才会得到正确的零到一的结果

 

addpoint函数的作用是添加点,第一空填要要添加点的对象,第二空如果填位置信息,表示添加的点的位置放在哪里,如果填一个整数信息,比如ptnum,表示根据提供的点序号的点,完全添加一个一模一样的点,这个点具备原来的点的所有属性,如果原来的点有组新点也会在组里

[展开全文]

就我个人的微薄理解,我觉得没必要弄那个solve,让DOP起始帧自动适应,都从第一帧开始解算就行了,反正前面没点的话,解算也不占内存,也耗不了多少时间,后期四个象限的缓存合并的话其实还方便一些

[展开全文]

浮力只和温度有关,没有温度,浮力给多大烟雾都不会向浮力指定的方向运动

[展开全文]

smoke object节点的closed boundaries选项是使流体框边界封闭,使烟雾和流体框边缘有一个碰撞的效果      

 

gas resize fluid dynamic节点的enforce boundraies选项是控制流体框的哪一个边界进不进行自适应

[展开全文]

f@density *= chf("dissipation");这句话的意思是这样的,在每一帧解算之前都让当前DOP网络中的density场乘上一个0.97,这样每一帧的density场都比上一帧少一点点,烟雾也就会随着时间的增长而消散

 

float vel_length = length(v@vel);
if(vel_length > chf("vel_threshold")){
    v@vel *= chf("drag_value");
}
这两句话的意思是,当速度大于某个值的时候,便让速度每一帧都乘一个0.98,速度便会一帧比一帧慢一点,模拟一个拖拽的作用,if语句是很有必要的,不然烟雾一出生速度便会减少

 

float v_len = length(v@v);
vector unit_v = normalize(v@v);
v@v = clamp(v_len, 0, chf("max_v")) * unit_v;速度是一个矢量,有大小,有方向,先把大小和方向分开,就是自己设置的两个变量,然后对速度的大小进行一个clamp,最大不超过自己设置的值,再乘回方向,这就是速度前置(这个是用的pointwrangle节点,我是在houdini17里面做的,属性定好之后直接使用volume rasterize attribute节点进行采样)

[展开全文]

SDF sign distance field 标记的距离场

 

VDB是一个稀疏体积,几何体边界内是负值,几何体边界外是正值,VDB只有在几何体边界才会计算值,远离这个边界就不再计算新值,这样能最大化的进行优化

VDB也是一个primtive,用primtive节点,勾选adjusit vector type writer调整矢量类型写入,勾选writer 16 bit float写入16位浮点,可以减小VDB的缓存

[展开全文]