绒毛/短毛渲染

news2024/12/23 23:03:38

文章目录

  • 多层毛发模型
    • 1
    • 2
    • 3
  • 代码实现
    • cginc
    • shader

refer:
腾讯游戏学堂
主要来自这里,感谢讲解!

  • 首先说一点,很多文章都把毛发和头发弄混,根本就不是一回事好吧,为了区分,我的两篇文章分别用了长毛短毛来区分

多层毛发模型

1

  • 关于这个模型,大部分文章都会引用这张图,图很好,但是原论文中单独的一张图放到这里,完全没有语境,让人看着非常费解,并且和Unity的实现有一点区别;
  • 这里我把原图的注释翻译并且重写了,希望能帮助你理解这个模型:

当然直接一张图是看不明白的,这里我来用通俗的话来讲明白这个多层毛发模型


2

首先,真实世界的毛发是这样的:

  1. 圆柱形,越靠近末端越细,直到尖尖
  2. 透光,越细越透光(不是因为其有间隙才透光,而是毛发本身就是半透明的)
  3. 虽然透光,但是不是完全透光,因此毛发的根部收到的光照更少

也就是如下这样:
在这里插入图片描述

现实中的毛发就是这样,但是我们不可能去真的一根根建模
于是讨巧的模拟毛发的算法便被提出:
纹理很好用,我们想把所有的毛发信息记录在纹理中,但是纹理只是2维的,如何记录三维的毛发信息呢?
不卖关子,做法就是用很多层纹理叠起来,把毛发分段记录下来:
在这里插入图片描述

  • 只不过这毛发不是实体的,而是分层的面片,侧面看会露馅,不过好解决,只要层数足够多,就可以掩盖这一点
    在这里插入图片描述

  • 将没有毛发的空袭部分alpha值记为0,就像是用透明度这个工具一点点雕刻,这样便可以雕刻出毛皮的样子

  • 至于具体怎么去雕刻毛发,去看下面的代码实现

  • 另外前面还有说到毛发是会透光的,那么我们在渲染的时候就可以修改其透明度,越靠近末梢越透明

  • 我还说到毛发根部受到的光更少,这个现象我们用环境光遮蔽来实现



3

这样一来,便将渲染毛发这种听起来不可思议的事情,转化成了渲染一堆叠起来的纹理了,这听起来就实际多了,不过这仍然会消耗大量的算力

尽管开销很大,但这也是当前渲染毛发最好的方法了(实时),别的开销更大
感兴趣可以看一下这位UP的案例,个人觉得超棒

因为开销很大,所有在性能调优方面就极为重要

  • 王者荣耀中妲己的角色展示界面,其尾巴就是用的这个模型,而其在绝大部分移动平台上都可以流程运行,效果还很棒,说明优化很到位
  • 如果每一层都调用一个pass去绘制的话太浪费了,应该使用实例化(GPU instance)来减少调用,实例化可以看learnopengl,讲的很好
  • 通过改变毛发的形状,可以在较少的层级下接近更多层级的效果:
  • 环境光遮蔽,添加与否的差别,个人觉得差距还挺大的

                 


代码实现

先放出本代码的最终效果

微观细节:

  • 可以看到是一片一片的组成的毛发
    在这里插入图片描述

一点补充说明:

  • 我用了很笨的方法,一个Pass挤出一层,所以我调用了非常多的Pass,性能开销很大,应该用实例化,但是还没学到,学到了来补
  • 由于大量的重复Pass,因此将着色器的主体写在了cginc中,调用即可
  • 另外上图是没有进行任何光照运算的结果(除了AO),本人在尝试用phong进行光照时,效果非常奇怪,推测是多层透明的影响,毕竟本文是讲毛发的,光照不再讨论了,这部分我以后再尝试不同的光照模型(主要试试各向异性的模型,如kajiya)




cginc

就不逐行写注释了,主要的要点如下:

  • v2f vert_fur(appdata v, float layer_offset)
    注意这个,cginc中的vert和frag可以传入参数,这也就是毛发模型能够在每一层细微调整的关键(每个pass的参数不一样,直接properties导入太多太复杂)
  • alpha = step(layer_offset, alpha);
    这个是雕刻毛发的关键,step函数是:step(a,x); x<a 返回0 x>=0 返回1;而step中的alpha是从噪声图中读取的
  • alpha *= (1-layer_offset);
    逐层进行透明度衰减
  • col.xyz *= pow(layer_offset, _AO ); 进行环境光遮蔽,AO具体可以看我的这篇文章(还没写,新坑)
