其他分享
首页 > 其他分享> > 卡通渲染小结

卡通渲染小结

作者:互联网

最近接触了一段时间的卡渲,也制作了相应比较典型的两个demo,接下来便好好总结一下对卡通渲染的一些理解和心得。接下来先放上思维导图,让自己有一个清晰的思路去一步步总结。

在这里插入图片描述

我把卡通渲染分为了三个部分,其中最核心的两个部分是描边和着色,这是非写实主义(NPR)区别于写实主义最重要的的特点,其次最后在罗列了卡通渲染的几个比较重要的渲染类型分支,接下来便一一介绍。

1.描边(OutLine)

在卡通渲染里,有一个重要的特点就是大部分人物或是建筑都会有一定的描边效果,这是为了凸显卡通效果的一个表现。而提到描边实则就是找到模型的边缘,并对边缘位置的像素进行加深的绘制,另外描边也有外边和内边之分,这里主要提及的都是外轮廓,而内轮廓在图像处理里也可以实现。在RTR这本书里,它把描边分成了五种类型,这里挑选其中三种用的比较多比较常见的来细说一下。

1.1 基于视角和法线的描边(Shading Normal Contour Edges)

这种描边方法是最常用的一种,它取决于我们直观的感受而定义的一种方法。当我们的视线与某个表面相切时,一般该表面就为边缘,所以我们可以用dot(N,L)进行检测该像素点的边缘程度,最后通过一个阈值来控制轮廓线的宽度,但该方法最大的缺点是得到的描边粗细差别非常大,不利于进行精细的控制,在项目里用的也比较少。下图便可以看出这种方法得到的描边并不是特别的好看。

在这里插入图片描述

1.2 过程式的几何描边(Procedural Geometry)

这种描边方法在日式渲染里用的比较多,通常有两种方法,分别是Shell MethodZ-bias。另外在描边过程也有几个特定的情况需要处理,下面便一一介绍。

1.2.1 沿法线外扩(Shell method)

Shell Method方法的核心思想是使用两个pass进行物体的渲染,第一个pass负责把物体本来的样子渲染出来,第二个pass先设置正面剔除,然后在顶点处理时,把顶点沿法线的方向外扩一定的距离,使物体看起来膨胀起来,最后就可以得到描边的效果了。在一般情况下,我们可以通过利用顶点颜色来控制描边的一些细节,比如最常见的用A通道控制线条的粗细。对于shell Method来说,通过查阅资料发现有两种做法。

首先第一种在入门精要里提及到,首先把顶点和法线转换到视角空间里,并且把法线的z值进行一定的处理,防止内凹的物体描边时覆盖掉物体,最后再进行外扩。

第二种方法是把顶点和法线都转到裁剪空间中,然后在进行外扩。
下图为典型的外扩例子。

在这里插入图片描述

1.2.2 z值外扩(Z-bias)

Z-bias方法与shell Method类似,它的核心是把顶点的z值沿法线方向移动一个固定的位置,但实现出来的效果比shell Method要差一点,且并不是所有部分都能看出描边,这里就不多做阐述。

1.2.3 硬表面断裂(Hard surface fracture)

这一个是在做描边时发现的问题,在shell Method方法里,如果我们对立方体进行描边,我们会发现在顶点位置,两条边会断裂,如下图。

在这里插入图片描述
造成这个现象的原因是在边缘位置的法线不够平滑,所以完全往两个方向去扩展,并没有向折中的方向去扩展,经过查阅资料,这里给出的一个方法。它大致的思路是,首先通过脚本获取模型mesh里的相同索引相同位置的点,然后把这些点的法线进行平滑处理,再将平滑后的法线保存到模型的顶点颜色里。另外,也有几篇文章对该方法进行了优化,诸如平滑算法的优化,对fbx直接更改等等,在最后会放上链接,可以详细了解。

1.2.4.不受摄像机距离影响(Camera distance influence)

在几何式描边后,我们会发现,描边的粗细会受到摄像机距离远近的影响。我们都知道裁剪空间转换到最后的屏幕空间时需要进行透视除法,即把顶点除于它的w分量,而我们可以从观察空间转裁剪空间的过程里得知,w分量实则为z分量的取反值,z分量在观察空间里实则为相机距离顶点的距离,所以才会受到相应的影响。所以我们在外扩先乘以顶点的w分量,那么相机的距离便不再受到影响。

