3.js의 많은 물체에는 Needupdate 속성이 있으며 문서에는 거의 쓰여지지 않습니다 (3.js에는 많은 문서가 없으며 많은 문제가 여전히 Github의 문제에 의존해야합니다). 간단한 입문 프로그램의 경우이 속성을 사용할 수 없기 때문에 다양한 튜토리얼에서 온라인으로 작성하는 방법을 모릅니다.
그렇다면이 속성은 무엇에 사용됩니까? 간단히 말해서, 나는 렌더러 에게이 프레임의 캐시를 업데이트해야한다고 말합니다. 플래그 비트로 사용하는 것은 매우 간단하지만 캐시를 업데이트 해야하는 이유와 업데이트 할 캐시를 알아야하므로 신중하게 이해해야합니다.
왜 Needupdate우선, 왜 캐시가 필요한지 살펴 보겠습니다. 캐시의 존재는 일반적으로 데이터 전송 시간 수를 줄여 데이터 전송에 소요되는 시간을 줄입니다. 여기서는 물체 (메시)가 결국 화면에 성공적으로 표시되는 것이 쉽지 않다는 것도 사실입니다. 전장으로 세 번 옮겨야합니다.
첫 번째는 모든 정점 데이터와 로컬 디스크의 텍스처 데이터를 프로그램을 통해 메모리로 읽는 것입니다.
그런 다음 프로그램이 메모리에서 적절한 처리를 수행 한 후 화면으로 그려야하는 객체의 정점 데이터와 텍스처 데이터를 비디오 메모리로 전송해야합니다.
마지막으로, 각 프레임을 렌더링 할 때 비디오 메모리의 정점 데이터 및 텍스처 데이터는 어셈블리 및 드로잉을 위해 GPU로 플러시됩니다.
피라미드와 같은 데이터 전송 모델에 따르면 첫 번째 단계는 분명히 가장 느립니다. WebGL과 같은 환경에서 네트워크를 통해 전송되면 더 느려집니다. 두 번째는 메모리에서 비디오 메모리까지의 시간이며 나중에 간단한 데이터 테스트입니다.
그런 다음이 세 단계의 사용 빈도가 있습니다. 작은 시나리오의 경우 첫 번째 단계는 일회성입니다. 즉, 프로그램이 초기화 될 때마다 시나리오의 모든 데이터가 메모리에로드됩니다. 대규모 시나리오의 경우 일부 비동기 로딩이 수행 될 수 있지만 현재 우리가 고려하고있는 문제는 아닙니다. 두 번째 단계의 빈도는 이번에 이야기하는 것이 가장 중요한 것이어야합니다. 먼저,이 전송 단계를 수행하여 발생하는 소비를 테스트하는 간단한 프로그램을 작성하십시오.
var canvas = document.createElement ( 'canvas');
var _gl = canvas.getContext ( 'Experimental-webgl');
var vertices = [];
for (var i = 0; i <1000*3; i ++) {
vertices.push (i * math.random ());
}
var buffer = _gl.createBuffer ();
console.profile ( 'buffer_test');
bindbuffer ();
Console.profileend ( 'buffer_test');
함수 bindbuffer () {
for (var i = 0; i <1000; i ++) {
_gl.bindbuffer (_gl.array_buffer, 버퍼);
_gl.bufferdata (_gl.array_buffer, new float32array (정점), _gl.static_draw);
}
}
이 프로그램을 먼저 간단히 설명해 봅시다. 정점은 정점을 저장하는 배열입니다. 여기서는 1,000 개의 정점이 무작위로 생성됩니다. 각 정점에는 3 개의 좌표 x, y 및 z가 있으므로 3000 크기의 배열이 필요합니다. _gl.createbuffer 명령은 비디오 메모리에 정점 데이터를 저장하기위한 캐시를 열고 _gl.bufferdata를 사용하여 생성 된 정점 데이터를 메모리에서 비디오 메모리로 전송합니다. 여기서 우리는 장면에 1000 개의 정점이있는 1000 개의 객체가 있다고 가정하고, 각 정점은 332 비트 4 바이트의 플로트 데이터입니다. 거의 1000 x 1000 x 12 = 11m의 데이터를 계산하십시오. 프로파일은 약 15ms가 소요됩니다. 여기서 우리는 15ms가 얼마나 작은 시간인지 볼 수 있습니다. 그러나 실시간 프로그램의 경우 30fps의 프레임 속도를 보장하려면 각 프레임에 필요한 시간을 약 30ms로 제어해야합니다. 데이터 전송을 수행하는 데 어떻게 절반이 걸릴 수 있습니까? 큰 헤드는 GPU의 드로잉 작업 및 CPU의 다양한 처리 여야한다는 것을 알아야하며 전체 렌더링 프로세스에서 작업의 모든 단계마다 따뜻해 져야합니다.
따라서이 단계의 전송 수는 최소화되어야합니다. 실제로로드 될 때 모든 정점 데이터와 텍스처 데이터를 메모리에서 비디오 메모리로 전송하는 데 사용될 수 있습니다. 이것은 지금 세 개가하는 일입니다. 그려야 할 객체의 정점 데이터는 처음으로 비디오 메모리로 전송되고 버퍼를 지오메트리로 캐시합니다 .__ webglvertexbuffer. 그 후, 당신이 그릴 때마다, 당신은 지오메트리의 verticesneedupdate 속성을 판단하게됩니다. 업데이트 할 필요가 없으면 현재 캐시를 직접 사용하십시오. VerticesneedUpate가 사실이라는 것을 알면 지오메트리의 정점 데이터가 지오메트리로 전송됩니다 .__ webglvertexbuffer. 일반적으로 정적 객체에 대해서는이 단계가 필요하지 않습니다. 그러나 입자 시스템과 같은 정점을 사용하는 것과 같이 자주 변경되는 객체와 골격 애니메이션을 사용하는 메쉬가 발생하면 이러한 객체는 각 프레임에서 정점을 변경하여 각 프레임은 VerticesneedUpdate 속성을 렌더에게 알리려면 렌더러에게 데이터를 검토해야한다고 말해야합니다!
실제로 WebGL 프로그램에서는 정점 셰이더에서 더 많은 정점 위치가 변경되어 입자 효과 및 골격 애니메이션을 완료합니다. JavaScript의 컴퓨팅 능력의 한계로 인해 CPU 측에 배치되면 확장하는 것이 더 쉽지만 이러한 계산 작업 중 더 많은 것이 GPU 측에 배치됩니다. 이 경우 vertex 데이터를 되돌릴 필요가 없으므로 위의 사례는 실제 프로그램에서 많이 사용되지 않으며 텍스처 및 재료 캐시를 업데이트하는 것입니다.
위의 사례는 주로 정점 데이터가 전송되는 시나리오를 설명합니다. 정점 데이터 외에도 질감 인 큰 헤드도 있습니다. 1024*1024 크기의 R8G8B8A8 형식 텍스처는 최대 4m 메모리 크기를 차지해야하므로 다음 예를 살펴보십시오.
var canvas = document.createElement ( 'canvas');
var _gl = canvas.getContext ( 'Experimental-webgl');
var texture = _gl.createtexture ();
var img = 새로운 이미지;
img.onload = function () {
Console.profile ( '텍스처 테스트');
bindtexture ();
Console.profileend ( '텍스처 테스트');
}
img.src = 'test_tex.jpg';
함수 bindTexture () {
_gl.bindtexture (_gl.texture_2d, 텍스처);
_gl.teximage2d (_gl.texture_2d, 0, _gl.rgba, _gl.rgba, _gl.unsigned_byte, img);
}
여기서 1000 번 반복 할 필요가 없습니다. 한 번에 10241024의 질감을 전송하는 데 30ms가 필요하며 256256 사진은 거의 2ms입니다. 따라서 3.js에서는 텍스처는 처음에 한 번만 전송되어야합니다. 그 후, 텍스처가 수동으로 true로 설정되지 않으면 비디오 메모리로 전송 된 텍스처가 직접 사용됩니다.
캐시를 업데이트 해야하는 것위의 내용은 Three.JS가 두 경우를 통해 이러한 Needsupdate 속성을 추가 해야하는 이유를 설명합니다. 다음으로,이 캐시를 수동으로 업데이트하는 데 필요한 상황에서 알아볼 몇 가지 시나리오를 나열하십시오.
텍스처의 비동기 적재프론트 엔드 이미지가 비동기식으로로드되기 때문에 이것은 작은 구덩이입니다. IMG를 생성 한 직후에 texture.needsupdate = true를 작성하는 경우 Three.js 렌더러는 _gl.teximage2d를 사용하여 빈 텍스처 데이터를이 프레임의 비디오 메모리로 전송 한 다음이 플래그를 False로 설정합니다. 그런 다음 이미지가로드되면 비디오 메모리 데이터가 업데이트되지 않습니다. 따라서 텍스처를 작성하기 전에 온로드 이벤트에 전체 이미지가로드 될 때까지 기다려야합니다.
비디오 텍스처대부분의 텍스처는 위의 사례와 마찬가지로 사진이 직접로드 및 전송하는 경우와 마찬가지로 비디오 텍스처에는 적합하지 않습니다. 비디오는 그림 스트림이기 때문에 각 프레임에 표시 될 그림이 다르므로 각 프레임마다 True로 설정을 설정하여 그래픽 카드의 텍스처 데이터를 업데이트해야합니다.
렌더 버퍼를 사용하십시오렌더 버퍼는 비교적 특별한 객체입니다. 일반적으로 전체 장면이 그려진 후 프로그램이 화면으로 직접 플러시됩니다. 그러나 더 많은 포스트 프로세싱 또는 화면 기반 XXX (예 : 화면 기반 주변 발생)가있는 경우 렌더 버퍼에서 먼저 장면을 그려야합니다. 이 버퍼는 실제로 텍스처이지만 디스크에서로드되지 않은 이전 도면에서 생성됩니다. 렌더 버퍼를 초기화하고 저장하기 위해 3.js에는 특수 텍스처 객체 WebGlrenderTarget이 있습니다. 이 텍스처는 또한 각 프레임에서 true로 설정해야합니다.
자료의 Needupdate재료는 3.js에서 3.material로 설명됩니다. 실제로, 자료에는 전송할 데이터가 많지 않지만 왜 NeedupDate를 만들어야합니까? 여기서 셰이더에 대해 이야기하겠습니다. 셰이더는 셰이더로 번역되며 GPU의 정점 및 픽셀을 프로그래밍 할 가능성을 제공합니다. 그림에는 그늘진 용어가 있습니다. GPU의 음영은 비슷합니다. 빛의 빛과 어둠은 대상의 재료를 표현하기 위해 프로그램에 의해 계산됩니다. 자, Shader는 모든 프로그램과 마찬가지로 GPU에서 실행되는 프로그램이므로 컴파일 및 연결 작업을 수행해야합니다. WebGL에서 셰이더 프로그램은 런타임에 컴파일되므로 시간이 걸리므로 프로그램이 끝날 때까지 컴파일하고 실행하는 것이 가장 좋습니다. 따라서 재료가 3.js에서 초기화되면 셰이더 프로그램이 컴파일되고 연결되고 컴파일 링크 후 얻어진 프로그램 객체가 캐시됩니다. 일반적으로 재료는 더 이상 전체 셰이더를 다시 컴파일 할 필요가 없습니다. 재료를 조정하려면 셰이더의 균일 한 매개 변수 만 수정하면됩니다. 그러나 원래 Pong Shader를 Lambert Shader로 교체하는 것과 같은 전체 재료를 교체하면 재료를 설정해야합니다. 그러나이 상황은 드물며, 더 일반적인 상황은 아래에 언급 된 것입니다.
조명을 추가하고 제거하십시오이것은 장면에서 더 일반적이어야합니다. 방금 3.js를 사용하기 시작한 많은 사람들 이이 구덩이에 빠질 것입니다. 장면에 빛을 동적으로 추가 한 후에는 빛이 작동하지 않는다는 것을 알게됩니다. 그러나 3.js (예 : Phong, Lambert)와 같은 셰이더를 사용하는 경우 렌더러의 소스 코드를 살펴보면 3.js가 내장 셰이더 코드에서 #define을 사용하여 장면의 조명 수를 설정합니다. 이 #define의 값은 자료가 업데이트 될 때마다 문자열 스 플라이 싱 셰이더로 얻습니다. 코드는 다음과 같습니다.
"#define max_dir_lights" + parameters.maxdirlights,
"#define max_point_lights" + parameters.maxpointlights,
"#define max_spot_lights" + parameters.maxspotlights,
"#define max_hemi_lights" + parameters.maxhemilights,
실제로이 작문 방법은 GPU 레지스터의 사용을 효과적으로 줄일 수 있습니다. 빛이 하나만 있으면 한 표시등에 필요한 균일 한 변수 만 선언 할 수 있습니다. 그러나 조명 수가 변경되면 특히 추가 할 때 셰이더를 다시 스티치하고 컴파일하고 연결해야합니다. 이때, 당신은 또한 재료를 설정해야합니다.
텍스처를 변경하십시오여기서 텍스처 변경은 텍스처 데이터를 업데이트하는 것이 아니라 오히려 원래 재료가 텍스처를 사용했지만 나중에 사용되지 않았거나 원래 재료가 텍스처를 사용하지 않았다는 것을 의미합니다. 자료가 수동으로 업데이트되지 않으면 최종 효과는 귀하의 생각과 다릅니다. 이 문제의 이유는 위에서 언급 한 조명과 유사하며, 텍스처가 사용되었는지 여부를 결정하기 위해 매크로가 셰이더에 추가 되었기 때문입니다.
매개 변수 .map? "#define use_map": "",
매개 변수 .envmap? "#define use_envmap": "",
매개 변수 .LightMap? "#define use_lightmap": "",
매개 변수 .bumpmap? "#define use_bumpmap": "",
매개 변수 .normalmap? "#define use_normalmap": "",
매개 변수 .specularMap? "#define use_specularMap": "",
따라서 매번지도, Envmap 또는 Lightmap은 실제 값을 변경하므로 자료를 업데이트해야합니다.
다른 정점 데이터의 변경실제로 위의 텍스처의 변화는 문제를 일으킬 것입니다. 주로 초기화 중 텍스처가 없기 때문입니다. 그러나이 환경에서 동적으로 추가 된 경우 재료를 설정하는 것만으로는 충분하지 않습니다. 또한 geometry.uvsneedsupdate를 true로 설정해야합니다. 왜 그런 문제가 있습니까? 프로그램의 최적화 때문입니다. 렌더러에서 처음으로 지오메트리와 재료를 초기화 할 때, 텍스처가 없다고 판단되면 메모리의 데이터에 각 정점 UV 데이터가 있지만 Three.JS는이 데이터를 비디오 메모리에 복사하지 않습니다. 원래 의도는 귀중한 비디오 메모리 공간을 절약하는 것입니다. 그러나 텍스처를 추가 한 후 지오메트리는 텍스처 사용을 위해 이러한 UV 데이터를 지능적으로 전송하지 않습니다. 우리는 UVSNEDSUPDATE를 수동으로 설정하여 UV를 업데이트 할 시간임을 알려 주어야합니다.이 질문으로 인해 처음에는 오랫동안 속임수가되었습니다.
여러 유형의 정점 데이터의 NeedupDate 속성에 대해서는이 문제를 볼 수 있습니다.
https://github.com/mrdoob/three.js/wiki/updates
마침내Three.JS의 최적화는 좋은 일을하고 있지만 다양한 최적화로 인해 다양한 함정을 가져옵니다. 이를 수행하는 가장 좋은 방법은 소스 코드를 보거나 Github로 이동하여 문제를 언급하는 것입니다.