#ifndef FUR_INCLUDE
#define FUR_INCLUDE

#include "UnityCG.cginc"
#include "Lighting.cginc"

struct appdata
{
    float3 normal : NORMAL;
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

struct v2f
{
    float2 uv : TEXCOORD0;
    float2 uv_layer : TEXCOORD1;
    float4 vertex : SV_POSITION;
};

float _Length;
sampler2D _MainTex;
sampler2D _LayerMap;
float4 _MainTex_ST;
float4 _LayerMap_ST;
float _AO;

v2f vert_fur(appdata v, float layer_offset)
{
    v2f o;
    v.vertex.xyz += v.normal * _Length * layer_offset;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.uv_layer = TRANSFORM_TEX(v.uv, _LayerMap);

    return o;
}

fixed4 frag_fur(v2f i, float layer_offset) 
{
    float alpha = tex2D(_LayerMap, i.uv_layer).r;//读取layer纹理
    
    alpha = step(layer_offset, alpha); //雕刻毛发
    alpha *= 1-layer_offset; //透明度衰减计算
    fixed4 col = fixed4(tex2D(_MainTex, i.uv).rgb, alpha);//应用上述得到的透明度
    
    col.xyz *= pow(layer_offset, _AO ); //AO计算

    return col;
}

#endif




shader

Shader "Unlit/fur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _LayerMap ("Layer map", 2D) = "white"{}
        _Length ("fur length", range(0,1)) = 0.5
        _AO ("AO", range(0,1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha


        Cull off



        Pass{
            CGPROGRAM
            #pragma vertex vert0
            #pragma fragment frag0
            #include "layers.cginc"

            v2f vert0(appdata v){return vert_fur(v,0);}
            fixed4 frag0(v2f i):SV_TARGET{return frag_fur(i,0);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert1
            #pragma fragment frag1
            #include "layers.cginc"

            v2f vert1(appdata v){return vert_fur(v,0.01);}
            fixed4 frag1(v2f i):SV_TARGET{return frag_fur(i,0.01);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert2
            #pragma fragment frag2
            #include "layers.cginc"

            v2f vert2(appdata v){return vert_fur(v,0.02);}
            fixed4 frag2(v2f i):SV_TARGET{return frag_fur(i,0.02);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert3
            #pragma fragment frag3
            #include "layers.cginc"

            v2f vert3(appdata v){return vert_fur(v,0.03);}
            fixed4 frag3(v2f i):SV_TARGET{return frag_fur(i,0.03);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert4
            #pragma fragment frag4
            #include "layers.cginc"

            v2f vert4(appdata v){return vert_fur(v,0.04);}
            fixed4 frag4(v2f i):SV_TARGET{return frag_fur(i,0.04);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert5
            #pragma fragment frag5
            #include "layers.cginc"

            v2f vert5(appdata v){return vert_fur(v,0.05);}
            fixed4 frag5(v2f i):SV_TARGET{return frag_fur(i,0.05);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert6
            #pragma fragment frag6
            #include "layers.cginc"

            v2f vert6(appdata v){return vert_fur(v,0.06);}
            fixed4 frag6(v2f i):SV_TARGET{return frag_fur(i,0.06);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert7
            #pragma fragment frag7
            #include "layers.cginc"

            v2f vert7(appdata v){return vert_fur(v,0.07);}
            fixed4 frag7(v2f i):SV_TARGET{return frag_fur(i,0.07);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert8
            #pragma fragment frag8
            #include "layers.cginc"

            v2f vert8(appdata v){return vert_fur(v,0.08);}
            fixed4 frag8(v2f i):SV_TARGET{return frag_fur(i,0.08);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert9
            #pragma fragment frag9
            #include "layers.cginc"

            v2f vert9(appdata v){return vert_fur(v,0.09);}
            fixed4 frag9(v2f i):SV_TARGET{return frag_fur(i,0.09);}

            ENDCG
        }
       Pass{
            CGPROGRAM
            #pragma vertex vert10
            #pragma fragment frag10
            #include "layers.cginc"

            v2f vert10(appdata v){return vert_fur(v,0.10);}
            fixed4 frag10(v2f i):SV_TARGET{return frag_fur(i,0.1);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert11
            #pragma fragment frag11
            #include "layers.cginc"

            v2f vert11(appdata v){return vert_fur(v,0.11);}
            fixed4 frag11(v2f i):SV_TARGET{return frag_fur(i,0.11);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert12
            #pragma fragment frag12
            #include "layers.cginc"

            v2f vert12(appdata v){return vert_fur(v,0.12);}
            fixed4 frag12(v2f i):SV_TARGET{return frag_fur(i,0.12);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert13
            #pragma fragment frag13
            #include "layers.cginc"

            v2f vert13(appdata v){return vert_fur(v,0.13);}
            fixed4 frag13(v2f i):SV_TARGET{return frag_fur(i,0.13);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert14
            #pragma fragment frag14
            #include "layers.cginc"

            v2f vert14(appdata v){return vert_fur(v,0.14);}
            fixed4 frag14(v2f i):SV_TARGET{return frag_fur(i,0.14);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert15
            #pragma fragment frag15
            #include "layers.cginc"

            v2f vert15(appdata v){return vert_fur(v,0.15);}
            fixed4 frag15(v2f i):SV_TARGET{return frag_fur(i,0.15);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert16
            #pragma fragment frag16
            #include "layers.cginc"

            v2f vert16(appdata v){return vert_fur(v,0.16);}
            fixed4 frag16(v2f i):SV_TARGET{return frag_fur(i,0.16);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert17
            #pragma fragment frag17
            #include "layers.cginc"

            v2f vert17(appdata v){return vert_fur(v,0.17);}
            fixed4 frag17(v2f i):SV_TARGET{return frag_fur(i,0.17);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert18
            #pragma fragment frag18
            #include "layers.cginc"

            v2f vert18(appdata v){return vert_fur(v,0.18);}
            fixed4 frag18(v2f i):SV_TARGET{return frag_fur(i,0.18);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert19
            #pragma fragment frag19
            #include "layers.cginc"

            v2f vert19(appdata v){return vert_fur(v,0.19);}
            fixed4 frag19(v2f i):SV_TARGET{return frag_fur(i,0.19);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert20
            #pragma fragment frag20
            #include "layers.cginc"

            v2f vert20(appdata v){return vert_fur(v,0.20);}
            fixed4 frag20(v2f i):SV_TARGET{return frag_fur(i,0.20);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert21
            #pragma fragment frag21
            #include "layers.cginc"

            v2f vert21(appdata v){return vert_fur(v,0.21);}
            fixed4 frag21(v2f i):SV_TARGET{return frag_fur(i,0.21);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert22
            #pragma fragment frag22
            #include "layers.cginc"

            v2f vert22(appdata v){return vert_fur(v,0.22);}
            fixed4 frag22(v2f i):SV_TARGET{return frag_fur(i,0.22);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert23
            #pragma fragment frag23
            #include "layers.cginc"

            v2f vert23(appdata v){return vert_fur(v,0.23);}
            fixed4 frag23(v2f i):SV_TARGET{return frag_fur(i,0.23);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert24
            #pragma fragment frag24
            #include "layers.cginc"

            v2f vert24(appdata v){return vert_fur(v,0.24);}
            fixed4 frag24(v2f i):SV_TARGET{return frag_fur(i,0.24);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert25
            #pragma fragment frag25
            #include "layers.cginc"

            v2f vert25(appdata v){return vert_fur(v,0.25);}
            fixed4 frag25(v2f i):SV_TARGET{return frag_fur(i,0.25);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert26
            #pragma fragment frag26
            #include "layers.cginc"

            v2f vert26(appdata v){return vert_fur(v,0.26);}
            fixed4 frag26(v2f i):SV_TARGET{return frag_fur(i,0.26);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert27
            #pragma fragment frag27
            #include "layers.cginc"

            v2f vert27(appdata v){return vert_fur(v,0.27);}
            fixed4 frag27(v2f i):SV_TARGET{return frag_fur(i,0.27);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert28
            #pragma fragment frag28
            #include "layers.cginc"

            v2f vert28(appdata v){return vert_fur(v,0.28);}
            fixed4 frag28(v2f i):SV_TARGET{return frag_fur(i,0.28);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert29
            #pragma fragment frag29
            #include "layers.cginc"

            v2f vert29(appdata v){return vert_fur(v,0.29);}
            fixed4 frag29(v2f i):SV_TARGET{return frag_fur(i,0.29);}

            ENDCG
        }
        Pass{
            CGPROGRAM
            #pragma vertex vert30
            #pragma fragment frag30
            #include "layers.cginc"

            v2f vert30(appdata v){return vert_fur(v,0.3);}
            fixed4 frag30(v2f i):SV_TARGET{return frag_fur(i,0.3);}

            ENDCG
        }
    }

}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/443106.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数值分析(四) Hermite(埃尔米特)插值法及matlab代码

目录 前言一、Hermite插值1. Hermite定理2. 重节点差商3. 重节点Newton插值4. Hermite 插值公式4.1 三点三次 Hermite插值4.2 两点三次 Hermite插值4.3 2 n 1 2n1 2n1次Hermite插值多项式 二、Hermite插值算法及matlab代码1. 2 n 1 2n1 2n1次Hermite插值matlab代码实现2. 例…

2.1寸黑白TFT电子标签【基站版】

ESL_TFT_2.1_V4 产品参数 产品型号 ESL_TFT_2.1_V4 尺寸(mm) 69.5*35*14.8mm 显示技术 TFT 反射式 显示区域(mm) 23.71(H) * 48.55(V) 分辨率(像素) 250*122 像素尺寸(mm) 0.194*0.194 显示颜色 黑/白 视觉角度 45/45/60/60 工作温度 -20-70℃ 产品重量 …

Mysql第一章 字符集

字符集修改 一 在mysql5.7中输入汉字报错二 修改针对修改后创建的数据库&#xff0c;过去创建的数据库不算三 想要单独修改四 基本的mysql命令五 各级别的字符集 一 在mysql5.7中输入汉字报错 # 在5,7中&#xff0c;默认字符集为latin1,开发人员在数据库设计时&#xff0c;需要…

Python用curve_fit进行多元拟合

文章目录 入门参数多元拟合 入门 scipy.optimize中&#xff0c;curve_fit函数可调用非线性最小二乘法进行函数拟合&#xff0c;例如&#xff0c;现在有一个高斯函数想要被拟合 y a exp ⁡ − ( x − b c ) 2 y a\exp-(\frac{x-b}{c})^2 yaexp−(cx−b​)2 则调用方法如下 …

Zookeeper源码分析——Follower和Leader状态同步源码

Follower和Leader状态同步源码 当选举结束后&#xff0c;每个节点都需要根据自己的角色更新自己的状态。选举出的Leader更新自己状态为 Leader&#xff0c;其他节点更新自己状态为 Follower Leader更新状态入口&#xff1a; leader.lead() Follower更新状态入口&#xff1a; f…

VUE3 学习笔记(九)使用富文本编辑器tinymce最新版

目录 1、安装相关依赖 2、下载中文包 3. 引入皮肤和汉化包 4. 封装组件:在src/components下新建TEditor.vue&#xff0c;并写入以下代码 5. 注册及使用组件 6. Tinymce 版本&#xff08;截至2023-04-18&#xff09; 1、安装相关依赖 npm install tinymce -S npm install ti…

python+vue小型公司人事企业员工培训报名管理系统

该系统主要实现了公告信息管理、个人考勤管理、培训信息管理、员工管理、员工工资管理等主要模块功能。具体功能如下所示&#xff1a; 1. 公告信息管理&#xff1a;查看公告标题、公告类型、公告图片、发布日期等一系列信息。 2. 个人考勤管理&#xff1a;查看考勤名称、员工工…

Springboot整合Quartz定时任务框架(Spring解决方案)

目录 前言 介绍 集成 POM依赖 基础配置 1、配置数据源 2、配置JOB实例与触发器 3、配置SchedulerJobFactory 4、配置SchedulerFactoryBean 业务集成 job编写 接口编写 接口实现 前言 系统现在有定时任务触发业务场景的需求&#xff0c;并且频率及次数不固定&…

【案例教程】FVCOM流域、海洋水环境数值模拟方法及实践技术应用

近年来&#xff0c;随着人类活动产生营养负荷的增加&#xff0c;流域、海洋生态系统面临严重威胁。近岸水质数值模是近岸水环境保护的有效工具&#xff0c;已经应用于近岸水环境污染控制、水质规划管理中。FVCOM在近岸水环境模拟方面具有一定优势&#xff0c;如采用非结构化三角…

王道计组(23版)2_数据的表示和运算

1.数制和编码 十进制转换为二进制&#xff1a; 原码&#xff1a; [0]原0,0000 [-0]原1,0000 -1无法表示 补码&#xff1a; 按位取反&#xff0c;末位加1 [0.0000]补[-0.0000]补0.00000 反码&#xff1a; 按位取反 [0]反0,0000 [-0]反1,1111 移码&#xff1a; 与补码仅符号位…

从前端角度快速理解Transformer

从前端角度快速理解Transformer Transformer的三步曲从前端角度&#xff08;SEO和TDK&#xff09;理解TransformerSEO与TDK一个例子来理解 总结 声明&#xff1a;本文为原创&#xff0c;未经同意请勿转载或爬取&#xff0c;感谢配合&#x1f604; chatGPT今年年初的时候是非常火…

操作系统实验一 并发程序设计

1.实验目的 掌握Linux环境下&#xff0c;多进程之间并发程序设计方法&#xff0c;并通过程序的运行结果来验证分时系统和并发程序设计的优越性。 2.实验要求 熟悉Linux操作系统子进程创建方法以及任务执行时间测量方法 3.实验内容 在单进程&#xff08;单用户、单任务&#xff…

【MYSQL索引失效的场景有哪些】

创建一张表&#xff1a;id为主键&#xff08;primary key&#xff09;name为普通建&#xff08;index&#xff09; 插入数据&#xff1a; 用主键索引查询&#xff1a; 用普通建索引查询 对于执行计划&#xff0c;参数有&#xff1a; possible_keys 字段表示可能用到的索引&am…

KMM初探与编译过程详解

本文字数&#xff1a;22817字 预计阅读时间&#xff1a;58分钟 简介 KMM&#xff0c; 即Kotlin Multiplatform Mobile&#xff0c;是由Kotlin发布的移动端跨平台框架。相比于其他跨平台框架&#xff0c;KMM是原生UI逻辑共享的理念&#xff0c;共享重复逻辑性的工作来提升开发效…

Maven配置国内源以及jar下载失败处理详解

目录 1&#xff0c;配置Idea的Maven xml文件不存在&#xff1a; xml文件存在&#xff1a; 2&#xff0c;重新下载jar包 3&#xff0c;注意事项总结 1&#xff0c;配置Idea的Maven 需要配置的项目有两个&#xff0c;一个是当前项目&#xff0c;一个是新项目&#xff1a; 打…

抖音账号矩阵搭建管理获客系统

抖音矩阵号管理系统是一款企业矩阵运营管理工具&#xff0c;能够有效地帮助企业管理多个矩阵账号&#xff0c;并实现批量管理。在短视频矩阵系统中&#xff0c;自动获客工具和智能AI的帮助下&#xff0c;一个人也能轻松地管理多个账号。 一、矩阵账号管理&#xff1a; 首先&a…

leetCode算法第三天

继续练习leetcode中关于字符串的算法题&#xff0c;越练越觉得自己编码思想还很欠缺&#xff0c;继续努力。 文章目录 有效的括号括号生成串联所有单词的子串最长有效括号 有效的括号 leetcode链接&#xff1a;https://leetcode.cn/problems/valid-parentheses/ 解题思路&…

SPI协议

SPI数据接口 SPI&#xff08;Serial Peripheral Interface&#xff09;串行外设接口的简称&#xff0c;它是一种同步全双工通信协议。有 3根或者 4根数据线组成&#xff0c;包括 CLK、SOMI、SIMO、STE&#xff1a; CLK为时钟线&#xff0c;由主机控制输出。 SOMI…

国产数字温度传感芯片M117 Pin to Pin替代PT100和PT1000

高精度数字温度传感芯片 - M117&#xff0c;可Pin to Pin替代PT100/PT1000&#xff0c;且具功能差异化优势&#xff0c;支持行业应用的定制化需求。高测温精度0.1℃&#xff0c;用户无需进行校准。芯片感温原理基于CMOS半导体PN节温度与带隙电压的特性关系&#xff0c;经过小信…

电脑开机进不了系统卡在加载界面怎么办?

电脑开机进不了系统卡在加载界面怎么办&#xff1f;有用户电脑弹出需要进行系统更新&#xff0c;不小心点到了系统更新的选项。因为自己不想进行系统更新&#xff0c;所以马上将电脑关机了。但是关机之后却发现系统一直卡在开机的界面中&#xff0c;无法进入桌面中了。那么这个…