Draw Cloud
一个简单的云体渲染示例,这里主要用到了实例化渲染技术。
一个简单的示例
Result
Loading...
Live Editor
function render(props) { const drawVertex = `attribute vec2 uv; attribute vec3 position; attribute vec3 offset; attribute vec3 random; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; varying vec2 vUv; void rotate2d(inout vec2 v, float a){ mat2 m = mat2(cos(a), -sin(a), sin(a), cos(a)); v = m * v; } void main() { vUv = uv; // copy position so that we can modify the instances vec3 pos = position; pos *= 0.5 + random.z * random.z * 1.5; // rotate around y axis rotate2d(pos.xz, random.y / 2.0); // rotate around x axis just to add some extra variation rotate2d(pos.zy, random.x * 3.14); pos += offset; gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); } `; const drawFragment = `precision highp float; uniform sampler2D texture; uniform vec3 u_fogColor; uniform float u_fogNear; uniform float u_fogFar; varying vec2 vUv; void main() { float depth = gl_FragCoord.z / gl_FragCoord.w; float fogFactor = smoothstep(u_fogNear, u_fogFar, depth); vec4 tex = texture2D(texture, vUv); gl_FragColor = tex; gl_FragColor.w *= pow(gl_FragCoord.z, 20.0); gl_FragColor = mix(gl_FragColor, vec4(u_fogColor, gl_FragColor.w), fogFactor); // gl_FragColor = vec4(0.5, 1.0, 0.5, 1.0); } `; const refDom = useRef(null); const meshRef = useRef(null); const cameraRef = useRef(null); const renderRef = useRef(null); const mousePos = useRef([0, 0]); const store = leva.useCreateStore(); const fov = 30; const nearZ = 1; const farZ = 3000; const config = { fov: { value: fov, min: -50, max: 50, step: 1, onChange: (fov) => { if (cameraRef.current) { cameraRef.current.fov = fov; } }, }, nearZ: { value: nearZ, min: -50, max: 50, step: 0.1, onChange: (nearZ) => { if (cameraRef.current) { cameraRef.current.near = nearZ; } }, }, farZ: { value: farZ, min: -500, max: 500, step: 1, onChange: (farZ) => { if (cameraRef.current) { cameraRef.current.far = farZ; } }, }, cameraPosition: { value: [0, 0, 15], onChange: (p) => { if (cameraRef.current) { cameraRef.current.position.set(...p); } }, }, wireframe: { value: false, onChange: (p) => { if (meshRef.current) { meshRef.current.wireframe = p; } }, } }; leva.useControls(config, { store: store, }); const image = useBaseUrl('/assets/cloud.png'); const init = () => { const canvas = refDom.current; canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; const renderer = new Renderer(canvas, { alpha: true, antialias: true, premultipliedAlpha: true, }); renderer.state.setClearColor(new Color(0, 0, 0, 0)); renderRef.current = renderer; const camera = new PerspectiveCamera(fov, canvas.width / canvas.height, nearZ, farZ); cameraRef.current = camera; function resize(target) { const { width, height } = target.getBoundingClientRect(); renderer.setSize(width, height); camera.aspect = width / height; } const scene = new Scene(); const planeGeometry = new Plane(renderer, { width: 64, height: 64, widthSegments: 1, heightSegments: 1, }); const texture = new Texture(renderer, { flipY: true, width: 256, height: 256, minFilter: renderer.gl.LINEAR_MIPMAP_LINEAR }); texture.fromSrc(image); const program = new Program(renderer, { vertexShader: drawVertex, fragmentShader: drawFragment, uniforms: { texture: { value: texture, }, u_fogColor: { value: [0.27058823529411763, 0.5176470588235295, 0.7058823529411765], }, u_fogNear: { value: -100, }, u_fogFar: { value: 3000, }, }, cullFace: renderer.gl.BACK, depthTest: false, depthWrite: false, transparent: true, blendFunc: { src: renderer.gl.SRC_ALPHA, dst: renderer.gl.ONE_MINUS_SRC_ALPHA, srcAlpha: renderer.gl.ONE, dstAlpha: renderer.gl.ONE_MINUS_SRC_ALPHA, }, blendEquation: { modeRGB: renderer.gl.FUNC_ADD, modeAlpha: renderer.gl.FUNC_ADD, }, }); const num = 1000; let offset = new Float32Array(num * 3); let random = new Float32Array(num * 3); for (let i = 0; i < num; i++) { offset.set([Math.random() * 1000 - 500, -Math.random() * Math.random() * 200 - 15, i], i * 3); // unique random values are always handy for instances. // Here they will be used for rotation, scale and movement. random.set([Math.random(), Math.random(), Math.random()], i * 3); } const geometry = new Geometry(renderer, { position: planeGeometry.attributesData.position, uv: planeGeometry.attributesData.uv, normal: planeGeometry.attributesData.normal, index: planeGeometry.attributesData.index, offset: { divisor: 1, size: 3, data: offset }, random: { divisor: 1, size: 3, data: random }, }); const mesh = new Mesh(renderer, { mode: renderer.gl.TRIANGLES, geometry, program, }); mesh.setParent(scene); const plane = new Mesh(renderer, { geometry, program, wireframe: false, }); plane.setParent(scene); meshRef.current = plane; const raf = new Raf((t) => { const position = t * 30 % 1000; const [mouseX, mouseY] = mousePos.current; camera.position.x += (mouseX - camera.position.x) * 0.01; camera.position.y += (-mouseY - camera.position.y) * 0.01; camera.position.z = -position + 1000; renderer.render({ scene, camera }); }); return { canvas, resize, } } function onDocumentMouseMove(event) { if (!refDom.current) return; const windowHalfX = refDom.current.clientWidth / 2; const windowHalfY = refDom.current.clientHeight / 2; const mouseX = (event.clientX - windowHalfX) * 0.25; const mouseY = (event.clientY - windowHalfY) * 0.15; mousePos.current = [mouseX, mouseY]; } useEffect(() => { const { canvas, resize } = init(); observe(canvas, resize); document.addEventListener('mousemove', onDocumentMouseMove, false); const cv = document.createElement('canvas'); cv.width = 32; cv.height = window.innerHeight; const ctx = cv.getContext('2d'); const gradient = ctx.createLinearGradient(0, 0, 0, cv.height); gradient.addColorStop(0, '#1e4877'); gradient.addColorStop(0.5, '#4584b4'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, cv.width, cv.height); canvas.style.background = 'url(' + cv.toDataURL('image/png') + ')'; canvas.style.backgroundSize = '32px 100%'; return () => { unobserve(canvas, resize); document.removeEventListener('mousemove', onDocumentMouseMove, false); }; }, []); return ( <div className="live-wrap"> <div className="leva-wrap"> <Leva fill ></Leva> <LevaPanel collapsed store={store} fill></LevaPanel> </div> <canvas className="scene-canvas" ref={refDom}></canvas> </div> ); }