three.js GPGPU SPH - Compute Shader Texture

728x90
반응형

 

기존 Update 파트 

show sphere의 경우 여기는 그래픽 엔진으로 렌더링하지만, 

three.js에서는 mesh로 하고 있다.

 

주의할 것은 unity 프로젝트에서는 shader의 property에 접근해서 매 프레임마다 shader의 값이 얼마인지를 반영할 수 있었는데.

 

이렇게 buffer를 통해서 shader와 CPU 스크립트를 동기화하여,

번거롭게 GPU에서 CPU로 데이터를 전달하지 않아도 되는 방법이다.

 

근데 문제는 three.js에는 이걸 어떻게 대체하냐는 것이다.

 

https://threejs.org/docs/#api/en/textures/DataTexture

 

three.js docs

 

threejs.org

찾아보니 DataTexture라는 타입을 이용해서 

GPU에 전달하는 buffer처럼 사용하는 모양이다. 

 

가면 갈수록 어려워지는 기분이다. 

 

Compute Shader 작성하기 

유니티에서는 HLSL로 compute shader를 작성한다.

three.js는 기본 webGL이기 때문에 GLSL shader를 사용하는데

 

GLSL에는 HLSL과 달리 kernel이라는 기능이 없다.

CPU에서 특정 GPU의 함수를 dispatch하는게 아니라는 말이다.

 

아직 감이 잘 안잡히지만, 일단 작성을 해보자.

 

shader 변수 추가하기 

// Compute Shader에 변수를 전달
const fragmentShader = `
    void main() {
        vec2 uv = gl_FragCoord.xy / resolution.xy;
        vec4 color = texture2D(texture, uv);
        gl_FragColor = color;
    }
`;

// Compute Shader에 텍스처 전달
const variable = gpuCompute.addVariable('texture', fragmentShader, texture);

잠깐 코드를 보자. 

compute shader에 특별히 선언하지 않더라도

addVariable을 통해서 변수를 추가했다면 shader 안에서 그대로 사용할 수 있다.

 

하지만 그 형태는 기본적으로 texture sampler형태로 shader에 들어간다. 

float이든 vector 든 마음대로 바꿔 쓰려면 어떻게 할까? 

 

GPUComputationRenderer 사용법 

this.textureData = this.gpuCompute.createTexture();
this.textureData.image.data;
this.textureVariable = this.gpuCompute.addVariable('textureData', computefragment, this.textureData);
this.gpuCompute.setVariableDependencies(this.textureVariable, [this.textureVariable]);

우선 compute shader에 들어가는 변수는 DataTexture 형태로 만들어서 넣고,

compute shader에서 연산한 이후 Variable이라는 GPUComputeRenderer의 타입으로 변환되어 CPU로 가져온다.

 

material.uniforms.uTexture.value = gpuCompute.getCurrentRenderTarget(textureVariable).texture;

이렇게 가져온 variable의 texture를 shader의 uniform texture에 반영해서,

다음 GPU computing에서 사용하는 방식이다.

 

texture2D

vec4 color = texture2D(uTexture, uv);

texture2D에 sampler와 value가 들어가는데, 이 관계가 어떻게 되는 거더라? 

 

https://thebookofshaders.com/glossary/?search=texture2D

 

The Book of Shaders

Gentle step-by-step guide through the abstract and complex universe of Fragment Shaders.

thebookofshaders.com

 

texture2D는 sampler가 되는 texture에서 value에 해당하는 coordination의 값을 가져오는 것이다.

 

vec4 color = texture2D(uTexture, uv);

즉 위 코드를 기준으로 보면,

uTexture 상의 uv 좌표의 값을 color에 저장하는 것이다.

 

 

// cpu
const dtPosition = gpuCompute.createTexture();
const positionVariable = gpuCompute.addVariable('uCurrentPosition', computefragment, dtPosition )

...

// shader
vec2 position = texture2D( uCurrentPosition, vUv ).xy;

이전에 작업했던 코드를 보면,

uCurrentPosition은 CPU에서 texture를 생성하고 바로 넣어준 것이라 값이 없을 터이다.

 

즉 shader에서 각 texture에서 position에 들어가는 초기 xy 좌표는 모두 (0,0)일 것이다.

uniform sampler2D uTexture;

//vertexshader
void main()
{
    vec3 newpos = position;
    vec4 color = texture2D(uTexture, uv);

    newpos.x += color.x;

    vec4 mvPosition = modelViewMatrix * vec4(newpos, 1.0);
    gl_PointSize = 10.0 / -mvPosition.z;
    gl_Position = projectionMatrix * mvPosition;
}

이걸 vertex shader에서 gpu computation을 거듭하면서 1씩 증가시킨 것이다.

 

// 렌더 타겟에서 텍스처를 가져오는 함수
function readTextureData(renderer: THREE.WebGLRenderer, renderTarget: THREE.WebGLRenderTarget) {
  const width = renderTarget.width;
  const height = renderTarget.height;
  const size = width * height * 4; // RGBA 4채널
  const pixelBuffer = new Float32Array(size); // 텍스처 데이터를 저장할 배열

  // WebGLRenderer를 사용해 렌더 타겟의 픽셀 데이터를 읽어오기
  renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, pixelBuffer);

  // 픽셀 데이터 콘솔 출력 (RGBA 값)
  console.log(pixelBuffer);
}

texture를 render target으로 설정해서 data를 buffer에 담아오는 함수이다.

이렇게 buffer에 데이터를 담아서 직접 확인해볼 수 있다.

 

이렇게 shader texture의 사용법을 알아봤으니, SPH의 hlsl 코드를 반영해볼까.

 

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유