# 资料整理 ## 凹模型 直接贴图即可。 折射 模型高光 ## 凸模型 1. 使用视差贴图来控制瞳孔效果 + 折射模拟 ``` float2 viewL = mul(viewW, (float3x2) worldInverse); float2 offset = height * viewL; offset.y = -offset.y; texcoord -= parallaxScale * offset; ``` ![](https://pic2.zhimg.com/v2-80c97cefce517b57de36593a44425ce5_r.jpg) 2. Physically based refraction ```c++ // 角膜区域突起的模型 // Alternatively, use a displacement map // height = max(-positionL.z – eyeIrisDepth, 0.0); // 球形模型 // Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}] height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius ); // refractedW float w = n * dot( normalW, viewW ); float k = sqrt( 1.0 + ( w - n ) * ( w + n ) ); float3 refractedW = ( w - k ) * normalW - n * viewW; float cosAlpha = dot(frontNormalW, -refractedW); float dist = height / cosAlpha; float3 offsetW = dist * refractedW; float2 offsetL = mul(offsetW, (float3x2) worldInverse); texcoord += float2(mask, -mask) * offsetL; ``` ![](https://pic2.zhimg.com/80/v2-e14db6acc5d6f7400009c2a3ff3e2b89_720w.jpg) 代码中首先计算了height,即前房的高度,PPT中height有两种计算方式,分别对应两种眼睛的模型结构,对应的结构写在注释中了。 然后计算了refracted,这个是rtr中快速拟合的计算方法,n是空气与介质折射率的比值,关于refracted的推论可以参考: YivanLee:虚幻4渲染编程(人物篇)【第三卷:Human Eye Rendering】 [129 赞同 · 12 评论文章](https://zhuanlan.zhihu.com/p/151786832) 最后一段,先通过frontNormalW与refractedW的点积计算出α角的cos值(上图中的α应该是标识错误,α是-refractedW与frontNormalW的夹角)。然后已知height,通过比值可以计算出refractedW的模长dist。offsetW即为完整的refractedW向量。最后转换到本地空间,乘上眼睛的Mask,加到原本的UV上。 之后就是使用偏转后的UV去采样贴图了。 与视差相同,这里也是在本地与世界空间中进行的计算,同样会有轴向问题,主要是normalW、viewW和frontNormalW参与的计算,normal与view可以转换到切线空间计算,而frontNormalW代表的是模型向前的朝向,这个必须要指定,不过图方便的话,把frontNormalW改成切线空间法线也不是不可以。 ### 多层复合模型 樱花大战cedec2020分享:https://blog.ch-wind.com/cedec2020-new-sakura-wars-note/ >本作的眼睛分为三个部分,眼白的部分是一个内凹的形状,瞳孔的部分则分为了向内凹的部分和向外突出的部分。 ![](../../public/UrealEngineNPR渲染实现/樱花大战眼睛效果.png) >瞳孔的高光叠加在其突出的半透明部分上。根据摄像角度的不同,各个部分的贴图会分开进行移动,使得在哪个角度高光都能处在一个刚好的位置。 控制上,有针对高光上下左右的移动强度与控制移动范围的参数共同作用。 ![](../../public/UrealEngineNPR渲染实现/樱花大战眼睛贴图.png) >从左边开始,是作为基础颜色的Albedo,以及用于Mask瞳孔的Alpha贴图,用于在Albedo上进行叠加的spt贴图,以及两张瞳孔高光,以及反应环境的matcapture贴图。 虽然很多动画风格的渲染中会省略掉瞳孔中的虹彩部分,但是本作为了提高角色靠近时的效果,进行了详细的绘制,同时为了体现环境的变化与matcap的贴图进行叠加。 高光贴图有两张,分别使用不同的UV动画进行控制,用于表现眼睛的湿润感。虽然是很细微的操作,但是对于表现角色的感情非常的有用。 ![](../../public/UrealEngineNPR渲染实现/UntiyChanSSS_Eye.jpg) SunnySideUp UnityChan ### 其他效果实现 #### 眼睛高光效果 1. 贴图高光。使用事先绘制的高光形状贴图,贴到最外面的。并且使用ViewDirection来控制。设定4个UV Coord, 根据 View=》眼睛的本地坐标系=》Normalize后的向量进行插值。 2. ~~PBR的思路~~ #### Matcap反射效果 Matcap材质+球形法线贴图 ```c++ float3 NormalBlend_MatcapUV_Detail = viewNormal.rgb * float3(-1,-1,1); float3 NormalBlend_MatcapUV_Base = (mul( UNITY_MATRIX_V, float4(viewDirection,0)).rgb*float3(-1,-1,1)) + float3(0,0,1); float3 noSknewViewNormal = NormalBlend_MatcapUV_Base * dot(NormalBlend_MatcapUV_Base, NormalBlend_MatcapUV_Detail) / NormalBlend_MatcapUV_Base.b - NormalBlend_MatcapUV_Detail; float2 ViewNormalAsMatCapUV = noSknewViewNormal.rg * 0.5 + 0.5; ``` #### 焦散效果 >焦散的表现反倒简单了,直接画在眼睛贴图上都可以,考虑到卡通表达的自由性,焦散是否存在与焦散的形状都可以没有限制,只要好看就行。 下图也是miHoYo的分享,可以简单的理解为直接贴张Mask上去,然后用光照方向和菲涅尔去影响强度变化。 ![](https://pic4.zhimg.com/80/v2-c4859a4c140a895547136557e9c4f01b_720w.jpg) 使用Mask贴图、NoL与菲尼尔来控制 # 其他参考整理 ![[ToonEye.png|400]] ![[ToonEyes.png|400]] ## PBR Refrection(有问题) ``` //input anteriorChamberDepth 贴图 使用HeightMap //input radius //input mask 贴图 //input texcoord //input n 折射率比值 //input frontNormalW 贴图float3(0,0,1) Local=>World // 角膜区域突起的模型 height = max(-positionL.z – eyeIrisDepth, 0.0); // 球形模型 Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}] float height = anteriorChamberDepth ;//* saturate( 1.0 - 18.4 * radius * radius ); float3 normalW=Parameters.WorldNormal; float3 viewW= mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);//CameraViewToTranslatedWorld // refractedW float w = n * dot( normalW, viewW ); float k = sqrt( 1.0 + ( w - n ) * ( w + n ) ); float3 refractedW = ( w - k ) * normalW - n * viewW; refractedW=-normalize(refractedW); //float3 frontNormalW=mul(float(0,0,1),float(3x3)GetPrimitiveData(Parameters.PrimitiveId).LocalToWorld) * -1; float cosAlpha = dot(frontNormalW, refractedW); float dist = height / cosAlpha; float3 offsetW = dist * refractedW; float2 offsetL = mul(offsetW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal); texcoord += float2(mask, -mask) * offsetL; return texcoord; ``` ### 原始代码 ``` // 角膜区域突起的模型 // Alternatively, use a displacement map // height = max(-positionL.z – eyeIrisDepth, 0.0); // 球形模型 // Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}] height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius ); // refractedW float w = n * dot( normalW, viewW ); float k = sqrt( 1.0 + ( w - n ) * ( w + n ) ); float3 refractedW = ( w - k ) * normalW - n * viewW; float cosAlpha = dot(frontNormalW, -refractedW); float dist = height / cosAlpha; float3 offsetW = dist * refractedW; float2 offsetL = mul(offsetW, (float3x2) worldInverse); texcoord += float2(mask, -mask) * offsetL; ``` ## Matcap反射效果 ``` float2 CalcMatcapUV(FMaterialPixelParameters Parameters,float3 normalTexture) { float3 ViewVector=Parameters.CameraVector * -1; float3 ViewSpaceRightVector=normalize(cross(ViewVector , mul( float3(0.0,1.0,0.0) , ResolvedView.ViewToTranslatedWorld ))); float3 ViewSpaceUpVector=cross(ViewVector ,ViewSpaceRightVector); float3x3 Matrix=float3x3( ViewSpaceRightVector.x,ViewSpaceUpVector.x,ViewVector.x, ViewSpaceRightVector.y,ViewSpaceUpVector.y,ViewVector.y, ViewSpaceRightVector.z,ViewSpaceUpVector.z,ViewVector.z ); float3 ZeroOneNormal=mul(normalize(Parameters.WorldNormal+normalTexture),Matrix)*0.5+0.5; return float2(ZeroOneNormal.x,ZeroOneNormal.y*-1); } ``` ### 这个不太行 ``` //input normalTexture 法线贴图 float3 TangentWS=Parameters.TangentToWorld[0]; float3 NormalWS=Parameters.TangentToWorld[2]; float3 BinormalWS=cross(NormalWS, TangentWS); float3 worldNormal; worldNormal.x = dot(float3(TangentWS.x,BinormalWS.x,NormalWS.x), normalTexture); worldNormal.y = dot(float3(TangentWS.y,BinormalWS.y,NormalWS.y), normalTexture); worldNormal.z = dot(float3(TangentWS.z,BinormalWS.z,NormalWS.z), normalTexture); worldNormal = normalize(worldNormal); float3 e = normalize(GetWorldPosition(Parameters) - ResolvedView.WorldCameraOrigin); float3 reflectVector = reflect(e, worldNormal); float3 reflectVectorVS = normalize(mul(reflectVector,ResolvedView.TranslatedWorldToView)); float m = 2.82842712474619 * sqrt(reflectVectorVS.z + 1.0); float2 cap = reflectVectorVS.xy / m + 0.5; cap=cap*0.5 + 0.5; return cap; ``` ## 高光贴图位置调整 ``` //input float2 LeftUp,LeftDown,RightUp,RightDown; float3 viewW = mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld); float3 viewL = mul(viewW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal); float2 viewL2D = normalize(viewL.xy); float2 Left=lerp(LeftUp,LeftDown,viewL2D.y); float2 Right=lerp(RightUp,RightDown,viewL2D.y); return lerp(Left,Right,viewL2D.x); ``` ## 焦散 - dot(Parameters.WorldNormal,View.DirectionalLightDirection); - Fresnel 乘以焦散Mask即可