threejs-卡通轮廓效果(threejs模型轮廓发光)
在我的 3D 游戏和设计中,我经常选择可爱的低多边形卡通风格。我一直想给我的模型一个真正的卡通轮廓,所以这就是我们今天要做的工作。在以后的文章中,我们将着眼于为三角形着色以使其看起来也很卡通。
本文是「我正在进行的中等难度 ThreeJS 教程系列」的一部分。我一直想要在介绍“如何绘制立方体”和“让我们用疯狂的着色器填充屏幕”关卡之间找到一些东西。所以就在这里。
如果你在网上搜索“OpenGL 轮廓效果”,你会遇到很多相互矛盾的信息。经过大量研究,我确定有两种常用方法可以使用现代 3D 图形 API 创建轮廓。
- 绘制一个对象两次;一次是轮廓颜色,一次是正常。
- 通过在像素级别检测边缘的后处理效果运行整个场景。
第二种选择是当今 Unity 等现代游戏引擎中最常用的。但是我不想使用它,因为它涉及多个后处理步骤,这在移动 GPU 上可能很慢并且消耗更多内存。此外,后处理和 WebVR 目前还没有很好地融合,所以我暂时避免使用它。(当我介绍发光效果时,我们会重新讨论这个)。让我们关注第一种方法,两次绘制同一个对象。
两次渲染一个对象可能看起来很浪费,但请记住,大多数 GPU 都是带宽有限的。一旦你把几何图形传到 GPU 上,它就可以一遍又一遍地渲染同样的东西,几乎没有成本。在大多数情况下,我想概述的东西是静态几何。
让我们从两次渲染圆环结开始,一次是黑色,一次是黄色。完成这项工作的这个技巧是缩放轮廓,使其比主要对象略大。
//create a cubeobj = new THREE.Group()const c1 = new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshLambertMaterial({ color:’black’, }))const s = 1.03c1.scale.set(s,s,s)obj.add(c1)obj.add(new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshPhongMaterial({ color:’yellow’, })))
运行它,看看会发生什么。唔。根据您使用的几何形状,您将看到全黑或双色调、部分黑色和部分黄色的东西。还注意到一些从黄色中突出的黑色三角形吗?这就是所谓的z战斗。那么问题是什么。
实际上,这有点道理。黑结略微扩大,因此在看不到黄色的地方,黑夜就在它的前面。那么我们该如何解决呢?
15 秒内解释剔除
我们将利用一个小技巧。
当 GPU 渲染三角形时,它通常只渲染正面的三角形。这些是面向相机的三角形。根据定义,任何背对相机的三角形都是不可见的,因此无需费心绘制它们。如果我们有一个球体,那么实际上只会绘制前半球。GPU 已「剔除」构成背面半球的三角形。
对于轮廓效果,我们希望仅使用正面几何图形绘制常规对象。这已经在发生。但是,对于轮廓,我们只希望绘制背面的三角形。然后它们将位于规则形状的后面,仅在边缘可见。
碰巧的是,ThreeJS 已经知道如何绘制正面和背面。我们只需要告诉它我们想要什么。下面的代码和上面一样,只是side正确设置了两种材质的属性。
obj = new THREE.Group()const c1 = new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshLambertMaterial({ color:’black’, side: THREE.BackSide }))const s = 1.03c1.scale.set(s,s,s)obj.add(c1)obj.add(new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshPhongMaterial({ color:’yellow’, side: THREE.FrontSide })))
现在看起来像这样:
image.png
完美的!
修复法线
实际上不,它不是很完美。如果你仔细观察,你会发现物体背面的轮廓比正面的轮廓要细。那是因为我们只是在放大整个对象。这种幼稚的方法只适用于像球体这样的完美凸面物体。对于结或任何真实模型,我们需要正确地加厚几何图形。
碰巧大多数几何图形上已经有法线。这些法线垂直于几何体的表面。如果我们在法线方向上扩展点,那么一切都应该正常工作。我们可以通过稍微修改的顶点着色器来做到这一点。有关自定义顶点着色器的说明,请参阅其他文章。
//create a cubeconst mat = new THREE.MeshLambertMaterial({ color:’black’, side:THREE.BackSide })mat.onBeforeCompile = (shader) => { const token = ‘#include <begin_vertex>’ const customTransform = ` vec3 transformed = position objectNormal*0.02; ` shader.vertexShader = shader.vertexShader.replace(token,customTransform)}
上面的代码为轮廓对象创建了一个自定义材质。其余与之前相同。在着色器内部,它objectNormal向每个顶点的位置添加一小部分,将其向外扩展。将 更改0.02为更大的值以获得更粗的轮廓。
现在看起来像这样:
image.png
壮丽的。您已经创建了一个类似于卡通的轮廓。