three.js_html5 튜토리얼 기술의 needUpdate 적용에 대한 간략한 토론

WBOY
풀어 주다: 2016-05-16 15:50:57
원래의
2685명이 탐색했습니다.

three.js의 많은 개체에는 needUpdate 속성이 있는데, 이는 문서에서 거의 언급되지 않습니다(그러나 three.js에 대한 문서는 많지 않으며 github의 문제로 해결해야 하는 문제가 많습니다). 간단한 초급 프로그램에서는 이 속성이 사용되지 않기 때문에 이것을 작성하는 데 능숙하지 않습니다.
이 속성은 간단히 말해서 렌더러에게 이 프레임에 대한 캐시를 업데이트해야 한다고 알려줍니다. 플래그로 사용하는 것은 매우 간단하지만 캐시가 필요한 이유를 알고 싶기 때문입니다. 업데이트되기 위해서는 어떤 캐시가 업데이트되는지가 필요하므로 잘 이해해야 합니다.
NeedsUpdate가 필요한 이유
먼저 캐시가 필요한 이유를 살펴보겠습니다. 캐시는 일반적으로 데이터 전송 횟수를 줄여 프로그램에 소요되는 시간을 줄이기 위해 존재합니다. 여기서도 마찬가지입니다. 일반적으로 객체(Mesh)가 화면에 성공적으로 표시되기란 쉽지 않습니다. 전장에 세 번 전송해야 합니다.
먼저 모든 정점 데이터와 텍스처 데이터가 있습니다. 프로그램을 통해 로컬 디스크에서 메모리로 읽어옵니다.
그런 다음 프로그램은 메모리에서 적절한 처리를 마친 후 화면에 그려야 하는 객체의 정점 데이터와 텍스처 데이터를 비디오 메모리로 전송합니다.
마지막으로 각 프레임을 렌더링할 때 비디오 메모리의 정점 데이터와 텍스처 데이터가 조립 및 그리기를 위해 GPU로 플러시됩니다.
피라미드 데이터 전송 모델에 따르면 첫 번째 단계는 분명히 가장 느립니다. WebGL과 같은 환경에서 네트워크를 통해 전송되면 두 번째 단계는 메모리에서 비디오로 전송되는 시간입니다. 메모리에 대한 간단한 데이터 테스트는 나중에 수행됩니다.
그리고 이 세 단계의 사용 빈도가 있습니다. 작은 장면의 경우 첫 번째 단계는 일회성 작업입니다. 즉, 프로그램이 초기화될 때마다 장면의 모든 데이터가 메모리에 로드됩니다. 대규모 시나리오의 경우 일부 비동기 로딩이 수행될 수 있지만 현재는 고려되지 않습니다. 이번에 이야기할 가장 중요한 것은 두 번째 단계의 빈도일 것입니다. 먼저 이 단계의 전송으로 인한 소모량을 테스트하는 간단한 프로그램을 작성해 보겠습니다.

코드 복사
코드는 다음과 같습니다.

var canvas = document.createElement('canvas')
var _gl = canvas.getContext( '실험적 -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'); 🎜> function binBuffer(){
for(var i = 0; i < 1000; i ){
_gl.bindBuffer(_gl.ARRAY_BUFFER, buffer)
_gl.bufferData(_gl.ARRAY_BUFFER, 새로운 Float32Array(정점), _gl.STATIC_DRAW)
}
}


