Geometry Shader - Silhouette edge rendering

Similar documents
Fur Shader GRAPHICS PROGRAMMING 2

VANSTEENKISTE LEO DAE GD ENG UNFOLD SHADER. Introduction

DirectX10 Effects. Sarah Tariq

Nonphotorealism. Christian Miller CS Fall 2011

Advanced Real- Time Cel Shading Techniques in OpenGL Adam Hutchins Sean Kim

Non-Photorealistic Experimentation Jhon Adams

DirectX10 Effects and Performance. Bryan Dudash

Shaders (some slides taken from David M. course)

Volume Shadows Tutorial Nuclear / the Lab

A model to blend renderings

CS 354R: Computer Game Technology

Non-photorealistic Rendering

Real-Time Non- Photorealistic Rendering

Graphics Hardware. Instructor Stephen J. Guy

Real - Time Rendering. Graphics pipeline. Michal Červeňanský Juraj Starinský

MAXIS-mizing Darkspore*: A Case Study of Graphic Analysis and Optimizations in Maxis Deferred Renderer

Enhancing Information on Large Scenes by Mixing Renderings

DirectX 11 First Elements

The Application Stage. The Game Loop, Resource Management and Renderer Design

Shaders. Slide credit to Prof. Zwicker

Could you make the XNA functions yourself?

Non-Photo Realistic Rendering. Jian Huang

CMSC427 Advanced shading getting global illumination by local methods. Credit: slides Prof. Zwicker

Rendering Subdivision Surfaces Efficiently on the GPU

CS451Real-time Rendering Pipeline

Canonical Shaders for Optimal Performance. Sébastien Dominé Manager of Developer Technology Tools

3D Programming. 3D Programming Concepts. Outline. 3D Concepts. 3D Concepts -- Coordinate Systems. 3D Concepts Displaying 3D Models

Advanced Computer Graphics: Non-Photorealistic Rendering

Rasterization Overview

TSBK03 Screen-Space Ambient Occlusion

CS452/552; EE465/505. Finale!

Interactive & Cross-platform development studio

Software Engineering. ò Look up Design Patterns. ò Code Refactoring. ò Code Documentation? ò One of the lessons to learn for good design:

Lab 3 Shadow Mapping. Giuseppe Maggiore

Chapter 7 - Light, Materials, Appearance

CIS 581 Interactive Computer Graphics

Graphics Hardware. Graphics Processing Unit (GPU) is a Subsidiary hardware. With massively multi-threaded many-core. Dedicated to 2D and 3D graphics

Shader Series Primer: Fundamentals of the Programmable Pipeline in XNA Game Studio Express

Computer Graphics (CS 543) Lecture 10: Soft Shadows (Maps and Volumes), Normal and Bump Mapping

Beginning Direct3D Game Programming: 1. The History of Direct3D Graphics

A Trip Down The (2011) Rasterization Pipeline

Game Architecture. 2/19/16: Rasterization

CSE 167: Introduction to Computer Graphics Lecture #5: Rasterization. Jürgen P. Schulze, Ph.D. University of California, San Diego Fall Quarter 2015

Introduction to Shaders.

White Paper. Solid Wireframe. February 2007 WP _v01

Rendering. Converting a 3D scene to a 2D image. Camera. Light. Rendering. View Plane

How to Work on Next Gen Effects Now: Bridging DX10 and DX9. Guennadi Riguer ATI Technologies

Spring 2009 Prof. Hyesoon Kim

INFOGR Computer Graphics

Shaders in Eve Online Páll Ragnar Pálsson

Computer Graphics CS 543 Lecture 1 (Part I) Prof Emmanuel Agu. Computer Science Dept. Worcester Polytechnic Institute (WPI)

Spring 2011 Prof. Hyesoon Kim

Direct Rendering of Trimmed NURBS Surfaces

Hands-On Workshop: 3D Automotive Graphics on Connected Radios Using Rayleigh and OpenGL ES 2.0

