【01】Unity URP 卡通渲染 原神角色渲染记录-Diffuse: Ramp ...
点击下面链接,B站上传了实际Game窗口效果,视频有压缩,实际运行效果更好些~系列一共5篇:
【01 | 当前浏览】Unity URP 卡通渲染 原神角色记录-Diffuse: Ramp + AO + Double Shadow
【02】Unity URP 卡通渲染 原神角色记录-Specular: Metal + Non-Metal
【03】Unity URP 卡通渲染 原神角色记录-Function-Based Light and Shadow: Emission + SDF脸部阴影
【04】Unity URP 卡通渲染 原神角色记录-Depth-Based Effect: 7Spaces + 屏幕空间等距深度边缘光Rim Light
【05】Unity URP 卡通渲染 原神角色记录-Double Pass Effect: Render Feature + 平滑法线Outline
1. 贴图种类
漫反射Diffuse分为3类: 头发Diffuse, 脸部diffuse, 身体Body(含Dress)Diffuse。最后会给出3类diffuse的处理方法。
首先,来看看贴图种类:Ramp图,LightMap贴图,Diffuse主色贴图,SDF阴影贴图,脸部阴影范围图,Metal高光控制图(也可用于非Metal的高亮控制)
各种贴图
2. 贴图可视化, 通道图
主要来看看贴图各个通道的样子,正常是不带描边和Rim边缘光的,我懒得注释了,直接带着看不影响。重点关注LightMap,VertexColor,BaseColor的A通道,以及Ramp颜色采样图。
通道图RGBA的具体用途和含义,请看图片下面的解释说明!
BaseColor.a 主贴图A通道, 分离神之眼,头发,眼睛,腮红,耳坠
VertexColor.rgb, 用于调节皮肤和头发描边颜色, 同时用于调节Ramp的感光度
VertexColor.a, 控制描边的粗细,眼睛和嘴不需要描边
Lightmap.a, 用于材质分层,如皮肤,衣服的不同布料类型等等
Lightmap.r, 用于高光类型分层,白色是金属,灰色是非金属,黑色无高光
Lightmap.b, 用于高光的强度分层,材料的roughness,越暗越粗糙,黑色非高光
Lightmap.g, 用于区分AO区域,AO就是常暗区域,有光照也是暗
3. 贴图可视化, Ramp图
宵宫Body Ramp图,10条颜色,下面5条是夜晚,上面5条是白天。lambert系数, -1\rightarrow0 表示在阴影中,对应Ramp图从左到右。关于Lambert系数,一会儿详细说一下。
Body Ramp图,lambert系数对应关系,最左边-1, 最右边0。一般用半lambert采样,颜色更明亮通透。
宵宫Hair Ramp图,4条颜色,下面2条是夜晚,上面2条是白天。分别代表头发1级阴影,2级阴影的颜色。
用于头发1级阴影,2级阴影的颜色
4. 关于Lambert系数
Lambert系数,lambert = N dot L,N是法线normal的方向,L是主光源方向,其中N和L都是单位向量,unit vector.
正常lambert系数的取值范围:-1 \rightarrow1
Lambert系数的用途1:判断光照区间,[-1, 0] 区间表示在阴影中,表示在光照中。
Lambert系数的用途2:通过光照区间,进行Ramp的颜色采样,ramp图uv两个轴,lambert系数可以作为u, 即横坐标,进行采样。作为采样使用时,一般使用半lambert,使得颜色更透亮。HalfLambert = Lambert * 0.5 + 0.5。
5. URP管线,使用HLSL编写Shader,一些准备工作
//主光源
Light mainLight = GetMainLight();
float4 mainLightColor = float4(mainLight.color, 1); //获取主光源颜色
float3 lDir = normalize(mainLight.direction); //主光源方向
//基础色
half4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
half4 vertexColor = IN.vertColor; //顶点色
//通道图
float4 lightMap = SAMPLE_TEXTURE2D(_LightMap, sampler_LightMap, IN.uv);
float4 metalMap = SAMPLE_TEXTURE2D(_MetalMap, sampler_MetalMap, IN.vDirWS.xy * 0.5 + 0.5);
float4 faceShadowMap = SAMPLE_TEXTURE2D(_MetalMap, sampler_MetalMap, IN.uv);
//方向点积
float ndotLRaw = dot(IN.nDirWS, lDir);
float ndotL = max(0.0, ndotLRaw);
float ndotH = max(0, dot(IN.nDirWS, normalize(IN.vDirWS + lDir)));
float ndotV = max(0, dot(IN.nDirWS, IN.vDirWS));6. AO常暗区域的处理
AO = Ambient Occulusion, 通常是发生的原因是,两个物体挨在一起,中间的缝隙部分是黑的。原因是被2个紧挨的物体挡住了,无法受到光照。
那么在Shader中,处理lambert系数时,可以分为感受光照的lambert和采样lambert两大类。为了处理AO的情况,我们把G通道的AO值和lambert相乘,表示受AO印象的lambert.
//lambert系数(光照): 光照面积, 光照面积AO, 平滑光照面积AO
float lambert = ndotL;
half lambertAO = lambert * saturate(lightMap.g * 2);
half lambertRampAO = smoothstep(0, _BodyShadowSmooth, lambertAO);7. 日夜状态的处理
日夜系数,白天是0.5,晚上是0。实际情况为了采样在颜色条中间,我们再额外加0.03让颜色采样不至于卡在边界。
//日夜状态
half dayOrNight = (1 - step(0.1, _InNight)) * 0.5 + 0.03;8. Ramp uv轴,等同于xy轴的处理
前面提过ramp图,用于颜色的采样,我们需要知道xy坐标,也就是uv坐标。
横向坐标x,也就是u坐标,我们用半lambert系数,由于有顶点色的存在,我们使用顶点色来作为u轴的一个偏移量,调整u坐标。
纵向坐标y,也就是v坐标,我们需要考虑日夜情况,以及不同材质类型所对应的颜色。前面在通道图可视化的时候,Lightmap.a是用来区分材质类型的。
这里可以用Lightmap.a的值来做材质分层的映射,不用手动判断材质分层的取值范围。如果想更精准,可以手动控制范围。
但需要注意的是,Lightmap.a其范围是,我们需要进行压缩,把 -> ,之后再加上日夜状态的系数。
//lambert系数(采样): 半lambert采样, 偏移半lambert采样
half halfSampler = saturate(lambertRampAO * 0.5 + 0.5);
half rampOffset = step(0.5, vertexColor.g) == 1 ? vertexColor.g : vertexColor.g - 1;
half adjustedHalfSampler = saturate(halfSampler + rampOffset);9. Hair Double Shadow,头发两级阴影的处理
如果我们注意看宵宫的头发,会发现其实有3个层次的阴影。深色阴影,浅色阴影,以及浅色阴影的平滑部分。
需要注意的是,这3个阴影并没有超出正常的光照范围, 我的思路是利用原始lambert [-1, 0]的区间,分别处理这3级阴影。
深色阴影,浅色阴影,以及浅色阴影的平滑部分
从前面的ramp图展示中,我们发现头发的ramp图里有2个颜色,分别代表了深色阴影,浅色阴影的颜色,那么阴影的处理代码如下:
//1级暗阴影
float vDark = saturate(0.4 + dayOrNight);
float2 uvDark = float2(halfSampler, vDark);
float4 hairShadowD = SAMPLE_TEXTURE2D(_ShadowRampMap, sampler_ShadowRampMap, uvDark);
//2级亮阴影
float vLight = saturate(0.3 + dayOrNight);
float2 uvLight = float2(halfSampler, vLight);
float4 hairShadowL = SAMPLE_TEXTURE2D(_ShadowRampMap, sampler_ShadowRampMap, uvLight);
//计算1级阴影,2级阴影范围
float3 darkShadow = step(ndotLRaw, _HairDarkShadowArea) * hairShadowD;
float lightShadowArea = step(_HairDarkShadowArea, ndotLRaw) - step(_HairShadowArea, ndotLRaw);
float3 lightShadow = lightShadowArea * hairShadowL;
//2级阴影平滑
float lightSmoothArea = smoothstep(_HairShadowArea, _HairShadowSmooth, ndotLRaw);
float3 lightShadowSmooth = _HairSmoothShadowIntensity * lerp(hairShadowL, baseColor, lightSmoothArea)* shadowUpperBound;
10. 漫反射,Body Diffuse的计算,身体部分
到这里,我们计算整个身体,包含dress小裙子部分的diffuse color. 需要注意的是,要考虑AO的情况,处理的代码如下:
//漫反射diffuse: Ramp+AO
float rampV = saturate(lightMap.a * 0.45 + dayOrNight);
float2 rampUV = float2(adjustedHalfSampler, rampV);
half4 rampShadow = SAMPLE_TEXTURE2D(_ShadowRampMap, sampler_ShadowRampMap, rampUV);
diffuse = lerp(rampShadow, mainLightColor, lambertRampAO) * baseColor;我们来看一下,如果只输出这部分color的效果。
注意看衣服的AO部分
11. 漫反射,Face Diffuse的计算,脸部
脸部是没有AO的,但脸部后面会有描边Outline的问题,眼睛和嘴的地方不要描边。面部的SDF阴影,会在后面单独一篇文章记录。
//结合朝向使用阴影图 Decide which lightmap to use, and compute shadow area
diffuse = lerp(faceShadowColor, mainLightColor, inLight) * baseColor;看一下加了面部的效果:
脸部需要SDF阴影,下一篇会提及,脸部无高光。
鼻子部分的阴影是SDF图,正常这里没有阴影
12. 漫反射,Hair Diffuse的计算,头发
头发需要还原出,3层阴影的感觉,我这里的实现,效果不够顺滑。
//计算头发和头饰颜色
float3 diffuseHair = (darkShadow + lightShadow + litHair + lightShadowSmooth) * baseColor * isHair;
float3 diffuseHairAccessory = baseColor * step(lightMap.r, _HairRange);
//结合diffuse和AO
diffuse = (diffuseHair + diffuseHairAccessory) * step(_HairRange, lightMap.g);
diffuse += hairShadowD * (1 - step(_HairRange, lightMap.g)) * baseColor;看一下头发的效果:
头部阴影的3个层次
头发的AO
最后,再说一下这个阴影的范围,要符合正常光照逻辑,对比游戏中的阴影范围如下:
头发阴影和面部阴影对齐
头发阴影和面部阴影对齐
13. 整体效果
最后,我们来看一下漫反射Diffuse的整体效果:
忽略面部阴影
下次预告:【02】Unity URP 卡通渲染 原神角色记录-Specular: Metal + Non-Metal。下一篇,会记录有光高光,Specular相关内容。
14. 参考链接
感谢各位知乎大佬的分享,列表如下:
世界:【Unity技术美术】 原神Shader渲染还原解析
T.yz:[卡通渲染]二、原神角色渲染还原 - Diffuse-1
雪羽:原神角色渲染Shader分析还原
清清:从仿原神角色渲染到MMD制作(小记)
2173:【02】从零开始的卡通渲染-着色篇1 孩子很喜欢,到家就开整[爱]
页:
[1]