2021년 3월 30일 화요일

오토데스크 포지 모델의 공간 컬러 재질 렌더링 방법

이 글은 오토데스크 포지(Forge) 기반 프로그램 개발 시 공간 컬러 렌더링 방법을 간단히 분석 정리한 것이다.

일반적인 컬러 렌더링 방법
일반적인 Room 렌더링 방법은 아래와 같이 setThemingColor 사용하는 것이다.
   let color = new THREE.Vector4(0, 1, 0, 1);
   NOP_VIEWER.setThemingColor(roomSolidId, color, NOP_VIEWER.model); 

참고로, 버전마다 API사용법이 변경되므로, 사용전 이를 체크할 필요가 있다. 이 함수는 6.5버전에서 차일드 객체로 함께 색상을 변경할 수 있도록 수정되었다. 
여기서, NOP_VIEWER는 다음과 같이 포지 뷰어를 정의한 것이다.
   viewer = new Autodesk.Viewing.GuiViewer3D(htmlElement);

roomSolidId는 방 객체가 포함하는 솔리드(solid) 형상 ID이다. 참고로, 포지 모델 객체 계층 구조는 다음과 같다. 
   Model - Container - Element - Solid 

여기서 Container는 건축 객체 요소 그룹, 공간 그룹일 수 있다. 공간 그룹은 Storey 같은 것이다. 각 객체 계층 요소들은 고유의 ID와 속성(properties)를 가진다. 각 정보를 얻는 방법은 포지 API를 통하며, 몇몇 함수들을 비동기 콜백함수(callback function)을 사용해야 한다. 대표적으로 getProperties 와 같은 객체 속성 얻는 함수들이 비동기 함수이다.

좀 더 자세한 컬러 재질 렌더링 관련 내용은 아래 링크를 참고한다.

솔리드 형상 자체 렌더링 지정
포지는 웹 기반 뷰어에서 성능 개선을 위해, 솔리드 재질을 직접 설정하는 함수를 제공하지 않는다. 다만, 포지는 수정된 쓰리(Three.js)를 사용하여 다음과 같이 그래픽 처리한다. 그래픽 렌더링 파이프라인은 쉐이더 확장 기능을 제공하여, 사용자화할 수 있다.
 
객체 모델 > 솔리드 형상 > 프래그먼트 형상 정보 > THREE.js > WebGL > 그래픽 렌더링

이런 이유로, 별도로 투명, 재질 등 처리를 위해서는 솔리드 모델을 직접 해킹해서 솔리드를 구성하는 페이스를 직접 THREE.js 로 WebGL을 렌더링해야 한다. 렌더링 코드는 포지 뷰어에 확장 모듈로 등록이 가능하다. 이는 아래 링크에 그 내용이 설명되어 있다. 다만, 3년전 버전이라 현재 버전에서는 제대로 동작하려면 수정이 필요하다.
이 소스 코드의 주요 내용은 다음과 같다. 

