原神怎么搞渲染,自从原神上线以来,其精美的画面和逼真的角色渲染吸引了无数玩家的关注。想要学习如何在游戏中实现这样的渲染效果,下面就是一个从零开始的原神角色渲染教程,让你轻松掌握渲染的技巧和方法。无论你是编程新手,还是已经有一定开发经验的专业人士,都能从中受益匪浅。让我们一起探究原神如何搞渲染,创造出更加精美逼真的游戏角色吧!
从零开始原神角色渲染教程
序言:第一步:打 开 原 神
1、脸部的漫反射
基于SDF的实现的面部阴影,有关SDF的原理和制作方法可以参考以下大佬的文章
欧克欧克: https://zhuanlan.zhihu.com/p/337944099
橘子猫: https://zhuanlan.zhihu.com/p/356185096
原神面部的实现方法可以参考这位大佬的文章,具体原理也解释的很清楚
黑魔姬:https://zhuanlan.zhihu.com/p/279334552
其中需要注意的是,如果直接用光向量参与点乘的话,会导致阴影变化过快,因此需要参考这位大佬的文章,改用光的投影向量进行计算,即可实现游戏中的平滑过渡阴影
ruofeng133: https://blog.csdn.net/A13155283231/article/details/109705794
面部阴影颜色我是通过新建一个uv,用于采样身体部分的ramp贴图,U向随便设了一个靠左的值,目的是采样左侧还没有渐变的颜色;V向是Ramp的第二行和第七行的值(分别对应皮肤的白天和晚上的颜色,用shaderfeature去控制具体采哪一行),然后再乘上自定义的第二行阴影颜色,作为输出的阴影部分颜色;用自定义的第二行基本颜色,作为输出的基本颜色;两者通过先前生成的面部衰减阈值去lerp,即可实现面部的漫反射效果
float2 ColorUV; #if _DAYNIGHT_SWITCH_DAY ColorUV = float2(0.35, 0.85); #else ColorUV = float2(0.35, 0.35); #endif half3 faceShadowColor = SAMPLE_TEXTURE2D(_RampMap, sampler_RampMap, ColorUV).rgb; faceShadowColor *= _ShadColLine2.rgb; half3 faceBaseColor = _BaseColLine2.rgb; faceShadow = lerp(faceShadowColor, faceBaseColor, lightAttenuation); #endif
阶段成果:

2、身体的漫反射
卡渲基本知识可参考2173大佬的文章
2173: https://zhuanlan.zhihu.com/p/110025903
有关原神ramp和lightmap的用途分析可参考以下大佬的文章
雪羽: https://zhuanlan.zhihu.com/p/360229590
世界:https://zhuanlan.zhihu.com/p/435005339
下面是我的做法:
正常采样五行ramp,在顺序固定的情况下,用不同的mask去乘,就能得到对应mask的ramp颜色,最后将五行的颜色相加,即可得到整体的颜色
P.S.尝试过直接用lightmap.a来采,但效果不太对。lightmap.a的灰度除了头发和身体的部分是固定的,其它部分貌似都不太一样,而且还有可能遇到角色的软布料和硬金属在同一个灰度下(猜测要用不同材质球去区分)。所以这里为了让shader能够泛用,并且能够在可控的范围内高度定制,还是采用了多次采样ramp的方法。
首先是将lightmap.a的各个灰度提取成遮罩,用数组的形式保存
float rampAreaMask0 = 0; float rampAreaMask1 = step(0.00, rampAreaMask) - step(0.25, rampAreaMask); float rampAreaMask2 = step(0.20, rampAreaMask) - step(0.45, rampAreaMask); float rampAreaMask3 = step(0.40, rampAreaMask) - step(0.65, rampAreaMask); float rampAreaMask4 = step(0.60, rampAreaMask) - step(0.85, rampAreaMask); float rampAreaMask5 = step(0.80, rampAreaMask) - step(1.05, rampAreaMask); float AllRampAreaMask[6] = {rampAreaMask0, rampAreaMask1, rampAreaMask2, rampAreaMask3, rampAreaMask4, rampAreaMask5};
接着新建5个rampUV,U向是用不同的值去smoothstep后的半兰伯特,V向按照0.95, 0.85 ··· 0.05的方法去采ramp图,然后用shaderfeature去控制采的是上面五行还是下面五行,目的是求出动态的阴影颜色
这还没完,我们再新建5个shadowUV,U向这次换成smoothstep(0.25, 0.45, lightcolor.g),V向和ramp一样,目的是求出静态的阴影颜色
float rampValue1 = smoothstep(_ParamLine1.y, _ParamLine1.z, halfLambert); //以此类推 float rampValue5 = smoothstep(_ParamLine5.y, _ParamLine5.z, halfLambert); float shadowValue = smoothstep(0.25, 0.45, shadowWeight); float2 rampUV1, rampUV2, rampUV3, rampUV4, rampUV5; float2 shadowUV1, shadowUV2, shadowUV3, shadowUV4, shadowUV5; #if _DAYNIGHT_SWITCH_DAY rampUV1 = float2(rampValue1, 0.95); shadowUV1 = float2(shadowValue, 0.95); rampUV2 = float2(rampValue2, 0.85); shadowUV2 = float2(shadowValue, 0.85); rampUV3 = float2(rampValue3, 0.75); shadowUV3 = float2(shadowValue, 0.75); rampUV4 = float2(rampValue4, 0.65); shadowUV4 = float2(shadowValue, 0.65); rampUV5 = float2(rampValue5, 0.55); shadowUV5 = float2(shadowValue, 0.55); #else rampUV1 = float2(rampValue1, 0.45); shadowUV1 = float2(shadowValue, 0.45); //以此类推 rampUV5 = float2(rampValue5, 0.05); shadowUV5 = float2(shadowValue, 0.05); #endif
接下来开始组合颜色
//属性 [HDR]_BaseColLine1 ("Line1 BaseCol", Color) = (1, 1, 1, 1) [HDR]_ShadColLine1 ("Line1 ShadCol", Color) = (1, 1, 1, 1) // ParamLine: X:RampAreaMask, Y:RampSmooth1, Z:RampSmooth2, W:RampSmooth3 _ParamLine1 ("Line1 Param", Float) = (0.0, 0.2, 0.7, 0.66) //方法 half3 rampColor1 = var_RampMap1 * _ShadColLine1.rgb; rampColor1 = lerp(rampColor1, _BaseColLine1.rgb, smoothstep(0.65, _ParamLine1.w, halfLambert)); rampColor1 *= AllRampAreaMask[floor(_ParamLine1.x)]; //以此类推 half3 rampColor = rampColor1 + rampColor2 + rampColor3 + rampColor4 + rampColor5;
这里求的是动态阴影-亮部(基本色)的部分,当前阶段效果如下:

