202 lines
8.8 KiB
Markdown
202 lines
8.8 KiB
Markdown
|
# 资料整理
|
|||
|
|
|||
|
## 凹模型
|
|||
|
直接贴图即可。
|
|||
|
折射
|
|||
|
模型高光
|
|||
|
|
|||
|
## 凸模型
|
|||
|
1. 使用视差贴图来控制瞳孔效果 + 折射模拟
|
|||
|
```
|
|||
|
float2 viewL = mul(viewW, (float3x2) worldInverse);
|
|||
|
float2 offset = height * viewL;
|
|||
|
offset.y = -offset.y;
|
|||
|
texcoord -= parallaxScale * offset;
|
|||
|
```
|
|||
|

|
|||
|
|
|||
|
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;
|
|||
|
```
|
|||
|

|
|||
|
|
|||
|
代码中首先计算了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/
|
|||
|
|
|||
|
>本作的眼睛分为三个部分,眼白的部分是一个内凹的形状,瞳孔的部分则分为了向内凹的部分和向外突出的部分。
|
|||
|

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

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

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

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