1. 재질 적용하고 싶은 룸 획득
    _viewer.search('Rooms', 
            function (idArray) {
                $.each(idArray, function(num, dbid) {
                            _viewer.getProperties(dbid,
                                function(propData) {
                                    //check if this room is from specific floor
                                    var findit = propData.properties.filter(function(item) { 
                                        return (item.displayName === 'Layer' 
                                        && item.displayValue === _specificFloorName); 
                                    });  // 특정 레이어에 속한 방만 탐색하는 필터 정의
    
                                    if(findit.length > 0){  // 방을 찾으면
                                        _specific_Floor_Rooms_Array.push({roomid:dbid,
                                                                 defaultcolor:null,
                                                                 facemeshes:null}) // 렌더링할 방 아이디 추가
                                    } 
                                    if (num > idArray.length -2 )
                                        renderRoomShader();  // 방 렌더링

2. 재질 정의
    this.load = function() {  
        _defaultFaceMaterial =  createFaceMaterial("#b4ff77", 0.9, true);

        var colorHexArray = ["#ff77b4", "#b4ff77", "#77b4ff", "#c277ff", "#ffc277", "#f8ff77"];
        for(var k = 0; k < colorHexArray.length; k++){
            var material = createFaceMaterial(colorHexArray[k], 0.9, true);  // 재질 추가
            _materialArray.push(material);
        } 
        return true;
    };

3. 룸 렌더링을 위한 쉐이더 정의. 솔리드 형상 구성하는 정점, 면 얻기 위해 프래그먼트 접근 후 기하형상 데이터 획득. 이후, 룸에 대한 새로운 재질이 적용된 솔리드 생성.
function renderRoomShader( ) // 방 렌더링
{
        var  colorIndex = 0;
        $.each( _specific_Floor_Rooms_Array,
            function(num,room) {    
            console.log('room dbid:' + room.roomid);
            
            if(colorIndex > 5)
                colorIndex = 0;
    
            var faceMeshArray = [];
    
            var instanceTree =  _viewer.model.getData().instanceTree; 
            instanceTree.enumNodeFragments(room.roomid, function(fragId){  // 솔리드 구성하는 기하 데이터 획득
                    var renderProxy = _viewer.impl.getRenderProxy(
                         _viewer.model,
                        fragId);
            
                    var matrix = renderProxy.matrixWorld;
                    var indices = renderProxy.geometry.ib;
                    var positions = renderProxy.geometry.vb;
                    var stride = renderProxy.geometry.vbstride;
                    var offsets = renderProxy.geometry.offsets;
                
                    if (!offsets || offsets.length === 0) {
                        offsets = [{start: 0, count: indices.length, index: 0}];
                    }
                
                    var vA = new THREE.Vector3();
                    var vB = new THREE.Vector3();
                    var vC = new THREE.Vector3();
        
    
                    for (var oi = 0, ol = offsets.length; oi < ol; ++oi) {
                
                        var start = offsets[oi].start;
                        var count = offsets[oi].count;
                        var index = offsets[oi].index;
                
                        var checkFace = 0;
        
                        for (var i = start, il = start + count; i < il; i += 3) {
                        
                            var a = index + indices[i];
                            var b = index + indices[i + 1];
                            var c = index + indices[i + 2];
                
                            vA.fromArray(positions, a * stride);
                            vB.fromArray(positions, b * stride);
                            vC.fromArray(positions, c * stride);
                    
                            vA.applyMatrix4(matrix);
                            vB.applyMatrix4(matrix);
                            vC.applyMatrix4(matrix);
                
                            var faceGeometry = createFaceGeometry(vA, vB, vC);
                            var faces = faceGeometry.faces;
        
                            for(var f = 0; f < faces.length; f++){
                                     var faceMesh = drawFaceMesh(faceGeometry,
                                      _overlaySceneName, 
                                      _materialArray[colorIndex],
                                      renderProxy);
                                    faceMeshArray.push(faceMesh); 
                            }
                        }
                    }
                });
    
            room.defaultcolor = _materialArray[colorIndex];
            room.facemeshes = faceMeshArray;
    
            colorIndex++;
        });      
    }
     
앞의 과정을 거친후 룸의 솔리드 모델을 렌더링하는 방식이다. 이 방식은 복잡하고 별도 재질 렌더링을 위한 데이터를 관리해야 한다.

이외에 THREE WebGL 형상 객체를 포지 뷰어의 구현 객체에 오버레이해서 렌더링하는 방법이 있다. 다음은 포인트 클라우드를 포지에 렌더링하는 예이다.
const GridWidth = 1000;
const GridHeight = 1000;
const PointsCount = GridWidth * GridHeight;
const PointSize = 0.1;
const positions = new Float32Array(PointsCount * 3);
const colors = new Float32Array(PointsCount * 3);
let i = 0;
for (var x = 0; x < GridWidth; x++) {
  for (var y = 0; y < GridHeight; y++) {
    const u = x / GridWidth, v = y / GridHeight;
    positions[3 * i] = u;
    positions[3 * i + 1] = v;
    positions[3 * i + 2] = 0.0;
    colors[3 * i] = u;
    colors[3 * i + 1] = v;
    colors[3 * i + 2] = 0.0;
    i++;
  }
}

const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.computeBoundingBox();
geometry.isPoints = true;

viewer.impl.createOverlayScene('pointclouds');
viewer.impl.addOverlay('pointclouds', points);

댓글 없음:

댓글 쓰기