其次我们发现描边的粗细并不一致,其实这是受到了屏幕空间大小的影响,因为在最后NDC转屏幕坐标的时候,我们需要得到屏幕像素的宽度,而这也会对顶点的位置产生影响,所以导致一边宽一边窄。处理的方法就是,把先把近裁剪平面右上角的顶点位置转换到裁剪空间,然后就可以得到宽高比。我们可以选择对x分量y分量进行处理,即把缩放所乘的宽高统一到一个变量控制,即只通过宽或只通过高对增量有影响,就可以避免受到屏幕宽高比的影响。

另外在阅读一篇水墨画渲染时,发现有时候可能会改变相机的FOV,从而也导致描边出现粗细变化,这里跟之前思路一样,在从观察空间转裁剪空间里,相机投影矩阵里包含了FOV的参数,所以法线的xy值在转换的时候会受到影响,所以这里逆向回去,只要提前把xy分量分别除于 unity_CameraProjection[1].y(即cot(FOV / 2)) 就可以保证矩阵里的FOV参数不对偏移量产生影响,至此受相机影响的问题大部分都解决了。

1.3.基于图像处理的描边(Edge Detection by Image Processing)

最后一种描边方法属于后处理方法的一种,在美式渲染里用的比较多。它的原理是通过获取相机的深度图法线图,用自己指定的算子(如Roberts算子Sobel算子等)来评估边缘的程度,然后把该程度与定义的一个阈值来确定它们之间是否是边缘。而获取深度图和法线图在后处理都是很常规的操作,最后通过在shader里进行比对,在脚本里重新绘制画面就可以完成描边了。不过需要注意的是,在前向渲染里,获取深度图和法线图需要重新对屏幕进行渲染,而在延迟渲染里则不需要,所以如果是前者需要留意性能上的问题。下图为典型的图像处理描边。

在这里插入图片描述

2.着色(Shading)

着色风格也是卡渲很重要的一个特点,我们可以大致的分为Cel ShadingTone Based Shading。很多卡通类的游戏的着色风格都是基于这两种着色,又或是在此基础上进行修改,毕竟非真实主义渲染(NPR)更看重的是效果,而非逻辑上的对错。下面就简单介绍一下这两种着色风格。

2.1.卡通着色(Cel Shading)

卡通着色最大的核心就是把多色阶降为低色阶,减少颜色的种类,并且颜色过度会比较,呈现大块状的颜色。而具体的做法一般是由美术定义一张一维的ramp图,ramp图里是几种纯色块,然后通过光照和法线的点积作为坐标进行采样。而如果还想涉及到视角方向的参数作为采样坐标,可以把ramp图定义成二维的,然后v的分量可以定义为视角和法线的点积。下图为典型的卡通着色。

在这里插入图片描述

2.2.基于色调着色(Tone Based Shading)

Tone Based Shading做法上与Cel Shading类似,只不过不是简单的几种色块,而是定义了两个新的概念,冷色调暖色调。而且最终着色也不是大片的纯色块,而是通过光照和法线的点积,即兰伯特模型进行冷暖色调间的插值,得到最终的颜色。下图为典型的着色小球。

在这里插入图片描述

3.卡通渲染风格

卡通渲染也有分许多不同的风格,其中日式卡渲美式卡渲是比较大的两个流派,所以我也制作了两个相应的demo,其中还有许多不同种类的渲染风格,诸如油画素描等,这些就大致讲一下原理,就不会深入讨论了。

3.1.日式卡渲(原神可莉demo)

日式卡渲是卡通渲染里比较大的一条分支了,主要代表作有崩坏3塞尔达传说等等。日式卡渲里用的描边方法主要是几何描边的方法,在GUILTY GEAR Xrd(罪恶装备)里,它们引入了用顶点颜色的四个通道存储描边的一些细节参数和其他着色,这更大增加了轮廓细节上的把控。

而在着色方面,日式卡渲更多用的是大块的纯色作为基调,它把Cel Shading和Tone Based Shading相结合,也引入了冷暖明暗两个色阶,具体还是要看项目里的美术来决定。

在一般情况,我们会定义一个明暗的阈值,在通过参数判断明暗区间,在取相应的明暗色调,下面就简单介绍一下一个日式风格的卡渲,原神可莉demo的一个还原。

