前言:很早之前美术有个眼球效果的需求,然后扔给我一张原画图,让我照着做,前期并没有太多的需求,唯一的需求就是要像,越像越好!好吧,我是美术的搬运工,让我做啥我做啥,观察了一下原画,发现原画眼睛的效果偏迪士尼卡通风格,写实但又很夸张,所以我并没有走基于物理渲染的那一套光照模型,而是基与艺术导向来写shader,这样写的好处是用最简单的光照模型模拟出最贴近原画的效果,但坏处是不稳定,完全不遵循物理守恒,在不同的环境下可能出现不协调的现象,最后实现效果如下:
这种效果差不多扛了大半年,最后要改,要提升效果,美术说要晶莹剔透的感觉,尤其是侧面感觉太平,好吧,之前没有考虑进去折射,因为之前考虑到移动平台,眼睛在游戏里面不会拉的太近,看到的只能是个形状和一个高光点所以把这部分的消耗给去掉了,但是现在某些情况会看的很近,所以不带折射的眼球的弊端就暴露了出来,所以整理了下思路,查找了很多眼球的资料尤其这篇
大星星:写实角色眼睛的制作zhuanlan.zhihu.com核心理论知识说的很全面,我就不重复了,我只分享一下实现的过程!其实核心就是折射的实现,在正面瞳孔要有往里凹的感觉,在侧面因为折射,能看见完整形变的瞳孔效果,最终效果如下:
折射部分主要利用了视差图,理论部分:
个人理解:因为viewDir是在切线空间的(xy与uv对齐),所以viewDir.xy对应的就是offset
所以由简单的比例关系推导出offset值:
但是这种方法对于平面的物体得出的视差结果较为精确,对于曲面的物体,会有1,2之间的误差
恰巧我们要制作的眼球就是曲面的球体,得出的结果美术们肯定不满意,连我自己这关都过不了,所以就去各大成熟渲染引擎偷师了一下,发现Marmoset这款引擎对眼球的折射处理的非常好,如下所示:
折射效果正是我们想要的,扒开源代码发现前面思路是一样的,只是后面在修正误差的时候,采用的是比Parallax Map,更精确的Relief Map,通过线性步进法逐步逼近,得出最真实的高度,减少曲面的误差,翻译成unity代码如下:
void SurfaceParallaxMap(inout FragmentState s)
{
half3 dir = half3(dot(-s.vertexEye, s.vertexTangent),
dot(-s.vertexEye, s.vertexBitangent),
dot(-s.vertexEye, s.vertexNormal));
half2 maxOffset = dir.xy * (_uParallaxDepthOffset / (abs(dir.z) + 0.001));
float minSamples = 12.0;
float maxSamples = 12.0;
float samples = saturate(3.0 * length(maxOffset));
float incr = rcp(lerp(minSamples, maxSamples, samples));
half2 tc0 = s.vertexTexCoord - _uParallaxDepthCenter * maxOffset;
float h0 = ParallaxSample(tc0);
for (float i = incr; i <= 1.0; i += incr)
{
half2 tc = tc0 + maxOffset * i;
float h1 = ParallaxSample(tc);
if (i >= h1)
{
//hit! now interpolate
float r1 = i, r0 = i - incr;
float t = (h0 - r0) / ((h0 - r0) + (-h1 + r1));
float r = (r0 - t * r0) + t * r1;
s.vertexTexCoord = tc0 + r * maxOffset;
break;
}
h0 = h1;
}
//standard normal mapping
SurfaceNormalMap(s);
}
UE4中custom节点和code
half3 dir = tangentEye;
half2 maxOffset = dir.xy * (uParallaxDepthOffset / (abs(dir.z) + 0.001));
float minSamples = 12.0;
float maxSamples = 12.0;
float samples = saturate(3.0 * length(maxOffset));
float incr = rcp(lerp(minSamples, maxSamples, samples));
half2 tc0 = vertexTexCoord - uParallaxDepthCenter * maxOffset;
float h0 = 1 - (Texture2DSample(mixMap,Material.Texture2D_0Sampler, tc0).r);
for (float i = incr; i <= 1.0; i += incr)
{
half2 tc = tc0 + maxOffset * i;
float h1 = 1 - (Texture2DSample(mixMap, Material.Texture2D_0Sampler,tc).r);
if (i >= h1)
{
//hit! now interpolate
float r1 = i, r0 = i - incr;
float t = (h0 - r0) / ((h0 - r0) + (-h1 + r1));
float r = (r0 - t * r0) + t * r1;
vertexTexCoord = tc0 + r * maxOffset;
break;
}
h0 = h1;
}
half2 UV=vertexTexCoord;
return UV;
美术的工具链:
一、模型部分:分eyeBall和eyeCornea的高模和低模
1、eyeBall的高模是一个往里凹的模型,低模是一个往外凸的模型。如图:
虹膜部分可以通过置换贴图来实现,如图所示:
2、eyeCornea高地模都是往外凸的模型,如图所示:
二、贴图部分:
1、法线贴图:根据高模按个人爱好在maya,Xnormal,max中烘焙出eyeBall和eyeCornea高模的法线贴图
2、Albedo贴图:虽然网上有很多制作眼球的软件可以烘焙出相应的贴图,但是美术需要学习成本,最快的还是PS吧,随心所欲,不用学习
3、剩下的贴图有了这两张基础贴图,可以在SP中产出
最终会有六张贴图
- Eyeball Normal
- Eyeball Gloss
- Parallax
- Eyeball Albedo
- Eyeball Spec
- Cornea Normal
Unity完整代码如下:
EyeCornea的shader:
Shader "M1Toolv5/MarmosetEyeCornea"
{
Properties
{
[Header(Surface)]
_tNormalMap("Normal Map", 2D) = "bump" {}
[Header(Microsurface)]
_tGlossMap("Gloss Map", 2D) = "white"{}
_Gloss("Gloss",Range(0,1)) = 0.088
_uGlossHorizonSmooth("Horizon Smoothing",Range(0,1)) = 0.98
[Header(Albedo)]
[Header(Diffusion)]
_ScatterDepth("Scatter Depth",Range(0,1)) = 0.98
[Header(Reflectivity)]
_tSpecularMap("Specular Map", 2D) = "white"{}
_uSpecularColor("SpecularColor",Color) = (1,1,1,1)
_SpecularMapIntensity("Intensity",Range(0,1)) = 0.088
_Fresnel("Fresnel",Range(0,1)) = 0.98
_uFresnelColor("FresnelColor",Color) = (1,1,1,1)
[Header(Reflection)]
_HorizonOcclusion("Horizon Occlusion",Range(0,1)) = 0.088
[Header(EveReflection)]
_Cubemap("CubeMap", CUBE) = ""{}
_CubemapIntensity("CubemapIntensity",Range(0,8)) = 1
_CubemapRotation("CubemapRotation",Range(-360,360)) = 1
}
SubShader
{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
LOD 100
Pass
{
Blend SrcAlpha One
Name "FORWARD"
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile __ M1_GAMMA_CORRECT
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "MarmosetEyeCG.cginc"
// #include "m1v5Env.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 tangent : TANGENT;
float3 normal : NORMAL;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float3 normalDir : TEXCOORD1;
float3 vDir : TEXCOORD2;
float4 posWorld : TEXCOORD3;
float3 tspace0 : TEXCOORD4;
float3 tspace1 : TEXCOORD5;
float3 tspace2 : TEXCOORD6;
};
v2f vert (appdata v)
{
v2f o;
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.vDir = normalize(_WorldSpaceCameraPos.xyz - o.posWorld.xyz);
float3 wTangent = UnityObjectToWorldDir(v.tangent.xyz);
float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
float3 wBitangent = cross(o.normalDir, wTangent) * tangentSign;
o.tspace0 = float3(wTangent.x, wBitangent.x, o.normalDir.x);
o.tspace1 = float3(wTangent.y, wBitangent.y, o.normalDir.y);
o.tspace2 = float3(wTangent.z, wBitangent.z, o.normalDir.z);
o.uv.xy = v.uv;
return o;
}
float4 frag (v2f i) : SV_Target
{
FragmentState s;
s.vertexEye = i.vDir;
s.vertexTangent = half3(i.tspace0.x, i.tspace1.x, i.tspace2.x);
s.vertexBitangent = half3(i.tspace0.y, i.tspace1.y, i.tspace2.y);
s.vertexNormal = i.normalDir;
s.vertexTexCoord = i.uv.xy;
s.tspace0 = i.tspace0;
s.tspace1 = i.tspace1;
s.tspace2 = i.tspace2;
s.albedo=1;
s.normal=1;
s.gloss= _Gloss;
s. reflectivity=1;
s. fresnel=1;
s. diffuseLight=0;
s. specularLight=0;
s. emissiveLight=0;
LightParams l;
l.color = _LightColor0.xyz;
l.direction = normalize(_WorldSpaceLightPos0.xyz);
l.attenuation = 1;
l.shadow = 1;
SurfaceNormalMap(s);
AlbedoMap(s);
ReflectivitySpecularMap(s);
DiffusionLambertianLight(s, l);
DiffusionLambertianEnv(s);
ReflectionGGXLight(s, l);
ReflectionGGXEnv(s);
half3 fincol = s.diffuseLight + s.specularLight;
return half4(fincol,1);
}
ENDCG
}
}
}
EyeBall部分的shader:
Shader "M1Toolv5/MarmosetEyeBall"
{
Properties
{
[Header(Surface)]
_tNormalMap("Normal Map", 2D) = "bump" {}
_uParallaxDepthOffset("Depth",Range(0,0.5)) = 0.088
_uParallaxDepthCenter("Depth Center",Range(0,1)) = 0.98
_mixMap("Mix Map", 2D) = "white"{}
[Header(Microsurface)]
_Gloss("Gloss",Range(0,1)) = 0.088
_uGlossHorizonSmooth("Horizon Smoothing",Range(0,1)) = 0.98
[Header(Albedo)]
_tAlbedoMap("Albedo Map", 2D) = "white"{}
_uAlbedoMapColor("AlbedoMap Color", Color) = (1,1,1,1)
[Header(Diffusion)]
_ScatterDepth("Scatter Depth",Range(0,1)) = 0.98
[Header(Reflectivity)]
_uSpecularColor("SpecularColor",Color) = (1,1,1,1)
_SpecularMapIntensity("Intensity",Range(0,1)) = 0.088
_Fresnel("Fresnel",Range(0,1)) = 0.98
_uFresnelColor("FresnelColor",Color) = (1,1,1,1)
[Header(Reflection)]
_HorizonOcclusion("Horizon Occlusion",Range(0,1)) = 0.088
[Header(SecondaryReflection)]
_SecHorizonOcclusion("Secondary Horizon Occlusion",Range(0,4)) = 0.088
_uGGXSecondaryGloss("Secondary Gloss",Range(0,1)) = 0.088
_uGGXSecondaryIntensity("Secondary Instensity",Range(0,4)) = 0.088
_uGGXSecondaryFresnel("Secondary Fresnel",Range(0,1)) = 0.088
[Header(EveReflection)]
_Cubemap("CubeMap", CUBE) = ""{}
_CubemapIntensity("CubemapIntensity",Range(0,8)) = 1
_CubemapRotation("CubemapRotation",Range(-360,360)) = 1
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
Pass
{
Name "FORWARD"
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile __ M1_GAMMA_CORRECT
#include "UnityCG.cginc"
#include "MarmosetEyeCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 tangent : TANGENT;
float3 normal : NORMAL;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float3 normalDir : TEXCOORD1;
float3 vDir : TEXCOORD2;
float4 posWorld : TEXCOORD3;
float3 tspace0 : TEXCOORD4;
float3 tspace1 : TEXCOORD5;
float3 tspace2 : TEXCOORD6;
};
v2f vert (appdata v)
{
v2f o;
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.vDir = normalize(_WorldSpaceCameraPos.xyz - o.posWorld.xyz);
float3 wTangent = UnityObjectToWorldDir(v.tangent.xyz);
float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
float3 wBitangent = cross(o.normalDir, wTangent) * tangentSign;
o.tspace0 = float3(wTangent.x, wBitangent.x, o.normalDir.x);
o.tspace1 = float3(wTangent.y, wBitangent.y, o.normalDir.y);
o.tspace2 = float3(wTangent.z, wBitangent.z, o.normalDir.z);
o.uv.xy = v.uv;
return o;
}
float4 frag (v2f i) : SV_Target
{
FragmentState s;
s.vertexEye = i.vDir;
s.vertexTangent = half3(i.tspace0.x, i.tspace1.x, i.tspace2.x);
s.vertexBitangent = half3(i.tspace0.y, i.tspace1.y, i.tspace2.y);
s.vertexNormal = i.normalDir;
s.vertexTexCoord = i.uv.xy;
s.tspace0 = i.tspace0;
s.tspace1 = i.tspace1;
s.tspace2 = i.tspace2;
s.albedo=1;
s.normal=1;
s.gloss= _Gloss;
s. reflectivity=1;
s. fresnel=1;
s. diffuseLight=0;
s. specularLight=0;
s. emissiveLight=0;
LightParams l;
l.color = _LightColor0.rgb;
l.direction = normalize(_WorldSpaceLightPos0.xyz);
l.attenuation = 1;
l.shadow = 1;
SurfaceParallaxMap(s);
MicrosurfaceGlossMap(s);
AlbedoMap(s);
ReflectivitySpecularMap(s);
DiffusionLambertianLight(s, l);
DiffusionLambertianEnv(s);
ReflectionGGXLight(s, l);
SecReflectionGGXLight(s,l);
ReflectionGGXEnv(s);
half3 fincol = s.diffuseLight + s.specularLight;
return half4(fincol,1);
}
ENDCG
}
}
}
CG部分代码:
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#define mix lerp
#define vec2 half2
#define vec3 half3
#define vec4 half4
#define M_PI 3.14159265
uniform vec4 uGlossSwizzle;
uniform vec2 uGlossScaleBias;
uniform float uGlossHorizonSmooth;
sampler2D _tNormalMap, _tParallaxHeightMap, _tGlossMap, _tAlbedoMap, _tSpecularMap, _mixMap;
half _uParallaxDepthOffset, _uParallaxDepthCenter, _uGlossHorizonSmooth;
half3 _uAlbedoMapColor, _uSpecularColor, _uFresnelColor;
half _uSpecularFresnel, _uGGXSecondaryGloss, _uGGXSecondaryIntensity, _uGGXSecondaryFresnel;
float4 _Character_LightColor_c;
half _Gloss, _SpecularMapIntensity;
samplerCUBE _Cubemap;
half _EnvGloss, _CubemapIntensity, _CubemapRotation, _Bright;
//half4 _LightColor0;
struct FragmentState
{
//inputs
vec3 vertexEye;
vec2 vertexTexCoord;
vec3 vertexNormal;
vec3 vertexTangent;
vec3 vertexBitangent;
//state
vec4 albedo;
vec3 normal;
float gloss;
vec3 reflectivity;
vec3 fresnel;
vec3 diffuseLight;
vec3 specularLight;
vec3 emissiveLight;
vec3 tspace0;
vec3 tspace1;
vec3 tspace2;
};
struct LightParams
{
vec3 color; // "colour"
vec3 direction; // normalized vector to light
float attenuation; // dimming (distance and other factors)
vec4 shadow; // shadow fraction
};
void SurfaceNormalMap(inout FragmentState s)
{
//sample and scale/bias the normal map
vec3 nsamp = UnpackNormal(tex2D(_tNormalMap, s.vertexTexCoord));
vec3 n = nsamp.xyz ;
//ortho-normalization
vec3 T = s.vertexTangent;
vec3 B = s.vertexBitangent;
vec3 N = s.vertexNormal;
n.x = dot(s.tspace0, nsamp);
n.y = dot(s.tspace1, nsamp);
n.z = dot(s.tspace2, nsamp);
//store our results
s.normal = normalize(n);
s.vertexTangent = T;
s.vertexBitangent = B;
s.vertexNormal = N;
// s.albedo.a = nsamp.a;
}
float ParallaxSample(half2 c)
{
return 1 - (tex2D(_mixMap, c).r);
}
void SurfaceParallaxMap(inout FragmentState s)
{
half3 dir = half3(dot(-s.vertexEye, s.vertexTangent),
dot(-s.vertexEye, s.vertexBitangent),
dot(-s.vertexEye, s.vertexNormal));
half2 maxOffset = dir.xy * (_uParallaxDepthOffset / (abs(dir.z) + 0.001));
float minSamples = 12.0;
float maxSamples = 12.0;
float samples = saturate(3.0 * length(maxOffset));
float incr = rcp(lerp(minSamples, maxSamples, samples));
half2 tc0 = s.vertexTexCoord - _uParallaxDepthCenter * maxOffset;
float h0 = ParallaxSample(tc0);
for (float i = incr; i <= 1.0; i += incr)
{
half2 tc = tc0 + maxOffset * i;
float h1 = ParallaxSample(tc);
if (i >= h1)
{
//hit! now interpolate
float r1 = i, r0 = i - incr;
float t = (h0 - r0) / ((h0 - r0) + (-h1 + r1));
float r = (r0 - t * r0) + t * r1;
s.vertexTexCoord = tc0 + r * maxOffset;
break;
}
h0 = h1;
}
//standard normal mapping
SurfaceNormalMap(s);
}
void MicrosurfaceGlossMap(inout FragmentState s)
{
float g = tex2D(_mixMap, s.vertexTexCoord).g;
s.gloss = g;
s.gloss *= _Gloss;
float h = saturate(dot(s.normal, s.vertexEye));
h = _uGlossHorizonSmooth - h * _uGlossHorizonSmooth;
s.gloss = mix(s.gloss, 1.0, h * h);
}
void AlbedoMap(inout FragmentState s)
{
s.albedo = tex2D(_tAlbedoMap, s.vertexTexCoord);
s.albedo.xyz *= _uAlbedoMapColor;
}
void ReflectivitySpecularMap(inout FragmentState s)
{
float t = tex2D(_mixMap, s.vertexTexCoord).b;
float swz = t.x;
s.reflectivity = t.rrr;
s.reflectivity *= _uSpecularColor * _SpecularMapIntensity;
s.albedo.xyz = s.albedo.xyz - s.albedo.xyz * s.reflectivity;
s.fresnel = _uSpecularFresnel;
}
void ReflectionGGXLight(inout FragmentState s, LightParams l)
{
//roughness
float roughness = 1.0 - s.gloss;
float a = max(roughness * roughness, 2e-3);
float a2 = a * a;
//light params
// adjustAreaLightSpecular(l, reflect(-s.vertexEye, s.normal), rcp(3.141592 * a2));
//various dot products
vec3 H = normalize(l.direction + s.vertexEye);
float NdotH = saturate(dot(s.normal, H));
float VdotN = saturate(dot(s.vertexEye, s.normal));
float LdotN = saturate(dot(l.direction, s.normal));
float VdotH = saturate(dot(s.vertexEye, H));
//horizon
float atten = l.attenuation;
float horizon = 1.0 - LdotN;
horizon *= horizon;
horizon *= horizon;
atten = atten - atten * horizon;
//incident light
vec3 spec = l.color * l.shadow.rgb * (atten * LdotN);
//microfacet distribution
float d = (NdotH * a2 - NdotH) * NdotH + 1.0;
d *= d;
float D = a2 / (3.141593 * d);
//geometric / visibility
float k = a * 0.5;
float G_SmithL = LdotN * (1.0 - k) + k;
float G_SmithV = VdotN * (1.0 - k) + k;
float G = 0.25 / (G_SmithL * G_SmithV);
//fresnel
vec3 reflectivity = s.reflectivity, fresn = s.fresnel;
vec3 F = reflectivity + (fresn - fresn * reflectivity) * exp2((-5.55473 * VdotH - 6.98316) * VdotH);
//final
s.specularLight += D * G * F * spec;
//(D * G) * (F * spec);
}
void SecReflectionGGXLight(inout FragmentState s, LightParams l)
{
//roughness
float roughness = 1.0 - s.gloss;
roughness = saturate(roughness - roughness*_uGGXSecondaryGloss);
float a = max(roughness * roughness, 2e-3);
float a2 = a * a;
//light params
// adjustAreaLightSpecular(l, reflect(-s.vertexEye, s.normal), rcp(3.141592 * a2));
//various dot products
vec3 H = normalize(l.direction + s.vertexEye);
float NdotH = saturate(dot(s.normal, H));
float VdotN = saturate(dot(s.vertexEye, s.normal));
float LdotN = saturate(dot(l.direction, s.normal));
float VdotH = saturate(dot(s.vertexEye, H));
//horizon
float atten = l.attenuation;
float horizon = 1.0 - LdotN;
horizon *= horizon;
horizon *= horizon;
atten = atten - atten * horizon;
//incident light
vec3 spec = l.color * l.shadow.rgb * (atten * LdotN);
//microfacet distribution
float d = (NdotH * a2 - NdotH) * NdotH + 1.0;
d *= d;
float D = a2 / (3.141593 * d);
//geometric / visibility
float k = a * 0.5;
float G_SmithL = LdotN * (1.0 - k) + k;
float G_SmithV = VdotN * (1.0 - k) + k;
float G = 0.25 / (G_SmithL * G_SmithV);
//fresnel
vec3 reflectivity = s.reflectivity, fresn = s.fresnel;
reflectivity *= _uGGXSecondaryIntensity;
fresn = _uGGXSecondaryFresnel;
vec3 F = reflectivity + (fresn - fresn * reflectivity) * exp2((-5.55473 * VdotH - 6.98316) * VdotH);
//final
s.specularLight += (D * G) * (F * spec);
}
void DiffusionLambertianLight(inout FragmentState s, LightParams l)
{
// adjustAreaLightDiffuse(l, s.vertexPosition);
float lambert = saturate(dot(s.normal, l.direction));//saturate((1.0 / 3.1415926) * dot(s.normal, l.direction));
s.diffuseLight += (lambert * l.attenuation) *
(l.color * l.shadow.rgb) *
s.albedo.xyz;
}
void DiffusionLambertianEnv(inout FragmentState s)
{
// float3 SH = GetDirDiffuse(s.normal, _v5CharacterSHIntensity, _v5CharacterSHRotation);
float4 SH = float4(ShadeSH9(float4(s.normal, 1.0)), 1);
s.diffuseLight += s.albedo.xyz * SH;
}
float3 RotateAroundYInDegreesCube(float3 vertex, float degrees)
{
half alpha = degrees * M_PI / 180.0;
half sina, cosa;
sincos(alpha, sina, cosa);
half2x2 m = half2x2(cosa, -sina, sina, cosa);
return half3(mul(m, vertex.xz), vertex.y).xzy;
}
float3 GetDirSpecularCube(float3 dir, samplerCUBE cube, float lodValue, float intensity, float rotation)
{
float3 newDir = RotateAroundYInDegreesCube(dir, rotation);
float3 cubeMapData = DecodeHDR(texCUBElod(_Cubemap, float4(newDir, lodValue)), 1);
//texCUBELod(cube, float4(newDir,lodValue)).xyz * intensity;
return cubeMapData * intensity;
}
void ReflectionGGXEnv(inout FragmentState s)
{
float3 worldRefl = reflect(-s.vertexEye, s.normal);
float3 spec_env_cubemap = GetDirSpecularCube(worldRefl, _Cubemap, _Gloss, _CubemapIntensity, _CubemapRotation);
s.specularLight += spec_env_cubemap;
}
UE4材质蓝图
后续优化:目前这种做法虽然能取得不错的折射效果,但是在手机里性能消耗还是太高,Relif Map的线性步进法,步进的次数越多,效果就越好,但是这样不但增加了采样,GPU高并行的计算也被打断了,而且分为两部分模型来制作眼睛效果,从美术制作流程上也是一种负担,后续试试将两部分合成一个,利用不同的法线贴图算两遍高光,一内一外