WebGL 레슨 3 - 회전 운동

Similar documents
WebGL 레슨 5 - 텍스쳐에 대하여

WebGL 레슨 1 - 삼각형과 사각형

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

지도상 유의점 m 학생들이 어려워하는 낱말이 있으므로 자세히 설명해주도록 한다. m 버튼을 무리하게 조작하면 고장이 날 위험이 있으므로 수업 시작 부분에서 주의를 준다. m 활동지를 보고 어려워하는 학생에게는 영상자료를 접속하도록 안내한다. 평가 평가 유형 자기 평가

Node.JS와 Express를 이용한 디렉터리 파싱

SIGIL 완벽입문

제이쿼리 (JQuery) 정의 자바스크립트함수를쉽게사용하기위해만든자바스크립트라이브러리. 웹페이지를즉석에서변경하는기능에특화된자바스크립트라이브러리. 사용법 $( 제이쿼리객체 ) 혹은 $( 엘리먼트 ) 참고 ) $() 이기호를제이쿼리래퍼라고한다. 즉, 제이쿼리를호출하는기호

쉽게 풀어쓴 C 프로그래밍

SproutCore에 홀딱 반했습니다.

쉽게 풀어쓴 C 프로그래밍

WS12. Security

zbrush 4r7 p3 crack macintoshinstmank

var answer = confirm(" 확인이나취소를누르세요."); // 확인창은사용자의의사를묻는데사용합니다. if(answer == true){ document.write(" 확인을눌렀습니다."); else { document.write(" 취소를눌렀습니다.");


SBR-100S User Manual

Web Scraper in 30 Minutes 강철

PathEye 공식 블로그 다운로드 받으세요!! 지속적으로 업그래이드 됩니다. 여러분의 의견을 주시면 개발에 반영하겠 습니다.

XSS Attack - Real-World XSS Attacks, Chaining XSS and Other Attacks, Payloads for XSS Attacks

Microsoft PowerPoint - chap01-C언어개요.pptx

소규모 비즈니스를 위한 플레이북 여기서 다룰 내용은 다음과 같습니다. 1. YouTube 소개 2. YouTube에서 비즈니스를 위한 채널 만들기 3. 눈길을 끄는 동영상 만들기 4. 고객의 액션 유도하기 5. 비즈니스에 중요한 잠재고객에게 더 많이 도달하기

PowerPoint 프레젠테이션

Microsoft 을 열면 깔끔한 사용자 중심의 메뉴 및 레이아웃이 제일 먼저 눈에 띕니다. 또한 은 스마트폰, 테블릿 및 클라우드는 물론 가 설치되어 있지 않은 PC 에서도 사용할 수 있습니다. 따라서 장소와 디바이스에 관계 없이 언제, 어디서나 문서를 확인하고 편집

View Licenses and Services (customer)

Microsoft PowerPoint - web-part01-ch10-문서객체모델.pptx

Prototype에서 jQuery로 옮겨타기

아이폰/아이팟 터치용 웹 애플리케이션 개발 팁 12개

C++ Programming

A Hierarchical Approach to Interactive Motion Editing for Human-like Figures

쉽게 풀어쓴 C 프로그래밍

Microsoft PowerPoint - chap04-연산자.pptx

Index 1. Intro Install Connect Scratch 1.4 (Offline Editor) Scratch 2.0 (Online Editor) Connect f

Windows 8에서 BioStar 1 설치하기

Microsoft PowerPoint - web-part03-ch20-XMLHttpRequest기본.pptx

PowerPoint Template

804NW±¹¹®

Microsoft Word - windows server 2003 수동설치_non pro support_.doc

JVM 메모리구조

비디오 / 그래픽 아답터 네트워크 만약에 ArcGolbe를 사용하는 경우, 추가적인 디스크 공간 필요. ArcGlobe는 캐시파일을 생성하여 사용 24 비트 그래픽 가속기 Oepn GL 2.0 이상을 지원하는 비디오카드 최소 64 MB 이고 256 MB 이상을 메모리