首先通过查阅资料,我们得知原神人物的渲染方案与崩3基本一致(毕竟都是米哈游出品),这里先列出使用的贴图素材。

在这里插入图片描述
贴图包含了原色贴图(Base Color)光照贴图(Light Map),阴影色阶图(Ramp),另外还有一张通过sdf生成的人物阴影图。这里重点讲一下Light Map,崩三沿用的是罪恶装备里的方案,它的R、G、B、A通道有着不同的作用,并且把阴影分成了两种层级。第一种为随着光照变化的一级阴影,第二种为不随光照变化固定的二级阴影,因此明暗间的区分就不能像普通的着色方法去实现了。下面就一步步分析。

首先采样主帖图,并且使用shell method方法进行描边,核心代码如下:

float3 viewnormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
float2 NDCnormal = normalize(TransformViewToProjection(viewnormal.xy)) * o.vertex.w;
float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));//将近裁剪面右上角位置的顶点变换到观察空间
float aspect = abs(nearUpperRight.y / nearUpperRight.x);//求得屏幕宽高比
NDCnormal.x *= aspect;
o.vertex.xy += 0.1 * _OutLineScale * NDCnormal.xy;

思路和上面说的一样,先把法线和顶点转换到裁剪空间,然后乘以w分量避免受到相机距离的影响,然后乘以屏幕的宽高比,防止宽高导致描边的不一,最后叠加两个pass即可,效果图如下:

在这里插入图片描述
因为在主帖图的A通道里,含有自发光的mask,所以我们可以定义几个参数来控制自发光的颜色大小等等,最后把它和原颜色加起来即可,具体代码如下

//自发光
float3 EmissionCol = lerp(_Emission,main.rgb,_EmissionK) * main.a * _EmissionScale;

接下来是LightMap的采样也是本次渲染的重点,我们先一一介绍LightMap贴图里的每个通道的作用,因为脸部的LightMap是其他素材,所以我们把它忽略掉,首先输出它的R通道

在这里插入图片描述
经过观察我们可以看到,在一些金属边框上的颜色输出比较亮,在经过查阅资料,我们大概可以理解为R通道的作用是高光强度金属光泽度这么一种感觉,然后输出它的G通道

在这里插入图片描述
我们可以发现输出G通道后,在一些褶皱位置,是全黑的,这非常像我们使用的Ao环境遮罩图,而考虑到我们使用的阴影渲染是二级阴影,所以该贴图通道可以用来作为阴影强度的计算,即与兰伯特模型共同计算阴影的比重,然后输出它的B通道

在这里插入图片描述
可以看到这跟R通道的表现区别不大,唯一的区别在于头发的表现上有所不同,我们可以很明显看出这是高光Mask的作用,借此表现出各向异性高光的头发,所以这跟高光项有关,最后输出它的A通道

在这里插入图片描述
我们可以看到整体被分成了三种不同的颜色,颜色恰好对应了0、128、255三个区间的颜色,考虑到我们还有ramp图的阴影采样素材,而ramp贴图是这样的。

在这里插入图片描述

这里我只截取了部分,可以看到ramp图一共六行,上下各三行。经过查阅资料,我发现原神的ramp图是有分白天和晚上的。也就是说上下的三行分别对应着早上的暖色调,和晚上的冷色调,而三行也恰巧对应着我们的LightMap里A通道的三个区间,那一切就很明了。我们使用A通道里的值与三行分别对应,那么它的身体各部分的阴影采样色就可以得到了。下面列举核心代码:

因为白天和黑夜采样的范围不同,身体和头发的ramp的行数也不同,所以这里使用了变体去控制不同的情况。

                //ramp图冷暖色读取
                float rampu = halfLambert * (1 / _RampRange + 0.03);
                #if _MESH_BODY
                //白天
                #if _DAY
                float4 rampCol[3];
                rampCol[0] = tex2D(_Ramp,float2(rampu,0.95));
                rampCol[1] = tex2D(_Ramp,float2(rampu,0.85));
                rampCol[2] = tex2D(_Ramp,float2(rampu,0.75));
                //黑夜
                #else
                float4 rampCol[3];
                rampCol[0] = tex2D(_Ramp,float2(rampu,0.45));
                rampCol[1] = tex2D(_Ramp,float2(rampu,0.35));
                rampCol[2] = tex2D(_Ramp,float2(rampu,0.25));
                #endif
                float3 Ramp1 = step(abs(LightColor.a * 255 - _Ramp1.x),_RampBlend) * rampCol[_Ramp1.y];
                float3 Ramp2 = step(abs(LightColor.a * 255 - _Ramp1.z),_RampBlend) * rampCol[_Ramp1.w];
                float3 Ramp3 = step(abs(LightColor.a * 255 - _Ramp2.x),_RampBlend) * rampCol[_Ramp2.y];
                float3 finalRamp = Ramp1 + Ramp2 + Ramp3;