먼저 이 프로그램을 간단히 설명하겠습니다. Vertices는 정점을 저장하는 배열입니다. 여기서는 각 정점에 x, y, z 세 개의 좌표가 있으므로 3000 크기의 배열이 필요합니다. _gl.createBuffer 명령은 비디오 메모리의 버퍼를 열어 정점 데이터를 저장한 다음 _gl.bufferData를 사용하여 생성된 정점 데이터의 복사본을 메모리에서 비디오 메모리로 전송합니다. 여기서는 한 장면에 1000개의 정점이 있는 1000개의 객체가 있다고 가정합니다. 각 정점은 32비트 및 4바이트의 부동 소수점 데이터로 계산하면 거의 1000 x 1000 x 12 = 11M의 데이터가 소모됩니다. 여기서는 15ms가 아주 작은 시간이라는 것을 알 수 있지만 실시간 프로그램의 경우 30fps의 프레임 속도를 보장하려면 각 프레임에 필요한 시간을 약 30ms로 제어해야 합니다. 데이터를 한 번만 처리하면 전송 시간이 절반밖에 걸리지 않습니다. 큰 머리는 GPU에서 그리는 작업이고 전체 렌더링 프로세스의 모든 단계가 인색해야 한다는 것을 알아야 합니다.
그래서 이 단계에서는 전송 횟수를 최소화해야 합니다. 실제로 모든 정점 데이터와 텍스처 데이터는 로드되는 즉시 메모리에서 비디오 메모리로 전송될 수 있습니다. 먼저 그려야 하는 객체(Geometry)의 정점 데이터를 디스플레이 메모리로 전송하고, 버퍼는 geometry.__webglVertexBuffer에 캐시합니다. 이후에는 매번 Geometry의 verticesNeedUpdate 속성이 판단됩니다. 업데이트가 필요하지 않으면 현재 캐시가 직접 사용됩니다. verticesNeedUpate가 true인 경우 Geometry의 정점 데이터가 geometry.__webglVertexBuffer로 다시 전송됩니다. 정적 개체의 경우, 예를 들어 정점을 입자로 사용하는 입자 시스템과 골격 애니메이션을 사용하는 메시를 사용하는 등 정점이 자주 변경되는 객체가 있는 경우 이러한 객체는 프레임마다 정점을 변경하므로 verticesNeedUpdate 속성을 설정해야 합니다. 매 프레임마다 true를 사용하여 데이터를 재전송해야 한다고 렌더러에 알렸습니다.
실제로 WebGL 프로그램에서는 입자 효과와 골격 애니메이션을 완성하기 위해 정점 셰이더에서 정점의 위치를 ​​대부분 변경하지만, JavaScript의 컴퓨팅 성능의 한계로 인해 CPU 측에서 계산하면 확장하기가 더 쉽습니다. , 이러한 계산 집약적인 작업의 대부분은 GPU 측에서 수행됩니다. 이 경우 정점 데이터를 재전송할 필요가 없으므로 위의 경우는 실제로 실제 프로그램에서는 많이 사용되지 않으며 텍스처 및 재질의 캐시가 업데이트되는 경우가 많습니다.
위의 경우는 주로 정점 데이터를 전송하는 장면을 설명한 것인데, 1024*1024 크기의 R8G8B8A8 형식의 텍스처는 최대 4M의 메모리를 차지하므로, 를 살펴보세요. 다음 예시

코드 복사
코드는 다음과 같습니다.

var canvas = document.createElement('canvas');
var _gl = canvas.getContext('experimental-webgl');
var 텍스처 = _gl.createTexture();
var img
img.onload = function(){
console.profile('texture test');
bindTexture()
console.profileEnd('texture test')
}
img. src = 'test_tex.jpg';
function binTexture(){
_gl.bindTexture(_gl.TEXTURE_2D, 텍스처)
_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _gl.RGBA , _gl .UNSIGNED_BYTE, img)
}