Chapter Answers. Appendix A. Chapter 1. This appendix provides answers to all of the book s chapter review questions.

Com S 336 Final Project Ideas

Deferred Rendering Due: Wednesday November 15 at 10pm

Real-Time Shadows. Last Time? Textures can Alias. Schedule. Questions? Quiz 1: Tuesday October 26 th, in class (1 week from today!

Today. Rendering algorithms. Rendering algorithms. Images. Images. Rendering Algorithms. Course overview Organization Introduction to ray tracing

Advanced Lighting Techniques Due: Monday November 2 at 10pm

Single Pass GPU Stylized Edges

3D GRAPHICS. design. animate. render

Graphics Programming. Computer Graphics, VT 2016 Lecture 2, Chapter 2. Fredrik Nysjö Centre for Image analysis Uppsala University

Technical Report. Mesh Instancing

Real-Time Graphics / C++ Programming and Design

Computer Graphics Introduction. Taku Komura

Cg 2.0. Mark Kilgard

Computer Graphics. Lecture 02 Graphics Pipeline. Edirlei Soares de Lima.

3D Production Pipeline

CS GPU and GPGPU Programming Lecture 7: Shading and Compute APIs 1. Markus Hadwiger, KAUST

CS559 Computer Graphics Fall 2015

I d like to start this section with a quote from David Byrne in an article for Utne. In the article he was mostly talking about 2D design and

NVIDIA nfinitefx Engine: Programmable Pixel Shaders

Textures. Texture coordinates. Introduce one more component to geometry

Mattan Erez. The University of Texas at Austin

Bringing AAA graphics to mobile platforms. Niklas Smedberg Senior Engine Programmer, Epic Games

NVIDIA FX Composer. Developer Presentation June 2004

LOD and Occlusion Christian Miller CS Fall 2011

CHAPTER 1 Graphics Systems and Models 3

Shadows & Decals: D3D10 techniques from Frostbite. Johan Andersson Daniel Johansson

CREATING AND USING NORMAL MAPS - A Tutorial

Deferred rendering using Compute shaders

DX10, Batching, and Performance Considerations. Bryan Dudash NVIDIA Developer Technology

Real-Time Shadows. Last Time? Today. Why are Shadows Important? Shadows as a Depth Cue. For Intuition about Scene Lighting

X. GPU Programming. Jacobs University Visualization and Computer Graphics Lab : Advanced Graphics - Chapter X 1

Non-Photorealistic Rendering

Ciril Bohak. - INTRODUCTION TO WEBGL

Introduction to Computer Graphics. Jürgen P. Schulze, Ph.D. University of California, San Diego Fall Quarter 2011

Practical Metaballs and Implicit Surfaces. Yury Uralsky NVIDIA Developer Technology

2.11 Particle Systems

Bringing Hollywood to Real Time. Abe Wiley 3D Artist 3-D Application Research Group

Programmable GPUs. Real Time Graphics 11/13/2013. Nalu 2004 (NVIDIA Corporation) GeForce 6. Virtua Fighter 1995 (SEGA Corporation) NV1

The Rasterization Pipeline

Image Precision Silhouette Edges

Last Time. Why are Shadows Important? Today. Graphics Pipeline. Clipping. Rasterization. Why are Shadows Important?

Technical Game Development II. Reference: Rost, OpenGL Shading Language, 2nd Ed., AW, 2006 The Orange Book Also take CS 4731 Computer Graphics

The Rasterization Pipeline

CIS 581 Interactive Computer Graphics (slides based on Dr. Han-Wei Shen s slides) Requirements. Reference Books. Textbook

CSE 167: Lecture #5: Rasterization. Jürgen P. Schulze, Ph.D. University of California, San Diego Fall Quarter 2012

Computer Graphics 1. Chapter 7 (June 17th, 2010, 2-4pm): Shading and rendering. LMU München Medieninformatik Andreas Butz Computergraphik 1 SS2010

Transcription:

Geometry Shader - Silhouette edge rendering Introduction This paper will describe the process of making an outline shader, using the Geometry Shader. The shader is designed for DirectX10 applications (and up), using shader model 4_0, and written in HLSL. "(NPR) is an area of computer graphics that focuses on enabling a wide variety of expressive styles for digital art. In contrast to traditional computer graphics, which has focused on photorealism, NPR is inspired by artistic styles such as painting, drawing, technical illustration, and animated cartoons. NPR has appeared in movies and video games in the form of "toon shading", as well as in scientific visualization, architectural illustration and experimental animation. An example of a modern use of this method is that of cel-shaded animation." [1] An outline effect is a very good example of NPR, and is combined most of the time with cell - shading. But how do you make an outline effect? Of course a lot of research has been done on this topic, and since the introduction of DirectX10, a new way of creating these outlines is available. Why use a geometry shader for this? Drawing outlines of polygonal meshes has been done before. Several games use outline shading, of which the most popular at the moment, has to be Borderlands. [2] But how did they do it? Following are the two most popular approaches of rendering outlines. [3] Image space This approach is a post processing effect. using the depthmap and the normalmap of your scene, you can check on a per pixel basis where an outline should be drawn. As you can imagine, that requires 2 texture lookups of images of a considerable size. (a) (b) (c) (d) (e) Image space silhouette detection using edge detection operators on the z-buffer and the normal buffer. (a) zbuffer, (b) edges extracted from the z-buffer, (c) normal buffer, (d) edges extracted from the normal buffer, and (e) combination of both edge buffers. (Courtesy of Aaron Hertzmann4 1999 ACM. ) Vantorre Pieter 2DAE GD3 1

object space One example of the object space approach works by classifying all polygons as front or back facing, store this in a hash-map, check if an edge has a front facing and a back facing triangle attached to it, and render those edges with a thickness. This requires some preprocessing, and if you have an animated model, this preprocessing has to be done each tick. As you can see, both methods require quite some computing time, and are quite limited in their capabilities. "Unfortunately, using shader inking has at least three drawbacks. For one, it uses only the vertex normal. Second, it can miss certain silhouette edges. And third, the varying edge thickness is very difficult to control." [4] Geometry shader approach With DirectX10, a new shader pipeline stage has been introduced: The Geometry stage. This stage allows you to process data from triangles, lines or points, which gives you alot more possibilities for making changes to the mesh itself. Knowing this, it is definitely worth a shot to do silhouette detection with the geometry shader. Let's get started. When is an edge an outline? An edge is the boundary of a triangle, if the mesh is closed there is a guarantee that every edge is part of at least 2 triangles. An edge is an outline when one of its triangles is front facing, and the other one is back facing. As illustrated below. Vantorre Pieter 2DAE GD3 2

when is a triangle front facing? a triangle is front facing when the angle between the face normal and the view direction is bigger than 90. This can be checked with dot product: the dot product will return a value smaller than 0 if the angle between the 2 vectors is bigger than 90. if(dot(facenormal, viewdirection) < 0) calculate view direction The view direction is the vector between the centre of the triangle you are processing and the camera position. (get camera position through the view inverse matrix) float3 viewdirection = normalize(basepoint - m_matviewinv[3].xyz); With basepoint = average of the 3 vertices' WorldPosition. The shader Now we know how we are going to do our silhouette detection. First, we need to setup a basic shader. The shader will need the following data structs: Vantorre Pieter 2DAE GD3 3

struct VS_DATA float3 Position : POSITION; float3 Normal : NORMAL; ; struct GS_DATA float4 Position : SV_POSITION; float3 Normal : NORMAL; float EdgeFlag : PSIZE; ; We need the EdgeFlag parameter to make a difference between the existing geometry and the new geometry. This allows us to perform different operations in the pixel shader. We also need the following pass through vertex shader: VS_DATA MainVS(VS_DATA vsdata) return vsdata; When creating the geometry shader, keep in mind that we need adjacency! //output maximum 12 vertices: 3 for the original tri, and 4 for the possible quad [maxvertexcount(12)] void MainGS(triangleadj VS_DATA vertices[6], inout TriangleStream<GS_DATA> tristream) When processing adjacent triangles, the indexes of your main triangle are 0,2,4, as you can see in the image. We can now check if the main triangle is front facing. Vantorre Pieter 2DAE GD3 4

float4 wp1 = mul(float4(vertices[0].position,1), m_matworld); float4 wp2 = mul(float4(vertices[2].position,1), m_matworld); float4 wp3 = mul(float4(vertices[4].position,1), m_matworld); // Get the normal of the current triangle float3 facenormal = GetNormal( wp1, wp2, wp3 ); float3 basepoint = float4((wp1 + wp2 + wp3)/3).xyz; float3 viewdirection = normalize(basepoint - m_matviewinv[3].xyz); viewdirection = normalize(viewdirection); float dotview = dot(facenormal, viewdirection); if(dotview < 0) The GetNormal method: float3 GetNormal( float3 A, float3 B, float3 C ) float3 AB = normalize(b - A); float3 AC = normalize(c - A); return normalize( cross(ab,ac) ); if the dotview is smaller than 0, it means the triangle is front facing. We can now perform the same check for each adjacent triangle to see if they are back facing // Iterate through the adjacent vertices - as described in the directx sample //"Direct3D10Workshop" - Exercise 2 for( uint i=0; i<6; i+=2 ) uint inexttri = (i+2)%6; wp1 = mul(float4(vertices[i].position,1), m_matworld); wp2 = mul(float4(vertices[i+1].position,1), m_matworld); wp3 = mul(float4(vertices[inexttri].position,1), m_matworld); facenormal = GetNormal( wp1, wp2, wp3 ); basepoint = float4((wp1 + wp2 + wp3)/3).xyz; viewdirection = normalize(basepoint - m_matviewinv[3].xyz); dotview = dot(facenormal, viewdirection); if(dotview >= 0) If the adjacent tri is back facing, we know where to make a new quad! Vantorre Pieter 2DAE GD3 5

for(int v = 0; v < 2; v++) float3 wspos = vertices[i].position + v * vertices[i].normal * m_edgelength; CreateVertex(triStream,wsPos,vertices[i].Normal,3); for(int v = 0; v < 2; v++) float3 wspos = vertices[inexttri].position + v * vertices[inexttri].normal * m_edgelength; CreateVertex(triStream,wsPos,vertices[iNextTri].Normal,3); tristream.restartstrip(); Don't forget to always output the original 3 vertices. //Create Existing Geometry CreateVertex(triStream,vertices[0].Position,vertices[0].Normal,0); CreateVertex(triStream,vertices[2].Position,vertices[2].Normal,0); CreateVertex(triStream,vertices[4].Position,vertices[4].Normal,0); The CreateVertex method transforms the positions and normals of the vertices to projection space, and appends the vertices to create new geometry. void CreateVertex(inout TriangleStream<GS_DATA> tristream, float3 pos, float3 normal, float edgeflag = 0) //Step 1. Create a GS_DATA object GS_DATA tempdata; //Step 2. Transform the position using the WVP Matrix and assign it to (GS_DATA object).position (Keep in mind: float3 -> float4) tempdata.position = mul(float4(pos,1),m_matworldviewproj); //Step 3. Transform the normal using the World Matrix and assign it to (GS_DATA object).normal (Only Rotation, No translation!) tempdata.normal = mul(normal, (float3x3)m_matworld); //Step 4. Assign texcoord to (GS_DATA object).texcoord tempdata.edgeflag = edgeflag; //Step 5. Append (GS_DATA object) to the TriangleStream parameter (TriangleStream::Append(...)) tristream.append(tempdata); Vantorre Pieter 2DAE GD3 6

That's it for the geometry shader! Be careful though, I tried developing this shader in fxcomposer [5], and this were the results: Not what you expected of course! After some debugging, I found out only the vertices 0,1,2 contained data. The other vertices were just 0! Not what you would expect from a shader developing program, it doesn't send you adjacency information. So, I had to embed this in my own DirectX environment. Vantorre Pieter 2DAE GD3 7

In addition, a very simple pixel shader can be written, to get rid of the plain black color: float4 MainPS(GS_DATA input) : SV_TARGET float4 color; if(input.edgeflag == 0) input.normal = normalize(input.normal); float diffusestrenght = dot(-input.normal, m_lightdir); diffusestrenght = saturate(diffusestrenght); //clamp between 0 and 1 float3 diffusecolor = float3(0.7,0.7,0.7); //http://www.gamedev.net/topic/491301-hlsl-dx10-dx9-compilation-error/ diffusecolor *= diffusestrenght; float3 viewdirection = normalize(input.position - m_matviewinv[3].xyz); float3 halfvector = normalize(viewdirection + m_lightdir); float specularstrenght = dot(halfvector, -input.normal); specularstrenght = saturate(specularstrenght); specularstrenght = pow(specularstrenght, 32); float3 speccolor = specularstrenght; else color = float4(diffusecolor + speccolor,1); color = float4(1,0,0,1); return color; Adjacency There are 3 common ways of sending adjacecy to your shader. All three methods make the index buffer double of its original size. The first, and probably best way of processing the mesh is by using a ID3DX10Mesh object. If you made a D3D10Mesh, you can call the following function: ID3DX10Mesh* mesh; mesh->generategsadjacency(); Sadly, using this mesh object was out of reach due to the structure of the engine I'm using. It's possible, but it would require too many changes to the framework to make it worth it. The second way is done by storing the indices in an unordered_map, using a struct called Half-Edge Data. [6] I've never worked with an hash container before, and after player around a bit, I figured this was beyond my capabilities. This leaves me but one other way of processing my indices: Vantorre Pieter 2DAE GD3 8

i use a double for loop to check each triangle against each other and to story the indices with adjacency. if(m_badjacent) vector<unsigned int> m_adjindices; for(int i = 0; i < m_indices.size(); ++i) // Fill the AdjacentIndices vector with impossible values m_adjindices.push_back(0xffff); m_adjindices.push_back(0xffff); UINT i0, i1, i2; UINT j0, j1, j2; for(uint i = 0; i < m_indices.size()/3; ++i) // first triangle i0 = m_indices[i *3 + 0]; i1 = m_indices[i *3 + 1]; i2 = m_indices[i *3 + 2]; // remember, main triangle is 0,2,4 m_adjindices[i * 6 + 0] = i0; m_adjindices[i * 6 + 1] = 0xffff; m_adjindices[i * 6 + 2] = i1; m_adjindices[i * 6 + 3] = 0xffff; m_adjindices[i * 6 + 4] = i2; m_adjindices[i * 6 + 5] = 0xffff; for(uint j = 0; j < m_indices.size()/3; ++j) if(j!= i) // don't check a triangle against itself // don't second triangle j0 = m_indices[j * 3 + 0]; j1 = m_indices[j * 3 + 1]; j2 = m_indices[j * 3 + 2]; //Check for i0 and i1 if(j0 == i0) if(j1 == i1) m_adjindices[i * 6 + 1] = j2; else if(j2 == i1) m_adjindices[i * 6 + 1] = j1; else if(j1 == i0) if(j0 == i1) m_adjindices[i * 6 + 1] = j2; else if(j2 == i1) m_adjindices[i * 6 + 1] = j0; else if(j2 == i0) if(j0 == i1) m_adjindices[i * 6 + 1] = j1; else if(j1 == i1) m_adjindices[i * 6 + 1] = j0; Vantorre Pieter 2DAE GD3 9

//Check for i1 and i2 if(j0 == i1) if(j1 == i2) m_adjindices[i * 6 + 3] = j2; else if(j2 == i2) m_adjindices[i * 6 + 3] = j1; else if(j1 == i1) if(j0 == i2) m_adjindices[i * 6 + 3] = j2; else if(j2 == i2) m_adjindices[i * 6 + 3] = j0; else if(j2 == i1) if(j0 == i2) m_adjindices[i * 6 + 3] = j1; else if(j1 == i2) m_adjindices[i * 6 + 3] = j0; //Check for i2 and i0 if(j0 == i2) if(j1 == i0) m_adjindices[i * 6 + 5] = j2; else if(j2 == i0) m_adjindices[i * 6 + 5] = j1; else if(j1 == i2) if(j0 == i0) m_adjindices[i * 6 + 5] = j2; else if(j2 == i0) m_adjindices[i * 6 + 5] = j0; else if(j2 == i2) if(j0 == i0) m_adjindices[i * 6 + 5] = j1; else if(j1 == i0) m_adjindices[i * 6 + 5] = j0; m_indices = m_adjindices; Apologies for the long code, but it is a very important part. [7] Vantorre Pieter 2DAE GD3 10

Conclusion The way I calculate the adjacency is probably the less efficient, and it does take some time to process a mesh. However, this processing normally only has to be done once, since most index buffers don't change. Don't forget to set your primitive topology correctly! IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ ); When testing the shader now, i get the following results: Now that is alot better! However, when taking a look at the mesh from a different angle, we get the following results: Vantorre Pieter 2DAE GD3 11