这里列举了身体部分的代码,头发的代码和身体基本类同,主要看ramp图来写。首先ramp图采样的u坐标,我们采样常规的兰伯特模型进行采样,并且为了可控,我们添加了 _RampRange 的参数来调节更好的效果。在V坐标上,我们尽量取颜色的中间值,避免混合。采样后我们把所有ramp图里的色阶放进一个数组里,然后根据LightMap里的A通道和一个自定义的阈值进行判断,用step函数判断是否在这个区间,最后把三个ramp颜色相加。这里step函数的意义和用if判断范围是一样的效果。

得到Ramp的颜色后,我们就可以进行阴影的渲染了。首先我们可以通过主帖图颜色乘以一个自定义的阴影颜色得到初始一、二级阴影颜色。考虑到我们使用了ramp图的阴影,我们就把一级阴影与ramp里的色调用ramp图的u坐标(即带参数的兰伯特)进行插值。代码如下:

//一级阴影(光照引起的阴影)
float3 ShallowShadowCol = lerp(finalRamp,_ShallowCol,rampu) * main.rgb;
float3 DarkShadowCol = main.rgb * _DarkCol;

定义完两种阴影颜色后,哪个区域该用那种阴影,我们就可以通过LightMap里的g通道的值进行判断了。首先我们计算一级阴影的比重,通过兰伯特和g通道的值相加与一个自定义的一级阴影的阈值做差并向下取整,最后用比重把阴影色和主帖图做插值混合。二级阴影思路一样,也定义一个二级阴影的阈值,然后一二级阴影进行混合得到二级阴影的颜色值。

最后,我们再定义一个比重,把一二级阴影颜色进行混合,得到最终的颜色值,代码如下:

float threshold = floor(halfLambert + LightColor.g - _ShallowArea);
ShallowShadowCol = lerp(ShallowShadowCol,main.rgb,threshold);
threshold = floor(halfLambert + LightColor.g - _DarkArea);
DarkShadowCol = lerp(DarkShadowCol,ShallowShadowCol,threshold);
//ShallowShadowCol *= RampShadowCol;
threshold = floor(LightColor.g + 0.9f);
float3 finalColor = saturate(lerp(DarkShadowCol,ShallowShadowCol,threshold));

最后得到的效果图如下:

在这里插入图片描述

接下来我们进行高光的计算,高光计算采用Blinn-Phong模型,然后乘以一个自定义的高光颜色高光强度、LightMap的R、B通道,最后把高光加到最终颜色上即可,代码很常规如下:

//高光计算
float3 specular = pow(max(0,dot(N,H)),_Gloss) * _SpecularCol * _SpecularScale * LightColor.b * LightColor.r;

效果图如下:

在这里插入图片描述
最后我们可以观察到,在游戏里,光线从背后照射时,人物的边缘有一些发光的效果,即边缘光,而说到边缘我们就可以使用菲涅耳系数作为边缘光的比重。又考虑到光线与人物视线在同一侧时,不应该有边缘光,所以我们可以使用视角V和L的点积作为一个系数进行判断,最后代码如下:

//边缘光
float Fresnel = fresnelSchlick(dot(N,V),float3(0.04,0.04,0.04)) * _FresnelScale * saturate(1 - dot(L,V));
finalColor *= (1 + Fresnel);

效果如下:

