本文正在参加「金石计划 . 瓜分6万现金大奖」
前言
相信很多人都见过2D的刮刮乐,甚至不少同学都自己写过2d的刮刮乐开奖页面。我们通常是用canvas来实现,通过将上面一层蒙版清除,露出底下的HTML(中奖结果,大概率是再接再厉)。看了那么久的2d的相信大家都看腻了吧。今天就来做个3d的刮刮乐,玩玩吧。
(效果如下)
代码地址
https://code.juejin.cn/pen/7159077867966431272
首先
在这里我使用threejs来实现这个3d的效果。首先,我开始搭建场景。基本的scene+light+renderer,渲染出一个threejs场景的canvas的dom元素加到html中。
`container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
camera.position.set(0, 200, 350);
camera.lookAt(0, 0, 0);scene = new THREE.Scene();
const sun = new THREE.DirectionalLight(0xFFFFFF, 1.0);
sun.position.set(300, 400, 175);
scene.add(sun);
const sun2 = new THREE.DirectionalLight(0x40A040, 0.6);
sun2.position.set(- 100, 350, - 200);
scene.add(sun2);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
stats = new Stats();
container.appendChild(stats.dom);
container.style.touchAction = 'none'; `
接下来,重头戏来了。
我们开始给场景添加刮刮乐的mesh。由于刮刮乐被刮开之后的形状的随机性,以及顶点数据不断变化的复杂性。我们这里使用shader(着色器)来实现。 和传统2d的刮刮乐一样,我们也是底下一层上面有中奖信息(或者再接再励),上面是一层蒙版。不同是这是两层mesh。底部的mesh,可以直接使用CanvasTexture,将文字信息贴到底层的plant上方的面,就可以了。这部分比较简单,就不赘述了。我们着重讲下顶上的蒙层究竟是如何实现:
首先排除threeBsp,因为鼠标在不断移动的时候,需要在蒙版上一个一个圆形扣除,数据的计算量非常大,导致刮刮乐直接变成卡卡乐。我们只能使用ShaderMaterial来实现。因为着色器的性能较好,直接gpu并行处理。
了解threejs的童鞋们应该知道高度图吧,我们在表示复杂的地形场景的时候,经常使用颜色的深浅来表示高度的层次。这里就是通过高度图的方式来进行抠图,直接把上面蒙层的高度(离屏渲染动态改变的高度图)改变,这样就可以露出了下面的开奖结果了。如此,我们只需要在高度图上,根据鼠标经过的点坐标,给高度图上绘制上颜色,整个功能就完成了。
思路捋顺了,事情已经完成了一大半,接下来就开始了枯燥的coding~~
1.定义一个离屏渲染的高度图
this.heightmapVariable = this.gpuCompute.addVariable(
'heightmap',
`#include <common>
uniform vec2 mousePos;
uniform float mouseSize;
uniform float viscosityConstant;
uniform float heightCompensation;
void main() {
vec2 cellSize = 1.0 / resolution.xy;
vec2 uv = gl_FragCoord.xy * cellSize;
// heightmapValue.x == height from previous frame
// heightmapValue.y == height from penultimate frame
// heightmapValue.z, heightmapValue.w not used
vec4 heightmapValue = texture2D( heightmap, uv );
// Get neighbours
vec4 north = texture2D( heightmap, uv + vec2( 0.0, cellSize.y ) );
vec4 south = texture2D( heightmap, uv + vec2( 0.0, - cellSize.y ) );
vec4 east = texture2D( heightmap, uv + vec2( cellSize.x, 0.0 ) );
vec4 west = texture2D( heightmap, uv + vec2( - cellSize.x, 0.0 ) );
// https://web.archive.org/web/20080618181901/http://freespace.virgin.net/hugo.elias/graphics/x_ggl.htm
// float newHeight = ( ( north.x + south.x + east.x + west.x ) * 0.5 - heightmapValue.y ) * viscosityConstant;
float newHeight = -1.0;
// Mouse influence
float mousePhase = clamp( length( ( uv - vec2( 0.5 ) ) * BOUNDS - vec2( mousePos.x, - mousePos.y ) ) * PI / mouseSize, 0.0, PI/8.0 );
newHeight -= ( cos( mousePhase ) - 1.0 ) * 32.0;
// heightmapValue.y = heightmapValue.x;
if (newHeight < heightmapValue.x) {
heightmapValue.x = newHeight-2.0;
}
gl_FragColor = heightmapValue;
}`
);
2.通过uniforms传给着色器材质使用。
3.通过GPUComputationRenderer渲染高度图
// this.gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, this.renderer);
//之前注册过这个属性
this.gpuCompute.setVariableDependencies(this.heightmapVariable, [
this.heightmapVariable,
]);
this.heightmapVariable.material.uniforms['mousePos'] = {
value: new THREE.Vector2(500, 500),
};
this.heightmapVariable.material.uniforms['mouseSize'] = { value: 400.0 };
//连锁反应
this.heightmapVariable.material.uniforms['viscosityConstant'] = {
value: 0.98,
};
// this.heightmapVariable.material.uniforms[ "heightCompensation" ] = { value: 9 };
this.heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed(1);
4.记录鼠标位置来改变高度图内容,并且改变蒙层
render() {
// Set uniforms: mouse interaction
const uniforms = this.heightmapVariable.material.uniforms;
if (this.mouseMoved) {
this.raycaster.setFromCamera(this.mouseCoords, this.camera);
const intersects = this.raycaster.intersectObject(this.meshRay);
if (intersects.length > 0) {
const point = intersects[0].point;
uniforms['mousePos'].value.set(point.x, point.z);
} else {
uniforms['mousePos'].value.set(10000, 10000);
}
this.mouseMoved = false;
} else {
uniforms['mousePos'].value.set(10000, 10000);
}
// Do the gpu computation
this.gpuCompute.compute();
// Get compute output in custom uniform
this.gglUniforms['heightmap'].value =
this.gpuCompute.getCurrentRenderTarget(this.heightmapVariable).texture;
// Render
this.renderer.render(this.scene, this.camera);
},
结束
到这里就结束了,是不是很简单。如果有啥疑问,欢迎评论区留言讨论,大家一起学习进步。