This means there are still some bugs, and this needs to be investigated further. When debugging the shader, I noticed the following: One of the adjacent vertices of the triangle where an artefact occurs, is 0! this explains the artefact, but sadly it does not explain what caused it. Future work There are several things I would love to add to this shader. Fix the artefacts! Add support for rendering creases prevent z-fighting of the rendered quads with the current geometry (give them a small offset, only applicable for creases of course) Provide edge consistency (close the gaps!) Stylizing the edges so they look similar to edges drawn by a human artist (generate texture coordinates) Use spine testing to allow edges do be drawn inwards also [8] Use a more efficient way of making the index buffer adjacent. Vantorre Pieter 2DAE GD3 12

Conclusion I learnt alot by making this shader. Now i truely understand the different spaces a model can be in, and the use of the different matrices used by the shader. I learnt to process indices with adjacency, how to debug shaders in Visual Studio, how to find edges of a model and alot more. I am not satisfied with the results of this paper, because I still have the artefacts that make this shader quite useless. If these artefacts are fixed however, I will be able to further develop this shader. Finding silhouette edges is also the basic of using shadow volumes, only there you check if the triangles are back facing from the light and not from the camera. I will try to implement shadow volumes later on. Thank you for reading Bibliografy [1] Non photorealistic rendering, Wikipedia, [Online]. Available: http://en.wikipedia.org/wiki/nonphotorealistic_rendering. [2] [Online]. Available: http://www.borderlandsthegame.com/. [3] I. e. a. 2003, A Developer s Guide to silhouette algorithms for polygonal models, [Online]. Available: http://www.inf.hs-anhalt.de/~schlechtweg/home/pdf/isenberg.2003.adg.pdf. [4] J. Doss, Inking the Cube: Edge Detection with Direct3D 10, Gamasutra, 2008. [Online]. Available: http://www.gamasutra.com/view/feature/130067/sponsored_feature_inking_the_.php. [5] Fx Composer, Nvidia, [Online]. Available: https://developer.nvidia.com/fx-composer. [6] Half Edge Data Struct, [Online]. Available: http://www.cgal.org/manual/latest/doc_html/cgal_manual/halfedgeds/chapter_main.html. [7] [Online]. Available: http://www.gamedev.net/topic/499275-good-news-read-please-how-togenerate-adjacency/. [8] [Online]. Available: http://gfx.cs.princeton.edu/pubs/cole_2010_tfm/. [9] P. H. &. P. Vázquez. [Online]. Available: http://cgstarad.com/docs/gscontours.pdf. [10] P. Rideout. [Online]. Available: http://prideout.net/blog/?p=54. [11] K. Hayton. [Online]. Available: http://www.khayton-portfolio.com/samples_silhouette.html. Vantorre Pieter 2DAE GD3 13