在这里插入图片描述
最后是我们人物脸部阴影的处理,这里借鉴了网上的一个小trick,可以适用于大多数的卡通人物脸部阴影。具体的方法是,利用生成的多张sdf图进行融合,然后通过光线和人物向前的方向的角度,判断是否使用阴影,用光线和人物向右的方向的角度,判断阴影采样的值,而这个方向,我是使用了脚本传进来的值,具体可以参考我另一篇生成sdf的文章和最后给的链接,核心代码如下:

                #if _MESH_FACE
                _ForwardDir = normalize(_ForwardDir);
                _RightDir = normalize(_RightDir);
                float2 lightData = float2(LightColor.r,tex2D(_LightMap,float2(-i.uv.x,i.uv.y)).r);
                lightData = pow(abs(lightData),_FaceShallowSpeed);
                float3 shadowCol = main.rgb * _ShallowCol;
                float ation = step(0.5,1 - (dot(L,_ForwardDir) * 0.5 + 0.5)) * min(step(dot(L,_RightDir),lightData.y),step(-dot(L,_RightDir),lightData.x));
                float3 finalColor = lerp(shadowCol,main.rgb,ation);

效果图如下:

在这里插入图片描述

最后我们在相机里加上bloom后处理,最终的效果如下:

在这里插入图片描述
至此,原神的可莉demo大致是还原的差不多了,当然可能在计算上还会有许多分支或者不同的trick来还原更好的效果。但是我们只需要知道大色块的渲染外扩描边使用ramp图做明暗或冷暖色阶等是日式卡渲的特点即可。

3.2.美式渲染(军团要塞医生demo)

美式渲染对比于日式渲染,最大显著的特点是,它们的色块不再是一片片的,而是经过插值融合,过渡的比较柔和,不再是硬边过渡。美式渲染也有很多经典的作品,诸如军团要塞2DOTA2等等。这里还原了军团要塞2里的一个医生人物demo,下面便一一介绍一下。

跟日式卡渲一样,我们先介绍使用的贴图素材。这里用到的贴图不多,主要有原色贴图法线贴图和一张ramp图。根据参阅军团要塞2的官方技术分享文档,我们可以得知,它们把着色分为了两个模块,分别为view dependent termview independent term。其实我们很明显可以知道,实则就是漫反射项高光反射项

首先view dependent term的计算公式如下:

在这里插入图片描述

我们可以看到该公式实则为兰伯特的一个变种,它把漫反射分量改为了warpped diffuse,然后A(n)我们可以看作是间接光的漫反射项,这样一看思路就非常的清晰了。

其中Kd为物体的反射率,即物体的原色贴图。A(n)为物体环境光部分的漫反射,通过用法线对Ambient cube进行采样。Ci为光照颜色,α、β、γ这三个参数实则是对兰伯特项(n和l的点积)的缩放偏移指数项。而w()是把兰伯特项转换成RGB颜色值的一个函数,最后再进行光照的累加

考虑到并没有拿到Mask的贴图,所以我们用一些参数进行代替,当然效果肯定是不如用贴图精细控制要好。这里我们把w()函数用采样一张一维ramp图进行代替来作为阴影的一个颜色值。

在这里插入图片描述
因为在论文中,A(n)是使用了一个trick,制作了多张Ambient Cube进行采样,这里我就稍微简化一下,直接采样天空盒,核心代码如下:

float3 warpDiffuse = _LightColor0.rgb * tex2D(_RampTex,float2(pow(saturate(_LambertA * NdotL + _LambertS),_LambertN),0.5)) * 2;
float3 iblDiffuse = DecodeHDR(UNITY_SAMPLE_TEXCUBE(unity_SpecCube0,N),unity_SpecCube0_HDR).rgb;
warpDiffuse += iblDiffuse;

效果如下:

在这里插入图片描述

view independent term的计算公式如下:

在这里插入图片描述
该公式计算的实则是一个高光反射和一个边缘光,而在大括号外也加了一个A(v),我把它理解为间接光的高光反射项,这样思路也变得清晰起来。

同样Ci为光照颜色,Ks为通过贴图采样得到的高光的Mask。Ri为反射方向,Fs为控制高光的菲涅耳项,Kspec为通过贴图采样得到的高光指数。Fr为另一个控制边缘光的菲涅耳项,Kr为从贴图采样得到的边缘光的Mask,Krim为边缘光的指数项。此外还引入了一个不受高光项干扰的边缘光和一个对间接光高光反射的影响。u为世界空间内向上的单位向量,**A(v)**即为用视线对ambient cube进行采样。

同样知道这些参数后,我们便可以把代码写出,核心代码如下:

