28.11.2012 20:30
Podstawy Stage3D część IV: korzystanie z shaderów [ekspert]
Shadery są już nie odłączanym elementem w grafice 3D i to do tego stopnia, że nie możliwe jest wyświetlanie obrazu bez ustawienia chociaż najprostszego programu cieniującego. Dlatego tym razem opisze pokrótce jak pracować z shaderami na przykładzie oświetlenia i choć te można symulować na kilka różnych sposobów to właśnie shadery będą najłatwiejszym rozwiązaniem na dodanie świtała w naszym wirtualnym świecie. Programu cieniujące, czyli zestawy instrukcji przekazywanych karcie graficznej wymagają znajomości AGAL który jest zupełnie innym językiem od ActionScript i na jego opanowanie potrzebny byłby całkowicie osobny poradnik - tych na szczęście jest całkiem sporo w Internecie, dlatego na razie pominę szczegóły i skupię się tylko na absolutnym minimum potrzebnym do zastosowania prostego cieniowania.
Mając kod z poprzedniego artykuły, od razu zacznijmy od przygotowania Fragment Shader'a:
Powyższe shadery będą rozjaśniać renderowane trójkąty zależnie od ich kąta i ustawienia względem kamery stwarzając wrażenie jakoby ta rzucała światło. Za nim cokolwiek zrobimy z tym kodem musimy go jeszcze skompilować do specjalnego "programu", który przygotujemy wykorzystując AGALMiniAssembler:
W tym momencie mamy w naszej aplikacji dwa shadery: standardowy programPair oraz generujący oświetlenie programPairLight. Pozostanie nam jeszcze tylko zdecydować z którego będziemy korzystać podczas renderowania grafiki 3D wykorzystując funkcje setProgram w obiekcie Context3D. Aby łatwiej było widać różnicę w ich działaniu stworzymy osobny obiekt Box3D (nazwijmy go "box3DLight"), a następnie dopiszemy kod odpowiedzialny za jego wyświetlanie przed wywołaniem present() ale po wyrysowania "box3D":
Jeśli wszystko pójdzie zgodnie z planem powinniśmy zobaczyć coś takiego:
W tym przykładzie operujemy jedynie na dwóch shaderach, jednak nic nie stoi na przeszkodzie by stworzyć ich o wiele, wiele więcej a następnie wymieniać je funkcją setProgram gdy tylko chcemy, podobnie jak w Graphics wywołujemy funkcję lineStyle.
Całość do pobrania stąd: Tutorial3D_light.zip.
Mając kod z poprzedniego artykuły, od razu zacznijmy od przygotowania Fragment Shader'a:
private const FRAGMENT_SHADER_LIGHT:String = "dp3 ft1, fc2, v1 n"+ "neg ft1, ft1 n"+ "max ft1, ft1, fc0 n"+ "mul ft2, fc4, ft1 n"+ "mul ft2, ft2, fc3 n"+ "add oc, ft2, fc1";oraz Vertex Shader'a:
private const VERTEX_SHADER_LIGHT:String = "mov vt0, va0n"+ "m44 op, vt0, vc0n"+ "nrm vt1.xyz, va0.xyzn"+ "mov vt1.w, va0.wn"+ "mov v1, vt1n" + "mov v2, va1";(Kod zapożyczony z http://blog.norbz.net/2012/04/stage3d-agal-from-scratch-part-vii-let-there-be-light)
Powyższe shadery będą rozjaśniać renderowane trójkąty zależnie od ich kąta i ustawienia względem kamery stwarzając wrażenie jakoby ta rzucała światło. Za nim cokolwiek zrobimy z tym kodem musimy go jeszcze skompilować do specjalnego "programu", który przygotujemy wykorzystując AGALMiniAssembler:
vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER_LIGHT, false); fragmentAssembly.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_LIGHT, false); programPairLight = renderContext.createProgram(); programPairLight.upload(vertexAssembly.agalcode, fragmentAssembly.agalcode);(Nie zapomnij utworzyć zmiennej programPairLight)
W tym momencie mamy w naszej aplikacji dwa shadery: standardowy programPair oraz generujący oświetlenie programPairLight. Pozostanie nam jeszcze tylko zdecydować z którego będziemy korzystać podczas renderowania grafiki 3D wykorzystując funkcje setProgram w obiekcie Context3D. Aby łatwiej było widać różnicę w ich działaniu stworzymy osobny obiekt Box3D (nazwijmy go "box3DLight"), a następnie dopiszemy kod odpowiedzialny za jego wyświetlanie przed wywołaniem present() ale po wyrysowania "box3D":
boxMat = box3DLight.getMatrix3D(); boxMat.identity(); boxMat.append(rotMat); boxMat.appendTranslation(posVec.x + 1, posVec.y + 1, posVec.z+1); finalTransform.identity(); finalTransform.append(boxMat); finalTransform.append(world); finalTransform.append(camera); finalTransform.append(projection); renderContext.setProgram(programPairLight); renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true); var p:Vector3D = camera.position.clone(); p.normalize(); var m3d:Matrix3D = finalTransform.clone(); m3d.invert(); p = m3d.transformVector(p); p.normalize(); p.negate(); renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([0,0,0,0])); renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0,0,0,0])); renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, Vector.<Number>([p.x,p.y,p.z,1])); renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, Vector.<Number>([1, 1, 1, 1])); renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 4, Vector.<Number>([0.6, 0.6, 0.6, 1]) ); box3DLight.render(renderContext);Jest tego trochę, ale najważniejsze dla nas to to co się dzieje po linijce setProgram (operacje na "boxMat" i "finalTransform" to znane nam już transformacje z 3D do 2D). Termin "program" jest tutaj nieprzypadkowy bo shadery podobnie jak każdy inny program na komputerze wykonują pewne operacje matematyczne na zmiennych i stałych, aczkolwiek wykorzystując do tego bardzo nisko-poziomowy język. Dodatkowo w shaderach istnieją duże ograniczenia i tak np. zmienne mogą być tworzone tylko w programie i może ich być góra 8. Na szczęście inaczej sprawa ma się ze stałymi, ponieważ te możemy przekazywać z zewnątrz wykorzystując do tego funkcje setProgramConstantsFromVector oraz setProgramConstantsFromMatrix (również maksymalnie 8) i to właśnie dzięki nim do programu cieniującego przekażemy informacje o położeniu światła (zmienna "p") i jego kolorze.
Jeśli wszystko pójdzie zgodnie z planem powinniśmy zobaczyć coś takiego:
W tym przykładzie operujemy jedynie na dwóch shaderach, jednak nic nie stoi na przeszkodzie by stworzyć ich o wiele, wiele więcej a następnie wymieniać je funkcją setProgram gdy tylko chcemy, podobnie jak w Graphics wywołujemy funkcję lineStyle.
Całość do pobrania stąd: Tutorial3D_light.zip.