여기서는 1000번 반복할 필요가 없습니다. 10241024 텍스처를 한 번 전송하는 데는 30ms가 걸리고, 256256 텍스처는 거의 2ms가 걸립니다. 따라서 three.js에서는 처음에 텍스처가 한 번만 전송됩니다. 그런 다음 Texture.needsUpdate 속성을 수동으로 true로 설정하지 않으면 비디오 메모리로 전송된 텍스처가 직접 사용됩니다.
어떤 캐시를 업데이트해야 하는지
위에서는 three.js에 이러한 needUpdate 속성을 추가해야 하는 이유를 두 가지 경우로 설명했습니다. 다음으로 어떤 상황에서 필요한지 알 수 있는 몇 가지 시나리오를 나열해 보겠습니다. 수동으로 업데이트해야 합니다.
텍스처 비동기 로딩
IMG를 생성한 후 바로 Texture.needsUpdate=true라고 쓰면 프런트엔드 이미지가 비동기적으로 로딩되기 때문에 이는 작은 함정입니다. 렌더러는 _gl.texImage2D를 사용하여 빈 텍스처 데이터를 이 프레임의 비디오 메모리로 전송한 다음 이 플래그를 false로 설정합니다. 그 후에는 이미지가 로드될 때까지 비디오 메모리 데이터가 업데이트되지 않습니다. Texture.needsUpdate = true
비디오 텍스처
대부분의 텍스처는 위의 경우처럼 이미지를 한 번만 로드하고 전송하지만, 그렇지 않습니다. 비디오 텍스처는 비디오가 그림 스트림이고 표시되는 그림이 각 프레임마다 다르기 때문에 그래픽 카드의 텍스처 데이터를 업데이트하려면 각 프레임에서 needUpdate를 true로 설정해야 합니다.
렌더 버퍼 사용
렌더 버퍼는 특수 개체입니다. 일반적으로 프로그램은 전체 장면을 화면에 직접 플러시하지만, 후처리가 더 많거나 이 화면 기반 xxx(예: 화면 기반 주변 폐색) 먼저 렌더 버퍼에 장면을 그려야 합니다. 이 버퍼는 실제로 텍스처이지만 디스크에서 로드하는 대신 이전 드로잉에 의해 생성됩니다. three.js에는 렌더 버퍼를 초기화하고 저장하기 위한 특수 텍스처 개체 WebGLRenderTarget이 있습니다. 이 텍스처도 각 프레임에서 needUpdate를 true로 설정해야 합니다.
Material's needUpdate
머티리얼은 3개입니다. Node.js는 THREE.Material을 통해 설명됩니다. 실제로 전송할 데이터가 없는데 왜 needUpdate를 만들어야 할까요? 여기서는 셰이더를 문자 그대로 번역하면 셰이더(Shader)에 대해서도 이야기하고 싶습니다. GPU에서 제공하는 것입니다. 페인팅에서는 페인팅의 셰이딩 방법을 나타내는 용어가 있습니다. 조명의 셰이딩은 프로그램을 통해 계산됩니다. 셰이더는 실행되므로 모든 프로그램과 마찬가지로 WebGL에서 셰이더 프로그램은 런타임에 컴파일되고 연결되어야 합니다. 따라서 프로그램을 한 번 컴파일하고 실행하는 것이 가장 좋습니다. 따라서 three.js에서는 머티리얼이 초기화될 때 셰이더 프로그램이 컴파일 및 링크되고, 컴파일 및 링크 후 얻은 프로그램 객체가 캐시됩니다. 일반적으로 재료는 전체 셰이더를 다시 컴파일할 필요가 없습니다. 재료를 조정하려면 셰이더의 균일한 매개변수만 수정하면 됩니다. 그러나 원본 퐁 셰이더를 램버트 셰이더로 교체하는 등 전체 머티리얼을 교체하는 경우에는 Material.needsUpdate를 true로 설정하고 다시 컴파일해야 합니다. 그러나 이러한 상황은 드물며, 아래에 언급된 상황이 더 일반적인 상황입니다.
조명 추가 및 삭제
이것은 장면에서 비교적 흔한 일입니다. 아마도 three.js를 사용하기 시작한 많은 사람들이 설정 후 장면에 동적으로 조명을 추가할 것입니다. 조명을 켜보니 조명이 작동하지 않는 것을 발견했습니다. 하지만 이는 phong 및 Lambert와 같은 three.js의 내장 셰이더를 사용할 때였습니다. 렌더러의 소스 코드를 보면 알 수 있습니다. three.js는 내장된 셰이더 코드에 있습니다. #define을 사용하여 장면의 조명 수를 설정하며, 이 #define 값은 머티리얼이 업데이트될 때마다 스트링 스플라이싱 셰이더에 의해 얻어집니다.