float3 Fresnel = fresnelSchlick(NdotV,float3(0.04,0.04,0.04));
float3 Specular = _LightColor0.rgb * max(_SpecularColor *_SpecularF * pow(max(0,VdotR),_SpecularE),_RimColor * Fresnel.r * _RimF * pow(VdotR,_RimE));
float3 drim = dot(N,float3(0,1,0)) * Fresnel.r * _RimF * _RimColor * DecodeHDR(UNITY_SAMPLE_TEXCUBE(unity_SpecCube0,V),unity_SpecCube0_HDR);

其中Specular为高光项,drim为独立于高光影响的边缘光,里面也包含了对天空盒的采样(近似模拟间接光的效果),最后我们把漫反射系数乘以原颜色,在和高光项和边缘光项相加即可得到最终结果。效果图如下:

在这里插入图片描述
最后因为文章没有提到描边的做法,所以我尝试了用美式渲染里用的比较多的基于图像的描边,即获取它的深度图和法线图,然后进行阈值判断。基于图像的后处理描边网上也非常多,在入门精要里也有案例,下面就列出一个核心判断代码:

half CheckSame(half4 center,half4 sample)
        {
            half2 centerNormal = center.xy;
            float centerDepth = DecodeFloatRG(center.zw);
            half2 sampleNormal = sample.xy;
            float sampleDepth = DecodeFloatRG(sample.zw);
            half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
            int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
            float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;
            int isSameDepth = diffDepth < 0.1 * centerDepth;
            return isSameNormal * isSameDepth ? 1.0 : 0.0;
        }

直接通过判断法线的差值和深度的差值分别与对应参数的阈值相比,返回的颜色为屏幕像素或是黑色像素。

最终的效果如下:

在这里插入图片描述
最后总结一下,美式渲染对比于日式渲染来说,在色彩上更多是插值融合软边比较多,不过每个项目都有不同的trick,归根结底还是对色彩和风格的把控,物理层面基本上不会涉及到。

3.3.油画卡渲

油画是西方绘画的一种典型代表,它的特点是色彩比较丰富,很多种颜色堆叠在一起,在视觉上会呈现出一种模糊的效果。油画不追求层次,且它有明显的边缘。所以在图像处理中,使用了一种滤波算法,定义了滤波半径灰度区间两个参数,具体可以最后的链接进行了解。

在这里插入图片描述

3.4.素描卡渲

素描风格的卡渲也是非常流行的一种卡通渲染,它的核心思想是通过定义不同的密度的笔触的贴图,然后通过兰伯特模型作为参数进行采样,越暗的地方自然采样密度越厚的贴图,而越亮的地方则相反。为了节省资源,我们可以把对应不同阶级的密度的渐进纹理存储在一张图的R、G、B通道中。纹理如下图。

在这里插入图片描述

3.5.水墨画卡渲

对于水墨画,我们更多的印象是笔画比较飘逸,所以我们可以在描边过程中,添加一个采样噪波图的参数,实现笔画的一个无序感。此外还可以定义多个pass,来对描边进行更进一步精细的把控。其次在着色上,我们对像素的颜色进行平滑的过度,不过实现起来的效果还是要根据模型和和贴图来看,具体效果因人而异。

在这里插入图片描述

总结:

对于卡渲来说,trick永远是最大的武器,只要效果做的够好看,你就是对的。因为里面更多是涉及到艺术层面,而不像PBR一样,要遵循物理层面的规律,所以只要有好点子,你就能做出好看的卡渲。

参考:

1.卡通渲染及其相关技术
2.非真实感渲染
3.原神角色渲染Shader分析还原
4.【从零到零点一的原神卡渲还原】记录还原的尝试和思考
5.【从负一到零的卡通渲染】卡渲笔记
6.【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密
7.[译] 崩坏3的卡通渲染实现方式拆解
8.神作面部阴影渲染还原
9.【01】从零开始的卡通渲染-描边篇
10.【03】卡通渲染LightMap的使用
11.Unity-一个简单的水墨渲染方法
12.Unity硬表面模型描边断裂问题解决过程记录
13.【Job/Toon Shading Workflow】自动生成硬表面模型Outline Normal
14.Illustrative Rendering in Team Fortress 2
15.Energy-Conserving Wrapped Diffuse
16.Extension to Energy-Conserving Wrapped Diffuse

标签:贴图,采样,渲染,阴影,描边,卡通,小结,float3
来源: https://blog.csdn.net/the_shy33/article/details/119462015