이 글은 오토데스크 포지(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);