需要注意在求第二行颜色的时候,需要根据是否为面部做一个判断
half3 rampColor2 = var_RampMap2 * _ShadColLine2.rgb; rampColor2 = lerp(rampColor2, _BaseColLine2.rgb, smoothstep(0.65, _ParamLine2.w, halfLambert)); rampColor2 *= AllRampAreaMask[floor(_ParamLine2.x)]; #if FACESHADOW_ON rampColor2 = GetGenshinFaceShadow(upWS, frontWS, lightDirWS, uv); #endif
接下来求静态阴影
half3 shadowColor1 = var_ShadowMap1 * _ShadColLine1.rgb; shadowColor1 *= AllRampAreaMask[floor(_ParamLine1.x)]; //以此类推 half3 shadowColor = shadowColor1 + shadowColor2 + shadowColor3 + shadowColor4 + shadowColor5;
当前阶段效果如下:

最后,将动态阴影和静态阴影结合,即可求出漫反射部分
diffuse = lerp(shadowColor, rampColor, smoothstep(0.24, 0.46, shadowWeight)); diffuse = min(diffuse, 1);
阶段成果:

看到这里有人可能会想:静态阴影就算不采样,只用自定的颜色的话,也能得出一样的效果。
这样说确实没问题,少五次采样能减少性能消耗,但与之对应的,是颜色调节的不可控,正是因为有了静态阴影的采样,才能在不改变任何参数的前提下保证动态阴影和静态阴影的颜色对比相对和谐,在此基础上对颜色进行微调更容易调出自己想要的效果。我的目的是力求最大程度上还原游戏中的效果,老婆好看才是最重要的(
二、高光部分v1.0(更新于2022.02.21)
首先求出基本的高光形状(思路还是挺简单的,一步一步debug,看看每一步的结果就能看懂了)
half specularMask = var_LightMap.r; half specularInt = var_LightMap.b; float specularShape = step(1 - specularMask, blinnPhong) * specularInt;
接着还是按照Line1-Line5的方式分别求颜色,最后再相加
float specBaseIntLine1 = specularShape * _SpecBaseIntLine1; float specShadIntLine1 = specularShape * _SpecShadIntLine1; float specIntLine1 = lerp(specShadIntLine1, specBaseIntLine1, smoothstep(0.66, 0.70, halfLambert)); specIntLine1 *= AllRampAreaMask[floor(_ParamLine1.x)]; //以此类推 specular = specIntLine1 + specIntLine2 + specIntLine3 + specIntLine4 + specIntLine5;
高光在亮部(基本色)和暗部(阴影)部分都会产生,只是阴影部分会稍微暗一些。那么就很好做了,直接用smoothstep的半兰伯特去区分亮、暗部,然后高光乘上不同的颜色去lerp,求出五行的值,最后再相加
阶段成果:

v1.1(更新于2022.02.26)
优化了金属高光的质感,尽可能贴合游戏内效果,但还是没有游戏里内味儿,后续还会对这一部分进行优化
这次的方案是将高光分成金属部分和非金属部分。首先利用lightmap.rb的灰度求出金属部分的mask
half specularMask = var_LightMap.r; half specularInt = var_LightMap.b; float metalMask = smoothstep(0.85, 0.95, specularMask); float metalSpecularMask = smoothstep(0.7, 0.75, specularInt) * metalMask;
接着对metalMap(用matcap的方式采样)的不同灰度提取出来,乘上不同的颜色再混合,最后乘上metalMask ,作为金属部分的颜色
求非金属部分的高光和v1.0的方案一样,但是有一点需要注意,就是需要将lightmap.rb剔除掉金属部分,再进行计算
specularMask = min(specularMask, 0.6); specularInt = min(specularInt, 0.6);
最后对用metalSpecularMask 对非金属部分的高光和金属高光做lerp,作为输出的高光颜色
specular = lerp(specular, 0.8 * specular + metalSpecular, metalSpecularMask);
阶段成果

这里用的是屏幕边缘光,原理是用原深度减去偏移后的深度,然后用一个阈值去和差做step,其结果就是边缘(光)
可参考下面两位大佬的文章去实现,两篇文章的原理都是一样的,只是偏移方式的不同
Jason Ma: https://zhuanlan.zhihu.com/p/139290492
Cutano: https://zhuanlan.zhihu.com/p/365339160
求出基本的边缘光效果后,还是通过不同的mask和不同的强度相乘,最后将五行叠加
float rimBaseIntLine1 = rimIntensity * _RimBaseIntLine1; rimBaseIntLine1 *= AllRampAreaMask[floor(_ParamLine1.x)]; //以此类推 rimLight = rimBaseIntLine1 + rimBaseIntLine2 + rimBaseIntLine3 + rimBaseIntLine4 + rimBaseIntLine5; rimLight = min(rimLight, 1);
阶段成果:

自发光的实现就比较简单,这里只讨论神之眼部分。游戏中在元素充能完成时(就是可以放大招的情况下)神之眼会一闪一闪的亮,那么我们就可以利用一个波动的函数去影响自发光的强度
函数可视化网站
IQ: https://graphtoy.com/
强烈推荐IQ大神的另一个网站
https://www.shadertoy.com/
基本的函数曲线是这样的:
abs(frac(t + x) - 0.5) * 2

然后对这个函数进行smoothstep:
smoothstep(0.0, 1.0, abs(frac(t + x) - 0.5) * 2)

然后再细微调整
smoothstep(0.1, 0.9, abs(frac(t + x) - 0.5) * 2)

应用到我们的自发光部分,就可以得到一闪一闪的效果了
//bloomMask = albedo.a bloomMask = step(0.1, bloomMask); half3 bloom = bloomMask * _BloomCol.rgb * smoothstep(0.1, 0.9, abs(frac(_Time.y * _BloomFreq) - 0.5) * 2);
阶段成果:

P.S.人物自发光的地方不止是神之眼。温迪,钟离和我老婆影在释放元素爆发的时候。辫子也会变亮(这个应该很多人都能发现吧)
将以上部分相加,就能得出基本的渲染效果
整合成果:

基本的描边原理其实很好懂,就是第一个pass剔除背面,第二个pass剔除正面,然后顶点沿法线方向外扩
再次放上2173老师的文章,其中包括NDC空间法线外扩和描边断裂修复方案,讲的非常详细,强烈推荐(我的卡渲启蒙)
2173: https://zhuanlan.zhihu.com/p/109101851
Jason Ma: https://zhuanlan.zhihu.com/p/107664564
关于描边的粗细,可以用顶点色来控制。但是我在外部设置好顶点色之后,利用MMD4 Mecanim插件导入unity,顶点色会乱掉,所以这里转变思路,还是采 贴 图
half outlineMask = tex2Dlod(_OutlineMaskMap, float4(uv, 0.0, 0.0)).g; float4 positionCS = 0; #if _OUTLINE_SWITCH_ABSOLUTE positionCS = TransformObjectToHClip(positionOS + tangentOS * _OutlineWidth * outlineMask); #else //NDC描边 #endif
关于描边的颜色,还记得之前做漫反射时利用lightmap.a做出来的5个mask了吗,没错,就通过它来乘,最后在相加得到最终的描边颜色
half3 outlineBaseColLine1 = _OutlineBaseColLine1.rgb * AllRampAreaMask[floor(_ParamLine1.x)]; //以此类推 half3 outlineCol = outlineBaseColLine1 + outlineBaseColLine2 + outlineBaseColLine3 + outlineBaseColLine4 + outlineBaseColLine5;
最终成果(吐槽一下,视频被压的好糊啊,颜色也不对。。)

追加一个b站链接,效果能好一点。。原神-Shader展示(技术美术demo)
拓展:
增加了一些开关,方便实时查看效果

至此,原神的角色渲染就完成了。没有截帧逆向,截帧逆向就好比打游戏开风灵月影,爽过之后索然无味,通过自己一步一步的还原才更有成就感(其实就是自己太菜了不会弄)
总结:卡渲真好玩,老婆真好看,嘿嘿嘿
计划:想在unity里做mmd,目前还在尝试跑通流程。临近毕业,事情逐渐多了起来,可能还要咕很久
P.S.要是早毕业几年就好了

通过这篇教程,我们可以深入了解到原神角色渲染的全过程。从建模到材质贴图、到灯光布局和渲染输出,每个步骤都需要仔细的调整和优化。希望大家可以在实践中获得更多经验和技巧,制作出更加精美的原神角色!