Introduce Photoshop-like alpha blending

This commit is contained in:
Hajime Hoshi 2014-12-20 05:22:10 +09:00
parent dc0e67f600
commit 6673a39fa2
13 changed files with 155 additions and 71 deletions

View File

@ -32,7 +32,7 @@ func textWidth(str string) int {
return charWidth * len(str)
}
func drawText(context ebiten.GraphicsContext, textures *Textures, str string, x, y, scale int, clr color.Color) {
func drawText(context ebiten.GraphicsContext, textures *Textures, str string, ox, oy, scale int, clr color.Color) {
fontTextureId := textures.GetTexture("font")
parts := []ebiten.TexturePart{}
@ -53,11 +53,9 @@ func drawText(context ebiten.GraphicsContext, textures *Textures, str string, x,
locationX += charWidth
}
geoMat := ebiten.GeometryMatrixI()
geoMat.Concat(ebiten.ScaleGeometry(float64(scale), float64(scale)))
geoMat.Concat(ebiten.TranslateGeometry(float64(x), float64(y)))
clrMat := ebiten.ColorMatrixI()
clrMat.Concat(ebiten.ScaleColor(clr))
geoMat := ebiten.ScaleGeometry(float64(scale), float64(scale))
geoMat.Concat(ebiten.TranslateGeometry(float64(ox), float64(oy)))
clrMat := ebiten.ScaleColor(clr)
context.DrawTexture(fontTextureId, parts, geoMat, clrMat)
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package ebiten
import (
"github.com/hajimehoshi/ebiten/internal"
"github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/opengl/internal/shader"
)
@ -92,11 +93,11 @@ func (r *RenderTarget) Size() (width int, height int) {
}
func u(x float64, width int) float32 {
return float32(x) / float32(opengl.AdjustSizeForTexture(width))
return float32(x) / float32(internal.AdjustSizeForTexture(width))
}
func v(y float64, height int) float32 {
return float32(y) / float32(opengl.AdjustSizeForTexture(height))
return float32(y) / float32(internal.AdjustSizeForTexture(height))
}
func textureQuads(parts []TexturePart, width, height int) []shader.TextureQuad {

View File

@ -22,8 +22,10 @@ import (
)
func newGraphicsContext(screenWidth, screenHeight, screenScale int) (*graphicsContext, error) {
// The defualt framebuffer should be 0.
r := opengl.NewRenderTarget(screenWidth*screenScale, screenHeight*screenScale, true)
r, t, err := opengl.NewZeroRenderTarget(screenWidth*screenScale, screenHeight*screenScale, true)
if err != nil {
return nil, err
}
screen, err := idsInstance.createRenderTarget(screenWidth, screenHeight, gl.NEAREST)
if err != nil {
@ -32,7 +34,7 @@ func newGraphicsContext(screenWidth, screenHeight, screenScale int) (*graphicsCo
c := &graphicsContext{
currents: make([]*RenderTarget, 1),
defaultR: &RenderTarget{r, nil},
defaultR: &RenderTarget{r, &Texture{t}},
screen: screen,
screenWidth: screenWidth,
screenHeight: screenHeight,

4
ids.go
View File

@ -67,7 +67,9 @@ func (i *ids) drawTexture(target *RenderTarget, texture *Texture, parts []Textur
}
projectionMatrix := target.glRenderTarget.ProjectionMatrix()
quads := textureQuads(parts, glTexture.Width(), glTexture.Height())
shader.DrawTexture(glTexture.Native(), projectionMatrix, quads, &geo, &color)
w, h := target.Size()
shader.DrawTexture(glTexture.Native(), target.texture.glTexture.Native(), w, h, projectionMatrix, quads, &geo, &color)
gl.Flush()
return nil
}

32
internal/math.go Normal file
View File

@ -0,0 +1,32 @@
/*
Copyright 2014 Hajime Hoshi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
func NextPowerOf2(x uint64) uint64 {
x -= 1
x |= (x >> 1)
x |= (x >> 2)
x |= (x >> 4)
x |= (x >> 8)
x |= (x >> 16)
x |= (x >> 32)
return x + 1
}
func AdjustSizeForTexture(size int) int {
return int(NextPowerOf2(uint64(size)))
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package opengl_test
package internal_test
import (
. "."
@ -35,8 +35,7 @@ func TestNextPowerOf2(t *testing.T) {
got := NextPowerOf2(testCase.arg)
wanted := testCase.expected
if wanted != got {
t.Errorf("Clp(%d) = %d, wanted %d",
testCase.arg, got, wanted)
t.Errorf("Clp(%d) = %d, wanted %d", testCase.arg, got, wanted)
}
}

View File

@ -18,6 +18,7 @@ package shader
import (
"github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
"sync"
)
@ -38,7 +39,7 @@ type Matrix interface {
}
// TODO: Use VBO
func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []TextureQuad, geo Matrix, color Matrix) {
func DrawTexture(native gl.Texture, target gl.Texture, width, height int, projectionMatrix [4][4]float64, quads []TextureQuad, geo Matrix, color Matrix) {
once.Do(func() {
initialize()
})
@ -47,27 +48,39 @@ func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []Text
return
}
// TODO: Check performance
shaderProgram := use(glMatrix(projectionMatrix), geo, color)
shaderProgram := use(glMatrix(projectionMatrix), width, height, geo, color)
gl.ActiveTexture(gl.TEXTURE0)
native.Bind(gl.TEXTURE_2D)
defer gl.Texture(0).Bind(gl.TEXTURE_2D)
gl.ActiveTexture(gl.TEXTURE1)
target.Bind(gl.TEXTURE_2D)
texture0UniformLocation := getUniformLocation(shaderProgram, "texture0")
texture1UniformLocation := getUniformLocation(shaderProgram, "texture1")
texture0UniformLocation.Uniform1i(0)
texture1UniformLocation.Uniform1i(1)
vertexAttrLocation := getAttributeLocation(shaderProgram, "vertex")
texCoordAttrLocation := getAttributeLocation(shaderProgram, "tex_coord")
texCoord0AttrLocation := getAttributeLocation(shaderProgram, "tex_coord0")
texCoord1AttrLocation := getAttributeLocation(shaderProgram, "tex_coord1")
gl.EnableClientState(gl.VERTEX_ARRAY)
gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
vertexAttrLocation.EnableArray()
texCoordAttrLocation.EnableArray()
texCoord0AttrLocation.EnableArray()
texCoord1AttrLocation.EnableArray()
defer func() {
texCoordAttrLocation.DisableArray()
texCoord1AttrLocation.DisableArray()
texCoord0AttrLocation.DisableArray()
vertexAttrLocation.DisableArray()
gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)
gl.DisableClientState(gl.VERTEX_ARRAY)
}()
vertices := []float32{}
texCoords := []float32{}
texCoords0 := []float32{}
texCoords1 := []float32{}
indicies := []uint32{}
// TODO: Check len(quads) and gl.MAX_ELEMENTS_INDICES?
for i, quad := range quads {
@ -85,12 +98,24 @@ func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []Text
u2 := quad.TextureCoordU2
v1 := quad.TextureCoordV1
v2 := quad.TextureCoordV2
texCoords = append(texCoords,
texCoords0 = append(texCoords0,
u1, v1,
u2, v1,
u1, v2,
u2, v2,
)
w := float32(internal.AdjustSizeForTexture(width))
h := float32(internal.AdjustSizeForTexture(height))
xx1 := x1 / w
xx2 := x2 / w
yy1 := y1 / h
yy2 := y2 / h
texCoords1 = append(texCoords1,
xx1, yy1,
xx2, yy1,
xx1, yy2,
xx2, yy2,
)
base := uint32(i * 4)
indicies = append(indicies,
base, base+1, base+2,
@ -98,6 +123,7 @@ func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []Text
)
}
vertexAttrLocation.AttribPointer(2, gl.FLOAT, false, 0, vertices)
texCoordAttrLocation.AttribPointer(2, gl.FLOAT, false, 0, texCoords)
texCoord0AttrLocation.AttribPointer(2, gl.FLOAT, false, 0, texCoords0)
texCoord1AttrLocation.AttribPointer(2, gl.FLOAT, false, 0, texCoords1)
gl.DrawElements(gl.TRIANGLES, len(indicies), gl.UNSIGNED_INT, indicies)
}

View File

@ -18,6 +18,7 @@ package shader
import (
"github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
)
type program struct {
@ -80,7 +81,7 @@ func getUniformLocation(program gl.Program, name string) gl.UniformLocation {
return location
}
func use(projectionMatrix [16]float32, geo Matrix, color Matrix) gl.Program {
func use(projectionMatrix [16]float32, width, height int, geo Matrix, color Matrix) gl.Program {
// TODO: Check the performance.
program := programColorMatrix
@ -99,6 +100,15 @@ func use(projectionMatrix [16]float32, geo Matrix, color Matrix) gl.Program {
tx, ty, 0, 1,
}
getUniformLocation(program.native, "modelview_matrix").UniformMatrix4fv(false, glModelviewMatrix)
txn := tx / float32(internal.AdjustSizeForTexture(width))
tyn := ty / float32(internal.AdjustSizeForTexture(height))
glModelviewMatrixN := [...]float32{
a, c, 0, 0,
b, d, 0, 0,
0, 0, 1, 0,
txn, tyn, 0, 1,
}
getUniformLocation(program.native, "modelview_matrix_n").UniformMatrix4fv(false, glModelviewMatrixN)
getUniformLocation(program.native, "texture").Uniform1i(0)

View File

@ -42,41 +42,53 @@ var shaders = map[shaderId]*shader{
source: `
uniform mat4 projection_matrix;
uniform mat4 modelview_matrix;
uniform mat4 modelview_matrix_n;
attribute vec2 vertex;
attribute vec2 tex_coord;
varying vec2 vertex_out_tex_coord;
attribute vec2 tex_coord0;
attribute vec2 tex_coord1;
varying vec2 vertex_out_tex_coord0;
varying vec2 vertex_out_tex_coord1;
void main(void) {
vertex_out_tex_coord = tex_coord;
vertex_out_tex_coord0 = tex_coord0;
vertex_out_tex_coord1 = (modelview_matrix_n * vec4(tex_coord1, 0, 1)).xy;
gl_Position = projection_matrix * modelview_matrix * vec4(vertex, 0, 1);
}
`,
},
// TODO: Now this shader is not used
shaderFragment: {
shaderType: gl.FRAGMENT_SHADER,
source: `
uniform sampler2D texture;
varying vec2 vertex_out_tex_coord;
varying vec2 vertex_out_tex_coord0;
void main(void) {
gl_FragColor = texture2D(texture, vertex_out_tex_coord);
gl_FragColor = texture2D(texture, vertex_out_tex_coord0);
}
`,
},
shaderColorMatrix: {
shaderType: gl.FRAGMENT_SHADER,
source: `
uniform sampler2D texture;
uniform sampler2D texture0;
uniform sampler2D texture1;
uniform mat4 color_matrix;
uniform vec4 color_matrix_translation;
varying vec2 vertex_out_tex_coord;
varying vec2 vertex_out_tex_coord0;
varying vec2 vertex_out_tex_coord1;
void main(void) {
vec4 color = texture2D(texture, vertex_out_tex_coord);
gl_FragColor = (color_matrix * color) + color_matrix_translation;
vec4 color0 = texture2D(texture0, vertex_out_tex_coord0);
vec4 color1 = texture2D(texture1, vertex_out_tex_coord1);
color0 = (color_matrix * color0) + color_matrix_translation;
gl_FragColor.a = color0.a + color1.a - color0.a * color1.a;
gl_FragColor.rgb = (color0.a * color0.rgb + (1.0 - color0.a) * color1.a * color1.rgb) / gl_FragColor.a;
}
`,
},
// TODO: Now this shader is not used
shaderSolidColor: {
shaderType: gl.FRAGMENT_SHADER,
source: `

View File

@ -16,6 +16,7 @@ limitations under the License.
package shader
// TODO: Rename X1, X2 -> X0, X1
type TextureQuad struct {
VertexX1 float32
VertexX2 float32

View File

@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
)
func orthoProjectionMatrix(left, right, bottom, top int) [4][4]float64 {
@ -43,12 +44,23 @@ type RenderTarget struct {
flipY bool
}
func NewRenderTarget(width, height int, flipY bool) *RenderTarget {
return &RenderTarget{
func NewZeroRenderTarget(width, height int, flipY bool) (*RenderTarget, *Texture, error) {
r := &RenderTarget{
width: width,
height: height,
flipY: flipY,
}
// The framebuffer 0 can't be enlarged, so any filter is acceptable.
t, err := NewTexture(width, height, gl.NEAREST)
if err != nil {
return nil, nil, err
}
// TODO: Does this affect the current rendering target?
gl.Framebuffer(0).Bind()
if err := framebufferTexture(t.native); err != nil {
return nil, nil, err
}
return r, t, err
}
func NewRenderTargetFromTexture(texture *Texture) (*RenderTarget, error) {
@ -80,22 +92,28 @@ func (r *RenderTarget) Dispose() {
}
func createFramebuffer(nativeTexture gl.Texture) (gl.Framebuffer, error) {
// TODO: Does this affect the current rendering target?
framebuffer := gl.GenFramebuffer()
framebuffer.Bind()
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, nativeTexture, 0)
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
return 0, errors.New("creating framebuffer failed")
if err := framebufferTexture(nativeTexture); err != nil {
return 0, err
}
// Set this framebuffer opaque because alpha values on a target might be
// confusing.
gl.ClearColor(0, 0, 0, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
return framebuffer, nil
}
func framebufferTexture(nativeTexture gl.Texture) error {
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, nativeTexture, 0)
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
return errors.New("creating framebuffer failed")
}
gl.ClearColor(0, 0, 0, 0)
gl.Clear(gl.COLOR_BUFFER_BIT)
return nil
}
func (r *RenderTarget) SetAsViewport() error {
gl.Flush()
r.framebuffer.Bind()
@ -104,21 +122,19 @@ func (r *RenderTarget) SetAsViewport() error {
return errors.New(fmt.Sprintf("glBindFramebuffer failed: %d", err))
}
gl.BlendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE)
width := AdjustSizeForTexture(r.width)
height := AdjustSizeForTexture(r.height)
width := internal.AdjustSizeForTexture(r.width)
height := internal.AdjustSizeForTexture(r.height)
gl.Viewport(0, 0, width, height)
return nil
}
func (r *RenderTarget) ProjectionMatrix() [4][4]float64 {
width := AdjustSizeForTexture(r.width)
height := AdjustSizeForTexture(r.height)
width := internal.AdjustSizeForTexture(r.width)
height := internal.AdjustSizeForTexture(r.height)
m := orthoProjectionMatrix(0, width, 0, height)
if r.flipY {
m[1][1] *= -1
m[1][3] += float64(r.height) / float64(AdjustSizeForTexture(r.height)) * 2
m[1][3] += float64(r.height) / float64(internal.AdjustSizeForTexture(r.height)) * 2
}
return m
}

View File

@ -18,32 +18,18 @@ package opengl
import (
"github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
"image"
"image/draw"
)
func NextPowerOf2(x uint64) uint64 {
x -= 1
x |= (x >> 1)
x |= (x >> 2)
x |= (x >> 4)
x |= (x >> 8)
x |= (x >> 16)
x |= (x >> 32)
return x + 1
}
func AdjustSizeForTexture(size int) int {
return int(NextPowerOf2(uint64(size)))
}
func adjustImageForTexture(img image.Image) *image.NRGBA {
width, height := img.Bounds().Size().X, img.Bounds().Size().Y
adjustedImageBounds := image.Rectangle{
image.ZP,
image.Point{
AdjustSizeForTexture(width),
AdjustSizeForTexture(height),
internal.AdjustSizeForTexture(width),
internal.AdjustSizeForTexture(height),
},
}
if nrgba, ok := img.(*image.NRGBA); ok && img.Bounds() == adjustedImageBounds {
@ -95,8 +81,8 @@ func createNativeTexture(textureWidth, textureHeight int, pixels []uint8, filter
}
func NewTexture(width, height int, filter int) (*Texture, error) {
w := AdjustSizeForTexture(width)
h := AdjustSizeForTexture(height)
w := internal.AdjustSizeForTexture(width)
h := internal.AdjustSizeForTexture(height)
native := createNativeTexture(w, h, nil, filter)
return &Texture{native, width, height}, nil
}

1
ui.go
View File

@ -52,7 +52,6 @@ func init() {
currentUI.use(func() {
gl.Init()
gl.Enable(gl.TEXTURE_2D)
gl.Enable(gl.BLEND)
})
}