three.js, WebGPU의 storage 데이터를 CPU와 GPU에서 변경하기
일단 GPU 단에 storage를 생성하기 위해서는 다음과 같은 코드가 필요합니다.
import * as THREE from 'three/webgpu';
import { storage, compute, float, Fn } from 'three/tsl';
const count = 4;
const initialData = new Float32Array([1.0, 2.0, 3.0, 4.0]);
const storageAttr = new THREE.StorageBufferAttribute(initialData, 1);
const storageNode = storage(storageAttr, 'float', count);
이 상태에서 CPU에서 storage의 값을 변경하기 위한 코드 예시는 다음과 같습니다.
// CPU 배열의 첫 번째 값을 100.0으로 변경
storageAttr.array[0] = 100.0;
// 변경 사항이 있음을 렌더러/GPU에 알림
storageAttr.needsUpdate = true;
GPU에서 변경하는 코드 예시는 다음과 같구요.
// 첫 번째 값에 1을 더함 storageNode.element(0).addAssign(1.0);
다음 코드는 storage 데이터 전체를 CPU에서 접근할 수 있도록 비동기적으로 읽는 코드입니다.
async function readDataFromGPU(renderer) {
// GPU 버퍼 데이터를 CPU TypedArray로 가져옴
const arrayBuffer = await renderer.getArrayBufferAsync(storageAttr);
const cpuResultArray = new Float32Array(arrayBuffer);
console.log("CPU에서 읽어온 GPU 스토리지 값:", cpuResultArray);
}
(코드 검증이 필요함)
three.js, 특정 각도에 항상 수렴하도록 하는 코드
const tick = () => {
timer.update()
const deltaTime = Math.min(1 / 30, timer.getDelta())
const wantedDegree = 0;
mesh.rotation.x +=
(THREE.MathUtils.degToRad(wantedDegree) - mesh.rotation.x) * deltaTime
// Render
renderPipeline.render()
}
위의 코드는 mesh를 x축에 대한 rotation이 변경될 경우 점진적으로 wantedDegree 값으로 복구하도록 하는 코드입니다. 이 코드는 각도 뿐만 아니라 일반적인 값에도 적용할 수 있습니다.
보호된 글: three.js, TSL 코드 : 구체 표면에 무작위로 분포하는 좌표
three.js, WebGPU에서 지오메트리를 구성하는 좌표에 파티클 배치하기

위의 결과를 위해서 3가지 방식이 있습니다. 첫번째는 …
private setupModel() {
const knotGeometry = new THREE.TorusKnotGeometry(1, 0.3, 256, 64);
const positionAttribute = knotGeometry.attributes.position;
const count = positionAttribute.count;
const instancePositions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
instancePositions[i * 3] = positionAttribute.getX(i);
instancePositions[i * 3 + 1] = positionAttribute.getY(i);
instancePositions[i * 3 + 2] = positionAttribute.getZ(i);
}
const spriteMaterial = new THREE.SpriteNodeMaterial({
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending
});
const dist = uv().distance(vec2(0.5, 0.5));
const alphaNode = dist.smoothstep(0.5, 0.0);
spriteMaterial.colorNode = color(0xff0000);
spriteMaterial.opacityNode = alphaNode;
const instancePositionNode = instancedBufferAttribute(
new THREE.InstancedBufferAttribute(instancePositions, 3)
);
spriteMaterial.positionNode = instancePositionNode;
spriteMaterial.scaleNode = vec2(.05);
// const spriteGeometry = new THREE.PlaneGeometry(0.08, 0.08);
// const instancedSprites = new THREE.Mesh(spriteGeometry, spriteMaterial);
const instancedSprites = new THREE.Sprite(spriteMaterial); // 상기 코드와 동일
instancedSprites.count = count;
this.scene.add(instancedSprites);
}
두번째 방식은 ...
private setupModel() {
const knotGeometry = new THREE.TorusKnotGeometry(1, 0.3, 256, 64);
const positionAttribute = knotGeometry.attributes.position;
const count = positionAttribute.count; // 정점 개수
const spriteMaterial = new THREE.SpriteNodeMaterial({
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending
});
const dist = uv().distance(vec2(0.5, 0.5));
const alphaNode = dist.smoothstep(0.5, 0.0);
spriteMaterial.colorNode = color(0xff0000);
spriteMaterial.opacityNode = alphaNode;
spriteMaterial.vertexNode = billboarding({
horizontal: true,
vertical: true,
horizontalRotation: false
});
const spriteGeometry = new THREE.PlaneGeometry(0.05, 0.05);
const instancedSprites = new THREE.InstancedMesh(spriteGeometry, spriteMaterial, count);
const dummy = new THREE.Object3D();
const vertex = new THREE.Vector3();
for (let i = 0; i < count; i++) {
vertex.fromBufferAttribute(positionAttribute, i);
dummy.position.copy(vertex);
dummy.updateMatrix();
instancedSprites.setMatrixAt(i, dummy.matrix);
}
instancedSprites.instanceMatrix.needsUpdate = true;
this.scene.add(instancedSprites);
}
세번째 방식은 다음과 같습니다.
private setupModel() {
const knotGeometry = new THREE.TorusKnotGeometry(1, 0.3, 256, 64);
const positionAttr = knotGeometry.attributes.position;
const count = positionAttr.count;
const positionArray = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
positionArray[i * 3] = positionAttr.getX(i);
positionArray[i * 3 + 1] = positionAttr.getY(i);
positionArray[i * 3 + 2] = positionAttr.getZ(i);
}
const positionStorageBuffer = new THREE.StorageBufferAttribute(positionArray, 3);
const positionStorageNode = storage(positionStorageBuffer, 'vec3', count);
const spriteMaterial = new THREE.SpriteNodeMaterial({
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending
});
const dist = uv().distance(vec2(0.5, 0.5));
const alphaNode = dist.smoothstep(0.5, 0.0);
spriteMaterial.colorNode = color(0xff0000);
spriteMaterial.opacityNode = alphaNode;
spriteMaterial.positionNode = positionStorageNode.element(THREE.TSL.instanceIndex);
const spriteGeometry = new THREE.PlaneGeometry(0.08, 0.08);
const instancedSprites = new THREE.Mesh(spriteGeometry, spriteMaterial);
instancedSprites.count = count;
this.scene.add(instancedSprites);
}
