卡通渲染总结-原神达达利亚
gif渣画质警告。7.多m的gif点这个链接
https://upload-images.jianshu.io/upload_images/28340786-c561331b1ebcd621.gif?imageMogr2/auto-orient/strip
惯例先上参考:
【Unity技术美术】 原神Shader渲染还原解析
原神角色渲染Shader分析还原
【01】Unity URP 卡通渲染 原神角色渲染记录-Diffuse: Ramp + AO + Double Shadow
【JTRP】屏幕空间深度边缘光 Screen Space Depth Rimlight
原神角色渲染Shader分析还原
我这程度挑战卡渲,还是太有勇气了……
时隔良久才来写文章,会简略很多。
贴图分析
当时漫天找资源,求爷爷告奶奶地要贴图,现在给像我一样懵逼不知道这些贴图哪来的小萌新指条明路,使用截帧软件NinjaRipper自己去游戏里拿。。
问题又出现了,大佬们的文章告诉我每个通道应该长得像最下面一排那样,但我找到的某些图是透明的。最后知道png图片自带透明度,而且不存在a通道里,贴图格式最好为tga(希望没说错)。
放到模型上
ramp
关于ramp每条功能,当时一直在凑参数,画了个图,现在快看不懂了……
half3 Cel_Ramp(half NdotL, half4 parameter) {parameter.g = smoothstep(0.0,0.1,parameter.g) ; half3 fixShadow = saturate(_fixShadowColor.rgb + parameter.g); half halfLambert =smoothstep(0.1,0.7,NdotL*0.2+0.3) * parameter.g * 2;//当没有分界线的时候,就是g不要乘2,要用step限制/* Skin = 1.0 white Silk = 0.7 Metal = 0.5 Soft = 0.3 Hard = 0.0 black coat */ #if _SHADERENUM_HAIR half v = -0.5 * _inNight + 0.95; #elif _SHADERENUM_BASE half v = parameter.a * 0.45 + -0.5 * _inNight + 0.55; #else half v = 0; #endifreturn SAMPLE_TEXTURE2D(_rampMap, sampler_rampMap, half2(halfLambert, v)).rgb * fixShadow; }
问题:亮面有ramp阴影,暗面没有,有时候双面都有
解决:clamp!!
问题:远处模糊方块近处没有
解决:mipmap关闭!!
lightmap,也就是g通道在0-0.5,但乘以2映射到0-1的做法是错的,怎么都取不出固定阴影。还有世界佬那个,用实时阴影与固定阴影相乘的,我得到的实时阴影很奇怪,干脆舍弃。
于是使用smoothstep函数,将0-0.1黑色部分留下,其余全部取1即取白。
固定阴影等于g通道乘以外部给出的固定阴影颜色,再截取到0-1,只影响黑色0-0.1的部分,给g里的阴影加上颜色。
接下来我的lambert很奇怪,是凑出来的结果,不要学。
为了肤色与脸色对上,为肤色单独创建一个材质,将这个颜色设为白色。
整体ramp预览
高光
对于头发的高光抄了最简单的,能用就行。
float3Cel_Specular(float3 NdotH,float3 NdotL,float3 baseColor,float4 var_ParamTex){#if _SHADERENUM_HAIRfloatspecularRadius=pow(NdotH,var_ParamTex.b*50);float3specularColor=var_ParamTex.b*baseColor;return_enableSpecular*smoothstep(0.3,0.4,specularRadius)*specularColor*lerp(_hairSpecularIntensity,1,step(0.9,var_ParamTex.b));#elif _SHADERENUM_BASEfloatspec=pow(saturate(NdotH),_shininess);floatw=0.4f;spec=lerp(0,1,smoothstep(-w,w,spec-1.1f+var_ParamTex.b));float3specularColor=_enableSpecular*_lightSpecColor*_specMulti*var_ParamTex.r*spec;//multi越小越黑return specularColor;#elsereturn0;#endif}
还花了两天把变体那块的文档看完了,必须为每个变体安排上,否则他就不编译。
高光-step函数
half spec = pow(saturate(dot(input.normalWS, halfViewLightWS)), _Shininess);spec = step(1.0f - LightMapColor.b, spec);half4 specularColor = _EnableSpecular * _LightSpecColor * _SpecMulti * LightMapColor.r * spec;from 雪羽
spec经step后,各像素有无(1 0)高光就知道了。作为高光控制参数。
金属度
float3 Cel_Metal(float3 nDir, float4 var_ParamTex, float3 baseColor) {float2 mcCoord = float2(dot(normalize(UNITY_MATRIX_V.xyz), normalize(nDir)), dot(normalize(UNITY_MATRIX_V.xyz), normalize(nDir))); float var_MatCap = SAMPLE_TEXTURE2D(_metalMap, sampler_metalMap, mcCoord * 0.5 + 0.5) * 2 + 0.3f;//金属图想要变白,增大值。漫反射色不变甚至变黑,减小值 return _enableMetalSpecular * var_MatCap * baseColor *var_ParamTex.r;//取0.95到1的部分,即glossiness图白色金属部分 //金属色乘漫反射色得到高光色,要与光源做反应的话用光源色 }金属度笔记
r通道glossiness告诉你哪里是金属、高光强的地方,b通道specular告诉你金属长啥样
整体金属高光预览
参数调不好
神之眼自发光
half3 Cel_Emission(half4 baseColor, half4 var_ParamTex) { half bloomMask= baseColor.a; half3 emission = _enableEmission * bloomMask * _emissionIntensity * abs(_SinTime.w);//abs((frac(_Time.y * 0.5) - 0.5) * 2) emission *= baseColor; return emission; }自发光预览
BaseColor汇合
half3 Cel_Base(half NdotL, half NdotH, half NdotV, half3 nDir, half4 baseColor, half4 var_ParamTex) { //baseColor = step(0.95, var_ParamTex.a) * baseColor * _skinWhite + (1-step(0.95, var_ParamTex.a))*baseColor; //提亮肤色 //baseColor = lerp(baseColor, baseColor * _skinWhite, step(0.95, var_ParamTex.a)); half3 rampColor = Cel_Ramp(NdotL, var_ParamTex); //float3 albedo = baseColor * rampColor; half3 albedo = lerp(baseColor, baseColor * rampColor, _rampDarkness); half3 specular = Cel_Specular(NdotH, NdotL, baseColor, var_ParamTex); half3 metal = Cel_Metal(nDir, var_ParamTex, baseColor); half3 emission = Cel_Emission(baseColor, var_ParamTex); half3 finalColor = albedo+ specular + metal ;//金属色与高光色相加 #if _SHADERENUM_BASE returnfinalColor + emission; #else return finalColor; #endif }SDF的脸
half3 Cel_Face(half3 baseColor, Light mainLight, float4 vuv){//https://zhuanlan.zhihu.com/p/552097741//左右阴影图 Sample flipped face light map float2 rightFaceUV = float2(-vuv.z, vuv.w); float4 faceShadowR = SAMPLE_TEXTURE2D(_paramTex, sampler_paramTex, rightFaceUV); float4 faceShadowL = SAMPLE_TEXTURE2D(_paramTex, sampler_paramTex, float2(vuv.z, vuv.w)); //人物朝向 Get character orientation float3 up = float3(0,1,0); float3 front=TransformObjectToWorldDir(float4(0.0,0.0,1.0,1.0)); float3 right = cross(up, front); float2 realLDir = normalize(mainLight.direction.xz); float realFDotL = dot(normalize(front.xz), realLDir);//FaceShadowRange float realRDotL = dot(normalize(right.xz), realLDir); //通过RdotL决定用哪张阴影图 float shadowTex = realRDotL < 0? faceShadowL: faceShadowR;//FaceShadow //获取当前像素的阴影阈值 float shadowMargin = shadowTex.r;//FaceShadow //判断是否在阴影中 float inShadow = -0.5 * realFDotL + 0.5 < shadowMargin;//小于marging,在阴影中 float2 shadowUV = float2(inShadow, -0.5 * _inNight + 1);//1day,0.5night half3 faceShadowColor = SAMPLE_TEXTURE2D(_rampMap, sampler_rampMap, shadowUV) * baseColor; faceShadowColor = lerp(baseColor, baseColor * faceShadowColor, _rampDarkness); return faceShadowColor;}
先分别采样左右的脸,然后定义上右前方向,文章说灯光朝向和灯光vector不一致,逆时针转90,投影后要归一化,不然长度会小于1,我不乘那个矩阵才是对的……接下来注释都有。采了ramp里的肤色。由于与肤色的u值并不完全相同,脸色与肤色还是有一定差距,不过已经相当可以了。到时候写个脚本把几个材质的是否夜晚选项统一起来。
怎么样是对的呢,光照在0度左右,鼻子上的阴影左右动。
脸部预览
外描边
描边及修正,不知道抄的哪位的,实际上精要里的描边也够了,总之都会断裂。实际游戏里看剧情,镜头拉近的时候也看到了断裂的边,瞬间心理平衡了哈哈哈。
唯一看懂的真正实现描边的函数,由于我实在不会绘制顶点色(在3d max与blender都试过,无效。另外max导入文件一定不能直接拖进去,否则直接放大一百倍),又在草地里学了在顶点着色器采样贴图的方法,现在实践一下对脸部绘制遮罩贴图来去除嘴角、眼眶的描边。
未处理的脸部描边:
在脸的a通道画遮罩。
反复调整区域达到完美。
游戏截图:
上代码。
float3 TransformPositionWSToOutlinePositionWS(float FaceMask, float3 positionWS, float positionVS_Z, float3 normalWS) { float texAlpha; #if !_SHADERENUM_FACE texAlpha = 1; #else texAlpha = FaceMask; #endif float outlineExpandAmount = texAlpha * _outlineWidth * GetOutlineCameraFovAndDistanceFixMultiplier(positionVS_Z); return positionWS + normalWS * outlineExpandAmount; } v2f OutlinePassVertex(a2v a) { v2f v = (v2f)0; ZERO_INITIALIZE(v2f, v); VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(a.normalOS, a.tangentOS);v.normWS = vertexNormalInput.normalWS;//原始法线 v.uv.xy = TRANSFORM_TEX(a.texcoord, _diffTex).xy; v.posWS = TransformObjectToWorld(a.vertex); v.posVS = TransformWorldToView(v.posWS); float FaceMask = SAMPLE_TEXTURE2D_LOD(_diffTex, sampler_diffTex,v.uv.xy, 0).a; v.posWS = TransformPositionWSToOutlinePositionWS(FaceMask, v.posWS, v.posVS.z, v.normWS); v.posCS = TransformWorldToHClip(v.posWS); //冯乐乐 // float4 posVS =mul(UNITY_MATRIX_MV, a.vertex); // float3 normalVS= mul((float3x3)UNITY_MATRIX_IT_MV, a.normalOS);//a.tangentOS // normalVS.z = -0.5 ; // posVS = posVS + float4(normalize(normalVS) , 0) * _outlineWidth ; // v.posCS = mul(UNITY_MATRIX_P , posVS) ; v.shadowCoord = TransformWorldToShadowCoord(v.posWS); // jave.lin : shadow recieve 将 世界坐标 转到 灯光坐标(阴影坐标) return v; }描边预览
边缘光
//================ //边缘光Rim: 屏幕空间深度边缘光 //如果我们把每个点的位置向着法线方向位移一段距离,再根据位移后的坐标采样相机深度图,把深度和自己的深度做对比,如果差值大于一个阈值,那么就是边缘。 //https://zhuanlan.zhihu.com/p/365339160 //一定要注意一点,要保持z分量不变,因为Homogeneous Clip Space中的w分量就是负的ViewSpace中的z分量,必须保持一致才能转换至正确的ViewPort(视口)坐标,否则采样的结果不对。 float3 offsetPosVS = float3(v.posVS.xy + v.normVS.xy * _rimWidth, v.posVS.z); float4 offsetPosCS = TransformWViewToHClip(offsetPosVS);//在观察空间偏移后转裁剪空间 float4 offsetPosVP = TransformHClipToViewPortPos(offsetPosCS);//从裁剪空间转view port space,视口空间(也就是屏幕UV坐标空间),获得位移后视图 //float3 nonHomogeneousCoord = v.posCS.xyz / v.posCS.w;//裁剪空间进行齐次除法得NDC //float2 screenUV = nonHomogeneousCoord.xy; float2 screenUV = v.scrPos.xy/v.scrPos.w; float depth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, screenUV).r; //获得原深度,链接文章中depth获取存疑 float linearEyeDepth = LinearEyeDepth(depth, _ZBufferParams); // 离相机越近越大(小???//解析深度为线性深度 float offsetDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, offsetPosVP).r;//获得位移后视图的深度值 float linearEyeOffsetDepth = LinearEyeDepth(offsetDepth, _ZBufferParams); float depthDiff = linearEyeOffsetDepth - linearEyeDepth; float rimMask = smoothstep(0, _rimThreshold, depthDiff); //float rimMask = step(_rimThreshold, depthDiff); float3 rim = _enableRim * saturate(rimMask - _rimRange);//_RimColor shadowColor step(0.1, lightMap.g) //==================边缘光预览
此外还有阴影ShadowCaster Pass,描边片元着色器,获取深度图的pass,urp自带后处理,就不放了。欣赏最终效果:
晚上调参后
白天
下次再调,要先把夜晚参数记下
页:
[1]