unity-shader-头发渲染-各向异性_unity各向異性shader-程序员宅基地

技术标签: unity  shader  Unity3d  Unity3D-Shader  头发  各向异性  Unity3D .  


title: unity-shader-头发渲染-各向异性
categories: Unity3d-Shader
tags: [unity, shader, 各向异性, 头发]
date: 2019-04-03 19:45:06
comments: false

unity-shader-头发渲染-各向异性


前篇

相关参考文章


效果

丢在模型上的效果, 头发的uv有部分展开的不好, 有断层.
这是从mmd吧获取并提取出来的模型. ( 提取方法: art-记一次提取mmd模型到max和unity )

建了个模拟头发的模型, 展开的uv根据 高光扰动图的方向 ( 我也知道很假 (:逃 )

丢在 unity 中的效果

再来一波, 头发的uv展开基本都这样


各向异性高光

Kajiya-Kay Shading

( 直接截图 Thorsten Scheuermann 的 PPT 讲解 )

这个 T 参数是指 发梢 到 发根 的方向 , 也就是 副切线 的方向, 而顶点中的 tangent 是指 垂直于 发梢 到 发根 方向 的向量, 所有求 T 的方法是 用这个 tangentnormal 叉乘得到

// vert
//求出沿着发梢到发根方向的切线
half4 p_tangent = mul(unity_ObjectToWorld, v.tangent);
o.tangent = normalize(p_tangent).xyz;
o.tangent = cross(o.tangent, o.worldNormal);

如图所示

  • 红色 为 法线,
  • 绿色 为 切线,
  • 蓝色 为 副切线 ( 即需要求的参数 T )
高光计算部分

s p e c u l a r = K s 1 ∗  smoothstep  ( − 1 , 0 , d o t ( t 1 , h ) ) ∗ sin ⁡ ( t 1 , h ) p 1 + K s 2 ∗  smoothstep  ( − 1 , 0 , dot ⁡ ( t 2 , h ) ) ∗ sin ⁡ ( t 2 , h ) p 2 specular=K_{s 1} * \text { smoothstep }\left(-1,0, d o t\left(t_{1}, h\right)\right) * \sin \left(t_{1}, h\right)^{p 1}+K_{s 2} * \text { smoothstep }\left(-1,0, \operatorname{dot}\left(t_{2}, h\right)\right) * \sin \left(t_{2}, h\right)^{p_{2}} specular=Ks1 smoothstep (1,0,dot(t1,h))sin(t1,h)p1+Ks2 smoothstep (1,0,dot(t2,h))sin(t2,h)p2

specular = 主高光 + 副高光

float StrandSpecular(float3 T, float3 V, float3 L, float exponent)
{
    float3 halfDir = normalize(L + V);
    float dotTH = dot(T, halfDir);
    float sinTH = max(0.01,sqrt(1 - pow(dotTH, 2)));
    float dirAtten = smoothstep(-1,0, dotTH);
    return dirAtten * pow(sinTH, exponent);
}

t1, t2 是指 副切线 的偏移

float3 ShiftTangent(float3 T, float3 N, float shift)
{
    float3 shiftedT = T + (shift * N);
    return normalize(shiftedT);
}

头发透明渲染深度问题

因为透明渲染是不写入深度缓存, 可以增加多一个pass只写如深度, 不输出颜色

Pass
{
    ZWrite On //写入深度,被遮挡的像素将不能通过深度测试
    ColorMask 0 //不输出颜色
}

shader

材质球相关参数.
LightMapMask 暂时没用. ( 也就是AO ), 副高光也没用到 ( FuHair… )

简单粗暴贴代码

Shader "test/NPR07_hair"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color("Color",Color) = (1,1,1,1)

        _Ramp ("Ramp", 2D) = "white" {}
        _Bump ("Normal", 2D) = "white" {}
        _HairLightRamp ("HairLightRamp", 2D) = "white" {}
        _LightMapMask ("LightMapMask", 2D) = "white" {}

        [Space(10)][Header(xxxxxxxxxxxxxxxx)]
        _Specular("Specular",Color) = (1,1,1,1)
        _SpecularScale("SpecularScale", Range(0, 5)) = 1

        [Space(10)][Header(xxxxxxxxxxxxxxxx)]
        _MainSpecularSmooth("MainHairSpecularSmooth", Range(-10, 100)) = 1
        _FuSpecularSmooth("FuHairSpecularSmooth", Range(-10, 100)) = 1
        _MainSpecularOff("MainHairSpecularOff", Range(-10, 10)) = 1
        _FuSpecularOff("FuHairSpecularOff", Range(-10, 10)) = 1

        [Space(10)][Header(xxxxxxxxxxxxxxxx)]
        _RimPower("RimPower", Range(0.2, 10)) = 1
    }
    
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            Tags { "LightMode"="ForwardBase" }
            
            Cull off
            
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #pragma multi_compile_fwdbase
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            #include "UnityShaderVariables.cginc"
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _Bump;
            float4 _Bump_ST;

            sampler2D _Ramp;

            fixed4 _Specular;
            fixed _SpecularScale;

            fixed _MainSpecularSmooth;
            fixed _FuSpecularSmooth;
            float _MainSpecularOff;
            float _FuSpecularOff;

            sampler2D _HairLightRamp;
            float4 _HairLightRamp_ST;

            float _RimPower;

            sampler2D _LightMapMask;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
                float4 tangent : TANGENT;
            }; 
            
            struct v2f {
                float4 pos : POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                
                SHADOW_COORDS(3)
                float3 tangent : TEXCOORD4;
                float2 hairLightUV:TEXCOORD5;
                float2 uv_Bump : TEXCOORD6;
                float3 normal : TEXCOORD7;
            };
            
            v2f vert (a2v v) {
                v2f o;
                
                o.pos = UnityObjectToClipPos( v.vertex);
                o.normal = v.normal;
                o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
                o.hairLightUV = TRANSFORM_TEX(v.texcoord, _HairLightRamp);
                o.uv_Bump = TRANSFORM_TEX(v.texcoord, _Bump);
                o.worldNormal  = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                TRANSFER_SHADOW(o);

                //求出沿着发梢到发根方向的切线
                half4 p_tangent = mul(unity_ObjectToWorld, v.tangent);
                o.tangent = normalize(p_tangent).xyz;
                o.tangent = cross(o.tangent, o.worldNormal);
                return o;
            }
            
            float3 ShiftTangent(float3 T, float3 N, float shift)
            {
                float3 shiftedT = T + (shift * N);
                return normalize(shiftedT);
            }

            float StrandSpecular(float3 T, float3 V, float3 L, float exponent)
            {
                float3 halfDir = normalize(L + V);
                float dotTH = dot(T, halfDir);
                float sinTH = max(0.01,sqrt(1 - pow(dotTH, 2)));
                float dirAtten = smoothstep(-1,0, dotTH);
                return dirAtten * pow(sinTH, exponent);
            }
            
            float3 LightMapColor(fixed3 worldLightDir,fixed3 worldNormalDir, fixed2 uv)
            {
                float LdotN = max(0, dot(worldLightDir, worldNormalDir));
                float3 lightColor = LdotN * tex2D(_LightMapMask, uv);
                return lightColor;
            }
            
            float4 frag(v2f i) : SV_Target { 
                fixed3 worldNormal = normalize(i.worldNormal);
                // fixed3 tangentNormal = UnpackNormal(tex2D(_Bump, i.uv_Bump)); // 暂时没用 法线图
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);
                
                //漫反射贴图采样
                fixed4 c = tex2D (_MainTex, i.uv);
                fixed3 albedo = c.rgb * _Color.rgb;
                
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //阴影值计算
                
                fixed diff =  dot(worldNormal, worldLightDir); //世界空间的法线坐标和光照方向点乘得到漫反射颜色
                diff = (diff * 0.5 + 0.5) * atten; //暗部提亮  当然这里也可以不提亮
                
                //将光线颜色和环境光颜色以及梯度图采样相乘得到最终的漫反射颜色
                fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;
                
                //头发高光图采样, 使用一张扰动图沿着物体切线方向扰动法线来达到效果
                float3 speTex = tex2D(_HairLightRamp, i.hairLightUV);
                //头发 主高光 偏移				
                float3 Ts = ShiftTangent(i.tangent, worldNormal, _MainSpecularOff * speTex);
                //头发 副高光 偏移
                float3 Tf = ShiftTangent(i.tangent, worldNormal, _FuSpecularOff * speTex);
                
                //头发 主副 高光值
                float specMain = StrandSpecular(Ts, worldViewDir, worldLightDir, _MainSpecularSmooth);
                float specFu = StrandSpecular(Tf, worldViewDir, worldLightDir, _FuSpecularSmooth);
                
                float specFinal = specMain;
                // 这里可以添加一个副高光
                // float specMask = tex2D(tSpecMask, uv); 
                // specFinal +=  specMask * specFu;
                // specFinal += specFu;

                specFinal *= _SpecularScale ;
                
                fixed3 specular = _Specular.rgb * specFinal * atten;
                
                //rim light term
                half rim = 1.0 - saturate(dot(worldViewDir, worldNormal));
                rim = pow(rim, _RimPower);
                
                fixed3 ao = LightMapColor(worldLightDir, worldNormal,i.uv).rgb;
                
                return fixed4((ambient + diffuse + specular + rim) /* * ao */, 1.0 );
            }
            
            ENDCG
        }
    }
    FallBack "Diffuse"
}