코드 복사
코드는 다음과 같습니다.

"#define MAX_DIR_LIGHTS " 매개변수 .maxDirLights,
"#define MAX_POINT_LIGHTS " 매개변수.maxPointLights,
"#define MAX_SPOT_LIGHTS " 매개변수.maxSpotLights,
"#define MAX_HEMI_LIGHTS " 매개변수.maxHemiLights,

이러한 작성 방법으로 GPU 레지스터 사용을 효과적으로 줄일 수 있는 것은 사실입니다. 조명이 하나만 있는 경우 조명 하나에 필요한 균일한 변수만 선언할 수 있지만 조명 수가 바뀔 때마다, 특히 다음과 같은 경우에는 더욱 그렇습니다. 추가 이때 셰이더를 다시 연결하고 컴파일하고 연결해야 합니다.
텍스처 변경
여기에서 텍스처를 변경하면 됩니다. 텍스처 데이터를 업데이트한다는 것이 아니라 원본 머티리얼이 텍스처를 사용했다가 사용을 중단했거나, 원본 머티리얼이 텍스처를 사용하지 않았다가 나중에 추가했기 때문입니다. 이 문제의 원인은 다음과 같습니다. 위의 조명을 추가하는 것은 거의 동일합니다. 텍스처 사용 여부를 결정하기 위해 셰이더에 매크로를 추가하기 때문입니다.

코드 복사
코드는 다음과 같습니다.

parameters.map ? "#define USE_MAP" : "",
parameters.envMap ? "#define USE_ENVMAP" : "",
parameters.lightMap ? "#define USE_LIGHTMAP" : "",
parameters.bumpMap ? "#define USE_BUMPMAP" : "",
parameters.normalMap ? Define USE_NORMALMAP" : "",
parameters.specularMap ? "#define USE_SPECULARMAP" : "",

그래서 map, envMap 또는 lightMap이 실제 값을 변경할 때마다 재질은 다음을 수행해야 합니다.
다른 정점 데이터의 변경
실제로 위의 텍스처 변경도 문제를 일으킬 수 있습니다. 주된 이유는 초기화 중에는 텍스처가 없지만 나중에 동적으로 추가되기 때문입니다. 이 환경에서는 Material.needsUpdate를 true로 설정하는 것만으로는 충분하지 않습니다. 또한 geode.uvsNeedsUpdate를 true로 설정해야 합니다. 왜 이 문제는 three.js에 의한 프로그램 최적화로 인해 발생합니까? 메모리의 데이터에는 각 정점에 대한 UV 데이터가 포함되어 있지만 텍스처가 없다고 판단되면 지오메트리와 재질이 렌더러에서 처음으로 초기화되지만 three.js는 여전히 이러한 데이터를 비디오에 복사하지 않습니다. 원래 의도는 귀중한 비디오 메모리 공간을 절약하는 것이었지만 텍스처를 추가한 후 지오메트리는 텍스처 사용을 위해 UV 데이터를 지능적으로 다시 전송하지 않으므로 업데이트할 시간임을 알려야 합니다. uv. 이 문제는 처음에 나를 정말 오랫동안 괴롭혔습니다.
여러 정점 데이터의 needUpdate 속성에 대한 자세한 내용은 이 문제를 참조하세요.
https://github.com/mrdoob/3.js/wiki/Updates
마지막으로
3 .js의 최적화는 좋지만 다양한 최적화는 다양한 함정을 가져옵니다. 이 경우 가장 좋은 방법은 github에서 소스 코드나 파일 문제를 살펴보는 것입니다.
관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