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

6个月零基础到入职

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

我觉得使用这个还挺麻烦的,干脆就自己手动的弄一下,其实单只是缓存一个发射源的话也耗不了多少时间

[展开全文]

GROUP    $OS 节点/组关联命名

disslove   删除点,线

 

[展开全文]

1,右键 copy parameter/paste relative references 相对参考;

 

 

[展开全文]

gas blur节点能对指定场里的数据进行模糊,被模糊的数据在smoke状态下显示自然也会被膨胀一圈

 

float vel_len = length(v@vel);
f@scalefield = fit(vel_len, 0, chf("max_vel_len"), 0, 1);这两句话的意思是根据速度的大小来控制scalefield场的值,速度为零的地方scalefield场的值为零,速度超过自己设定的值的地方scalefield场的值为1,中间进行自动插补

然后通过这个scalefield场控制disturbance,这样在烟雾速度大的地方disturbance强,烟雾速度越小disturbance强度越小,烟雾速度为零,disturbance影响也为零

 

gas calculate 节点是一个计算场的节点,destfield指定要计算哪一个场,source field表示要用哪一个场计算destfield指定的场,计算的方式是下面的calculation参数,指定用什么计算方式,现在这里用的是copy的方式,source field里面什么都没有填,这里表示的就是使用一个“无”来替换掉destfield指定的场,这就相当于删去了destfield指定的场

 

[展开全文]

整体的逻辑是这样的,先使用一个gas match filed节点,通过参考vel场,创建一个空的矢量类型的场,里边没有任何数据,取名叫做disturb场,这个disturb场和速度场的size,resolution是相同的,然后使用一个gas disturbance节点生成一些矢量场(disturbance的工作原理是这样的,在流体框中切割成一个一个的小方块,这个小方块的大小由block size这个参数决定,每一个小方块都随机生成不同方向的矢量场,通过disturb field参数把这些矢量场指定写入某一个场,并且影响这个场),写入空的disturb场,最后再使用一个gas field wrangle节点,把速度场和disturb场相加,这样就可以对速度场进行一个扰乱

 

 

其实直接在gas disturbance节点里面填写扰乱速度场就行,为什么要创建disturb场,是因为要节省解算的资源,在gas disturbance节点的黑盒子里进行操作的目的在于,让density值为零的地方不要生成扰乱的矢量场,这样能节省大量的资源进行解算,gas vop里面的操作是这样的,density场作为gas disturbance节点生成的矢量场影响范围的阈值,如果当density场的值满足自己设定的条件,才会执行括号的里的操作,如果当density场的值不满足自己设定的条件,输出的矢量场则是(0, 0, 0)

[展开全文]

gas match field节点的作用是通过参考某一个场而新建一个场,新建的场在size,reslution上与参考的场相匹配,新建的这个场里面是没有数据的。

gas disturbance的工作原理是这样的,在流体框中切割成一个一个的小方块,这个小方块的大小由block size这个参数决定,每一个小方块都随机生成不同方向的矢量场,通过disturb field参数把这些矢量场指定写入某一个场,并且影响这个场,threshold field参数是gas disturbance生成的矢量场影响的阈值,默认就是density场,density场的数值大于cutoff参数指定的值,gas disturbance生成的矢量场便不再影响,反之就会影响

[展开全文]

SDF sign distance field 标记的距离场

 

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

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

[展开全文]

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节点进行采样)

[展开全文]

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

 

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

[展开全文]

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

[展开全文]

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

[展开全文]

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

 

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

 

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

 

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

 

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

[展开全文]

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

[展开全文]

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

 

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

[展开全文]

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

 

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

[展开全文]

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代码想要修改具体的哪一根闪电,实现起来难上加难

[展开全文]


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两个条件设置的参数,用来确定闪电分支的多少

[展开全文]

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;
    
    // 设置一个点云的handle
    int handle = pcopen(1, "P", current_P, seach_radius, max_points);
    
    // 开始进行点云的查找迭代
    while(pciterate(handle)){
        pcimport(handle, "point.number", found_ptnum);
    }
}

    

 

 

 

     if(branch_count_temp > -1 && branch_count_temp <= branch_time_temp){
        branch_count_temp += 1;
        setpointattrib(0, "branch_count", idx, branch_count_temp, "set");
    }else if(branch_count_temp > -1 && branch_count_temp > branch_time_temp){
        branch_count_temp = -1;
        setpointattrib(0, "branch_count", idx, branch_count_temp, "set");
        
        setpointattrib(0, "Cd", idx, {0,0,0}, "set");
    }

这一整段代码的作用是使分支闪电存在的时间大于我们设定的时间范围之后,让分支闪电消失掉,之所以写在点云查找迭代的前面,也是因为SOPsolve这个节点在DOP网络里粒子系统中的循环,没有要改动的地方,唯一要改的可能就是我们自己设定的分支闪电存在的时间

[展开全文]

之所以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[]属性的数值

[展开全文]