Hair Shader 1.0.4 插件

  • 效果图

  • uv展开

  • 各种贴图

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yangxuan0261/article/details/89027809

智能推荐

1100多份全球各地的数据汇总,这是我目前看到的最全的数据资源_数据资源清单-程序员宅基地

文章浏览阅读380次。前两天一朋友给我发了一个excel文件,说是别人整理的数据资源。打开以后一看,内容太全了,是我目前看到的最全的一份数据资源列表。独乐乐不如众乐乐,分享出来让大家都受益一下吧。先给大家大致看看内容。整个Excel表按照按别,创建了10多个sheet页,每个页面一个数据类别,分别包括Global、Regional、 Country 、Thematic、 Event Disaster、 Type Disaster List、 Conflict 、Imagery、 Physical、 Conserv_数据资源清单

calloc和malloc区别、分析_malloc与calloc相同-程序员宅基地

文章浏览阅读780次。函数malloc()和calloc()都可以用来动态分配内存空间,但两者稍有区别 malloc()函数有一个参数,即要分配的内存空间的大小:void *malloc(size_t size); calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。void *calloc(_malloc与calloc相同

每天工作计划_算法工作计划-程序员宅基地

文章浏览阅读251次。如果坚持每天刷题3道,就离1200 codeforce不远了这周主要看:数据结构与算法 c++描述这本书_算法工作计划

Python 中的时间处理包datetime和arrow_python datetime的时间戳和arrow的时间戳不一样-程序员宅基地

文章浏览阅读1.8w次,点赞2次,收藏5次。Python 中的时间处理包datetime和arrow在获取贝壳分的时候用到了时间处理函数,想要获取上个月时间包括年、月、日等# 方法一:today = datetime.date.today() # 1. 获取「今天」first = today.replace(day=1) # 2. 获取当前月的第一天last_month = first - datetime.ti..._python datetime的时间戳和arrow的时间戳不一样

求字符串可匹配的最大长度_最长连续字符匹配-程序员宅基地

文章浏览阅读4.1k次。如:text=“abcdlijkfgd”,query=“abcdefg”,最大匹配为“abcd”_最长连续字符匹配

机器学习 Machine Learning- 吴恩达Andrew Ng 第11~15课总结-程序员宅基地

文章浏览阅读200次。YouTube课程地址https://www.youtube.com/playlist?list=PLOXON7BTL9IW7Ggbc09jLqGmzkwPI4-3V截止2021-5-3, 有112 个视频1. 第11课 Gradient descent intuition, Linear regression with one variableGradient descent intuition 阶梯下降算法介绍:a 表示 learning rate, 学习下降的速度右边的函数 deriv

随便推点

音频播放模块在短视频开发中的方案解决及所需开发环境说明_小视频模块开发方案-程序员宅基地

文章浏览阅读569次。在短视频开发中,音频媒体播放模块是仅次于视频媒体播放的另一大模块,这段源码就是用于解决音频媒体播放问题的方案。音频媒体播放前,主播需用手机等直播设备对音频媒体进行采集,向文件中添加流式信息,通过适当的压缩算法进行压缩,推流至流媒体服务器储存,直到用户点击音频播放的瞬间完成拉流、解码才能进行播放。public void startPlay(ChatMessageBean bean, Stri..._小视频模块开发方案

虚幻UE4中Matinee基础:骨架网格教程_ue4骨骼网格体-程序员宅基地

文章浏览阅读9.1k次,点赞4次,收藏15次。本教程的目标是教你在如何在Matinee中使用骨架网格的一些基础知识。 您将学习如何连接和操纵骨架网格并使骨架网格物体播放动画。_ue4骨骼网格体

linux密码破解与开机加密_lunnx password-程序员宅基地

文章浏览阅读901次。破解登录密码 GRUB:在引导装载程序菜单上,键入[e] 来进入编辑模式。你会面对一个引导项目列表。查找其中类似以下输出的句行: kernel /vmlinuz-2.4.18-0.4 ro root=/dev/hda2按箭头键直到这一行被突出显示,然后按 [e]。你现在可在文本结尾处空一格再添加 single 来告诉 GRUB 引导单用户 Linux 模式。按 [Enter] _lunnx password

[转]Socket send函数和recv函数详解_sendrecvgprs-程序员宅基地

文章浏览阅读760次。转载地址:http://hi.baidu.com/webeta/blog/item/4c04d109fdf21881d1581be8.html 引文:最近在wince5.0的设备上开发网络通信,因socket的send和recv的时间太长,而客户端不需要服务端的响应数据因此突发奇想,是否可以不用调用recv函数而直接去做其他的事情.刚开始使用的时候还成,速度倒是确实有提升.可最近用到一个操作,send函数后立刻就把GPRS网络断开了,服务器端一直无法收到客户端发送的数据,百思不得其解,google到一篇文章_sendrecvgprs

第七章 数组实验-程序员宅基地

文章浏览阅读973次。C语言设计实验报告试验项目:1、一维数组的运用2、二维数组的应用3、字符数组的应用姓名:李小玲  实验地点:514教室  实验时间:2019.5.29一、实验目的与要求7.3.1一维数组的运用实验1:写一个函数,对用随机函数产生的10个整数按从小到大的顺序排序(升序,用冒泡排序实现)1.定义一个一维数组,其大小为10,即它能存放10个数据。2..._c语言数组的应用实验

MyBatis-Configuration-程序员宅基地

文章浏览阅读58次。一、引用properties 配置文件db.propertiesdriver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://192.168.8.136:3306/mybatisusername=rootpassword=rootmybatis-config.xml<!-- 外部 properties 配置文件 -..._mybatis整合configuration