8.8 KiB
Raw Blame History

资料整理

凹模型

直接贴图即可。 折射 模型高光

凸模型

  1. 使用视差贴图来控制瞳孔效果 + 折射模拟
float2 viewL = mul(viewW, (float3x2) worldInverse);
float2 offset = height * viewL;
offset.y = -offset.y;
texcoord -= parallaxScale * offset;

  1. Physically based refraction
// 角膜区域突起的模型
// 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;

代码中首先计算了height即前房的高度PPT中height有两种计算方式分别对应两种眼睛的模型结构对应的结构写在注释中了。 然后计算了refracted这个是rtr中快速拟合的计算方法n是空气与介质折射率的比值关于refracted的推论可以参考 YivanLee虚幻4渲染编程(人物篇)【第三卷Human Eye Rendering】 129 赞同 · 12 评论文章

最后一段先通过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/

本作的眼睛分为三个部分,眼白的部分是一个内凹的形状,瞳孔的部分则分为了向内凹的部分和向外突出的部分。

瞳孔的高光叠加在其突出的半透明部分上。根据摄像角度的不同,各个部分的贴图会分开进行移动,使得在哪个角度高光都能处在一个刚好的位置。 控制上,有针对高光上下左右的移动强度与控制移动范围的参数共同作用。

从左边开始是作为基础颜色的Albedo以及用于Mask瞳孔的Alpha贴图用于在Albedo上进行叠加的spt贴图以及两张瞳孔高光以及反应环境的matcapture贴图。 虽然很多动画风格的渲染中会省略掉瞳孔中的虹彩部分但是本作为了提高角色靠近时的效果进行了详细的绘制同时为了体现环境的变化与matcap的贴图进行叠加。 高光贴图有两张分别使用不同的UV动画进行控制用于表现眼睛的湿润感。虽然是很细微的操作但是对于表现角色的感情非常的有用。

SunnySideUp UnityChan

其他效果实现

眼睛高光效果

  1. 贴图高光。使用事先绘制的高光形状贴图贴到最外面的。并且使用ViewDirection来控制。设定4个UV Coord, 根据 View=》眼睛的本地坐标系=》Normalize后的向量进行插值。
  2. PBR的思路

Matcap反射效果

Matcap材质+球形法线贴图

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上去然后用光照方向和菲涅尔去影响强度变化。

使用Mask贴图、NoL与菲尼尔来控制

其他参考整理

!ToonEye.png

!ToonEyes.png

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即可