[ 마이크로프로세서 1] 2 주차 3 차시. 포인터와구조체 2 주차 3 차시포인터와구조체 학습목표 1. C 언어에서가장어려운포인터와구조체를설명할수있다. 2. Call By Value 와 Call By Reference 를구분할수있다. 학습내용 1 : 함수 (Functi

연구노트

Microsoft PowerPoint - chap10-함수의활용.pptx

쓰리 핸드(삼침) 요일 및 2405 요일 시간, 및 요일 설정 1. 용두를 2의 위치로 당기고 반시계방향으로 돌려 전날로 를 설정합니다. 2. 용두를 시계방향으로 돌려 전날로 요일을 설정합니다. 3. 용두를 3의 위치로 당기고 오늘 와 요일이 표시될 때까지 시계방향으로

와플-4년-2호-본문-15.ps

Javascript

IRISCard Anywhere 5

학습목표 함수프로시저, 서브프로시저의의미를안다. 매개변수전달방식을학습한다. 함수를이용한프로그래밍한다. 2

Structure and Interpretation of Computer Programs: Assignment 3 Seung-Hoon Na October 4, George (아래 3개의 문제에 대한 구현이 모두 포함된 george.rkt파일을 제출하시오.

금오공대 컴퓨터공학전공 강의자료

ActFax 4.31 Local Privilege Escalation Exploit

[Brochure] KOR_TunA

특징 찾아보기 열쇠 없이 문을 열 수 있어요! 비밀번호 및 RF카드로도 문을 열 수 있습니다. 또한 비밀번호가 외부인에게 알려질 위험에 대비, 통제번호까지 입력해 둘 수 있어 더욱 안심하고 사용할 수 있습니다. 나만의 비밀번호 및 RF카드를 가질 수 있어요! 다수의 가

Print

03_queue

Microsoft PowerPoint - C++ 5 .pptx

로거 자료실

<4D F736F F F696E74202D203137C0E55FBFACBDC0B9AEC1A6BCD6B7E7BCC72E707074>

Xcovery 사용설명서

<4D F736F F F696E74202D B3E22032C7D0B1E220C0A9B5B5BFECB0D4C0D3C7C1B7CEB1D7B7A1B9D620C1A638B0AD202D20C7C1B7B9C0D320BCD3B5B5C0C720C1B6C0FD>

Łø·ŸÕ=¤ ¬ ÇX±xÒ¸ 06 - Èpº– 1

Microsoft PowerPoint - chap05-제어문.pptx

Windows 10 General Announcement v1.0-KO

Javascript

<B3EDB9AEC0DBBCBAB9FD2E687770>

<C6F7C6AEB6F5B1B3C0E72E687770>

커알못의 커널 탐방기 이 세상의 모든 커알못을 위해서

슬라이드 1

coverbacktong최종spread

1

메뉴얼41페이지-2

통계내지-수정.indd

»êÇÐ-150È£

Tcl의 문법

PowerPoint Presentation

ITFGc03ÖÁ¾š

152*220

!

1

Vector Differential: 벡터 미분 Yonghee Lee October 17, 벡터미분의 표기 스칼라미분 벡터미분(Vector diffrential) 또는 행렬미분(Matrix differential)은 벡터와 행렬의 미분식에 대 한 표

Microsoft PowerPoint 세션.ppt

중간고사

1504-<C804><CCB4>.pdf

Microsoft PowerPoint 자바스크립트(1).pptx

Microsoft PowerPoint - e pptx

%eb%8f%99%ec%9d%b8-[NO_09]%20%ec%9d%98%ea%b3%bc%eb%8c%80%ed%95%99%20%ec%86%8c%ec%8b%9d%ec%a7%80_F(%ec%b5%9c%ec%a2%85)-2.pdf

¹Ì·¡Æ÷·³-5±âºê·Î¼Å_1228.ps

UI TASK & KEY EVENT

<4D F736F F F696E74202D20C1A63034B0AD202D20C7C1B7B9C0D3B8AEBDBAB3CABFCD20B9ABB9F6C6DBC0D4B7C2>

¿©¼ºÀαÇ24È£

HTML5

경우 1) 80GB( 원본 ) => 2TB( 복사본 ), 원본 80GB 는 MBR 로디스크초기화하고 NTFS 로포맷한경우 복사본 HDD 도 MBR 로디스크초기화되고 80GB 만큼포맷되고나머지영역 (80GB~ 나머지부분 ) 은할당되지않음 으로나온다. A. Window P

CODESYS 런타임 설치과정

슬라이드 1

User Guide

카택스 비즈 관리자용 사용설명서 목차 사용 전에 시작하기 사용하기 설정하기 알아두기 훑어보기 차량 관리 운행내역 조회 부관리자 설정 자주묻는 질문 회원가입 사용자 관리 운행구간 조회 앱 권한 설정 GPS 오류 요인 부서 관리 운행일지 다운로드

C스토어 사용자 매뉴얼

PowerPoint Presentation

750 1,500 35

C# Programming Guide - Types

Python과 함께 배우는 신호 해석 제 5 강. 복소수 연산 및 Python을 이용한 복소수 연산 (제 2 장. 복소수 기초)

PowerPoint Presentation

Transcription:

Created by Firejune at 2011/04/20, Last modified 2016/09/11 WebGL 레슨 3 - 회전 운동 세 번째 WebGL 레슨에 오신 것을 환영합니다. 레슨 2에 이어 이번 레슨에서는 객체가 회전 운동을 할 수 있도록 합니다. 이번 학습은 NeHe OpenGL의 네 번째 튜토리얼을 바탕으로 합니다. 다음 동영상은 이번 레슨에서 얻어지는 결과물입니다. youtube.com/watch?v=zm3rqs-6cug WebGL을 지원하는 브라우저를 사용중이라면 여기를 클릭하여 실재로 작동하는 WebGL 버전을 확인할 수 있습니다. 만약 지원하지 않는

브라우저를 사용중이라면 레슨 0을 참고하여 설치하세요. 지금부터 자세한 내용을 설명하도록 합니다. 시작에 앞서... 이 레슨은 충분한 자바스크립트 프로그래밍 지식이 있지만, 3D 그래픽 구현 경험이 없는 사람들을 대상으로 하고 있습니다. 그리고 가능한 한 빨리 자신만의 3D를 구사하여 결과물을 만들수 있도록 하는 것에 목적을 둡니다. 레슨에 사용된 예제에서 실재 구현된 코드를 분석하여 어떤 일이 일어나는지 알아보고 그것을 이해하여 스스로 응용할 수 있도록 하는 것입니다. 만약 당신이 첫 번째 그리고 두 번쩨 레슨을 학습하지 않았다면 먼저 학습하는 것이 좋습니다. 이전과 동일하게 학습내용 중에는 버그 혹은 오류가 있을 수도 있습니다. 뭔가 잘못된 것을 발견하면 댓글로 알려주세요. 가능한 빨리 고치도록 하겠습니다. 여기에 사용된 예제의 소스코드를 얻는 방법은 두가지가 있습니다. 실제 예제가 작동하는 곳에서 "소스 보기"를 선택하거나, GitHub를 사용할 수 있다면 저장소에서 동일한 예제를 다운로드할 수 있습니다.(앞으로 학습할 예제들도 포함되어 있습니다.) 추천하는 방법은 두 번째로, 저장소에서 모두 가져온 다음 즐겨쓰는 텍스트 편집기에서 열어두고 학습하는 것입니다. 시작하기 전에 확실히 집고 넘어가야 하는 것이 있습니다. WebGL에서 3D장면 연출을 위해 애니메이션을 부여하는 것은 아주 간단합니다. 다른

장면을 연속해서 그릴려낼 뿐이니까요. 이 사실을 알고 난 후 저는 조금 허무했습니다. 당연하다고 말할 수 있을지도 모르지만, 제가 혼란스러워하는 이유는 이 보다 한층 더 높은 수준의 추상화를 사용하는 것이라고 생각했기 때문입니다. 예를 들자면, X지점에 그려진 사각형이 있습니다. 사각형을 이동하기 위하여 3D시스템에 Y지점까지 이동하라고 명령하면 자동으로 애니메이션을 제공해 줄 것이라고 생각했습니다. 그러나 실제로는 그렇지 않았고, X지점에 있는 사각형을 Y지점으로 이동하는 동작을 그려낸 후 Z지점까지 이동하는 동작을 매 프레임마다 계속해서 3D시스템에 주문하여 애니메이션을 구현해야 했습니다. 초기화 함수 - webglstart 어쨋든, 이것이 뜻하는 바는 drawscene이라 불리우는 함수를 반복적으로 호출할 수 밖에 없다는 것입니다. 애니메이션하기 위해서는 뭔가 약간씩 달라지도록 그리는 처리를 직접해야 하는 것입니다. 자, 이제 예제 코드의 하단으로 이동하여 초기화 함수인 webglstart의 변화를 살펴봅시다. function webglstart() { var canvas = document.getelementbyid("lesson03-canvas"); initgl(canvas); initshaders(); inittexture(); gl.clearcolor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.depth_test); tick(); // changed

유일한 변화는 함수의 마지막에 drawscene 대신에 tick이라는 함수를 호출한 것입니다. 이 함수는 화면 갱신을 위해 정해진 시간에 의해 정기적으로 뭔가를 그려내는 일을 합니다. 예를 들면, 81도로 회전한 삼각형을 82도로 회전하는 과정을 수행합니다. 위로 조금 이동하면 다음과 같은 함수를 발견할 수 있습니다. 재귀함수 - tick function tick() { requestanimframe(tick); 이 라인은 다음에 그려질 화면을 호출하는 과정입니다. requestanimframe 함수는 구글에서 제공하는 유틸리티 라이브러리인데 페이지의 상단에 위치한 <script> 태그에서 webgl-utils.js파일을 로드하여 사용할 수 있습니다. 이 라이브러리는 브라우저마다 독립적으로 구현된 기능(WebGL 장면을 다시 그려내는 시기를 브라우저에게 묻는 기능)을 하나로 통합하여 사용할 수 있게 합니다. 파이어폭스는 mozrequestanimationframe을 호출하고 크롬과 사파리는 webkitrequestanimationframe을 호출해야 합니다. 나중에는 requestanimationframe으로 통합될 것으로 예상되지만, 그 이전에는 이 라이브러리의 도움을 받기로 합니다. requestanimframe은 drawscene 함수가 정기적으로 호출되는 효과를 가집니다. 자바스크립트 내장함수인 setinterval을 사용할 수도 있었지만(예전에는 실제로도 많이 사용되었음), 사용자가 WebGL로 구현된 페이지를 보고있지 않아도(혹은 다른 탭을 조회 중) 지속적으로 작동하기

때문에 브라우저의 전반적인 성능이 떨어지는 문제가 있어 이와 같은 자체 함수를 제공하기 시작한 것입니다. 즉, 방문객이 WebGL로 구현된 화면을 조회하고 있을 때에만 발동하는 것입니다. tick 함수의 나머지를 보면, drawscene(); animate(); tick함수는 자기자신을 다음번에 다시 호출해 줄 것을 requestanimframe에 예약해 두고 drawscene과 animate 두개의 함수를 실행합니다. 장면 그리기 - drawscene drawscene함수는 index.html 소스의 2/3정도로 스크롤하면 발견할 수 있습니다. 먼저 눈에 들어온는 것은 함수 앞에 새롭게 정의된 두개의 전역 변수입니다. var rtri = 0; var rsquare = 0; 이들은 각 삼각형과 사각형의 회전상태를 보존할 것입니다. 모두 0도에서 시작하여 매 시간마다 조금씩 회전수치가 증가합니다.(여담 입니다만, 이런식의 전역 변수를 사용하는 것은 실제로 좋지 않은 방법입니다. 레슨 9에서 더 우아한 구조를 보여드리겠습니다.)

drawscene 함수의 변경사항은 삼각형을 그리는 부분입니다. 그 부분의 코드를 모두 올립니다, 새롭게 추가되거나 변경된 부분은 주석으로 표시했습니다. mat4.perspective(45, gl.viewportwidth / gl.viewportheight, 0.1, 100.0, pmatrix); mat4.identity(mvmatrix); mat4.translate(mvmatrix, [-1.5, 0.0, -7.0]); mvpushmatrix(); // added mvrotate(rtri, [0, 1, 0]); // added gl.bindbuffer(gl.array_buffer, trianglevertexpositionbuffer); gl.vertexattribpointer(shaderprogram.vertexpositionattribute, trianglevertexpositionbuffer.itemsize, gl.float, false, 0, 0); gl.bindbuffer(gl.array_buffer, trianglevertexcolorbuffer); gl.vertexattribpointer(shaderprogram.vertexcolorattribute, trianglevertexcolorbuffer.itemsize, gl.float, false, 0, 0); setmatrixuniforms(); gl.drawarrays(gl.triangles, 0, trianglevertexpositionbuffer.numitems); mvpopmatrix(); // added 여기서 무슨 일이 발생했는지 설명하기 위하여 레슨 1로 돌아가 봅시다. 다음과 같이 말했습니다. OpenGL은 장면을 그릴 때 대상 물체마다 현재(Current)의 위치와 회전값을 전달합니다. - 예를 들어, "20 만큼 전진하고 32도 만큼 회전하여 로봇을 그려"라는 식으로 말이죠. 실제로 복잡한 부분은 "요만큼 움직였고 이만큼 회전하여 그렸다"라는 지침이 있습니다.

이것은 "로봇을 그려라"라는 코드 한개의 함수로 캡슐화 할 수 있으므로 매우 편리합니다. 그리고 함수 호출 전에 이동 및 회전값을 변경함으로써 마음대로 로봇을 이동시킬 수 있습니다. 그렇습니다. model-view matrix에는 현재의 상태값이 보존되어 있습니다. 다음 코드를 봅시다. mat4.rotate(mvmatrix, degtorad(rtri), [0, 1, 0]); 아주 명백하지요. model-view matrix에 보존되어 있는 현재 회전수치를 수직 축 주위의 rtri에 표시된 각도만큼 회전하도록 변경합니다.(축이 두 번째 인수에 배열로 지정됩니다.) 이것은 삼각형을 그릴 때, rtri도 만큼 회전하고 있다는 것입니다. mat4.rotate에 의해 앵글의 각도가 변경됩니다. 여기에 사용된 degtorad 함수는 각도로 변환해 주는 간단한 일을 처리합니다. 그렇다면, mvpushmatrix과 mvpopmatrix는 무엇일까요? 이름에서 알 수 있을지도 모르지만, 이것들도 model-view matrix와 관련이 있습니다. 로봇을 그리는 비유로 돌아가 보면, A지점으로 이동하는 로봇을 그리려고 합니다. 최고 수준의 추상화로써 A지점으로 이동하는 구간의 일부분(옵셋) 만큼 이동하고 주전자를 그립니다. 로봇을 그리는 코드는 model-view matrix에서 변경됩니다. 이것은 로봇의 몸체에 적용되어 시작되며, 아래에는 다리, 위에는 머리, 그리고 팔이 붙어있습니다. 여기서 문제는 이처럼 A지점에서 옵셋만큼 이동 시키려고했을 때 발생합니다. A지점이 아니라 마지막으로 그린 것에 대해 옵셋만큼 이동하기 때문에 로봇의 팔에 들려있는 주전자는 뜬 상태로 렌더링됩니다. 이것은 의도대로 되지

않아요.(?) 요구되는 분명한 것은 로봇을 그리기 전에 model-view matrix를 보존할 방법과 다시 되돌릴 방법 이 되겠군요. 예 그렇습니다, mvpushmatrix과 mvpopmatrix에서 이것을 수행합니다. mvpushmatrix는 매트릭스를 스택(Stack)에 쌓습니다. mvpopmatrix는 스택에서 매트릭스를 꺼내 현재의 매트릭스를 바꿉니다. 스택을 이용한다는 것은 여러 단계의 중첩 그리기 코드의 비트를 이용할 수 있다는 것입니다. 각각 그리기 코드 안에서 model-view matrix를 조작하여 그려낸 후 복구합니다. 따라서 회전하는 삼각형의 그리기가 완료되면 mvpopmatrix을 통해 model-view matrix를 리턴합니다.(?) 다음 코드를 봅시다. mat4.translate(mvmatrix, [3.0, 0.0, 0.0]);...이 회전하지 않는 기준 좌표계에서의 장면을 가로질러 (model-view matrix를) 이동하도록 합니다. (만약 아직도 이 의미를 잘 모르겠다면, 코드를 복사하여 push/pop을 삭제한 후 실행하여 무슨 일이 일어나는가를 관찰하는 것이 좋습니다. 직접 보면 금세 알아차릴 수 있을 것입니다.) 이 세가지 변화는 삼각형이 사각형에 영향을 주지 않고 중심에서 수직축을 중심으로 회전합니다. 또한 사각형의 수평축을 중심으로 주위를 회전하는 유사한 세가지를 처리해 줍니다. mvpushmatrix(); // added mat4.rotate(mvmatrix, degtorad(rsquare), [1, 0, 0]); // added gl.bindbuffer(gl.array_buffer, squarevertexpositionbuffer);

gl.vertexattribpointer(shaderprogram.vertexpositionattribute, squarevertexpositionbuffer.itemsize, gl.float, false, 0, 0); gl.bindbuffer(gl.array_buffer, squarevertexcolorbuffer); gl.vertexattribpointer(shaderprogram.vertexcolorattribute, squarevertexcolorbuffer.itemsize, gl.float, false, 0, 0); setmatrixuniforms(); gl.drawarrays(gl.triangle_strip, 0, squarevertexpositionbuffer.numitems); mvpopmatrix(); // added...그리고 이것으로 drawscene 코드의 모든 변경은 끝났습니다. 살아 움직이도록 - animate 장면을 애니메이션하기 위해 필요한 또 다른 것은 각각의 렌더링에서 조금씩 다른 장면을 그릴 수 있도록 rtri과 rsquare의 값이 시간의 흐름에 따라 변경되도록 하는 것입니다. 이 과정은 drawscene과 마찬가지로 주기적으로 호출되는 animate라는 새롭게 추가된 함수에서 수행합니다. 다음 코드를 보시죠. var lasttime = 0; function animate() { var timenow = new Date().getTime(); if (lasttime!= 0) { var elapsed = timenow - lasttime; rtri += (90 * elapsed) / 1000.0; rsquare += (75 * elapsed) / 1000.0; lasttime = timenow;

장면을 애니메이션하는 간단한 방법은 animate 함수가 호출될 때 마다 일정한 값을 단순히 더하는 것입니다.(이 레슨의 기반이 되는 원래 OpenGL 레슨도 그렇게하고 있습니다) 그러나 여기에는 좀 더 좋은 방법이라고 생각되는 방법을 선택했습니다. 개체의 회전량은 이전 함수 호출에서 얼마나 경과했는지에 따라 결정됩니다. 좀더 정확히 말하면, 삼각형은 1초에 90도 사각형은 1초 동안 75도 회전합니다. 이 방법의 좋은 점은, 컴퓨터의 연산 처리능력에 상관없이 같은 속도로 움직이는 장면을 볼 수 있게 되는 것입니다. 느린 컴퓨터를 사용하는 사람은 단순히 움직임이 끊겨 보이게 될 뿐이죠. 이 것은 이번 레슨과 같은 간단한 데모에서는 문제없이 사용할 수 있지만, 게임과 같은 경우에는 분명히 문제가 됩니다. 나머지 추가한 코드들 이것으로, 주기적으로 호출되는 animate 함수와 drawscene 함수에 대하여 알아 보았습니다. 이제 추가적인 코드인 mvpushmatrix와 mvpopmatrix를 살펴봅시다. var mvmatrix = mat4.create(); var mvmatrixstack = []; // added var pmatrix = mat4.create(); // added function mvpushmatrix() { var copy = mat4.create(); mat4.set(mvmatrix, copy); mvmatrixstack.push(copy);

function mvpopmatrix() { if (mvmatrixstack.length == 0) { throw "Invalid popmatrix!"; mvmatrix = mvmatrixstack.pop(); 놀랄만한 것은 아무것도 없네요. 매트릭스를 유지하기 위한 스택를 가지고 적절하게 push와 pop이 되도록 정의하고 있습니다. 마지막으로 압서 언급한 degtorad 함수를 살펴 보도록 하겠습니다. 학교에서 가르쳐주는 수학을 기억한다면 그리 놀랄일은 없습니다. function degtorad(degrees) { return degrees * Math.PI / 180; 이것으로 끝입니다. 더 이상의 변경사항은 없습니다. 이제 당신은 WebGL을 이용하여 간단한 애니메이션을 장면에 담는 방법을 알게되었습니다. 의견이나 수정할 곳이 있으면 아래에 댓글로 남겨주세요. 그리고, 다음 레슨에서는 2D개체를 3D월드에 존재하는 진짜 3D개체로 만듭니다. 다음 레슨으로 이동하시려면 여기를 클릭하세요. 이 문서의 원본은 WebGL Lesson 3 a bit of movement입니다. Powered by TCPDF (www.tcpdf.org)